diff --git a/actors/miner/src/lib.rs b/actors/miner/src/lib.rs index cd825a620f..5662b1f1b1 100644 --- a/actors/miner/src/lib.rs +++ b/actors/miner/src/lib.rs @@ -1591,18 +1591,19 @@ impl Actor { return Err(actor_error!(illegal_argument, "too many deals for sector {} > {}", precommit.deal_ids.len(), deal_count_max)); } - let computed_cid = &verify_return.unsealed_cids[i]; - // 1. verify that precommit.unsealed_cid is correct // 2. create a new on_chain_precommit - let commd = precommit.unsealed_cid.unwrap(); - if commd.0 != *computed_cid { - return Err(actor_error!(illegal_argument, "computed {:?} and passed {:?} CommDs not equal", - computed_cid, commd)); + let declared_commd = precommit.unsealed_cid.unwrap(); + // This is not a CompactCommD, None means that nothing was computed and nothing needs to be checked + if let Some(computed_cid) = verify_return.unsealed_cids[i] { + // It is possible the computed commd is the zero commd so expand declared_commd + if declared_commd.get_cid(precommit.seal_proof)? != computed_cid { + return Err(actor_error!(illegal_argument, "computed {:?} and passed {:?} CommDs not equal", + computed_cid, declared_commd)); + } } - let on_chain_precommit = SectorPreCommitInfo { seal_proof: precommit.seal_proof, sector_number: precommit.sector_number, @@ -1610,7 +1611,7 @@ impl Actor { seal_rand_epoch: precommit.seal_rand_epoch, deal_ids: precommit.deal_ids, expiration: precommit.expiration, - unsealed_cid: commd, + unsealed_cid: declared_commd, }; // Build on-chain record. diff --git a/actors/miner/tests/prove_replica_test.rs b/actors/miner/tests/prove_replica_test.rs index 2cb85d57c6..dbe7c05b4e 100644 --- a/actors/miner/tests/prove_replica_test.rs +++ b/actors/miner/tests/prove_replica_test.rs @@ -6,10 +6,11 @@ use fvm_shared::{bigint::Zero, clock::ChainEpoch, econ::TokenAmount, ActorID}; use fil_actor_miner::ext::verifreg::AllocationID; use fil_actor_miner::ProveReplicaUpdates2Return; use fil_actor_miner::{ - CompactCommD, PieceActivationManifest, SectorOnChainInfo, SectorPreCommitInfo, - SectorPreCommitOnChainInfo, SectorUpdateManifest, State, + CompactCommD, PieceActivationManifest, ProveCommitSectors2Return, SectorActivationManifest, + SectorOnChainInfo, SectorPreCommitInfo, SectorPreCommitOnChainInfo, SectorUpdateManifest, + State, }; -use fil_actors_runtime::test_utils::{make_sealed_cid, MockRuntime}; +use fil_actors_runtime::test_utils::{make_piece_cid, make_sealed_cid, MockRuntime}; use fil_actors_runtime::{runtime::Runtime, BatchReturn, DealWeight, EPOCHS_IN_DAY}; use util::*; @@ -85,18 +86,14 @@ fn onboard_empty_sectors( precommits.iter().map(|sector| h.get_precommit(rt, sector.sector_number)).collect(); // Prove the sectors. - // Note: migrate this to ProveCommitSectors2 (batch) when the harness supports it. + // Use the new ProveCommitSectors2 rt.set_epoch(precommit_epoch + rt.policy.pre_commit_challenge_delay + 1); let sectors: Vec = precommits .iter() .map(|pc| { - h.prove_commit_sector_and_confirm( - rt, - pc, - h.make_prove_commit_params(pc.info.sector_number), - ProveCommitConfig::default(), - ) - .unwrap() + let sector_activation = make_activation_manifest(pc.info.sector_number, &[]); + h.prove_commit_sectors2(rt, &[sector_activation], false, false, false).unwrap(); + h.get_sector(rt, pc.info.sector_number) }) .collect(); @@ -105,7 +102,65 @@ fn onboard_empty_sectors( sectors } -fn make_empty_precommits( +#[test] +fn prove_commit2_basic() { + let h = ActorHarness::new_with_options(HarnessOptions::default()); + let rt = h.new_runtime(); + rt.set_balance(BIG_BALANCE.clone()); + let client_id: ActorID = 1000; + + h.construct_and_verify(&rt); + + // Precommit sectors + let precommit_epoch = *rt.epoch.borrow(); + let first_sector_number = 100; + let sector_count = 4; + let sector_expiry = *rt.epoch.borrow() + DEFAULT_SECTOR_EXPIRATION_DAYS * EPOCHS_IN_DAY; + let precommits = make_fake_commd_precommits( + &h, + first_sector_number, + precommit_epoch - 1, + sector_expiry, + sector_count, + ); + h.pre_commit_sector_batch_v2(&rt, &precommits, true, &TokenAmount::zero()).unwrap(); + let snos: Vec = + precommits.iter().map(|pci: &SectorPreCommitInfo| pci.sector_number).collect(); + + // Update them in batch, each with a single piece. + let piece_size = h.sector_size as u64; + let sector_activations = vec![ + make_activation_manifest(snos[0], &[(piece_size, 0, 0, 0)]), // No alloc or deal + make_activation_manifest(snos[1], &[(piece_size, client_id, 1000, 0)]), // Just an alloc + make_activation_manifest(snos[2], &[(piece_size, 0, 0, 2000)]), // Just a deal + make_activation_manifest(snos[3], &[(piece_size, client_id, 1001, 2001)]), // Alloc and deal + ]; + + rt.set_epoch(precommit_epoch + rt.policy.pre_commit_challenge_delay + 1); + let result = h.prove_commit_sectors2(&rt, §or_activations, true, true, false).unwrap(); + assert_eq!( + ProveCommitSectors2Return { activation_results: BatchReturn::ok(precommits.len() as u32) }, + result + ); + + let duration = sector_expiry - *rt.epoch.borrow(); + let expected_weight = DealWeight::from(piece_size) * duration; + let raw_power = StoragePower::from(h.sector_size as u64); + let verified_power = &raw_power * 10; + let raw_pledge = h.initial_pledge_for_power(&rt, &raw_power); + let verified_pledge = h.initial_pledge_for_power(&rt, &verified_power); + + // Sector 0: Even though there's no "deal", the data weight is set. + verify_weights(&rt, &h, snos[0], &expected_weight, &DealWeight::zero(), &raw_pledge); + // Sector 1: With an allocation, the verified weight is set instead. + verify_weights(&rt, &h, snos[1], &DealWeight::zero(), &expected_weight, &verified_pledge); + // Sector 2: Deal weight is set. + verify_weights(&rt, &h, snos[2], &expected_weight, &DealWeight::zero(), &raw_pledge); + // Sector 3: Deal doesn't make a difference to verified weight only set. + verify_weights(&rt, &h, snos[3], &DealWeight::zero(), &expected_weight, &verified_pledge); +} + +pub fn make_empty_precommits( h: &ActorHarness, first_sector_number: SectorNumber, challenge: ChainEpoch, @@ -126,6 +181,42 @@ fn make_empty_precommits( .collect() } +// Note this matches the faked commD computation in the testing harness +pub fn make_fake_commd_precommits( + h: &ActorHarness, + first_sector_number: SectorNumber, + challenge: ChainEpoch, + expiration: ChainEpoch, + count: usize, +) -> Vec { + (0..count) + .map(|i| { + let sector_number = first_sector_number + i as u64; + h.make_pre_commit_params_v2( + sector_number, + challenge, + expiration, + vec![], + CompactCommD(Some(make_piece_cid( + format!("unsealed-{}", sector_number).as_bytes(), + ))), + ) + }) + .collect() +} + +pub fn make_activation_manifest( + sector_number: SectorNumber, + piece_specs: &[(u64, ActorID, AllocationID, DealID)], +) -> SectorActivationManifest { + let pieces: Vec = piece_specs + .iter() + .enumerate() + .map(|(i, (sz, client, alloc, deal))| make_piece_manifest(i, *sz, *client, *alloc, *deal)) + .collect(); + SectorActivationManifest { sector_number, pieces } +} + fn make_update_manifest( st: &State, store: &impl Blockstore, diff --git a/actors/miner/tests/util.rs b/actors/miner/tests/util.rs index 55578104ff..1a8f29f09e 100644 --- a/actors/miner/tests/util.rs +++ b/actors/miner/tests/util.rs @@ -74,8 +74,9 @@ use fil_actor_miner::{ MinerConstructorParams as ConstructorParams, MinerInfo, Partition, PendingBeneficiaryChange, PieceActivationManifest, PieceChange, PieceReturn, PoStPartition, PowerPair, PreCommitSectorBatchParams, PreCommitSectorBatchParams2, PreCommitSectorParams, - ProveCommitAggregateParams, ProveCommitSectorParams, RecoveryDeclaration, - ReportConsensusFaultParams, SectorChanges, SectorContentChangedParams, + ProveCommitAggregateParams, ProveCommitSectorParams, ProveCommitSectors2Params, + ProveCommitSectors2Return, RecoveryDeclaration, ReportConsensusFaultParams, + SectorActivationManifest, SectorChanges, SectorContentChangedParams, SectorContentChangedReturn, SectorOnChainInfo, SectorPreCommitInfo, SectorPreCommitOnChainInfo, SectorReturn, SectorUpdateManifest, Sectors, State, SubmitWindowedPoStParams, TerminateSectorsParams, TerminationDeclaration, VerifiedAllocationKey, VestingFunds, @@ -843,32 +844,7 @@ impl ActorHarness { self.expect_query_network_info(rt); // expect randomness queries for provided precommits - let mut seal_rands = Vec::new(); - let mut seal_int_rands = Vec::new(); - - for precommit in precommits.iter() { - let seal_rand = TEST_RANDOMNESS_ARRAY_FROM_ONE; - seal_rands.push(seal_rand.clone()); - let seal_int_rand = TEST_RANDOMNESS_ARRAY_FROM_TWO; - seal_int_rands.push(seal_int_rand.clone()); - let interactive_epoch = - precommit.pre_commit_epoch + rt.policy.pre_commit_challenge_delay; - - let receiver = rt.receiver; - let buf = serialize(&receiver, "receiver address")?; - rt.expect_get_randomness_from_tickets( - DomainSeparationTag::SealRandomness, - precommit.info.seal_rand_epoch, - buf.clone().into(), - seal_rand, - ); - rt.expect_get_randomness_from_beacon( - DomainSeparationTag::InteractiveSealChallengeSeed, - interactive_epoch, - buf.into(), - seal_int_rand.clone(), - ); - } + let (seal_rands, seal_int_rands) = expect_validate_precommits(rt, &precommits)?; // verify syscall let mut svis = Vec::new(); @@ -1107,6 +1083,187 @@ impl ActorHarness { } } + pub fn prove_commit_sectors2( + &self, + rt: &MockRuntime, + sector_activations: &[SectorActivationManifest], + require_activation_success: bool, + require_notification_success: bool, + aggregate: bool, + ) -> Result { + fn make_proof(i: u8) -> RawBytes { + RawBytes::new(vec![i, i, i, i]) + } + let mut aggregate_proof = RawBytes::default(); + let mut sector_proofs = vec![]; + let precommits: Vec = + sector_activations.iter().map(|sa| self.get_precommit(rt, sa.sector_number)).collect(); + + let (seal_rands, seal_int_rands) = expect_validate_precommits(rt, &precommits)?; + let comm_ds: Vec<_> = precommits + .iter() + .map(|pc| pc.info.unsealed_cid.get_cid(pc.info.seal_proof).unwrap()) + .collect(); + + if aggregate { + aggregate_proof = make_proof(0); + let svis = precommits + .iter() + .enumerate() + .map(|(i, pci)| AggregateSealVerifyInfo { + sector_number: pci.info.sector_number, + randomness: Randomness(seal_rands.get(i).cloned().unwrap().into()), + interactive_randomness: Randomness( + seal_int_rands.get(i).cloned().unwrap().into(), + ), + sealed_cid: pci.info.sealed_cid, + unsealed_cid: comm_ds[i], + }) + .collect(); + rt.expect_aggregate_verify_seals(svis, aggregate_proof.clone().into(), Ok(())); + } else { + let mut svis = vec![]; + let mut result = vec![]; + sector_activations.iter().zip(precommits).enumerate().for_each(|(i, (sa, pci))| { + let proof = make_proof(sa.sector_number as u8); + sector_proofs.push(proof.clone()); + svis.push(SealVerifyInfo { + registered_proof: self.seal_proof_type, + sector_id: SectorID { + miner: STORAGE_MARKET_ACTOR_ADDR.id().unwrap(), + number: sa.sector_number, + }, + deal_ids: vec![], + randomness: Randomness(seal_int_rands.get(i).cloned().unwrap().into()), + interactive_randomness: Randomness( + seal_int_rands.get(i).cloned().unwrap().into(), + ), + proof: proof.into(), + sealed_cid: pci.info.sealed_cid, + unsealed_cid: comm_ds[i], + }); + result.push(true); + }); + rt.expect_batch_verify_seals(svis, Ok(result)) + } + + let params = ProveCommitSectors2Params { + sector_activations: sector_activations.into(), + aggregate_proof, + sector_proofs, + require_activation_success, + require_notification_success, + }; + rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, self.worker); + rt.expect_validate_caller_addr(self.caller_addrs()); + + let mut sector_allocation_claims = Vec::new(); + let mut sector_claimed_space = Vec::new(); + let mut expected_pledge = TokenAmount::zero(); + let mut expected_qa_power = StoragePower::zero(); + let mut expected_sector_notifications = Vec::new(); // Assuming all to f05 + for sa in sector_activations { + expect_compute_unsealed_cid_from_pieces( + rt, + sa.sector_number, + self.seal_proof_type, + &sa.pieces, + ); + let precommit = self.get_precommit(rt, sa.sector_number); + let claims = SectorAllocationClaims { + sector: sa.sector_number, + expiry: precommit.info.expiration, + claims: claims_from_pieces(&sa.pieces), + }; + sector_claimed_space.push(SectorClaimSummary { + claimed_space: claims.claims.iter().map(|c| c.size.0).sum::().into(), + }); + sector_allocation_claims.push(claims); + + let notifications = notifications_from_pieces(&sa.pieces); + if !notifications.is_empty() { + expected_sector_notifications.push(SectorChanges { + sector: sa.sector_number, + minimum_commitment_epoch: precommit.info.expiration, + added: notifications, + }); + } + let duration = precommit.info.expiration - *rt.epoch.borrow(); + let mut deal_size = DealWeight::zero(); + let mut verified_size = DealWeight::zero(); + for piece in &sa.pieces { + if piece.verified_allocation_key.is_some() { + verified_size += piece.size.0 * duration as u64; + } else { + deal_size += piece.size.0 * duration as u64; + } + } + let qa_power_delta = + qa_power_for_weight(self.sector_size, duration, &deal_size, &verified_size); + expected_qa_power += &qa_power_delta; + expected_pledge += self.initial_pledge_for_power(rt, &qa_power_delta); + } + + // Expect claiming of verified space for each piece that specified an allocation ID. + if !sector_allocation_claims.iter().all(|sector| sector.claims.is_empty()) { + rt.expect_send_simple( + VERIFIED_REGISTRY_ACTOR_ADDR, + CLAIM_ALLOCATIONS_METHOD, + IpldBlock::serialize_cbor(&ClaimAllocationsParams { + sectors: sector_allocation_claims, + all_or_nothing: require_activation_success, + }) + .unwrap(), + TokenAmount::zero(), + IpldBlock::serialize_cbor(&ClaimAllocationsReturn { + sector_results: BatchReturn::ok(sector_activations.len() as u32), + sector_claims: sector_claimed_space, + }) + .unwrap(), + ExitCode::OK, + ); + } + + // Expect pledge & power updates. + self.expect_query_network_info(rt); + expect_update_pledge(rt, &expected_pledge); + + // Expect SectorContentChanged notification to market. + let sector_notification_resps: Vec = expected_sector_notifications + .iter() + .map(|sn| SectorReturn { added: vec![PieceReturn { accepted: true }; sn.added.len()] }) + .collect(); + if !sector_notification_resps.is_empty() { + rt.expect_send_simple( + STORAGE_MARKET_ACTOR_ADDR, + SECTOR_CONTENT_CHANGED, + IpldBlock::serialize_cbor(&SectorContentChangedParams { + sectors: expected_sector_notifications, + }) + .unwrap(), + TokenAmount::zero(), + IpldBlock::serialize_cbor(&SectorContentChangedReturn { + sectors: sector_notification_resps, + }) + .unwrap(), + ExitCode::OK, + ); + } + + let result: ProveCommitSectors2Return = rt + .call::( + MinerMethod::ProveCommitSectors2 as u64, + IpldBlock::serialize_cbor(¶ms).unwrap(), + ) + .unwrap() + .unwrap() + .deserialize() + .unwrap(); + assert_eq!(sector_activations.len(), result.activation_results.size()); + rt.verify(); + Ok(result) + } + pub fn prove_replica_updates2_batch( &self, rt: &MockRuntime, @@ -3030,15 +3187,50 @@ fn expect_compute_unsealed_cid_from_pieces( let expected_unsealed_cid_inputs: Vec = pieces.iter().map(|p| PieceInfo { size: p.size, cid: p.cid }).collect(); let unsealed_cid = make_piece_cid(format!("unsealed-{}", sno).as_bytes()); - rt.expect_compute_unsealed_sector_cid( - seal_proof_type, - expected_unsealed_cid_inputs, - unsealed_cid.clone(), - ExitCode::OK, - ); + if !expected_unsealed_cid_inputs.is_empty() { + rt.expect_compute_unsealed_sector_cid( + seal_proof_type, + expected_unsealed_cid_inputs, + unsealed_cid.clone(), + ExitCode::OK, + ); + } unsealed_cid } +fn expect_validate_precommits( + rt: &MockRuntime, + precommits: &[SectorPreCommitOnChainInfo], +) -> Result<(Vec<[u8; 32]>, Vec<[u8; 32]>), ActorError> { + // expect randomness queries for provided precommits + let mut seal_rands = Vec::new(); + let mut seal_int_rands = Vec::new(); + + for precommit in precommits.iter() { + let seal_rand = TEST_RANDOMNESS_ARRAY_FROM_ONE; + seal_rands.push(seal_rand.clone()); + let seal_int_rand = TEST_RANDOMNESS_ARRAY_FROM_TWO; + seal_int_rands.push(seal_int_rand.clone()); + let interactive_epoch = precommit.pre_commit_epoch + rt.policy.pre_commit_challenge_delay; + + let receiver = rt.receiver; + let buf = serialize(&receiver, "receiver address")?; + rt.expect_get_randomness_from_tickets( + DomainSeparationTag::SealRandomness, + precommit.info.seal_rand_epoch, + buf.clone().into(), + seal_rand, + ); + rt.expect_get_randomness_from_beacon( + DomainSeparationTag::InteractiveSealChallengeSeed, + interactive_epoch, + buf.into(), + seal_int_rand.clone(), + ); + } + Ok((seal_rands, seal_int_rands)) +} + fn expect_update_pledge(rt: &MockRuntime, pledge_delta: &TokenAmount) { if !pledge_delta.is_zero() { rt.expect_send_simple(