From 86fd57c86cd316dea888238d3b19b28b6542090a Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Tue, 25 Jun 2024 18:16:37 +1000 Subject: [PATCH] NI-PoRep (#1559) * feat(miner): ProveCommitSectorsNI and tests * chore(niporep): fix parameter naming * chore(niporep): improve test function name * chore(niporep): check return values from method * fix(niporep): hard-fail on future sector seal randomness * fix(niporep): properly test aggregate fee payment at boundary * test(miner): add niporep params cbor forms vector tests * chore(miner): extract NI_AGGREGATE_FEE_BASE_SECTOR_COUNT constant --------- Co-authored-by: Nemanja <7816150+NemanjaLu92@users.noreply.github.com> --- Cargo.lock | 38 +- Cargo.toml | 35 +- actors/miner/Cargo.toml | 1 + actors/miner/src/commd.rs | 15 +- actors/miner/src/lib.rs | 364 +++++++++++- actors/miner/src/policy.rs | 19 +- actors/miner/src/state.rs | 49 ++ actors/miner/src/types.rs | 27 + actors/miner/tests/prove_commit_niporep.rs | 560 ++++++++++++++++++ actors/miner/tests/types_test.rs | 82 +++ actors/miner/tests/util.rs | 186 +++++- integration_tests/src/tests/mod.rs | 2 + .../src/tests/prove_commit_niporep_test.rs | 362 +++++++++++ integration_tests/src/util/mod.rs | 5 + runtime/src/runtime/policy.rs | 60 +- test_vm/tests/suite/mod.rs | 1 + .../tests/suite/prove_commit_niporep_test.rs | 19 + 17 files changed, 1773 insertions(+), 52 deletions(-) create mode 100644 actors/miner/tests/prove_commit_niporep.rs create mode 100644 actors/miner/tests/types_test.rs create mode 100644 integration_tests/src/tests/prove_commit_niporep_test.rs create mode 100644 test_vm/tests/suite/prove_commit_niporep_test.rs diff --git a/Cargo.lock b/Cargo.lock index 2aaa97f4a..2c4f279f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,9 +49,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anyhow" @@ -1556,6 +1556,7 @@ dependencies = [ "bitflags 2.4.1", "byteorder", "cid 0.10.1", + "const-hex", "fil_actor_account", "fil_actor_market", "fil_actor_power", @@ -1903,9 +1904,9 @@ dependencies = [ [[package]] name = "frc42_dispatch" -version = "5.0.0" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe63cf3ff3e332ef15fd19d95cffcb3fd2af14ccb3cb04abc730271c1362c4f" +checksum = "0b9567f7b38af6f277072c9ea230f7c5be12237905ef19348f4b490f563f3fef" dependencies = [ "frc42_hasher", "frc42_macros", @@ -1917,9 +1918,9 @@ dependencies = [ [[package]] name = "frc42_hasher" -version = "3.0.0" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a35e7214108f81cefc17b0466be01279f384faf913918a12dbc8528bb758a4" +checksum = "238a28ff638f138c4b4c75f4d35cd28cedcb45858cfcaa4df36dc25b0b3298db" dependencies = [ "fvm_sdk", "fvm_shared", @@ -1928,9 +1929,9 @@ dependencies = [ [[package]] name = "frc42_macros" -version = "3.0.0" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f50cd62b077775194bde67eef8076b31f915b9c099f3a7fd1a760363d65f145" +checksum = "c07758f10c4a85a14b59c1c2d63d6200122f144b42e495dabdc997bf1c917e62" dependencies = [ "blake2b_simd", "frc42_hasher", @@ -1941,9 +1942,9 @@ dependencies = [ [[package]] name = "frc46_token" -version = "9.0.0" +version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d3e91e8d92fcd274d4e56cbbec093c4c7613cd294b6c8a855dacc4f9dfff9c" +checksum = "af558bacb4e00ac1aa47115464f2bfa3d8dbd2a3b032dc325d20eaaaa22522e7" dependencies = [ "cid 0.10.1", "frc42_dispatch", @@ -2092,9 +2093,9 @@ dependencies = [ [[package]] name = "fvm_actor_utils" -version = "9.0.0" +version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1227ae02b9395085d6b4e7e4f5d7ea2fcbfd13197fade8fa8eaba8089b8865d3" +checksum = "fe8cad6375c09395099f25313600a53b1012a29595f9666a48c00eba7a8adaf9" dependencies = [ "anyhow", "cid 0.10.1", @@ -2182,9 +2183,9 @@ dependencies = [ [[package]] name = "fvm_ipld_hamt" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a53e14c789449cec999ca0e93d909490c921b967adb7a9ec8f12286fb809bd" +checksum = "48c900736087ff87cc51f669eee2f8e000c80717472242eb3f712aaa059ac3b3" dependencies = [ "anyhow", "byteorder", @@ -2220,11 +2221,10 @@ dependencies = [ [[package]] name = "fvm_sdk" -version = "4.0.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258cfc9a2e5dcb28ffcadd4abed504893996d31238488a07ef7d2a6a6e80e1ec" +checksum = "d4943c9b564d8417b7723a7b4a6925e54e7d29a0b870917fcdf6426f46430a09" dependencies = [ - "byteorder", "cid 0.10.1", "fvm_ipld_encoding", "fvm_shared", @@ -2236,9 +2236,9 @@ dependencies = [ [[package]] name = "fvm_shared" -version = "4.0.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a19ef48bbc1b22742002667b82944237cfdebc38e946c216aa8de1192392ea" +checksum = "31841282c98f3acd78c741f1e38ecebc97e72243c4ecb73a658f6529246a35a5" dependencies = [ "anyhow", "bitflags 2.4.1", diff --git a/Cargo.toml b/Cargo.toml index e61660660..014ea6a7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ "runtime", "test_vm", "vm_api", - "integration_tests" + "integration_tests", ] [workspace.package] @@ -54,10 +54,16 @@ fil_actors_runtime = { workspace = true } num-traits = { workspace = true } [dependencies] -clap = { version = "4.3.0", features = ["derive", "std", "help", "usage", "error-context"], default-features = false } +clap = { version = "4.3.0", features = [ + "derive", + "std", + "help", + "usage", + "error-context", +], default-features = false } [features] -default = [] ## translates to mainnet +default = [] ## translates to mainnet mainnet = [] caterpillarnet = [] butterflynet = [] @@ -110,22 +116,27 @@ rlp = { version = "0.5.1", default-features = false } substrate-bn = { version = "0.6.0", default-features = false } # IPLD/Encoding -cid = { version = "0.10.1", default-features = false, features = ["serde-codec", "std"] } -multihash = { version = "0.18.1", default-features = false , features = ["multihash-impl"]} +cid = { version = "0.10.1", default-features = false, features = [ + "serde-codec", + "std", +] } +multihash = { version = "0.18.1", default-features = false, features = [ + "multihash-impl", +] } libipld-core = { version = "0.13.1", features = ["serde-codec"] } integer-encoding = { version = "3.0.3", default-features = false } # helix-onchain -fvm_actor_utils = "9.0.0" -frc42_dispatch = "5.0.0" -frc46_token = "9.0.0" +fvm_actor_utils = "11.0.0" +frc42_dispatch = "7.0.0" +frc46_token = "11.0.0" # FVM -fvm_sdk = "~4.0" -fvm_shared = "~4.0" +fvm_sdk = "4.3.0" +fvm_shared = "4.3.0" fvm_ipld_encoding = "0.4.0" fvm_ipld_blockstore = "0.2.0" -fvm_ipld_hamt = "0.8.0" +fvm_ipld_hamt = "0.9.0" fvm_ipld_kamt = "0.3.0" fvm_ipld_amt = { version = "0.6.2" } fvm_ipld_bitfield = "0.6.0" @@ -149,7 +160,7 @@ fil_actor_system = { path = "actors/system" } fil_actor_verifreg = { path = "actors/verifreg" } fil_actors_evm_shared = { path = "actors/evm/shared" } fil_actors_runtime = { path = "runtime" } -fil_builtin_actors_state = { path = "state"} +fil_builtin_actors_state = { path = "state" } fil_actors_integration_tests = { version = "1.0.0", path = "integration_tests" } vm_api = { version = "1.0.0", path = "vm_api" } test_vm = { path = "test_vm" } diff --git a/actors/miner/Cargo.toml b/actors/miner/Cargo.toml index d41af88c3..047231f30 100644 --- a/actors/miner/Cargo.toml +++ b/actors/miner/Cargo.toml @@ -33,6 +33,7 @@ lazy_static = { workspace = true } log = { workspace = true } byteorder = { workspace = true } itertools = { workspace = true } +const-hex = { workspace = true } [dev-dependencies] fil_actors_runtime = { workspace = true, features = ["test_utils", "sector-default"] } diff --git a/actors/miner/src/commd.rs b/actors/miner/src/commd.rs index 0626710b6..c0df35b15 100644 --- a/actors/miner/src/commd.rs +++ b/actors/miner/src/commd.rs @@ -83,15 +83,20 @@ fn zero_commd(seal_proof: RegisteredSealProof) -> Result { seal_proof.update_to_v1(); let i = match seal_proof { RegisteredSealProof::StackedDRG2KiBV1P1 - | RegisteredSealProof::StackedDRG2KiBV1P1_Feat_SyntheticPoRep => 0, + | RegisteredSealProof::StackedDRG2KiBV1P1_Feat_SyntheticPoRep + | RegisteredSealProof::StackedDRG2KiBV1P2_Feat_NiPoRep => 0, RegisteredSealProof::StackedDRG512MiBV1P1 - | RegisteredSealProof::StackedDRG512MiBV1P1_Feat_SyntheticPoRep => 1, + | RegisteredSealProof::StackedDRG512MiBV1P1_Feat_SyntheticPoRep + | RegisteredSealProof::StackedDRG512MiBV1P2_Feat_NiPoRep => 1, RegisteredSealProof::StackedDRG8MiBV1P1 - | RegisteredSealProof::StackedDRG8MiBV1P1_Feat_SyntheticPoRep => 2, + | RegisteredSealProof::StackedDRG8MiBV1P1_Feat_SyntheticPoRep + | RegisteredSealProof::StackedDRG8MiBV1P2_Feat_NiPoRep => 2, RegisteredSealProof::StackedDRG32GiBV1P1 - | RegisteredSealProof::StackedDRG32GiBV1P1_Feat_SyntheticPoRep => 3, + | RegisteredSealProof::StackedDRG32GiBV1P1_Feat_SyntheticPoRep + | RegisteredSealProof::StackedDRG32GiBV1P2_Feat_NiPoRep => 3, RegisteredSealProof::StackedDRG64GiBV1P1 - | RegisteredSealProof::StackedDRG64GiBV1P1_Feat_SyntheticPoRep => 4, + | RegisteredSealProof::StackedDRG64GiBV1P1_Feat_SyntheticPoRep + | RegisteredSealProof::StackedDRG64GiBV1P2_Feat_NiPoRep => 4, _ => { return Err(actor_error!(illegal_argument, "unknown SealProof")); } diff --git a/actors/miner/src/lib.rs b/actors/miner/src/lib.rs index 433a5a0c7..db1096741 100644 --- a/actors/miner/src/lib.rs +++ b/actors/miner/src/lib.rs @@ -138,6 +138,7 @@ pub enum Method { // MovePartitions = 33, ProveCommitSectors3 = 34, ProveReplicaUpdates3 = 35, + ProveCommitSectorsNI = 36, // Method numbers derived from FRC-0042 standards ChangeWorkerAddressExported = frc42_dispatch::method_hash!("ChangeWorkerAddress"), ChangePeerIDExported = frc42_dispatch::method_hash!("ChangePeerID"), @@ -779,7 +780,7 @@ impl Actor { return Err(actor_error!(illegal_argument, "no sectors")); } - validate_seal_aggregate_proof(¶ms.aggregate_proof, sector_numbers.len(), policy)?; + validate_seal_aggregate_proof(¶ms.aggregate_proof, sector_numbers.len(), policy, true)?; // Load and validate pre-commits. // Fail if any don't exist, but otherwise continue with valid ones. @@ -1706,10 +1707,7 @@ impl Actor { "failed to add pre-commit deposit {}: {}", total_deposit_required, e ))?; - state.allocate_sector_numbers(store, §or_numbers, CollisionPolicy::DenyCollisions) - .map_err(|e| - e.wrap("failed to allocate sector numbers") - )?; + state.allocate_sector_numbers(store, §or_numbers, CollisionPolicy::DenyCollisions)?; state.put_precommitted_sectors(store, chain_infos) .map_err(|e| e.downcast_default(ExitCode::USR_ILLEGAL_STATE, "failed to write pre-committed sectors") @@ -1799,6 +1797,7 @@ impl Actor { ¶ms.aggregate_proof, params.sector_activations.len() as u64, policy, + true, )?; } @@ -2003,6 +2002,229 @@ impl Actor { Ok(()) } + fn prove_commit_sectors_ni( + rt: &impl Runtime, + params: ProveCommitSectorsNIParams, + ) -> Result { + let policy = rt.policy(); + let curr_epoch = rt.curr_epoch(); + let state: State = rt.state()?; + let store = rt.store(); + let info = get_miner_info(rt.store(), &state)?; + + validate_seal_aggregate_proof( + ¶ms.aggregate_proof, + params.sectors.len() as u64, + policy, + false, + )?; + + rt.validate_immediate_caller_is( + info.control_addresses.iter().chain(&[info.worker, info.owner]), + )?; + + if params.proving_deadline >= policy.wpost_period_deadlines { + return Err(actor_error!( + illegal_argument, + "proving deadline index {} invalid", + params.proving_deadline + )); + } + + if !deadline_is_mutable( + policy, + state.current_proving_period_start(policy, curr_epoch), + params.proving_deadline, + curr_epoch, + ) { + return Err(actor_error!( + forbidden, + "proving deadline {} must not be the current or next deadline ", + params.proving_deadline + )); + } + + if consensus_fault_active(&info, rt.curr_epoch()) { + return Err(actor_error!( + forbidden, + "ProveCommitSectorsNI not allowed during active consensus fault" + )); + } + + if !can_prove_commit_ni_seal_proof(rt.policy(), params.seal_proof_type) { + return Err(actor_error!( + illegal_argument, + "unsupported seal proof type {}", + i64::from(params.seal_proof_type) + )); + } + + if params.aggregate_proof_type != RegisteredAggregateProof::SnarkPackV2 { + return Err(actor_error!(illegal_argument, "aggregate proof type must be SnarkPackV2")); + } + + let (validation_batch, proof_inputs, sector_numbers) = validate_ni_sectors( + rt, + ¶ms.sectors, + params.seal_proof_type, + params.require_activation_success, + )?; + + if validation_batch.success_count == 0 { + return Err(actor_error!(illegal_argument, "no valid NI commits specified")); + } + let valid_sectors = validation_batch.successes(¶ms.sectors); + + verify_aggregate_seal( + rt, + // All the proof inputs, even for invalid activations, + // must be provided as witnesses to the aggregate proof. + &proof_inputs, + valid_sectors[0].sealer_id, + params.seal_proof_type, + params.aggregate_proof_type, + ¶ms.aggregate_proof, + )?; + + // With no data, QA power = raw power + let qa_sector_power = raw_power_for_sector(info.sector_size); + + let rew = request_current_epoch_block_reward(rt)?; + let pwr = request_current_total_power(rt)?; + let circulating_supply = rt.total_fil_circ_supply(); + let pledge_inputs = NetworkPledgeInputs { + network_qap: pwr.quality_adj_power_smoothed, + network_baseline: rew.this_epoch_baseline_power, + circulating_supply, + epoch_reward: rew.this_epoch_reward_smoothed, + }; + + let sector_day_reward = expected_reward_for_power( + &pledge_inputs.epoch_reward, + &pledge_inputs.network_qap, + &qa_sector_power, + fil_actors_runtime::EPOCHS_IN_DAY, + ); + + let sector_storage_pledge = expected_reward_for_power( + &pledge_inputs.epoch_reward, + &pledge_inputs.network_qap, + &qa_sector_power, + INITIAL_PLEDGE_PROJECTION_PERIOD, + ); + + let sector_initial_pledge = initial_pledge_for_power( + &qa_sector_power, + &pledge_inputs.network_baseline, + &pledge_inputs.epoch_reward, + &pledge_inputs.network_qap, + &pledge_inputs.circulating_supply, + ); + + let sectors_to_add = valid_sectors + .iter() + .map(|sector| SectorOnChainInfo { + sector_number: sector.sector_number, + seal_proof: params.seal_proof_type, + sealed_cid: sector.sealed_cid, + deprecated_deal_ids: vec![], + expiration: sector.expiration, + activation: curr_epoch, + deal_weight: DealWeight::zero(), + verified_deal_weight: DealWeight::zero(), + initial_pledge: sector_initial_pledge.clone(), + expected_day_reward: sector_day_reward.clone(), + expected_storage_pledge: sector_storage_pledge.clone(), + power_base_epoch: curr_epoch, + replaced_day_reward: TokenAmount::zero(), + sector_key_cid: None, + flags: SectorOnChainInfoFlags::SIMPLE_QA_POWER, + }) + .collect::>(); + + let sectors_len = sectors_to_add.len(); + + let total_pledge = BigInt::from(sectors_len) * sector_initial_pledge; + + let (needs_cron, fee_to_burn) = rt.transaction(|state: &mut State, rt| { + let current_balance = rt.current_balance(); + let available_balance = state + .get_unlocked_balance(¤t_balance) + .with_context_code(ExitCode::USR_ILLEGAL_STATE, || { + "failed to calculate unlocked balance" + })?; + if available_balance < total_pledge { + return Err(actor_error!( + insufficient_funds, + "insufficient funds for aggregate initial pledge requirement {}, available: {}", + total_pledge, + available_balance + )); + } + let needs_cron = !state.deadline_cron_active; + state.deadline_cron_active = true; + + state.allocate_sector_numbers( + store, + §or_numbers, + CollisionPolicy::DenyCollisions, + )?; + + state + .put_sectors(store, sectors_to_add.clone()) + .with_context_code(ExitCode::USR_ILLEGAL_STATE, || "failed to put new sectors")?; + + state.assign_sectors_to_deadline( + policy, + store, + rt.curr_epoch(), + sectors_to_add, + info.window_post_partition_sectors, + info.sector_size, + params.proving_deadline, + )?; + + state + .add_initial_pledge(&total_pledge) + .with_context_code(ExitCode::USR_ILLEGAL_STATE, || { + "failed to add initial pledgs" + })?; + + let fee_to_burn = repay_debts_or_abort(rt, state)?; + + Ok((needs_cron, fee_to_burn)) + })?; + + burn_funds(rt, fee_to_burn)?; + + let len_for_aggregate_fee = if sectors_len <= NI_AGGREGATE_FEE_BASE_SECTOR_COUNT { + 0 + } else { + sectors_len - NI_AGGREGATE_FEE_BASE_SECTOR_COUNT + }; + pay_aggregate_seal_proof_fee(rt, len_for_aggregate_fee)?; + + notify_pledge_changed(rt, &total_pledge)?; + + let state: State = rt.state()?; + state.check_balance_invariants(&rt.current_balance()).map_err(balance_invariants_broken)?; + + for sector in valid_sectors.iter() { + emit::sector_activated(rt, sector.sector_number, None, &[])?; + } + + if needs_cron { + let new_dl_info = state.deadline_info(rt.policy(), curr_epoch); + enroll_cron_event( + rt, + new_dl_info.last(), + CronEventPayload { event_type: CRON_EVENT_PROVING_DEADLINE }, + )?; + } + + Ok(ProveCommitSectorsNIReturn { activation_results: validation_batch }) + } + fn check_sector_proven( rt: &impl Runtime, params: CheckSectorProvenParams, @@ -4705,6 +4927,123 @@ fn validate_precommits( Ok((batch.gen(), verify_infos)) } +fn validate_ni_sectors( + rt: &impl Runtime, + sectors: &[SectorNIActivationInfo], + seal_proof_type: RegisteredSealProof, + all_or_nothing: bool, +) -> Result<(BatchReturn, Vec, BitField), ActorError> { + let receiver = rt.message().receiver(); + let miner_id = receiver.id().unwrap(); + let curr_epoch = rt.curr_epoch(); + let activation_epoch = curr_epoch; + let challenge_earliest = curr_epoch - rt.policy().max_prove_commit_ni_randomness_lookback; + let unsealed_cid = CompactCommD::empty().get_cid(seal_proof_type).unwrap(); + let entropy = serialize(&receiver, "address for get verify info")?; + + if sectors.is_empty() { + return Ok((BatchReturn::empty(), vec![], BitField::new())); + } + let mut batch = BatchReturnGen::new(sectors.len()); + + let mut verify_infos = vec![]; + let mut sector_numbers = BitField::new(); + for (i, sector) in sectors.iter().enumerate() { + let mut fail_validation = false; + + if sector_numbers.get(sector.sector_number) { + return Err(actor_error!( + illegal_argument, + "duplicate sector number {}", + sector.sector_number + )); + } + + if sector.sector_number > MAX_SECTOR_NUMBER { + warn!("sector number {} out of range 0..(2^63-1)", sector.sector_number); + fail_validation = true; + } + + sector_numbers.set(sector.sector_number); + + if let Err(err) = validate_expiration( + rt.policy(), + curr_epoch, + activation_epoch, + sector.expiration, + seal_proof_type, + ) { + warn!("invalid expiration: {}", err); + fail_validation = true; + } + + if sector.sealer_id != miner_id { + warn!("sealer must be the same as the receiver actor for all sectors"); + fail_validation = true; + } + + if sector.sector_number != sector.sealing_number { + warn!("sealing number must be same as sector number for all sectors"); + fail_validation = true; + } + + if !is_sealed_sector(§or.sealed_cid) { + warn!("sealed CID had wrong prefix"); + fail_validation = true; + } + + if sector.seal_rand_epoch >= curr_epoch { + // hard-fail because we can't access necessary randomness from the future + return Err(actor_error!( + illegal_argument, + "seal challenge epoch {} must be before now {}", + sector.seal_rand_epoch, + curr_epoch + )); + } + + if sector.seal_rand_epoch < challenge_earliest { + warn!( + "seal challenge epoch {} too old, must be after {}", + sector.seal_rand_epoch, challenge_earliest + ); + fail_validation = true; + } + + verify_infos.push(SectorSealProofInput { + registered_proof: seal_proof_type, + sector_number: sector.sealing_number, + randomness: Randomness( + rt.get_randomness_from_tickets( + DomainSeparationTag::SealRandomness, + sector.seal_rand_epoch, + &entropy, + )? + .into(), + ), + interactive_randomness: Randomness(vec![1u8; 32]), + sealed_cid: sector.sealed_cid, + unsealed_cid, + }); + + if fail_validation { + if all_or_nothing { + return Err(actor_error!( + illegal_argument, + "invalid NI commit {} while requiring activation success: {:?}", + i, + sector + )); + } + batch.add_fail(ExitCode::USR_ILLEGAL_ARGUMENT); + } else { + batch.add_success(); + } + } + + Ok((batch.gen(), verify_infos, sector_numbers)) +} + // Validates a batch of sector sealing proofs. fn validate_seal_proofs( seal_proof_type: RegisteredSealProof, @@ -4731,20 +5070,26 @@ fn validate_seal_aggregate_proof( proof: &RawBytes, sector_count: u64, policy: &Policy, + interactive: bool, ) -> Result<(), ActorError> { - if sector_count > policy.max_aggregated_sectors { + let (min, max) = match interactive { + true => (policy.min_aggregated_sectors, policy.max_aggregated_sectors), + false => (policy.min_aggregated_sectors_ni, policy.max_aggregated_sectors_ni), + }; + + if sector_count > max { return Err(actor_error!( illegal_argument, "too many sectors addressed, addressed {} want <= {}", sector_count, - policy.max_aggregated_sectors + max )); - } else if sector_count < policy.min_aggregated_sectors { + } else if sector_count < min { return Err(actor_error!( illegal_argument, "too few sectors addressed, addressed {} want >= {}", sector_count, - policy.min_aggregated_sectors + min )); } if proof.len() > policy.max_aggregated_proof_size { @@ -5686,6 +6031,7 @@ impl ActorCode for Actor { GetMultiaddrsExported => get_multiaddresses, ProveCommitSectors3 => prove_commit_sectors3, ProveReplicaUpdates3 => prove_replica_updates3, + ProveCommitSectorsNI => prove_commit_sectors_ni, } } diff --git a/actors/miner/src/policy.rs b/actors/miner/src/policy.rs index 6f025e42c..66e20130a 100644 --- a/actors/miner/src/policy.rs +++ b/actors/miner/src/policy.rs @@ -20,6 +20,9 @@ use super::{PowerPair, BASE_REWARD_FOR_DISPUTED_WINDOW_POST}; /// Precision used for making QA power calculations pub const SECTOR_QUALITY_PRECISION: i64 = 20; +/// Base number of sectors before imposing the additional aggregate fee in ProveCommitSectorsNI +pub const NI_AGGREGATE_FEE_BASE_SECTOR_COUNT: usize = 5; + lazy_static! { /// Quality multiplier for committed capacity (no deals) in a sector pub static ref QUALITY_BASE_MULTIPLIER: BigInt = BigInt::from(10); @@ -29,6 +32,7 @@ lazy_static! { /// Quality multiplier for verified deals in a sector pub static ref VERIFIED_DEAL_WEIGHT_MULTIPLIER: BigInt = BigInt::from(100); + } /// The maximum number of partitions that may be required to be loaded in a single invocation, @@ -51,6 +55,10 @@ pub fn can_pre_commit_seal_proof(policy: &Policy, proof: RegisteredSealProof) -> policy.valid_pre_commit_proof_type.contains(proof) } +pub fn can_prove_commit_ni_seal_proof(policy: &Policy, proof: RegisteredSealProof) -> bool { + policy.valid_prove_commit_ni_proof_type.contains(proof) +} + /// Checks whether a seal proof type is supported for new miners and sectors. pub fn can_extend_seal_proof_type(_proof: RegisteredSealProof) -> bool { true @@ -98,7 +106,12 @@ pub fn seal_proof_sector_maximum_lifetime(proof: RegisteredSealProof) -> Option< | StackedDRG2KiBV1P1_Feat_SyntheticPoRep | StackedDRG8MiBV1P1_Feat_SyntheticPoRep | StackedDRG512MiBV1P1_Feat_SyntheticPoRep - | StackedDRG64GiBV1P1_Feat_SyntheticPoRep => Some(EPOCHS_IN_YEAR * 5), + | StackedDRG64GiBV1P1_Feat_SyntheticPoRep + | StackedDRG32GiBV1P2_Feat_NiPoRep + | StackedDRG2KiBV1P2_Feat_NiPoRep + | StackedDRG8MiBV1P2_Feat_NiPoRep + | StackedDRG512MiBV1P2_Feat_NiPoRep + | StackedDRG64GiBV1P2_Feat_NiPoRep => Some(EPOCHS_IN_YEAR * 5), _ => None, } } @@ -158,6 +171,10 @@ pub fn qa_power_for_sector(size: SectorSize, sector: &SectorOnChainInfo) -> Stor qa_power_for_weight(size, duration, §or.deal_weight, §or.verified_deal_weight) } +pub fn raw_power_for_sector(size: SectorSize) -> StoragePower { + BigInt::from(size as u64) +} + /// Determine maximum number of deal miner's sector can hold pub fn sector_deals_max(policy: &Policy, size: SectorSize) -> u64 { cmp::max(256, size as u64 / policy.deal_limit_denominator) diff --git a/actors/miner/src/state.rs b/actors/miner/src/state.rs index 9fe58a9b2..c2c96f0e9 100644 --- a/actors/miner/src/state.rs +++ b/actors/miner/src/state.rs @@ -553,6 +553,55 @@ impl State { Ok(()) } + #[allow(clippy::too_many_arguments)] + pub fn assign_sectors_to_deadline( + &mut self, + policy: &Policy, + store: &BS, + current_epoch: ChainEpoch, + mut sectors: Vec, + partition_size: u64, + sector_size: SectorSize, + deadline_idx: u64, + ) -> Result<(), ActorError> { + let mut deadlines = self.load_deadlines(store)?; + let mut deadline = deadlines.load_deadline(store, deadline_idx)?; + + // Sort sectors by number to get better runs in partition bitfields. + sectors.sort_by_key(|info| info.sector_number); + + if !deadline_is_mutable( + policy, + self.current_proving_period_start(policy, current_epoch), + deadline_idx, + current_epoch, + ) { + return Err(actor_error!( + illegal_argument, + "proving deadline {} must not be the current or next deadline ", + deadline_idx + )); + } + + let quant = self.quant_spec_for_deadline(policy, deadline_idx); + let proven = false; + deadline + .add_sectors(store, partition_size, proven, §ors, sector_size, quant) + .with_context_code(ExitCode::USR_ILLEGAL_STATE, || { + format!("failed to add sectors to deadline {}", deadline_idx) + })?; + + deadlines + .update_deadline(policy, store, deadline_idx, &deadline) + .with_context_code(ExitCode::USR_ILLEGAL_STATE, || { + format!("failed to update deadline {}", deadline_idx) + })?; + self.save_deadlines(store, deadlines) + .with_context_code(ExitCode::USR_ILLEGAL_STATE, || "failed to save deadlines")?; + + Ok(()) + } + /// Pops up to `max_sectors` early terminated sectors from all deadlines. /// /// Returns `true` if we still have more early terminations to process. diff --git a/actors/miner/src/types.rs b/actors/miner/src/types.rs index 62f731ea6..a0a97d9c3 100644 --- a/actors/miner/src/types.rs +++ b/actors/miner/src/types.rs @@ -136,6 +136,27 @@ pub struct ProveCommitSectorParams { pub proof: RawBytes, } +// Note no UnsealedCID because it must be "zero" data. +#[derive(Clone, Debug, Eq, PartialEq, Serialize_tuple, Deserialize_tuple)] +pub struct SectorNIActivationInfo { + pub sealing_number: SectorNumber, // Sector number used to generate replica id + pub sealer_id: ActorID, // Must be set to ID of receiving actor for now + pub sealed_cid: Cid, // CommR + pub sector_number: SectorNumber, // Unique id of sector in actor state + pub seal_rand_epoch: ChainEpoch, + pub expiration: ChainEpoch, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize_tuple, Deserialize_tuple)] +pub struct ProveCommitSectorsNIParams { + pub sectors: Vec, // Information about sealing of each sector + pub aggregate_proof: RawBytes, // Aggregate proof for all sectors + pub seal_proof_type: RegisteredSealProof, // Proof type for each seal (must be an NI-PoRep variant) + pub aggregate_proof_type: RegisteredAggregateProof, // Proof type for aggregation + pub proving_deadline: u64, // The Window PoST deadline index at which to schedule the new sectors + pub require_activation_success: bool, // Whether to abort if any sector activation fails +} + #[derive(Clone, Debug, Eq, PartialEq, Serialize_tuple, Deserialize_tuple)] pub struct ProveCommitSectors3Params { // Activation manifest for each sector being proven. @@ -201,6 +222,12 @@ pub struct ProveCommitSectors3Return { pub activation_results: BatchReturn, } +#[derive(Clone, Debug, Eq, PartialEq, Serialize_tuple, Deserialize_tuple)] +#[serde(transparent)] +pub struct ProveCommitSectorsNIReturn { + pub activation_results: BatchReturn, +} + #[derive(Serialize_tuple, Deserialize_tuple)] pub struct CheckSectorProvenParams { pub sector_number: SectorNumber, diff --git a/actors/miner/tests/prove_commit_niporep.rs b/actors/miner/tests/prove_commit_niporep.rs new file mode 100644 index 000000000..8cbe80f67 --- /dev/null +++ b/actors/miner/tests/prove_commit_niporep.rs @@ -0,0 +1,560 @@ +use fil_actors_runtime::runtime::RuntimePolicy; +use fvm_ipld_encoding::ipld_block::IpldBlock; +use fvm_shared::{bigint::BigInt, clock::ChainEpoch, error::ExitCode}; + +use fil_actor_miner::{ + Actor, Method, SectorNIActivationInfo, SectorOnChainInfo, SectorOnChainInfoFlags, + NI_AGGREGATE_FEE_BASE_SECTOR_COUNT, +}; +use num_traits::Zero; +use util::*; + +mod util; + +const PERIOD_OFFSET: ChainEpoch = 100; + +#[test] +fn prove_zero_sectors_ni_fail() { + let h = ActorHarness::new(PERIOD_OFFSET); + let rt = h.new_runtime(); + rt.balance.replace(BIG_BALANCE.clone()); + let miner = rt.receiver.id().unwrap(); + let policy = rt.policy(); + + let seal_randomness_epoch = PERIOD_OFFSET + 1; + let activation_epoch = seal_randomness_epoch + 400; + let expiration = activation_epoch + policy.min_sector_expiration + 1; + + rt.set_epoch(seal_randomness_epoch); + h.construct_and_verify(&rt); + + rt.set_epoch(activation_epoch); + let params = h.make_prove_commit_ni_params(miner, &[], seal_randomness_epoch, expiration, 0); + + let res = rt.call::( + Method::ProveCommitSectorsNI as u64, + IpldBlock::serialize_cbor(¶ms).unwrap(), + ); + + assert!(res.is_err()); + assert_eq!(res.unwrap_err().exit_code(), ExitCode::USR_ILLEGAL_ARGUMENT); +} + +#[test] +fn prove_one_sector_aggregate_ni() { + let h = ActorHarness::new(PERIOD_OFFSET); + let rt = h.new_runtime(); + rt.balance.replace(BIG_BALANCE.clone()); + let miner = rt.receiver.id().unwrap(); + let policy = rt.policy(); + + let seal_randomness_epoch = PERIOD_OFFSET + 1; + let activation_epoch = seal_randomness_epoch + 400; + let expiration = activation_epoch + policy.min_sector_expiration + 1; + + rt.set_epoch(seal_randomness_epoch); + h.construct_and_verify(&rt); + + rt.set_epoch(activation_epoch); + + let sector_nums = (0..1).collect::>(); + let params = + h.make_prove_commit_ni_params(miner, §or_nums, seal_randomness_epoch, expiration, 0); + + let res = h.prove_commit_sectors_ni(&rt, params, true, noop()); + assert!(res.is_ok()); + + let activation_results = res.unwrap().activation_results; + assert_eq!(activation_results.success_count, 1); + assert!(activation_results.all_ok()); + + let deadlines = h.get_state(&rt).load_deadlines(&rt.store).unwrap(); + let deadline = deadlines.load_deadline(&rt.store, 0).unwrap(); + assert_eq!(deadline.live_sectors, 1); +} + +#[test] +fn prove_sectors_ni_short_duration_fail() { + let h = ActorHarness::new(PERIOD_OFFSET); + let rt = h.new_runtime(); + rt.balance.replace(BIG_BALANCE.clone()); + let miner = rt.receiver.id().unwrap(); + let policy = rt.policy(); + + let seal_randomness_epoch = PERIOD_OFFSET + 1; + let activation_epoch = seal_randomness_epoch + 400; + let expiration = activation_epoch + policy.min_sector_expiration - 1; + + rt.set_epoch(seal_randomness_epoch); + h.construct_and_verify(&rt); + + rt.set_epoch(activation_epoch); + + let sector_nums = (0..1).collect::>(); + let params = + h.make_prove_commit_ni_params(miner, §or_nums, seal_randomness_epoch, expiration, 0); + + let res = h.prove_commit_sectors_ni(&rt, params, true, noop()); + assert!(res.is_err()); + assert_eq!(res.unwrap_err().exit_code(), ExitCode::USR_ILLEGAL_ARGUMENT); +} + +#[test] +fn prove_sectors_max_aggregate_ni() { + let h = ActorHarness::new(PERIOD_OFFSET); + let rt = h.new_runtime(); + rt.balance.replace(BIG_BALANCE.clone()); + let miner = rt.receiver.id().unwrap(); + let policy = rt.policy(); + + let seal_randomness_epoch = PERIOD_OFFSET + 1; + let activation_epoch = seal_randomness_epoch + 400; + let expiration = activation_epoch + policy.min_sector_expiration + 1; + let proving_deadline = 42; + + rt.set_epoch(seal_randomness_epoch); + h.construct_and_verify(&rt); + + rt.set_epoch(activation_epoch); + + let sector_nums = (0..rt.policy.max_aggregated_sectors_ni).collect::>(); + let params = h.make_prove_commit_ni_params( + miner, + §or_nums, + seal_randomness_epoch, + expiration, + proving_deadline, + ); + let seal_proof_type = params.seal_proof_type; + + let res = h.prove_commit_sectors_ni(&rt, params, true, noop()); + + assert!(res.is_ok()); + + let activation_results = res.unwrap().activation_results; + assert_eq!(activation_results.success_count, rt.policy.max_aggregated_sectors_ni as u32); + assert!(activation_results.all_ok()); + + let deadlines = h.get_state(&rt).load_deadlines(&rt.store).unwrap(); + let deadline = deadlines.load_deadline(&rt.store, proving_deadline).unwrap(); + let partitions = deadline.partitions_amt(&rt.store).unwrap(); + let partition = partitions.get(0).unwrap().unwrap(); + let partition_sectors: Vec = partition.sectors.iter().collect(); + + assert_eq!(deadline.live_sectors, rt.policy.max_aggregated_sectors_ni); + + // Check if the sectors in partition are the ones we just committed + assert!(partition_sectors + .iter() + .rev() + .take(rt.policy.max_aggregated_sectors_ni as usize) + .rev() + .eq(sector_nums.iter())); + + let sectors: Vec = + sector_nums.iter().map(|sector_num| h.get_sector(&rt, *sector_num)).collect(); + + for (on_chain_sector, sector_num) in sectors.iter().zip(sector_nums) { + assert_eq!(sector_num, on_chain_sector.sector_number); + assert_eq!(seal_proof_type, on_chain_sector.seal_proof); + assert!(on_chain_sector.deprecated_deal_ids.is_empty()); + assert_eq!(activation_epoch, on_chain_sector.activation); + assert_eq!(expiration, on_chain_sector.expiration); + assert_eq!(BigInt::zero(), on_chain_sector.deal_weight); + assert_eq!(BigInt::zero(), on_chain_sector.verified_deal_weight); + assert_eq!(activation_epoch, on_chain_sector.power_base_epoch); + assert!(on_chain_sector.flags.contains(SectorOnChainInfoFlags::SIMPLE_QA_POWER)); + } +} + +#[test] +fn ni_prove_partialy_valid_sectors_not_required_activation() { + let h = ActorHarness::new(PERIOD_OFFSET); + let rt = h.new_runtime(); + rt.balance.replace(BIG_BALANCE.clone()); + let miner = rt.receiver.id().unwrap(); + let policy = rt.policy(); + + let seal_randomness_epoch = PERIOD_OFFSET + 1; + let activation_epoch = seal_randomness_epoch + 400; + let expiration = activation_epoch + policy.min_sector_expiration + 1; + let proving_deadline = 42; + + rt.set_epoch(seal_randomness_epoch); + h.construct_and_verify(&rt); + + rt.set_epoch(activation_epoch); + + let num_success: usize = 2; + let sector_nums = + (0..((NI_AGGREGATE_FEE_BASE_SECTOR_COUNT + num_success) as u64)).collect::>(); + let num_fails = NI_AGGREGATE_FEE_BASE_SECTOR_COUNT; + let mut params = h.make_prove_commit_ni_params( + miner, + §or_nums, + seal_randomness_epoch, + expiration, + proving_deadline, + ); + params.require_activation_success = false; + + let seal_proof_type = params.seal_proof_type; + + // Purposefully fail some sectors by setting the seal_rand_epoch to the activation_epoch + let res = h.prove_commit_sectors_ni( + &rt, + params, + true, + fail_for_seal_rand_epoch( + num_fails, + activation_epoch - policy.max_prove_commit_ni_randomness_lookback - 1, + ), + ); + assert!(res.is_ok()); + + let activation_results = res.unwrap().activation_results; + assert_eq!(activation_results.success_count, num_success as u32); + assert_eq!(activation_results.fail_codes.len(), num_fails); + + let deadlines = h.get_state(&rt).load_deadlines(&rt.store).unwrap(); + let deadline = deadlines.load_deadline(&rt.store, proving_deadline).unwrap(); + let partitions = deadline.partitions_amt(&rt.store).unwrap(); + let partition = partitions.get(0).unwrap().unwrap(); + let partition_sectors: Vec = partition.sectors.iter().collect(); + + assert_eq!(deadline.live_sectors, num_success as u64); + + // Check if the sectors in partition are the ones we just committed + let success_sectors = sector_nums.into_iter().skip(num_fails).collect::>(); + assert!(partition_sectors.iter().rev().take(num_success).rev().eq(success_sectors.iter())); + + let sectors: Vec = + success_sectors.iter().map(|sector_num| h.get_sector(&rt, *sector_num)).collect(); + + for (on_chain_sector, sector_num) in sectors.iter().zip(success_sectors) { + assert_eq!(sector_num, on_chain_sector.sector_number); + assert_eq!(seal_proof_type, on_chain_sector.seal_proof); + assert!(on_chain_sector.deprecated_deal_ids.is_empty()); + assert_eq!(activation_epoch, on_chain_sector.activation); + assert_eq!(expiration, on_chain_sector.expiration); + assert_eq!(BigInt::zero(), on_chain_sector.deal_weight); + assert_eq!(BigInt::zero(), on_chain_sector.verified_deal_weight); + assert_eq!(activation_epoch, on_chain_sector.power_base_epoch); + assert!(on_chain_sector.flags.contains(SectorOnChainInfoFlags::SIMPLE_QA_POWER)); + } +} + +#[test] +fn ni_prove_partialy_valid_sectors_by_sealer_id_not_required_activation() { + let h = ActorHarness::new(PERIOD_OFFSET); + let rt = h.new_runtime(); + rt.balance.replace(BIG_BALANCE.clone()); + let miner = rt.receiver.id().unwrap(); + let policy = rt.policy(); + + let seal_randomness_epoch = PERIOD_OFFSET + 1; + let activation_epoch = seal_randomness_epoch + 400; + let expiration = activation_epoch + policy.min_sector_expiration + 1; + let proving_deadline = 42; + + rt.set_epoch(seal_randomness_epoch); + h.construct_and_verify(&rt); + + rt.set_epoch(activation_epoch); + + let sector_nums = (0..rt.policy.max_aggregated_sectors_ni).collect::>(); + let num_fails = sector_nums.len() / 2; + let num_success = sector_nums.len() - num_fails; + let mut params = h.make_prove_commit_ni_params( + miner, + §or_nums, + seal_randomness_epoch, + expiration, + proving_deadline, + ); + params.require_activation_success = false; + + let seal_proof_type = params.seal_proof_type; + + // Purposefully fail some sectors by setting invalid sealer id + let res = + h.prove_commit_sectors_ni(&rt, params, true, fail_for_invalid_sealer_id(num_fails, miner)); + assert!(res.is_ok()); + + let activation_results = res.unwrap().activation_results; + assert_eq!(activation_results.success_count, num_success as u32); + assert_eq!(activation_results.fail_codes.len(), num_fails); + + let deadlines = h.get_state(&rt).load_deadlines(&rt.store).unwrap(); + let deadline = deadlines.load_deadline(&rt.store, proving_deadline).unwrap(); + let partitions = deadline.partitions_amt(&rt.store).unwrap(); + let partition = partitions.get(0).unwrap().unwrap(); + let partition_sectors: Vec = partition.sectors.iter().collect(); + + assert_eq!(deadline.live_sectors, num_success as u64); + + // Check if the sectors in partition are the ones we just committed + let success_sectors = sector_nums.into_iter().skip(num_fails).collect::>(); + assert!(partition_sectors.iter().rev().take(num_success).rev().eq(success_sectors.iter())); + + let sectors: Vec = + success_sectors.iter().map(|sector_num| h.get_sector(&rt, *sector_num)).collect(); + + for (on_chain_sector, sector_num) in sectors.iter().zip(success_sectors) { + assert_eq!(sector_num, on_chain_sector.sector_number); + assert_eq!(seal_proof_type, on_chain_sector.seal_proof); + assert!(on_chain_sector.deprecated_deal_ids.is_empty()); + assert_eq!(activation_epoch, on_chain_sector.activation); + assert_eq!(expiration, on_chain_sector.expiration); + assert_eq!(BigInt::zero(), on_chain_sector.deal_weight); + assert_eq!(BigInt::zero(), on_chain_sector.verified_deal_weight); + assert_eq!(activation_epoch, on_chain_sector.power_base_epoch); + assert!(on_chain_sector.flags.contains(SectorOnChainInfoFlags::SIMPLE_QA_POWER)); + } +} + +#[test] +fn ni_prove_partialy_valid_sectors_required_activation() { + let h = ActorHarness::new(PERIOD_OFFSET); + let rt = h.new_runtime(); + rt.balance.replace(BIG_BALANCE.clone()); + let miner = rt.receiver.id().unwrap(); + let policy = rt.policy(); + + let seal_randomness_epoch = PERIOD_OFFSET + 1; + let activation_epoch = seal_randomness_epoch + 400; + let expiration = activation_epoch + policy.min_sector_expiration + 1; + let proving_deadline = 42; + + rt.set_epoch(seal_randomness_epoch); + h.construct_and_verify(&rt); + + rt.set_epoch(activation_epoch); + + let sector_nums = (0..rt.policy.max_aggregated_sectors_ni).collect::>(); + let mut params = h.make_prove_commit_ni_params( + miner, + §or_nums, + seal_randomness_epoch, + expiration, + proving_deadline, + ); + params.require_activation_success = true; + + // Purposefully fail some sectors by setting the seal_rand_epoch to the activation_epoch + params.sectors[0].seal_rand_epoch = activation_epoch; + + let res = + h.prove_commit_sectors_ni(&rt, params, true, fail_for_seal_rand_epoch(1, activation_epoch)); + assert!(res.is_err()); + assert_eq!(res.unwrap_err().exit_code(), ExitCode::USR_ILLEGAL_ARGUMENT); +} + +#[test] +fn prove_sectors_multiple_max_aggregate_ni() { + let h = ActorHarness::new(PERIOD_OFFSET); + let rt = h.new_runtime(); + rt.balance.replace(BIG_BALANCE.clone()); + let miner = rt.receiver.id().unwrap(); + let policy = rt.policy(); + + let seal_randomness_epoch = PERIOD_OFFSET + 1; + let activation_epoch = seal_randomness_epoch + 400; + let expiration = activation_epoch + policy.min_sector_expiration + 1; + let proving_deadline = 42; + + rt.set_epoch(seal_randomness_epoch); + h.construct_and_verify(&rt); + + rt.set_epoch(activation_epoch); + + // Iterating multiple times to verify that the all sectors will be in the same deadline + for i in 0..3 { + let sector_nums = + (i * 1000..i * 1000 + rt.policy.max_aggregated_sectors_ni).collect::>(); + let params = h.make_prove_commit_ni_params( + miner, + §or_nums, + seal_randomness_epoch, + expiration, + proving_deadline, + ); + let seal_proof_type = params.seal_proof_type; + + let res = h.prove_commit_sectors_ni(&rt, params, i == 0, noop()); + assert!(res.is_ok()); + + let deadlines = h.get_state(&rt).load_deadlines(&rt.store).unwrap(); + let deadline = deadlines.load_deadline(&rt.store, proving_deadline).unwrap(); + let partitions = deadline.partitions_amt(&rt.store).unwrap(); + let partition = partitions.get(0).unwrap().unwrap(); + let partition_sectors: Vec = partition.sectors.iter().collect(); + + assert_eq!(deadline.live_sectors, (i + 1) * rt.policy.max_aggregated_sectors_ni); + + // Check if the last max_aggregated_sectors_ni sectors in partition are the ones we just committed + assert!(partition_sectors + .iter() + .rev() + .take(rt.policy.max_aggregated_sectors_ni as usize) + .rev() + .eq(sector_nums.iter())); + + let sectors: Vec = + sector_nums.iter().map(|sector_num| h.get_sector(&rt, *sector_num)).collect(); + + for (on_chain_sector, sector_num) in sectors.iter().zip(sector_nums) { + assert_eq!(sector_num, on_chain_sector.sector_number); + assert_eq!(seal_proof_type, on_chain_sector.seal_proof); + assert!(on_chain_sector.deprecated_deal_ids.is_empty()); + assert_eq!(activation_epoch, on_chain_sector.activation); + assert_eq!(expiration, on_chain_sector.expiration); + assert_eq!(BigInt::zero(), on_chain_sector.deal_weight); + assert_eq!(BigInt::zero(), on_chain_sector.verified_deal_weight); + assert_eq!(activation_epoch, on_chain_sector.power_base_epoch); + assert!(on_chain_sector.flags.contains(SectorOnChainInfoFlags::SIMPLE_QA_POWER)); + } + } +} + +#[test] +fn prove_too_many_sectors_ni_fail() { + let h = ActorHarness::new(PERIOD_OFFSET); + let rt = h.new_runtime(); + rt.balance.replace(BIG_BALANCE.clone()); + let miner = rt.receiver.id().unwrap(); + let policy = rt.policy(); + + let seal_randomness_epoch = PERIOD_OFFSET + 1; + let activation_epoch = seal_randomness_epoch + 400; + let expiration = activation_epoch + policy.min_sector_expiration + 1; + + rt.set_epoch(seal_randomness_epoch); + h.construct_and_verify(&rt); + + rt.set_epoch(activation_epoch); + + let sector_nums = (0..rt.policy.max_aggregated_sectors_ni + 1).collect::>(); + + let params = + h.make_prove_commit_ni_params(miner, §or_nums, seal_randomness_epoch, expiration, 0); + + let res = rt.call::( + Method::ProveCommitSectorsNI as u64, + IpldBlock::serialize_cbor(¶ms).unwrap(), + ); + + assert!(res.is_err()); + assert_eq!(res.unwrap_err().exit_code(), ExitCode::USR_ILLEGAL_ARGUMENT); +} + +#[test] +fn ni_prove_fail_sector_number_already_in_use() { + let h = ActorHarness::new(PERIOD_OFFSET); + let rt = h.new_runtime(); + rt.balance.replace(BIG_BALANCE.clone()); + let miner = rt.receiver.id().unwrap(); + let policy = rt.policy(); + + let seal_randomness_epoch = PERIOD_OFFSET + 1; + let activation_epoch = seal_randomness_epoch + 400; + let expiration = activation_epoch + policy.min_sector_expiration + 1; + let proving_deadline = 42; + + rt.set_epoch(seal_randomness_epoch); + h.construct_and_verify(&rt); + + rt.set_epoch(activation_epoch); + + let sector_nums = (0..10).collect::>(); + let params = h.make_prove_commit_ni_params( + miner, + §or_nums, + seal_randomness_epoch, + expiration, + proving_deadline, + ); + + let res = h.prove_commit_sectors_ni(&rt, params, true, noop()); + assert!(res.is_ok()); + + // Overlap sectors nums with the previous sectors nums that were committed + let sector_nums = (9..20).collect::>(); + let params = h.make_prove_commit_ni_params( + miner, + §or_nums, + seal_randomness_epoch, + expiration, + proving_deadline, + ); + let res = h.prove_commit_sectors_ni(&rt, params, false, noop()); + assert!(res.is_err()); + assert_eq!(res.unwrap_err().exit_code(), ExitCode::USR_ILLEGAL_ARGUMENT); +} + +#[test] +fn ni_prove_fail_duplicated_sector_numbers() { + let h = ActorHarness::new(PERIOD_OFFSET); + let rt = h.new_runtime(); + rt.balance.replace(BIG_BALANCE.clone()); + let miner = rt.receiver.id().unwrap(); + let policy = rt.policy(); + + let seal_randomness_epoch = PERIOD_OFFSET + 1; + let activation_epoch = seal_randomness_epoch + 400; + let expiration = activation_epoch + policy.min_sector_expiration + 1; + let proving_deadline = 42; + + rt.set_epoch(seal_randomness_epoch); + h.construct_and_verify(&rt); + + rt.set_epoch(activation_epoch); + + let mut sector_nums = (0..10).collect::>(); + // Duplicate the first sector number + sector_nums.push(*sector_nums.first().unwrap()); + + let params = h.make_prove_commit_ni_params( + miner, + §or_nums, + seal_randomness_epoch, + expiration, + proving_deadline, + ); + + let res = h.prove_commit_sectors_ni(&rt, params, true, noop()); + assert!(res.is_err()); + assert_eq!(res.unwrap_err().exit_code(), ExitCode::USR_ILLEGAL_ARGUMENT); +} + +fn fail_for_seal_rand_epoch( + num_fails: usize, + bad_seal_rand_epoch: i64, +) -> impl FnMut(&mut SectorNIActivationInfo, usize) -> bool { + move |s: &mut SectorNIActivationInfo, index: usize| { + if index < num_fails { + s.seal_rand_epoch = bad_seal_rand_epoch; + true + } else { + false + } + } +} + +fn fail_for_invalid_sealer_id( + num_fails: usize, + miner_id: u64, +) -> impl FnMut(&mut SectorNIActivationInfo, usize) -> bool { + move |s: &mut SectorNIActivationInfo, index: usize| { + if index < num_fails { + s.sealer_id = miner_id + 1; + true + } else { + false + } + } +} + +fn noop() -> impl FnMut(&mut SectorNIActivationInfo, usize) -> bool { + |_, _| false +} diff --git a/actors/miner/tests/types_test.rs b/actors/miner/tests/types_test.rs new file mode 100644 index 000000000..deee9c891 --- /dev/null +++ b/actors/miner/tests/types_test.rs @@ -0,0 +1,82 @@ +// Tests to match with Go github.com/filecoin-project/go-state-types/builtin/*/miner +mod serialization { + use std::str::FromStr; + + use cid::Cid; + use fil_actor_miner::{ProveCommitSectorsNIParams, SectorNIActivationInfo}; + use fvm_ipld_encoding::ipld_block::IpldBlock; + use fvm_shared::sector::{RegisteredAggregateProof, RegisteredSealProof}; + + #[test] + fn prove_commit_sectors_ni_params() { + let test_cases = vec![ + ( + ProveCommitSectorsNIParams { + sectors: vec![], + aggregate_proof: vec![].into(), + seal_proof_type: RegisteredSealProof::StackedDRG32GiBV1P1, + aggregate_proof_type: RegisteredAggregateProof::SnarkPackV2, + proving_deadline: 2, + require_activation_success: false, + }, + // [[],byte[],8,1,2,false] + "868040080102f4", + ), + ( + ProveCommitSectorsNIParams { + sectors: vec![SectorNIActivationInfo { + sealing_number: 1, + sealer_id: 2, + sealed_cid: Cid::from_str("bagboea4seaaqa").unwrap(), + sector_number: 3, + seal_rand_epoch: 4, + expiration: 5, + }], + seal_proof_type: RegisteredSealProof::StackedDRG32GiBV1P2_Feat_NiPoRep, + aggregate_proof: vec![0xde, 0xad, 0xbe, 0xef].into(), + aggregate_proof_type: RegisteredAggregateProof::SnarkPackV2, + proving_deadline: 6, + require_activation_success: true, + }, + // [[[1,2,bagboea4seaaqa,3,4,5]],byte[deadbeef],18,1,6,true] + "8681860102d82a49000182e2039220010003040544deadbeef120106f5", + ), + ( + ProveCommitSectorsNIParams { + sectors: vec![ + SectorNIActivationInfo { + sealing_number: 1, + sealer_id: 2, + sealed_cid: Cid::from_str("bagboea4seaaqa").unwrap(), + sector_number: 3, + seal_rand_epoch: 4, + expiration: 5, + }, + SectorNIActivationInfo { + sealing_number: 6, + sealer_id: 7, + sealed_cid: Cid::from_str("bagboea4seaaqc").unwrap(), + sector_number: 8, + seal_rand_epoch: 9, + expiration: 10, + }, + ], + seal_proof_type: RegisteredSealProof::StackedDRG32GiBV1P2_Feat_NiPoRep, + aggregate_proof: vec![0xde, 0xad, 0xbe, 0xef].into(), + aggregate_proof_type: RegisteredAggregateProof::SnarkPackV2, + proving_deadline: 11, + require_activation_success: false, + }, + // [[[1,2,bagboea4seaaqa,3,4,5],[6,7,bagboea4seaaqc,8,9,10]],byte[deadbeef],18,1,11,false] + "8682860102d82a49000182e20392200100030405860607d82a49000182e2039220010108090a44deadbeef12010bf4", + ), + ]; + + for (params, expected_hex) in test_cases { + let encoded = IpldBlock::serialize_cbor(¶ms).unwrap().unwrap(); + assert_eq!(const_hex::encode(&encoded.data), expected_hex); + let decoded: ProveCommitSectorsNIParams = IpldBlock::deserialize(&encoded).unwrap(); + assert_eq!(params, decoded); + } + } +} diff --git a/actors/miner/tests/util.rs b/actors/miner/tests/util.rs index 7de6fe29c..861c75d66 100644 --- a/actors/miner/tests/util.rs +++ b/actors/miner/tests/util.rs @@ -1,6 +1,6 @@ #![allow(clippy::all)] -use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::convert::TryInto; use std::iter; use std::ops::Neg; @@ -79,9 +79,13 @@ use fil_actor_miner::{ SectorReturn, SectorUpdateManifest, Sectors, State, SubmitWindowedPoStParams, TerminateSectorsParams, TerminationDeclaration, VerifiedAllocationKey, VestingFunds, WindowedPoSt, WithdrawBalanceParams, WithdrawBalanceReturn, CRON_EVENT_PROVING_DEADLINE, - NO_QUANTIZATION, REWARD_VESTING_SPEC, SECTORS_AMT_BITWIDTH, SECTOR_CONTENT_CHANGED, + NI_AGGREGATE_FEE_BASE_SECTOR_COUNT, NO_QUANTIZATION, REWARD_VESTING_SPEC, SECTORS_AMT_BITWIDTH, + SECTOR_CONTENT_CHANGED, +}; +use fil_actor_miner::{ + raw_power_for_sector, ProveCommitSectorsNIParams, ProveCommitSectorsNIReturn, + ProveReplicaUpdates3Params, ProveReplicaUpdates3Return, SectorNIActivationInfo, }; -use fil_actor_miner::{ProveReplicaUpdates3Params, ProveReplicaUpdates3Return}; use fil_actor_power::{ CurrentTotalPowerReturn, EnrollCronEventParams, Method as PowerMethod, UpdateClaimedPowerParams, }; @@ -536,6 +540,40 @@ impl ActorHarness { ProveCommitSectorParams { sector_number: sector_no, proof: vec![0u8; 192].into() } } + pub fn make_prove_commit_ni_params( + &self, + sealer_id: ActorID, + sector_nums: &[SectorNumber], + seal_rand_epoch: ChainEpoch, + expiration: ChainEpoch, + proving_deadline: u64, + ) -> ProveCommitSectorsNIParams { + fn make_proof(i: u8) -> RawBytes { + RawBytes::new(vec![i, i, i, i]) + } + + let sectors = sector_nums + .iter() + .map(|sector_num| SectorNIActivationInfo { + sealing_number: *sector_num, + sealer_id, + sector_number: *sector_num, + sealed_cid: make_sector_commr(*sector_num), + seal_rand_epoch, + expiration, + }) + .collect::>(); + + ProveCommitSectorsNIParams { + sectors, + seal_proof_type: RegisteredSealProof::StackedDRG32GiBV1P2_Feat_NiPoRep, + aggregate_proof: make_proof(0), + aggregate_proof_type: RegisteredAggregateProof::SnarkPackV2, + proving_deadline, + require_activation_success: true, + } + } + pub fn pre_commit_sector_batch( &self, rt: &MockRuntime, @@ -806,6 +844,148 @@ impl ActorHarness { Ok(self.get_sector(rt, sector_number)) } + pub fn prove_commit_sectors_ni( + &self, + rt: &MockRuntime, + mut params: ProveCommitSectorsNIParams, + first_for_miner: bool, + mut fail_fn: impl FnMut(&mut SectorNIActivationInfo, usize) -> bool, + ) -> Result { + let failed_sectors = params.sectors.iter_mut().enumerate().fold( + HashSet::new(), + |mut failed_sectors, (index, sector)| { + if fail_fn(sector, index) { + failed_sectors.insert(sector.sealing_number); + } + + failed_sectors + }, + ); + let fail_count = failed_sectors.len(); + + let expected_success_sectors = params + .sectors + .iter() + .map(|s| s.sealing_number) + .filter(|s| !failed_sectors.contains(&s)) + .collect::>(); + let expected_success_count = expected_success_sectors.len(); + assert_eq!(params.sectors.len(), expected_success_count + fail_count); + + rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, self.worker); + + let randomness = Randomness(TEST_RANDOMNESS_ARRAY_FROM_ONE.to_vec()); + + let entropy = serialize(&rt.receiver, "address for get verify info").unwrap(); + + params.sectors.iter().for_each(|s| { + rt.expect_get_randomness_from_tickets( + fil_actors_runtime::runtime::DomainSeparationTag::SealRandomness, + s.seal_rand_epoch, + entropy.to_vec(), + TEST_RANDOMNESS_ARRAY_FROM_ONE, + ); + }); + + params.sectors.iter().filter(|s| !failed_sectors.contains(&s.sector_number)).for_each( + |s| { + expect_sector_event(rt, "sector-activated", &s.sealing_number, None, &vec![]); + }, + ); + + let seal_verify_info = params + .sectors + .iter() + .map(|sector| AggregateSealVerifyInfo { + sector_number: sector.sealing_number, + randomness: randomness.clone(), + interactive_randomness: Randomness(vec![1u8; 32]), + sealed_cid: sector.sealed_cid.clone(), + unsealed_cid: CompactCommD::empty().get_cid(params.seal_proof_type).unwrap(), + }) + .collect::>(); + + rt.expect_aggregate_verify_seals(seal_verify_info, params.aggregate_proof.to_vec(), Ok(())); + rt.expect_validate_caller_addr(self.caller_addrs()); + + self.expect_query_network_info(rt); + + if params.sectors.len() - fail_count > NI_AGGREGATE_FEE_BASE_SECTOR_COUNT { + let aggregate_fee = aggregate_prove_commit_network_fee( + params.sectors.len() - fail_count - NI_AGGREGATE_FEE_BASE_SECTOR_COUNT, + &rt.base_fee.borrow(), + ); + rt.expect_send_simple( + BURNT_FUNDS_ACTOR_ADDR, + METHOD_SEND, + None, + aggregate_fee, + None, + ExitCode::OK, + ); + } + + let qa_sector_power = raw_power_for_sector(self.sector_size); + let sector_pledge = self.initial_pledge_for_power(rt, &qa_sector_power); + let total_pledge = BigInt::from(expected_success_count) * sector_pledge; + + rt.expect_send_simple( + STORAGE_POWER_ACTOR_ADDR, + PowerMethod::UpdatePledgeTotal as u64, + IpldBlock::serialize_cbor(&total_pledge).unwrap(), + TokenAmount::zero(), + None, + ExitCode::OK, + ); + + if first_for_miner { + let state = self.get_state(rt); + let dlinfo = new_deadline_info_from_offset_and_epoch( + &rt.policy, + state.proving_period_start, + *rt.epoch.borrow(), + ); + let cron_params = make_deadline_cron_event_params(dlinfo.last()); + rt.expect_send_simple( + STORAGE_POWER_ACTOR_ADDR, + PowerMethod::EnrollCronEvent as u64, + IpldBlock::serialize_cbor(&cron_params).unwrap(), + TokenAmount::zero(), + None, + ExitCode::OK, + ); + } + + let result = rt.call::( + Method::ProveCommitSectorsNI as u64, + IpldBlock::serialize_cbor(¶ms).unwrap(), + ); + + let result = result + .map(|r| { + let ret: ProveCommitSectorsNIReturn = r.unwrap().deserialize().unwrap(); + let successes = ret.activation_results.successes(¶ms.sectors); + + assert_eq!(params.sectors.len(), ret.activation_results.size()); + assert_eq!(expected_success_count as u32, ret.activation_results.success_count); + assert_eq!(fail_count, ret.activation_results.fail_codes.len()); + + successes.iter().for_each(|s| { + assert!(expected_success_sectors.contains(&s.sealing_number)); + }); + + ret + }) + .or_else(|e| { + rt.reset(); + Err(e) + })?; + + rt.verify(); + + Ok(result) + } + pub fn prove_commit_aggregate_sector( &self, rt: &MockRuntime, diff --git a/integration_tests/src/tests/mod.rs b/integration_tests/src/tests/mod.rs index aaf21a9a2..ba1637e88 100644 --- a/integration_tests/src/tests/mod.rs +++ b/integration_tests/src/tests/mod.rs @@ -38,5 +38,7 @@ mod withdraw_balance_test; pub use withdraw_balance_test::*; mod prove_commit3_test; pub use prove_commit3_test::*; +mod prove_commit_niporep_test; +pub use prove_commit_niporep_test::*; mod replica_update3_test; pub use replica_update3_test::*; diff --git a/integration_tests/src/tests/prove_commit_niporep_test.rs b/integration_tests/src/tests/prove_commit_niporep_test.rs new file mode 100644 index 000000000..00e91127f --- /dev/null +++ b/integration_tests/src/tests/prove_commit_niporep_test.rs @@ -0,0 +1,362 @@ +use fil_actors_runtime::runtime::Policy; +use fvm_ipld_encoding::ipld_block::IpldBlock; +use fvm_ipld_encoding::RawBytes; +use fvm_shared::bigint::BigInt; +use fvm_shared::econ::TokenAmount; +use fvm_shared::error::ExitCode; +use fvm_shared::sector::{RegisteredAggregateProof, RegisteredSealProof, SectorNumber}; +use num_traits::Zero; + +use export_macro::vm_test; +use fil_actor_miner::{Method as MinerMethod, SectorOnChainInfoFlags}; +use fil_actor_miner::{ + ProveCommitSectorsNIParams, ProveCommitSectorsNIReturn, SectorNIActivationInfo, +}; +use fil_actors_runtime::test_utils::make_sealed_cid; +use vm_api::trace::{EmittedEvent, ExpectInvocation}; +use vm_api::util::{apply_ok, DynBlockstore}; +use vm_api::VM; + +use crate::expects::Expect; +use crate::util::{ + advance_by_deadline_to_epoch, advance_by_deadline_to_index, create_accounts, create_miner, + deadline_state, declare_recovery, override_compute_unsealed_sector_cid, sector_info, + submit_windowed_post, try_sector_info, +}; + +#[vm_test] +pub fn prove_commit_ni_whole_success_test(v: &dyn VM) { + // Expectations depend on the correct unsealed CID for empty sector. + override_compute_unsealed_sector_cid(v); + let addrs = create_accounts(v, 3, &TokenAmount::from_whole(10_000)); + let seal_proof = RegisteredSealProof::StackedDRG32GiBV1P2_Feat_NiPoRep; + let (owner, worker, _, _) = (addrs[0], addrs[0], addrs[1], addrs[2]); + let worker_id = worker.id().unwrap(); + let (maddr, _) = create_miner( + v, + &owner, + &worker, + seal_proof.registered_window_post_proof().unwrap(), + &TokenAmount::from_whole(8_000), + ); + let miner_id = maddr.id().unwrap(); + let policy = Policy::default(); + + // Onboard a batch of sectors + let seal_rand_epoch = v.epoch(); + let activation_epoch = seal_rand_epoch + policy.max_prove_commit_ni_randomness_lookback / 2; + let expiration = activation_epoch + policy.min_sector_expiration + 1; + let first_sector_number: SectorNumber = 100; + let sector_nos = [ + first_sector_number, + first_sector_number + 1, + first_sector_number + 2, + first_sector_number + 3, + first_sector_number + 4, + ]; + let proving_deadline = 7; + + let sectors_info: Vec = sector_nos + .iter() + .map(|sector_number| SectorNIActivationInfo { + sealing_number: *sector_number, + sealer_id: miner_id, + sector_number: *sector_number, + sealed_cid: make_sealed_cid(format!("sn: {}", sector_number).as_bytes()), + seal_rand_epoch, + expiration, + }) + .collect(); + + // Prove-commit NI-PoRep + let aggregate_proof = RawBytes::new(vec![1, 2, 3, 4]); + let params = ProveCommitSectorsNIParams { + sectors: sectors_info.clone(), + seal_proof_type: RegisteredSealProof::StackedDRG32GiBV1P2_Feat_NiPoRep, + aggregate_proof, + aggregate_proof_type: RegisteredAggregateProof::SnarkPackV2, + proving_deadline, + require_activation_success: true, + }; + + v.set_epoch(activation_epoch); + + let pcsni_ret: ProveCommitSectorsNIReturn = apply_ok( + v, + &worker, + &maddr, + &TokenAmount::zero(), + MinerMethod::ProveCommitSectorsNI as u64, + Some(params.clone()), + ) + .deserialize() + .unwrap(); + + assert_eq!(pcsni_ret.activation_results.size(), 5); + assert!(pcsni_ret.activation_results.all_ok()); + assert_eq!(pcsni_ret.activation_results.codes(), [ExitCode::OK].repeat(5)); + + let events: Vec = sector_nos + .iter() + .map(|sector_number| { + Expect::build_sector_activation_event( + "sector-activated", + miner_id, + *sector_number, + None, + &vec![], + ) + }) + .collect(); + + ExpectInvocation { + from: worker_id, + to: maddr, + method: MinerMethod::ProveCommitSectorsNI as u64, + params: Some(IpldBlock::serialize_cbor(¶ms).unwrap()), + subinvocs: None, + events: Some(events), + ..Default::default() + } + .matches(v.take_invocations().last().unwrap()); + + // Checks on sector state. + let sectors = sector_nos + .iter() + .map(|sector_number| sector_info(v, &maddr, *sector_number)) + .collect::>(); + + for (on_chain_sector, input_sector) in sectors.iter().zip(sectors_info) { + assert_eq!(input_sector.sector_number, on_chain_sector.sector_number); + assert_eq!(params.seal_proof_type, on_chain_sector.seal_proof); + assert_eq!(input_sector.sealed_cid, on_chain_sector.sealed_cid); + assert!(on_chain_sector.deprecated_deal_ids.is_empty()); + assert_eq!(activation_epoch, on_chain_sector.activation); + assert_eq!(input_sector.expiration, on_chain_sector.expiration); + assert_eq!(BigInt::zero(), on_chain_sector.deal_weight); + assert_eq!(BigInt::zero(), on_chain_sector.verified_deal_weight); + assert_eq!(activation_epoch, on_chain_sector.power_base_epoch); + assert!(on_chain_sector.flags.contains(SectorOnChainInfoFlags::SIMPLE_QA_POWER)); + } + + let deadline = deadline_state(v, &maddr, proving_deadline); + assert_eq!(deadline.live_sectors, sector_nos.len() as u64); +} + +#[vm_test] +pub fn prove_commit_ni_partial_success_not_required_test(v: &dyn VM) { + // Expectations depend on the correct unsealed CID for empty sector. + override_compute_unsealed_sector_cid(v); + let addrs = create_accounts(v, 3, &TokenAmount::from_whole(10_000)); + let seal_proof = RegisteredSealProof::StackedDRG32GiBV1P2_Feat_NiPoRep; + let (owner, worker, _, _) = (addrs[0], addrs[0], addrs[1], addrs[2]); + let worker_id = worker.id().unwrap(); + let (maddr, _) = create_miner( + v, + &owner, + &worker, + seal_proof.registered_window_post_proof().unwrap(), + &TokenAmount::from_whole(8_000), + ); + let miner_id = maddr.id().unwrap(); + let policy = Policy::default(); + + // Onboard a batch of sectors + let seal_rand_epoch = v.epoch(); + let activation_epoch = seal_rand_epoch + policy.max_prove_commit_ni_randomness_lookback / 2; + let expiration = activation_epoch + policy.min_sector_expiration + 1; + let first_sector_number: SectorNumber = 100; + let sector_nos: Vec<_> = (first_sector_number..first_sector_number + 20).collect(); + let proving_deadline = 7; + + let mut sectors_info: Vec = sector_nos + .iter() + .map(|sector_number| SectorNIActivationInfo { + sealing_number: *sector_number, + sealer_id: miner_id, + sector_number: *sector_number, + sealed_cid: make_sealed_cid(format!("sn: {}", sector_number).as_bytes()), + seal_rand_epoch, + expiration, + }) + .collect(); + + // non-fatal errors + sectors_info[0].seal_rand_epoch = + v.epoch() - policy.max_prove_commit_ni_randomness_lookback - 1; + sectors_info[2].sealer_id = miner_id + 1; + + let invalid_sector_nos = vec![sector_nos[0], sector_nos[2]]; + let valid_sector_nos: Vec<_> = + sector_nos.iter().enumerate().filter(|&(i, _)| i != 0 && i != 2).map(|(_, &v)| v).collect(); + let valid_sectors_info: Vec<_> = sectors_info + .iter() + .enumerate() + .filter(|&(i, _)| i != 0 && i != 2) + .map(|(_, v)| v.clone()) + .collect(); + + // Prove-commit NI-PoRep + let aggregate_proof = RawBytes::new(vec![1, 2, 3, 4]); + let params = ProveCommitSectorsNIParams { + sectors: sectors_info.clone(), + seal_proof_type: RegisteredSealProof::StackedDRG32GiBV1P2_Feat_NiPoRep, + aggregate_proof, + aggregate_proof_type: RegisteredAggregateProof::SnarkPackV2, + proving_deadline, + require_activation_success: false, + }; + + v.set_epoch(activation_epoch); + + let pcsni_ret: ProveCommitSectorsNIReturn = apply_ok( + v, + &worker, + &maddr, + &TokenAmount::zero(), + MinerMethod::ProveCommitSectorsNI as u64, + Some(params.clone()), + ) + .deserialize() + .unwrap(); + + assert_eq!(pcsni_ret.activation_results.size(), sector_nos.len()); + assert!(!pcsni_ret.activation_results.all_ok()); + assert_eq!( + pcsni_ret.activation_results.codes(), + (0..sector_nos.len()) + .map(|i| if i == 0 || i == 2 { ExitCode::USR_ILLEGAL_ARGUMENT } else { ExitCode::OK }) + .collect::>() + ); + + let events: Vec = valid_sector_nos + .iter() + .map(|sector_number| { + Expect::build_sector_activation_event( + "sector-activated", + miner_id, + *sector_number, + None, + &vec![], + ) + }) + .collect(); + + ExpectInvocation { + from: worker_id, + to: maddr, + method: MinerMethod::ProveCommitSectorsNI as u64, + params: Some(IpldBlock::serialize_cbor(¶ms).unwrap()), + subinvocs: None, + events: Some(events), + ..Default::default() + } + .matches(v.take_invocations().last().unwrap()); + + // Checks on sector state. + invalid_sector_nos + .iter() + .for_each(|sector_number| assert!(try_sector_info(v, &maddr, *sector_number).is_none())); + + let sectors = valid_sector_nos + .iter() + .map(|sector_number| sector_info(v, &maddr, *sector_number)) + .collect::>(); + + for (on_chain_sector, input_sector) in sectors.iter().zip(valid_sectors_info) { + assert_eq!(input_sector.sector_number, on_chain_sector.sector_number); + assert_eq!(params.seal_proof_type, on_chain_sector.seal_proof); + assert_eq!(input_sector.sealed_cid, on_chain_sector.sealed_cid); + assert!(on_chain_sector.deprecated_deal_ids.is_empty()); + assert_eq!(activation_epoch, on_chain_sector.activation); + assert_eq!(input_sector.expiration, on_chain_sector.expiration); + assert_eq!(BigInt::zero(), on_chain_sector.deal_weight); + assert_eq!(BigInt::zero(), on_chain_sector.verified_deal_weight); + assert_eq!(activation_epoch, on_chain_sector.power_base_epoch); + assert!(on_chain_sector.flags.contains(SectorOnChainInfoFlags::SIMPLE_QA_POWER)); + } + + // Check if sectors are properly assigned to deadline + let deadline = deadline_state(v, &maddr, proving_deadline); + assert_eq!(deadline.live_sectors, valid_sector_nos.len() as u64); + + let store = &DynBlockstore::wrap(v.blockstore()); + let partition = deadline.load_partition(store, 0).unwrap(); + for sector_number in invalid_sector_nos { + assert!(!partition.sectors.get(sector_number)); + } + for sector_number in &valid_sector_nos { + assert!(partition.sectors.get(*sector_number)); + assert!(partition.unproven.get(*sector_number)); + } + + // Advance to proving deadline and submit WindowPoSt + let deadline_info = advance_by_deadline_to_index(v, &maddr, proving_deadline); + + let deadline = deadline_state(v, &maddr, proving_deadline); + let submissions = deadline.optimistic_proofs_amt(store).unwrap(); + assert_eq!(submissions.count(), 0); + + submit_windowed_post(v, &worker, &maddr, deadline_info, 0, Some(partition.unproven_power)); + + // Check if post is registered in deadline submissions + let deadline = deadline_state(v, &maddr, proving_deadline); + let submissions = deadline.optimistic_proofs_amt(store).unwrap(); + assert_eq!(submissions.count(), 1); + + // Move to next deadline and check if sectors are active + let deadline_info = advance_by_deadline_to_index( + v, + &maddr, + proving_deadline + 1 % policy.wpost_proving_period as u64, + ); + let deadline = deadline_state(v, &maddr, proving_deadline); + let partition = deadline.load_partition(store, 0).unwrap(); + + for sector_number in &valid_sector_nos { + assert!(partition.active_sectors().get(*sector_number)); + assert!(partition.faults.is_empty()); + } + + // Move to next deadline period while skipping window post submission + // and check if sectors are faulty + advance_by_deadline_to_epoch(v, &maddr, deadline_info.close + policy.wpost_proving_period); + let deadline = deadline_state(v, &maddr, proving_deadline); + let partition = deadline.load_partition(store, 0).unwrap(); + + for sector_number in &valid_sector_nos { + assert!(partition.faults.get(*sector_number)); + assert!(partition.active_sectors().is_empty()); + assert!(partition.recoveries.is_empty()); + } + + // Recover faulty sectors + for sector_number in &valid_sector_nos { + declare_recovery(v, &worker, &maddr, proving_deadline, 0, *sector_number); + } + let deadline = deadline_state(v, &maddr, proving_deadline); + let partition = deadline.load_partition(store, 0).unwrap(); + for sector_number in &valid_sector_nos { + assert!(partition.faults.get(*sector_number)); + assert!(partition.recoveries.get(*sector_number)); + } + + // Move to next deadline period and prove sectors + let deadline_info = advance_by_deadline_to_index(v, &maddr, proving_deadline); + submit_windowed_post(v, &worker, &maddr, deadline_info, 0, None); + + // Move to next deadline and check if sectors are active + advance_by_deadline_to_index( + v, + &maddr, + proving_deadline + 1 % policy.wpost_proving_period as u64, + ); + let deadline = deadline_state(v, &maddr, proving_deadline); + let partition = deadline.load_partition(store, 0).unwrap(); + + for sector_number in &valid_sector_nos { + assert!(partition.active_sectors().get(*sector_number)); + assert!(partition.faults.is_empty()); + assert!(partition.recoveries.is_empty()); + } +} diff --git a/integration_tests/src/util/mod.rs b/integration_tests/src/util/mod.rs index 487c48544..87a53837d 100644 --- a/integration_tests/src/util/mod.rs +++ b/integration_tests/src/util/mod.rs @@ -150,6 +150,11 @@ pub fn sector_info(v: &dyn VM, m: &Address, s: SectorNumber) -> SectorOnChainInf st.get_sector(&DynBlockstore::wrap(v.blockstore()), s).unwrap().unwrap() } +pub fn try_sector_info(v: &dyn VM, m: &Address, s: SectorNumber) -> Option { + let st: MinerState = get_state(v, m).unwrap(); + st.get_sector(&DynBlockstore::wrap(v.blockstore()), s).unwrap() +} + pub fn miner_power(v: &dyn VM, m: &Address) -> PowerPair { let st: PowerState = get_state(v, &STORAGE_POWER_ACTOR_ADDR).unwrap(); let claim = st.get_claim(&DynBlockstore::wrap(v.blockstore()), m).unwrap().unwrap(); diff --git a/runtime/src/runtime/policy.rs b/runtime/src/runtime/policy.rs index 3f8004264..333f30075 100644 --- a/runtime/src/runtime/policy.rs +++ b/runtime/src/runtime/policy.rs @@ -81,6 +81,18 @@ pub struct Policy { /// used to ensure it is not predictable by miner. pub pre_commit_challenge_delay: ChainEpoch, + /// Maximum amount of sectors that can be aggregated in NI PoRep. + pub max_aggregated_sectors_ni: u64, + + /// Minimum amount of sectors that can be aggregated. + pub min_aggregated_sectors_ni: u64, + + /// Number of epochs between publishing the commit and when the randomness for non interactive PoRep is drawn + pub max_prove_commit_ni_randomness_lookback: ChainEpoch, + + /// Allowed non interactive proof types for new miners + pub valid_prove_commit_ni_proof_type: ProofSet, + /// Lookback from the deadline's challenge window opening from which to sample chain randomness for the challenge seed. pub wpost_challenge_lookback: ChainEpoch, @@ -182,6 +194,10 @@ impl Default for Policy { posted_partitions_max: policy_constants::POSTED_PARTITIONS_MAX, max_pre_commit_randomness_lookback: policy_constants::MAX_PRE_COMMIT_RANDOMNESS_LOOKBACK, + valid_prove_commit_ni_proof_type: ProofSet::default_seal_ni_proofs(), + max_aggregated_sectors_ni: policy_constants::MAX_AGGREGATED_SECTORS_NI, + min_aggregated_sectors_ni: policy_constants::MIN_AGGREGATED_SECTORS_NI, + max_prove_commit_ni_randomness_lookback: policy_constants::MAX_PROVE_COMMIT_NI_LOOKBACK, pre_commit_challenge_delay: policy_constants::PRE_COMMIT_CHALLENGE_DELAY, wpost_challenge_lookback: policy_constants::WPOST_CHALLENGE_LOOKBACK, fault_declaration_cutoff: policy_constants::FAULT_DECLARATION_CUTOFF, @@ -196,7 +212,7 @@ impl Default for Policy { chain_finality: policy_constants::CHAIN_FINALITY, valid_post_proof_type: ProofSet::default_post_proofs(), - valid_pre_commit_proof_type: ProofSet::default_seal_proofs(), + valid_pre_commit_proof_type: ProofSet::default_precommit_seal_proofs(), minimum_verified_allocation_size: StoragePower::from_i32( policy_constants::MINIMUM_VERIFIED_ALLOCATION_SIZE, ) @@ -291,6 +307,16 @@ pub mod policy_constants { #[cfg(feature = "short-precommit")] pub const PRE_COMMIT_CHALLENGE_DELAY: ChainEpoch = 10; + // Maximum number of epochs within which to fetch a valid seal randomness from the chain for + // a non-interactive PoRep proof. This balances the need to tie the seal to a particular chain with + // but makes allowance for service providers to offer pre-sealed sectors within a larger window of + // time. + pub const MAX_PROVE_COMMIT_NI_LOOKBACK: ChainEpoch = 180 * EPOCHS_IN_DAY; + + pub const MAX_AGGREGATED_SECTORS_NI: u64 = 65; + + pub const MIN_AGGREGATED_SECTORS_NI: u64 = 1; + // This lookback exists so that deadline windows can be non-overlapping (which make the programming simpler) // but without making the miner wait for chain stability before being able to start on PoSt computation. // The challenge is available this many epochs before the window is actually open to receiving a PoSt. @@ -365,7 +391,7 @@ pub struct ProofSet(Vec); const REGISTERED_POST_PROOF_VARIANTS: usize = 15; /// The number of total possible types (enum variants) of RegisteredSealProof -const REGISTERED_SEAL_PROOF_VARIANTS: usize = 15; +const REGISTERED_SEAL_PROOF_VARIANTS: usize = 20; impl ProofSet { /// Create a `ProofSet` for enabled `RegisteredPoStProof`s @@ -395,7 +421,7 @@ impl ProofSet { } /// Create a `ProofSet` for enabled `RegisteredSealProof`s - pub fn default_seal_proofs() -> Self { + pub fn default_precommit_seal_proofs() -> Self { let mut proofs = vec![false; REGISTERED_SEAL_PROOF_VARIANTS]; #[cfg(feature = "sector-2k")] { @@ -432,6 +458,34 @@ impl ProofSet { ProofSet(proofs) } + pub fn default_seal_ni_proofs() -> Self { + let mut proofs = vec![false; REGISTERED_SEAL_PROOF_VARIANTS]; + #[cfg(feature = "sector-2k")] + { + proofs[i64::from(RegisteredSealProof::StackedDRG2KiBV1P2_Feat_NiPoRep) as usize] = true; + } + #[cfg(feature = "sector-8m")] + { + proofs[i64::from(RegisteredSealProof::StackedDRG8MiBV1P2_Feat_NiPoRep) as usize] = true; + } + #[cfg(feature = "sector-512m")] + { + proofs[i64::from(RegisteredSealProof::StackedDRG512MiBV1P2_Feat_NiPoRep) as usize] = + true; + } + #[cfg(feature = "sector-32g")] + { + proofs[i64::from(RegisteredSealProof::StackedDRG32GiBV1P2_Feat_NiPoRep) as usize] = + true; + } + #[cfg(feature = "sector-64g")] + { + proofs[i64::from(RegisteredSealProof::StackedDRG64GiBV1P2_Feat_NiPoRep) as usize] = + true; + } + ProofSet(proofs) + } + /// Checks if the requested proof type exists in the set pub fn contains>(&self, proof: P) -> bool { let index: i64 = proof.into(); diff --git a/test_vm/tests/suite/mod.rs b/test_vm/tests/suite/mod.rs index 292713eca..a2a9e91e2 100644 --- a/test_vm/tests/suite/mod.rs +++ b/test_vm/tests/suite/mod.rs @@ -12,6 +12,7 @@ mod market_miner_withdrawal_test; mod multisig_test; mod power_scenario_tests; mod prove_commit3_test; +mod prove_commit_niporep_test; mod publish_deals_test; mod replica_update3_test; mod replica_update_test; diff --git a/test_vm/tests/suite/prove_commit_niporep_test.rs b/test_vm/tests/suite/prove_commit_niporep_test.rs new file mode 100644 index 000000000..1b332a9de --- /dev/null +++ b/test_vm/tests/suite/prove_commit_niporep_test.rs @@ -0,0 +1,19 @@ +use fil_actors_integration_tests::tests::{ + prove_commit_ni_partial_success_not_required_test, prove_commit_ni_whole_success_test, +}; +use fil_actors_runtime::test_blockstores::MemoryBlockstore; +use test_vm::TestVM; + +#[test] +fn prove_commit_ni_whole_success() { + let store = MemoryBlockstore::new(); + let v = TestVM::new_with_singletons(store); + prove_commit_ni_whole_success_test(&v); +} + +#[test] +fn prove_commit_ni_partial_success_not_required() { + let store = MemoryBlockstore::new(); + let v = TestVM::new_with_singletons(store); + prove_commit_ni_partial_success_not_required_test(&v); +}