diff --git a/actors/market/src/emit.rs b/actors/market/src/emit.rs new file mode 100644 index 000000000..35348b38e --- /dev/null +++ b/actors/market/src/emit.rs @@ -0,0 +1,76 @@ +use fil_actors_runtime::runtime::Runtime; +use fil_actors_runtime::{ActorError, EventBuilder}; +use fvm_shared::deal::DealID; +use fvm_shared::ActorID; + +/// Indicates a deal has been published. +pub fn deal_published( + rt: &impl Runtime, + client: ActorID, + provider: ActorID, + deal_id: DealID, +) -> Result<(), ActorError> { + rt.emit_event( + &EventBuilder::new() + .typ("deal-published") + .with_parties(deal_id, client, provider) + .build()?, + ) +} + +/// Indicates a deal has been activated. +pub fn deal_activated( + rt: &impl Runtime, + deal_id: DealID, + client: ActorID, + provider: ActorID, +) -> Result<(), ActorError> { + rt.emit_event( + &EventBuilder::new() + .typ("deal-activated") + .with_parties(deal_id, client, provider) + .build()?, + ) +} + +/// Indicates a deal has been terminated. +pub fn deal_terminated( + rt: &impl Runtime, + deal_id: DealID, + client: ActorID, + provider: ActorID, +) -> Result<(), ActorError> { + rt.emit_event( + &EventBuilder::new() + .typ("deal-terminated") + .with_parties(deal_id, client, provider) + .build()?, + ) +} + +/// Indicates a deal has been completed successfully. +pub fn deal_completed( + rt: &impl Runtime, + deal_id: DealID, + client: ActorID, + provider: ActorID, +) -> Result<(), ActorError> { + rt.emit_event( + &EventBuilder::new() + .typ("deal-completed") + .with_parties(deal_id, client, provider) + .build()?, + ) +} + +trait WithParties { + fn with_parties(self, id: DealID, client: ActorID, provider: ActorID) -> EventBuilder; +} + +impl WithParties for EventBuilder { + fn with_parties(self, id: DealID, client: ActorID, provider: ActorID) -> EventBuilder { + self.field_indexed("id", &id) + .field_indexed("client", &client) + .field_indexed("provider", &provider) + } +} diff --git a/actors/market/src/lib.rs b/actors/market/src/lib.rs index 5517bd0a8..725107866 100644 --- a/actors/market/src/lib.rs +++ b/actors/market/src/lib.rs @@ -54,6 +54,7 @@ pub mod policy; pub mod testing; mod deal; +mod emit; mod state; mod types; @@ -470,19 +471,26 @@ impl Actor { // notify clients, any failures cause the entire publish_storage_deals method to fail // it's unsafe to ignore errors here, since that could be used to attack storage contract clients // that might be unaware they're making storage deals - for (i, valid_deal) in valid_deals.iter().enumerate() { + for (valid_deal, &deal_id) in valid_deals.iter().zip(&new_deal_ids) { _ = extract_send_result(rt.send_simple( &valid_deal.proposal.client, MARKET_NOTIFY_DEAL_METHOD, IpldBlock::serialize_cbor(&MarketNotifyDealParams { proposal: valid_deal.serialized_proposal.to_vec(), - deal_id: new_deal_ids[i], + deal_id, })?, TokenAmount::zero(), )) .with_context_code(ExitCode::USR_ILLEGAL_ARGUMENT, || { format!("failed to notify deal with proposal cid {}", valid_deal.cid) })?; + + emit::deal_published( + rt, + valid_deal.proposal.client.id().unwrap(), + valid_deal.proposal.provider.id().unwrap(), + deal_id, + )?; } Ok(PublishStorageDealsReturn { ids: new_deal_ids, valid_deals: valid_input_bf }) @@ -555,7 +563,7 @@ impl Actor { let mut deal_states: Vec<(DealID, DealState)> = vec![]; let mut batch_gen = BatchReturnGen::new(params.sectors.len()); let mut activations: Vec = vec![]; - let mut activated_deals = BTreeSet::::new(); + let mut activated_deals: HashSet = HashSet::new(); let mut sectors_deals: Vec<(SectorNumber, SectorDealIDs)> = vec![]; 'sector: for sector in params.sectors { @@ -598,8 +606,7 @@ impl Actor { validated_proposals.push(proposal); } - let mut verified_infos = vec![]; - let mut nonverified_deal_space = BigInt::zero(); + let mut activated = vec![]; // Given that all deals validated, prepare the state updates for them all. // There's no continue below here to ensure updates are consistent. // Any error must abort. @@ -609,16 +616,12 @@ impl Actor { let alloc_id = pending_deal_allocation_ids.delete(deal_id)?.unwrap_or(NO_ALLOCATION_ID); - if alloc_id != NO_ALLOCATION_ID { - verified_infos.push(VerifiedDealInfo { - client: proposal.client.id().unwrap(), - allocation_id: alloc_id, - data: proposal.piece_cid, - size: proposal.piece_size, - }) - } else { - nonverified_deal_space += proposal.piece_size.0; - } + activated.push(ActivatedDeal { + client: proposal.client.id().unwrap(), + allocation_id: alloc_id, + data: proposal.piece_cid, + size: proposal.piece_size, + }); // Prepare initial deal state. deal_states.push(( @@ -640,11 +643,17 @@ impl Actor { sectors_deals .push((sector.sector_number, SectorDealIDs { deals: sector.deal_ids.clone() })); - activations.push(SectorDealActivation { - nonverified_deal_space, - verified_infos, - unsealed_cid: data_commitment, - }); + activations.push(SectorDealActivation { activated, unsealed_cid: data_commitment }); + + for (deal_id, proposal) in sector.deal_ids.iter().zip(&validated_proposals) { + emit::deal_activated( + rt, + *deal_id, + proposal.client.id().unwrap(), + proposal.provider.id().unwrap(), + )?; + } + batch_gen.add_success(); } @@ -843,6 +852,13 @@ impl Actor { .entry(state.sector_number) .or_default() .push(id); + + emit::deal_terminated( + rt, + id, + deal.client.id().unwrap(), + deal.provider.id().unwrap(), + )?; } st.remove_sector_deal_ids(rt.store(), &provider_deals_to_remove)?; @@ -926,13 +942,14 @@ impl Actor { // https://github.com/filecoin-project/builtin-actors/issues/1389 // handling of legacy deals is still done in cron. we handle such deals here and continue to // reschedule them. eventually, all legacy deals will expire and the below code can be removed. - let (slash_amount, _payment_amount, remove_deal) = st.process_deal_update( - rt.store(), - &state, - &deal_proposal, - &dcid, - curr_epoch, - )?; + let (slash_amount, _payment_amount, completed, remove_deal) = st + .process_deal_update( + rt.store(), + &state, + &deal_proposal, + &dcid, + curr_epoch, + )?; if remove_deal { // TODO: remove handling for terminated-deal slashing when marked-for-termination deals are all processed @@ -949,6 +966,15 @@ impl Actor { .entry(state.sector_number) .or_default() .push(deal_id); + + if !completed { + emit::deal_terminated( + rt, + deal_id, + deal_proposal.client.id().unwrap(), + deal_proposal.provider.id().unwrap(), + )?; + } } else { if !slash_amount.is_zero() { return Err(actor_error!( @@ -972,6 +998,15 @@ impl Actor { ); new_updates_scheduled.entry(next_epoch).or_default().push(deal_id); } + + if completed { + emit::deal_completed( + rt, + deal_id, + deal_proposal.client.id().unwrap(), + deal_proposal.provider.id().unwrap(), + )?; + } } epochs_completed.push(i); } @@ -1247,7 +1282,7 @@ impl Actor { )); } - let (_, payment_amount, remove_deal) = match st.process_deal_update( + let (_, payment_amount, completed, remove_deal) = match st.process_deal_update( rt.store(), &deal_state, &deal_proposal, @@ -1269,6 +1304,15 @@ impl Actor { .entry(deal_state.sector_number) .or_default() .push(deal_id); + + if !completed { + emit::deal_terminated( + rt, + deal_id, + deal_proposal.client.id().unwrap(), + deal_proposal.provider.id().unwrap(), + )?; + } } else { deal_state.last_updated_epoch = curr_epoch; new_deal_states.push((deal_id, deal_state)); @@ -1279,6 +1323,15 @@ impl Actor { payment: payment_amount, }); batch_gen.add_success(); + + if completed { + emit::deal_completed( + rt, + deal_id, + deal_proposal.client.id().unwrap(), + deal_proposal.provider.id().unwrap(), + )?; + } } st.put_deal_states(rt.store(), &new_deal_states)?; diff --git a/actors/market/src/state.rs b/actors/market/src/state.rs index d5db93a5a..4f6642f82 100644 --- a/actors/market/src/state.rs +++ b/actors/market/src/state.rs @@ -827,6 +827,7 @@ impl State { ( /* slash_amount */ TokenAmount, /* payment_amount */ TokenAmount, + /* is_deal_completed */ bool, /* remove */ bool, ), ActorError, @@ -859,7 +860,7 @@ impl State { // this is a safe no-op but can happen if a storage provider calls settle_deal_payments too early if deal.start_epoch > epoch { - return Ok((TokenAmount::zero(), TokenAmount::zero(), false)); + return Ok((TokenAmount::zero(), TokenAmount::zero(), false, false)); } let payment_end_epoch = if ever_slashed { @@ -921,15 +922,15 @@ impl State { self.slash_balance(store, &deal.provider, &slashed, Reason::ProviderCollateral) .context("slashing balance")?; - return Ok((slashed, payment_remaining + elapsed_payment, true)); + return Ok((slashed, payment_remaining + elapsed_payment, false, true)); } if epoch >= deal.end_epoch { self.process_deal_expired(store, deal, state)?; - return Ok((TokenAmount::zero(), elapsed_payment, true)); + return Ok((TokenAmount::zero(), elapsed_payment, true, true)); } - Ok((TokenAmount::zero(), elapsed_payment, false)) + Ok((TokenAmount::zero(), elapsed_payment, false, false)) } pub fn process_slashed_deal( diff --git a/actors/market/src/types.rs b/actors/market/src/types.rs index 432000914..1ebb0dfd7 100644 --- a/actors/market/src/types.rs +++ b/actors/market/src/types.rs @@ -102,11 +102,11 @@ pub struct BatchActivateDealsParams { pub compute_cid: bool, } -// Information about a verified deal that has been activated. +// Information about a deal that has been activated. #[derive(Serialize_tuple, Deserialize_tuple, Debug, Clone, Eq, PartialEq)] -pub struct VerifiedDealInfo { +pub struct ActivatedDeal { pub client: ActorID, - pub allocation_id: AllocationID, + pub allocation_id: AllocationID, // NO_ALLOCATION_ID for unverified deals. pub data: Cid, pub size: PaddedPieceSize, } @@ -114,11 +114,8 @@ pub struct VerifiedDealInfo { // Information about a sector-grouping of deals that have been activated. #[derive(Serialize_tuple, Deserialize_tuple, Debug, Clone, Eq, PartialEq)] pub struct SectorDealActivation { - /// The total size of the non-verified deals activated. - #[serde(with = "bigint_ser")] - pub nonverified_deal_space: BigInt, - /// Information about each verified deal activated. - pub verified_infos: Vec, + /// Information about each deal activated. + pub activated: Vec, /// Unsealed CID computed from the deals specified for the sector. /// A None indicates no deals were specified, or the computation was not requested. pub unsealed_cid: Option, diff --git a/actors/market/tests/activate_deal_failures.rs b/actors/market/tests/activate_deal_failures.rs index 0ca20fb96..43d17dd4b 100644 --- a/actors/market/tests/activate_deal_failures.rs +++ b/actors/market/tests/activate_deal_failures.rs @@ -131,6 +131,7 @@ fn assert_activation_failure( deal_ids: vec![deal_id], }], false, + &[], ) .unwrap(); let res: BatchActivateDealsResult = diff --git a/actors/market/tests/batch_activate_deals.rs b/actors/market/tests/batch_activate_deals.rs index ec5d9376a..d37877e05 100644 --- a/actors/market/tests/batch_activate_deals.rs +++ b/actors/market/tests/batch_activate_deals.rs @@ -168,7 +168,7 @@ fn sectors_fail_and_succeed_independently_during_batch_activation() { }, ]; - let res = batch_activate_deals_raw(&rt, PROVIDER_ADDR, sectors_deals, false).unwrap(); + let res = batch_activate_deals_raw(&rt, PROVIDER_ADDR, sectors_deals, false, &[id_4]).unwrap(); let res: BatchActivateDealsResult = res.unwrap().deserialize().expect("VerifyDealsForActivation failed!"); @@ -227,7 +227,7 @@ fn handles_sectors_empty_of_deals_gracefully() { SectorDeals { sector_number: 3, deal_ids: vec![], sector_type, sector_expiry: END_EPOCH }, ]; - let res = batch_activate_deals_raw(&rt, PROVIDER_ADDR, sectors_deals, false).unwrap(); + let res = batch_activate_deals_raw(&rt, PROVIDER_ADDR, sectors_deals, false, &[id_1]).unwrap(); let res: BatchActivateDealsResult = res.unwrap().deserialize().expect("VerifyDealsForActivation failed!"); @@ -273,7 +273,7 @@ fn fails_to_activate_single_sector_duplicate_deals() { sector_expiry: END_EPOCH, }, ]; - let res = batch_activate_deals_raw(&rt, PROVIDER_ADDR, sectors_deals, false).unwrap(); + let res = batch_activate_deals_raw(&rt, PROVIDER_ADDR, sectors_deals, false, &[]).unwrap(); let res: BatchActivateDealsResult = res.unwrap().deserialize().expect("VerifyDealsForActivation failed!"); @@ -328,7 +328,8 @@ fn fails_to_activate_cross_sector_duplicate_deals() { }, ]; - let res = batch_activate_deals_raw(&rt, PROVIDER_ADDR, sectors_deals, false).unwrap(); + let res = + batch_activate_deals_raw(&rt, PROVIDER_ADDR, sectors_deals, false, &[id_1, id_3]).unwrap(); let res: BatchActivateDealsResult = res.unwrap().deserialize().expect("VerifyDealsForActivation failed!"); diff --git a/actors/market/tests/cron_tick_deal_expiry.rs b/actors/market/tests/cron_tick_deal_expiry.rs index ab6f373ad..987c4a572 100644 --- a/actors/market/tests/cron_tick_deal_expiry.rs +++ b/actors/market/tests/cron_tick_deal_expiry.rs @@ -181,6 +181,13 @@ fn expired_deal_should_unlock_the_remaining_client_and_provider_locked_balance_a let p_escrow = get_balance(&rt, &PROVIDER_ADDR).balance; // move the current epoch so that deal is expired + expect_emitted( + &rt, + "deal-completed", + deal_id, + CLIENT_ADDR.id().unwrap(), + PROVIDER_ADDR.id().unwrap(), + ); rt.set_epoch(END_EPOCH + 1000); cron_tick(&rt); @@ -217,6 +224,13 @@ fn all_payments_are_made_for_a_deal_client_withdraws_collateral_and_client_accou // move the current epoch so that deal is expired rt.set_epoch(END_EPOCH + 100); + expect_emitted( + &rt, + "deal-completed", + _deal_id, + CLIENT_ADDR.id().unwrap(), + PROVIDER_ADDR.id().unwrap(), + ); cron_tick(&rt); assert_eq!(deal_proposal.client_collateral, get_balance(&rt, &CLIENT_ADDR).balance); diff --git a/actors/market/tests/cron_tick_deal_slashing.rs b/actors/market/tests/cron_tick_deal_slashing.rs index a341e4923..e9e6eb24c 100644 --- a/actors/market/tests/cron_tick_deal_slashing.rs +++ b/actors/market/tests/cron_tick_deal_slashing.rs @@ -7,7 +7,6 @@ use fil_actors_runtime::runtime::Policy; use fvm_shared::clock::ChainEpoch; use fvm_shared::econ::TokenAmount; use fvm_shared::sector::SectorNumber; - use num_traits::Zero; mod harness; @@ -87,8 +86,13 @@ fn deal_is_slashed() { // terminate rt.set_epoch(tc.termination_epoch); - let (pay, slashed) = - terminate_deals_and_assert_balances(&rt, CLIENT_ADDR, PROVIDER_ADDR, &[sector_number]); + let (pay, slashed) = terminate_deals_and_assert_balances( + &rt, + CLIENT_ADDR, + PROVIDER_ADDR, + &[sector_number], + &[deal_id], + ); assert_eq!(tc.payment, pay); assert_eq!(deal_proposal.provider_collateral, slashed); @@ -127,7 +131,7 @@ fn deal_is_slashed_at_the_end_epoch_should_not_be_slashed_and_should_be_consider // set current epoch to deal end epoch and attempt to slash it -> should not be slashed // as deal is considered to be expired. rt.set_epoch(END_EPOCH); - terminate_deals_and_assert_balances(&rt, CLIENT_ADDR, PROVIDER_ADDR, &[SECTOR_NUMBER]); + terminate_deals_and_assert_balances(&rt, CLIENT_ADDR, PROVIDER_ADDR, &[SECTOR_NUMBER], &[]); let duration = END_EPOCH - START_EPOCH; let current = END_EPOCH + 300; @@ -172,8 +176,13 @@ fn deal_payment_and_slashing_correctly_processed_in_same_crontick() { // set slash epoch of deal let slash_epoch = current + Policy::default().deal_updates_interval + 1; rt.set_epoch(slash_epoch); - let (pay, slashed) = - terminate_deals_and_assert_balances(&rt, CLIENT_ADDR, PROVIDER_ADDR, &[sector_number]); + let (pay, slashed) = terminate_deals_and_assert_balances( + &rt, + CLIENT_ADDR, + PROVIDER_ADDR, + &[sector_number], + &[deal_id], + ); let duration = slash_epoch - current; assert_eq!(duration * &deal_proposal.storage_price_per_epoch, pay); assert_eq!(deal_proposal.provider_collateral, slashed); @@ -228,7 +237,13 @@ fn slash_multiple_deals_in_the_same_epoch() { // set slash epoch of deal at 100 epochs past last process epoch let epoch = process_epoch(START_EPOCH, deal_id3) + 100; rt.set_epoch(process_epoch(START_EPOCH, deal_id3) + 100); - terminate_deals_and_assert_balances(&rt, CLIENT_ADDR, PROVIDER_ADDR, &[SECTOR_NUMBER]); + terminate_deals_and_assert_balances( + &rt, + CLIENT_ADDR, + PROVIDER_ADDR, + &[SECTOR_NUMBER], + &[deal_id1, deal_id2, deal_id3], + ); // next epoch run should clean up any remaining state rt.set_epoch(epoch + 1); @@ -285,8 +300,13 @@ fn regular_payments_till_deal_is_slashed_and_then_slashing_is_processed() { // now terminate the deal 1 epoch later rt.set_epoch(process_start + Policy::default().deal_updates_interval + 1); - let (pay, slashed) = - terminate_deals_and_assert_balances(&rt, CLIENT_ADDR, PROVIDER_ADDR, &[SECTOR_NUMBER]); + let (pay, slashed) = terminate_deals_and_assert_balances( + &rt, + CLIENT_ADDR, + PROVIDER_ADDR, + &[SECTOR_NUMBER], + &[deal_id], + ); assert_eq!(pay, 1 * &deal_proposal.storage_price_per_epoch); assert_eq!(slashed, deal_proposal.provider_collateral); @@ -339,7 +359,7 @@ fn regular_payments_till_deal_expires_and_then_we_attempt_to_slash_it_but_it_wil // as deal is considered to be expired. let duration = END_EPOCH - current; rt.set_epoch(END_EPOCH); - terminate_deals(&rt, PROVIDER_ADDR, &[SECTOR_NUMBER]); + terminate_deals(&rt, PROVIDER_ADDR, &[SECTOR_NUMBER], &[]); // next epoch for cron schedule is endEpoch + 300 -> // setting epoch to higher than that will cause deal to be expired, payment will be made diff --git a/actors/market/tests/deal_api_test.rs b/actors/market/tests/deal_api_test.rs index df02abfb4..a962bdd76 100644 --- a/actors/market/tests/deal_api_test.rs +++ b/actors/market/tests/deal_api_test.rs @@ -130,7 +130,7 @@ fn activation() { // terminate early let terminate_epoch = activate_epoch + 100; rt.set_epoch(terminate_epoch); - terminate_deals(&rt, PROVIDER_ADDR, &[sector_number]); + terminate_deals(&rt, PROVIDER_ADDR, &[sector_number], &[id]); // terminated deal had it's state cleaned up expect_abort_contains_message( diff --git a/actors/market/tests/deal_termination.rs b/actors/market/tests/deal_termination.rs index f0542be7e..97bf6f47f 100644 --- a/actors/market/tests/deal_termination.rs +++ b/actors/market/tests/deal_termination.rs @@ -95,8 +95,13 @@ fn deal_is_terminated() { // terminate rt.set_epoch(tc.termination_epoch); - let (pay, slashed) = - terminate_deals_and_assert_balances(&rt, CLIENT_ADDR, PROVIDER_ADDR, &[sector_number]); + let (pay, slashed) = terminate_deals_and_assert_balances( + &rt, + CLIENT_ADDR, + PROVIDER_ADDR, + &[sector_number], + &[deal_id], + ); assert_eq!(tc.termination_payment, pay); assert_eq!(deal_proposal.provider_collateral, slashed); @@ -180,12 +185,18 @@ fn settle_payments_then_terminate_deal_in_the_same_epoch() { // settle payments then terminate rt.set_epoch(termination_epoch); let expected_payment = deal_duration * &proposal.storage_price_per_epoch; - let ret = settle_deal_payments(&rt, PROVIDER_ADDR, &[deal_id]); + let ret = settle_deal_payments(&rt, PROVIDER_ADDR, &[deal_id], &[], &[]); assert_eq!( ret.settlements.get(0).unwrap(), &DealSettlementSummary { completed: false, payment: expected_payment.clone() } ); - terminate_deals_and_assert_balances(&rt, CLIENT_ADDR, PROVIDER_ADDR, &[sector_number]); + terminate_deals_and_assert_balances( + &rt, + CLIENT_ADDR, + PROVIDER_ADDR, + &[sector_number], + &[deal_id], + ); assert_deal_deleted(&rt, deal_id, &proposal, sector_number); // end state should be equivalent to only calling termination @@ -225,8 +236,14 @@ fn terminate_a_deal_then_settle_it_in_the_same_epoch() { // terminate then attempt to settle payment rt.set_epoch(termination_epoch); - terminate_deals_and_assert_balances(&rt, CLIENT_ADDR, PROVIDER_ADDR, &[sector_number]); - let ret = settle_deal_payments(&rt, PROVIDER_ADDR, &[deal_id]); + terminate_deals_and_assert_balances( + &rt, + CLIENT_ADDR, + PROVIDER_ADDR, + &[sector_number], + &[deal_id], + ); + let ret = settle_deal_payments(&rt, PROVIDER_ADDR, &[deal_id], &[], &[]); assert_eq!(ret.results.codes(), vec![EX_DEAL_EXPIRED]); assert_deal_deleted(&rt, deal_id, &proposal, sector_number); diff --git a/actors/market/tests/harness.rs b/actors/market/tests/harness.rs index ffd21cb4d..0c22506ec 100644 --- a/actors/market/tests/harness.rs +++ b/actors/market/tests/harness.rs @@ -51,8 +51,8 @@ use fil_actors_runtime::{ network::EPOCHS_IN_DAY, runtime::{builtins::Type, Policy, Runtime}, test_utils::*, - ActorError, BatchReturn, Set, SetMultimap, BURNT_FUNDS_ACTOR_ADDR, CRON_ACTOR_ADDR, - DATACAP_TOKEN_ACTOR_ADDR, REWARD_ACTOR_ADDR, STORAGE_MARKET_ACTOR_ADDR, + ActorError, BatchReturn, EventBuilder, Set, SetMultimap, BURNT_FUNDS_ACTOR_ADDR, + CRON_ACTOR_ADDR, DATACAP_TOKEN_ACTOR_ADDR, REWARD_ACTOR_ADDR, STORAGE_MARKET_ACTOR_ADDR, STORAGE_POWER_ACTOR_ADDR, SYSTEM_ACTOR_ADDR, VERIFIED_REGISTRY_ACTOR_ADDR, }; @@ -381,6 +381,27 @@ pub fn activate_deals( current_epoch: ChainEpoch, sector_number: SectorNumber, deal_ids: &[DealID], +) -> BatchActivateDealsResult { + activate_deals_for( + rt, + sector_expiry, + provider, + current_epoch, + sector_number, + deal_ids, + deal_ids, + ) +} + +/// Activate a single sector of deals +pub fn activate_deals_for( + rt: &MockRuntime, + sector_expiry: ChainEpoch, + provider: Address, + current_epoch: ChainEpoch, + sector_number: SectorNumber, + deal_ids: &[DealID], + expected_deal_activations: &[DealID], ) -> BatchActivateDealsResult { rt.set_epoch(current_epoch); let compute_cid = false; @@ -394,6 +415,7 @@ pub fn activate_deals( sector_type: RegisteredSealProof::StackedDRG8MiBV1, }], compute_cid, + expected_deal_activations, ) .unwrap(); @@ -428,7 +450,12 @@ pub fn batch_activate_deals( sector_type: RegisteredSealProof::StackedDRG8MiBV1, }) .collect(); - let ret = batch_activate_deals_raw(rt, provider, sectors_deals, compute_cid).unwrap(); + + let deal_ids = + sectors.iter().flat_map(|(_, _, deal_ids)| deal_ids).cloned().collect::>(); + + let ret = + batch_activate_deals_raw(rt, provider, sectors_deals, compute_cid, &deal_ids).unwrap(); let ret: BatchActivateDealsResult = ret.unwrap().deserialize().expect("VerifyDealsForActivation failed!"); @@ -444,12 +471,24 @@ pub fn batch_activate_deals_raw( provider: Address, sectors_deals: Vec, compute_cid: bool, + expected_activated_deals: &[DealID], ) -> Result, ActorError> { rt.set_caller(*MINER_ACTOR_CODE_ID, provider); rt.expect_validate_caller_type(vec![Type::Miner]); let params = BatchActivateDealsParams { sectors: sectors_deals, compute_cid }; + for deal_id in expected_activated_deals { + let dp = get_deal_proposal(rt, *deal_id); + + expect_emitted( + rt, + "deal-activated", + *deal_id, + dp.client.id().unwrap(), + dp.provider.id().unwrap(), + ); + } let ret = rt.call::( Method::BatchActivateDeals as u64, IpldBlock::serialize_cbor(¶ms).unwrap(), @@ -620,6 +659,16 @@ pub fn cron_tick_and_assert_balances( updated_provider_locked = TokenAmount::zero(); } + if is_deal_expired { + expect_emitted( + rt, + "deal-completed", + deal_id, + client_addr.id().unwrap(), + provider_addr.id().unwrap(), + ); + } + cron_tick(rt); let client_acct = get_balance(rt, &client_addr); @@ -810,6 +859,13 @@ pub fn publish_deals( None, ExitCode::OK, ); + expect_emitted( + rt, + "deal-published", + deal_id, + deal.client.id().unwrap(), + deal.provider.id().unwrap(), + ); deal_id += 1; } @@ -890,6 +946,8 @@ pub fn settle_deal_payments( rt: &MockRuntime, caller: Address, deal_ids: &[DealID], + completed_deals: &[DealID], + terminated_deals: &[DealID], ) -> SettleDealPaymentsReturn { let mut deal_id_bitfield = BitField::new(); for deal_id in deal_ids { @@ -900,6 +958,29 @@ pub fn settle_deal_payments( rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, caller); rt.expect_validate_caller_any(); + + for deal_id in completed_deals { + let deal = get_deal_proposal(rt, *deal_id); + expect_emitted( + rt, + "deal-completed", + *deal_id, + deal.client.id().unwrap(), + deal.provider.id().unwrap(), + ); + } + + for deal_id in terminated_deals { + let deal = get_deal_proposal(rt, *deal_id); + expect_emitted( + rt, + "deal-terminated", + *deal_id, + deal.client.id().unwrap(), + deal.provider.id().unwrap(), + ); + } + let res = rt.call::(Method::SettleDealPaymentsExported as u64, params).unwrap().unwrap(); let res: SettleDealPaymentsReturn = res.deserialize().unwrap(); @@ -922,7 +1003,7 @@ pub fn settle_deal_payments_no_change( let client_acct = get_balance(rt, &client_addr); let provider_acct = get_balance(rt, &provider_addr); - settle_deal_payments(rt, caller, deal_ids); + settle_deal_payments(rt, caller, deal_ids, &[], &[]); let st: State = rt.get_state(); let new_client_acct = get_balance(rt, &client_addr); @@ -938,6 +1019,7 @@ pub fn settle_deal_payments_and_assert_balances( provider_addr: Address, current_epoch: ChainEpoch, deal_id: DealID, + already_terminated: bool, ) -> (TokenAmount, TokenAmount) { // fetch current client escrow balances let c_acct = get_balance(rt, &client_addr); @@ -989,7 +1071,15 @@ pub fn settle_deal_payments_and_assert_balances( updated_provider_locked = TokenAmount::zero(); } - settle_deal_payments(rt, provider_addr, &[deal_id]); + let mut completed: Vec = vec![]; + let mut terminated: Vec = vec![]; + if is_deal_expired { + completed.push(deal_id); + } else if s.slash_epoch != EPOCH_UNDEFINED && !already_terminated { + terminated.push(deal_id); + } + + settle_deal_payments(rt, provider_addr, &[deal_id], &completed, &terminated); let client_acct = get_balance(rt, &client_addr); let provider_acct = get_balance(rt, &provider_addr); @@ -1414,6 +1504,7 @@ pub fn terminate_deals_and_assert_balances( client_addr: Address, provider_addr: Address, sectors: &[SectorNumber], + expected_terminations: &[DealID], ) -> (/*total_paid*/ TokenAmount, /*total_slashed*/ TokenAmount) { // Accumulate deal IDs for all sectors let deal_ids = get_sector_deal_ids(rt, provider_addr.id().unwrap(), sectors); @@ -1464,7 +1555,7 @@ pub fn terminate_deals_and_assert_balances( &client_before.locked - &total_payment - &payment_remaining - &client_unlocked; let updated_provider_locked = &provider_before.locked - &total_slashed; - terminate_deals(rt, provider_addr, sectors); + terminate_deals(rt, provider_addr, sectors, expected_terminations); let client_acct = get_balance(rt, &client_addr); let provider_acct = get_balance(rt, &provider_addr); @@ -1481,6 +1572,7 @@ pub fn terminate_deals_no_change( client_addr: Address, provider_addr: Address, sectors: &[SectorNumber], + expected_terminations: &[DealID], ) { let st: State = rt.get_state(); let epoch_cid = st.deal_ops_by_epoch; @@ -1489,7 +1581,7 @@ pub fn terminate_deals_no_change( let client_acct = get_balance(rt, &client_addr); let provider_acct = get_balance(rt, &provider_addr); - terminate_deals(rt, provider_addr, sectors); + terminate_deals(rt, provider_addr, sectors, expected_terminations); let st: State = rt.get_state(); let new_client_acct = get_balance(rt, &client_addr); @@ -1499,7 +1591,12 @@ pub fn terminate_deals_no_change( assert_eq!(provider_acct, new_provider_acct); } -pub fn terminate_deals(rt: &MockRuntime, miner_addr: Address, sectors: &[SectorNumber]) { +pub fn terminate_deals( + rt: &MockRuntime, + miner_addr: Address, + sectors: &[SectorNumber], + expected_terminations: &[DealID], +) { let deal_ids = get_sector_deal_ids(rt, miner_addr.id().unwrap(), sectors); // calculate the expected amount to be slashed for the provider that it is burnt let curr_epoch = *rt.epoch.borrow(); @@ -1524,7 +1621,7 @@ pub fn terminate_deals(rt: &MockRuntime, miner_addr: Address, sectors: &[SectorN ); } - let ret = terminate_deals_raw(rt, miner_addr, sectors).unwrap(); + let ret = terminate_deals_raw(rt, miner_addr, sectors, expected_terminations).unwrap(); assert!(ret.is_none()); rt.verify(); } @@ -1557,6 +1654,7 @@ pub fn terminate_deals_raw( rt: &MockRuntime, miner_addr: Address, sector_numbers: &[SectorNumber], + terminated_deals: &[DealID], ) -> Result, ActorError> { rt.set_caller(*MINER_ACTOR_CODE_ID, miner_addr); rt.expect_validate_caller_type(vec![Type::Miner]); @@ -1564,6 +1662,17 @@ pub fn terminate_deals_raw( let bf = BitField::try_from_bits(sector_numbers.iter().copied()).unwrap(); let params = OnMinerSectorsTerminateParams { epoch: *rt.epoch.borrow(), sectors: bf }; + for deal_id in terminated_deals { + let d = get_deal_proposal(rt, *deal_id); + expect_emitted( + rt, + "deal-terminated", + *deal_id, + d.client.id().unwrap(), + d.provider.id().unwrap(), + ) + } + rt.call::( Method::OnMinerSectorsTerminate as u64, IpldBlock::serialize_cbor(¶ms).unwrap(), @@ -1617,14 +1726,6 @@ where ret } -pub fn piece_info_from_deal(id: DealID, deal: &DealProposal) -> PieceChange { - PieceChange { - data: deal.piece_cid, - size: deal.piece_size, - payload: serialize(&id, "deal id").unwrap(), - } -} - // market cron tick uses last_updated_epoch == EPOCH_UNDEFINED to determine if a deal is new // it will not process such deals // however, for testing we need to simulate deals that are already in the system that should be @@ -1647,3 +1748,23 @@ fn simulate_legacy_deal( rt.replace_state(&state); } + +pub fn piece_info_from_deal(id: DealID, deal: &DealProposal) -> PieceChange { + PieceChange { + data: deal.piece_cid, + size: deal.piece_size, + payload: serialize(&id, "deal id").unwrap(), + } +} + +pub fn expect_emitted(rt: &MockRuntime, typ: &str, id: DealID, client: ActorID, provider: ActorID) { + rt.expect_emitted_event( + EventBuilder::new() + .typ(typ) + .field_indexed("id", &id) + .field_indexed("client", &client) + .field_indexed("provider", &provider) + .build() + .unwrap(), + ); +} diff --git a/actors/market/tests/market_actor_legacy_tests.rs b/actors/market/tests/market_actor_legacy_tests.rs index 8b2dec49b..4ab98f551 100644 --- a/actors/market/tests/market_actor_legacy_tests.rs +++ b/actors/market/tests/market_actor_legacy_tests.rs @@ -56,7 +56,7 @@ fn slash_a_deal_and_make_payment_for_another_deal_in_the_same_epoch() { // slash deal1 let slash_epoch = process_epoch(start_epoch, deal_id2) + ChainEpoch::from(100); rt.set_epoch(slash_epoch); - terminate_deals(&rt, PROVIDER_ADDR, &[sector_1]); + terminate_deals(&rt, PROVIDER_ADDR, &[sector_1], &[deal_id1]); cron_tick(&rt); assert_deal_deleted(&rt, deal_id1, &d1, sector_1); @@ -133,7 +133,7 @@ fn settling_deal_fails_when_deal_update_epoch_is_in_the_future() { // set current epoch of the deal to the end epoch so it's picked up for "processing" in the next cron tick. rt.set_epoch(end_epoch); expect_abort(ExitCode::USR_ILLEGAL_STATE, cron_tick_raw(&rt)); - let ret = settle_deal_payments(&rt, MinerAddresses::default().provider, &[deal_id]); + let ret = settle_deal_payments(&rt, MinerAddresses::default().provider, &[deal_id], &[], &[]); assert_eq!(ret.results.codes(), &[ExitCode::USR_ILLEGAL_STATE]); check_state_with_expected( @@ -379,13 +379,16 @@ fn locked_fund_tracking_states() { // slash deal1 rt.set_epoch(curr + 1); - terminate_deals(&rt, m1.provider, &[deal_id1]); + terminate_deals(&rt, m1.provider, &[sector_number], &[deal_id1]); // cron tick to slash deal1 and expire deal2 rt.set_epoch(end_epoch); csf = TokenAmount::zero(); clc = TokenAmount::zero(); plc = TokenAmount::zero(); + + expect_emitted(&rt, "deal-completed", deal_id2, d2.client.id().unwrap(), p2.id().unwrap()); + cron_tick(&rt); assert_locked_fund_states(&rt, csf, plc, clc); check_state(&rt); diff --git a/actors/market/tests/market_actor_test.rs b/actors/market/tests/market_actor_test.rs index 7e6744cb3..2cc9e930e 100644 --- a/actors/market/tests/market_actor_test.rs +++ b/actors/market/tests/market_actor_test.rs @@ -338,7 +338,7 @@ fn worker_balance_after_withdrawal_must_account_for_slashed_funds() { // terminate the deal rt.set_epoch(publish_epoch + 1); - terminate_deals(&rt, PROVIDER_ADDR, &[sector_number]); + terminate_deals(&rt, PROVIDER_ADDR, &[sector_number], &[deal_id]); assert_deal_deleted(&rt, deal_id, &proposal, sector_number); // provider cannot withdraw any funds since it's been terminated @@ -939,6 +939,14 @@ fn provider_and_client_addresses_are_resolved_before_persisting_state_and_sent_t ExitCode::OK, ); + expect_emitted( + &rt, + "deal-published", + deal_id, + client_resolved.id().unwrap(), + provider_resolved.id().unwrap(), + ); + let ret: PublishStorageDealsReturn = rt .call::( Method::PublishStorageDeals as u64, @@ -1355,7 +1363,7 @@ fn terminating_a_deal_removes_proposal_synchronously() { ); // terminating the deal deletes proposal, state and pending_proposal but leaves deal op in queue - terminate_deals(&rt, addrs.provider, &[sector_number]); + terminate_deals(&rt, addrs.provider, &[sector_number], &[deal_id]); assert_deal_deleted(&rt, deal_id, &proposal, sector_number); check_state(&rt); @@ -1389,7 +1397,7 @@ fn settling_deal_fails_when_deal_update_epoch_is_in_the_future() { // set current epoch of the deal to the end epoch so it's picked up for "processing" in the next cron tick. rt.set_epoch(end_epoch); - let ret = settle_deal_payments(&rt, MinerAddresses::default().provider, &[deal_id]); + let ret = settle_deal_payments(&rt, MinerAddresses::default().provider, &[deal_id], &[], &[]); assert_eq!(ret.results.codes(), &[ExitCode::USR_ILLEGAL_STATE]); check_state_with_expected( @@ -1427,6 +1435,7 @@ fn settling_payments_for_a_deal_at_its_start_epoch_results_in_zero_payment_and_n MinerAddresses::default().provider, start_epoch, deal_id, + false, ); assert_eq!(TokenAmount::zero(), pay); assert_eq!(TokenAmount::zero(), slashed); @@ -1520,6 +1529,7 @@ fn fail_when_current_epoch_greater_than_start_epoch_of_deal() { deal_ids: vec![deal_id], }], false, + &[], ) .unwrap(); @@ -1557,6 +1567,7 @@ fn fail_when_end_epoch_of_deal_greater_than_sector_expiry() { deal_ids: vec![deal_id], }], false, + &[], ) .unwrap(); @@ -1610,6 +1621,7 @@ fn fail_to_activate_all_deals_if_one_deal_fails() { deal_ids: vec![deal_id1, deal_id2], }], false, + &[], ) .unwrap(); let res: BatchActivateDealsResult = @@ -1704,7 +1716,7 @@ fn locked_fund_tracking_states() { None, ExitCode::OK, ); - settle_deal_payments(&rt, OWNER_ADDR, &[deal_id1, deal_id2, deal_id3]); + settle_deal_payments(&rt, OWNER_ADDR, &[deal_id1, deal_id2, deal_id3], &[], &[]); let duration = curr - start_epoch; let payment: TokenAmount = 2 * &d1.storage_price_per_epoch * duration; let mut csf = (csf - payment) - d3.total_storage_fee(); @@ -1717,19 +1729,19 @@ fn locked_fund_tracking_states() { let duration = curr - last_payment_epoch; let payment = 2 * d1.storage_price_per_epoch * duration; csf -= payment; - settle_deal_payments(&rt, OWNER_ADDR, &[deal_id1, deal_id2, deal_id3]); + settle_deal_payments(&rt, OWNER_ADDR, &[deal_id1, deal_id2, deal_id3], &[], &[]); assert_locked_fund_states(&rt, csf.clone(), plc.clone(), clc.clone()); // terminate deal1 rt.set_epoch(curr + 1); - terminate_deals(&rt, m1.provider, &[sector_number]); + terminate_deals(&rt, m1.provider, &[sector_number], &[deal_id1]); // attempt to settle payments which terminates deal1 and expires deal2 rt.set_epoch(end_epoch); csf = TokenAmount::zero(); clc = TokenAmount::zero(); plc = TokenAmount::zero(); - settle_deal_payments(&rt, OWNER_ADDR, &[deal_id1, deal_id2, deal_id3]); + settle_deal_payments(&rt, OWNER_ADDR, &[deal_id1, deal_id2, deal_id3], &[deal_id2], &[]); assert_locked_fund_states(&rt, csf, plc, clc); check_state(&rt); } @@ -1940,6 +1952,14 @@ fn insufficient_client_balance_in_a_batch() { ExitCode::OK, ); + expect_emitted( + &rt, + "deal-published", + next_deal_id, + deal2.client.id().unwrap(), + deal2.provider.id().unwrap(), + ); + rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, WORKER_ADDR); let ret: PublishStorageDealsReturn = rt @@ -2082,6 +2102,14 @@ fn insufficient_provider_balance_in_a_batch() { rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, WORKER_ADDR); + expect_emitted( + &rt, + "deal-published", + next_deal_id, + deal2.client.id().unwrap(), + deal2.provider.id().unwrap(), + ); + let ret: PublishStorageDealsReturn = rt .call::( Method::PublishStorageDeals as u64, @@ -2223,6 +2251,14 @@ fn psd_restricted_correctly() { ExitCode::OK, ); + expect_emitted( + &rt, + "deal-published", + next_deal_id, + deal.client.id().unwrap(), + deal.provider.id().unwrap(), + ); + let ret: PublishStorageDealsReturn = rt .call::( Method::PublishStorageDealsExported as MethodNum, diff --git a/actors/market/tests/on_miner_sectors_terminate.rs b/actors/market/tests/on_miner_sectors_terminate.rs index 93b8283c9..f510f9b2e 100644 --- a/actors/market/tests/on_miner_sectors_terminate.rs +++ b/actors/market/tests/on_miner_sectors_terminate.rs @@ -40,11 +40,11 @@ fn terminate_multiple_deals_from_single_provider() { activate_deals_legacy(&rt, sector_expiry, PROVIDER_ADDR, current_epoch, id1, &[id1]); activate_deals_legacy(&rt, sector_expiry, PROVIDER_ADDR, current_epoch, id2, &[id2]); - terminate_deals(&rt, PROVIDER_ADDR, &[id0]); + terminate_deals(&rt, PROVIDER_ADDR, &[id0], &[id0]); assert_deal_deleted(&rt, id0, &deal0, id0); assert_deals_not_marked_terminated(&rt, &[id1, id2]); - terminate_deals(&rt, PROVIDER_ADDR, &[id1, id2]); + terminate_deals(&rt, PROVIDER_ADDR, &[id1, id2], &[id1, id2]); assert_deal_deleted(&rt, id0, &deal0, id0); assert_deal_deleted(&rt, id1, &deal1, id1); assert_deal_deleted(&rt, id1, &deal2, id2); @@ -85,13 +85,19 @@ fn terminate_multiple_deals_from_multiple_providers() { generate_and_publish_deal(&rt, CLIENT_ADDR, &addrs2, start_epoch, end_epoch + 1); activate_deals_legacy(&rt, sector_expiry, provider2, current_epoch, sector_number, &[id3, id4]); - terminate_deals_and_assert_balances(&rt, CLIENT_ADDR, PROVIDER_ADDR, &[sector_number]); + terminate_deals_and_assert_balances( + &rt, + CLIENT_ADDR, + PROVIDER_ADDR, + &[sector_number], + &[id0, id1, id2], + ); assert_deal_deleted(&rt, id0, &deal0, sector_number); assert_deal_deleted(&rt, id1, &deal1, sector_number); assert_deal_deleted(&rt, id2, &deal2, sector_number); assert_deals_not_marked_terminated(&rt, &[id3, id4]); - terminate_deals_and_assert_balances(&rt, CLIENT_ADDR, provider2, &[sector_number]); + terminate_deals_and_assert_balances(&rt, CLIENT_ADDR, provider2, &[sector_number], &[id3, id4]); assert_deal_deleted(&rt, id3, &deal3, sector_number); assert_deal_deleted(&rt, id4, &deal4, sector_number); } @@ -123,7 +129,7 @@ fn ignore_sector_that_does_not_exist() { &[deal1], ); assert!(ret.activation_results.all_ok()); - terminate_deals(&rt, PROVIDER_ADDR, &[sector_number + 1]); + terminate_deals(&rt, PROVIDER_ADDR, &[sector_number + 1], &[]); let s = get_deal_state(&rt, deal1); assert_eq!(s.slash_epoch, -1); @@ -176,7 +182,13 @@ fn terminate_valid_deals_along_with_just_expired_deal() { let new_epoch = end_epoch - 1; rt.set_epoch(new_epoch); - terminate_deals_and_assert_balances(&rt, CLIENT_ADDR, PROVIDER_ADDR, &[sector_number]); + terminate_deals_and_assert_balances( + &rt, + CLIENT_ADDR, + PROVIDER_ADDR, + &[sector_number], + &[id0, id1], + ); assert_deal_deleted(&rt, id0, &deal0, sector_number); assert_deal_deleted(&rt, id1, &deal1, sector_number); assert_deal_deleted(&rt, id1, &deal2, sector_number); @@ -231,12 +243,26 @@ fn terminate_valid_deals_along_with_expired_and_cleaned_up_deal() { let new_epoch = end_epoch - 1; rt.set_epoch(new_epoch); + expect_emitted( + &rt, + "deal-completed", + deal_ids[1], + CLIENT_ADDR.id().unwrap(), + PROVIDER_ADDR.id().unwrap(), + ); + cron_tick(&rt); // expired deal deleted normally assert_deal_deleted(&rt, deal_ids[1], &deal2, sector_number); assert_deals_not_marked_terminated(&rt, &deal_ids[0..0]); - terminate_deals_and_assert_balances(&rt, CLIENT_ADDR, PROVIDER_ADDR, &[sector_number]); + terminate_deals_and_assert_balances( + &rt, + CLIENT_ADDR, + PROVIDER_ADDR, + &[sector_number], + &[deal_ids[0]], + ); // terminated deal deleted assert_deal_deleted(&rt, deal_ids[0], &deal1, sector_number); @@ -274,7 +300,7 @@ fn do_not_terminate_deal_if_end_epoch_is_equal_to_or_less_than_current_epoch() { ); assert!(ret.activation_results.all_ok()); rt.set_epoch(end_epoch); - terminate_deals_and_assert_balances(&rt, CLIENT_ADDR, PROVIDER_ADDR, &[sector_number]); + terminate_deals_and_assert_balances(&rt, CLIENT_ADDR, PROVIDER_ADDR, &[sector_number], &[]); assert_deals_not_marked_terminated(&rt, &[deal1]); // deal2 has end epoch less than current epoch when terminate is called @@ -297,7 +323,7 @@ fn do_not_terminate_deal_if_end_epoch_is_equal_to_or_less_than_current_epoch() { ); assert!(ret.activation_results.all_ok()); rt.set_epoch(end_epoch + 1); - terminate_deals_and_assert_balances(&rt, CLIENT_ADDR, PROVIDER_ADDR, &[sector_number]); + terminate_deals_and_assert_balances(&rt, CLIENT_ADDR, PROVIDER_ADDR, &[sector_number], &[]); assert_deals_not_marked_terminated(&rt, &[deal2]); check_state(&rt); diff --git a/actors/market/tests/publish_storage_deals_failures.rs b/actors/market/tests/publish_storage_deals_failures.rs index 045815d2e..700350fad 100644 --- a/actors/market/tests/publish_storage_deals_failures.rs +++ b/actors/market/tests/publish_storage_deals_failures.rs @@ -24,7 +24,6 @@ use cid::Cid; use fil_actor_market::ext::account::{AuthenticateMessageParams, AUTHENTICATE_MESSAGE_METHOD}; mod harness; - use fvm_ipld_encoding::ipld_block::IpldBlock; use fvm_shared::sys::SendFlags; use harness::*; @@ -376,6 +375,14 @@ fn fail_when_deals_have_different_providers() { ExitCode::OK, ); + expect_emitted( + &rt, + "deal-published", + next_deal_id, + deal1.client.id().unwrap(), + deal1.provider.id().unwrap(), + ); + let psd_ret: PublishStorageDealsReturn = rt .call::( Method::PublishStorageDeals as u64, diff --git a/actors/market/tests/random_cron_epoch_during_publish.rs b/actors/market/tests/random_cron_epoch_during_publish.rs index f6e985242..67f392774 100644 --- a/actors/market/tests/random_cron_epoch_during_publish.rs +++ b/actors/market/tests/random_cron_epoch_during_publish.rs @@ -119,8 +119,15 @@ fn activation_after_deal_start_epoch_but_before_it_is_processed_fails() { let curr_epoch = START_EPOCH + 1; rt.set_epoch(curr_epoch); - let res = - activate_deals(&rt, SECTOR_EXPIRY, PROVIDER_ADDR, curr_epoch, SECTOR_NUMBER, &[deal_id]); + let res = activate_deals_for( + &rt, + SECTOR_EXPIRY, + PROVIDER_ADDR, + curr_epoch, + SECTOR_NUMBER, + &[deal_id], + &[], + ); assert_eq!(res.activation_results.codes(), vec![EX_DEAL_EXPIRED]); check_state(&rt); } diff --git a/actors/market/tests/settle_deal_payments.rs b/actors/market/tests/settle_deal_payments.rs index 2c25c4e88..c296cb4bf 100644 --- a/actors/market/tests/settle_deal_payments.rs +++ b/actors/market/tests/settle_deal_payments.rs @@ -37,7 +37,7 @@ fn timedout_deal_is_slashed_and_deleted() { ); // settle deal payments -> should time out and get slashed - settle_deal_payments(&rt, CLIENT_ADDR, &[deal_id]); + settle_deal_payments(&rt, CLIENT_ADDR, &[deal_id], &[], &[]); let client_acct = get_balance(&rt, &CLIENT_ADDR); assert_eq!(c_escrow, client_acct.balance); @@ -77,7 +77,7 @@ fn can_manually_settle_deals_in_the_cron_queue() { rt.set_epoch(START_EPOCH + 100); // manually call settle_deal_payments - let ret = settle_deal_payments(&rt, addrs.provider, &[deal_id]); + let ret = settle_deal_payments(&rt, addrs.provider, &[deal_id], &[], &[]); let payment = ret.settlements[0].payment.clone(); assert_eq!(&payment, &(&deal_proposal.storage_price_per_epoch * 100)); @@ -89,6 +89,14 @@ fn can_manually_settle_deals_in_the_cron_queue() { assert_eq!(&client_updated.balance, &incremental_client_escrow); assert_eq!(&provider_updated.balance, &incremental_provider_escrow); + expect_emitted( + &rt, + "deal-completed", + deal_id, + deal_proposal.client.id().unwrap(), + deal_proposal.provider.id().unwrap(), + ); + // advance to deal end epoch and call cron rt.set_epoch(END_EPOCH); cron_tick(&rt); @@ -209,6 +217,7 @@ fn batch_settlement_of_deals_allows_partial_success() { CLIENT_ADDR, addrs.provider, &[terminating_sector_number], + &[terminated_id], ); // attempt to settle all the deals + a random non-existent deal id @@ -227,6 +236,8 @@ fn batch_settlement_of_deals_allows_partial_success() { &rt, addrs.provider, &[continuing_id, finished_id, terminated_id, unactivated_id, 9999], + &[finished_id], + &[], ); assert_eq!( diff --git a/actors/market/tests/transient_marked_for_termination.rs b/actors/market/tests/transient_marked_for_termination.rs index e857b61a0..cde1866be 100644 --- a/actors/market/tests/transient_marked_for_termination.rs +++ b/actors/market/tests/transient_marked_for_termination.rs @@ -92,6 +92,13 @@ fn deal_scheduled_for_termination_cannot_be_settled_manually() { None, ExitCode::OK, ); + expect_emitted( + &rt, + "deal-terminated", + slashed_deal, + slashed_prop.client.id().unwrap(), + slashed_prop.provider.id().unwrap(), + ); cron_tick(&rt); // assert that the slashed deal was terminated @@ -99,7 +106,7 @@ fn deal_scheduled_for_termination_cannot_be_settled_manually() { // attempt to settle payment for both deals again - partially succeeds because not found deals are ignored rt.set_epoch(scheduled_epoch + 1); - let ret = settle_deal_payments(&rt, PROVIDER_ADDR, &[deal_id_1, slashed_deal]); + let ret = settle_deal_payments(&rt, PROVIDER_ADDR, &[deal_id_1, slashed_deal], &[], &[]); let expected_payment = deal_1_prop.storage_price_per_epoch * (scheduled_epoch + 1 - START_EPOCH); assert_eq!(ret.results.codes(), vec![ExitCode::OK, EX_DEAL_EXPIRED]); diff --git a/actors/market/tests/verify_deals_for_activation_test.rs b/actors/market/tests/verify_deals_for_activation_test.rs index 33daa9e07..b34c2cb53 100644 --- a/actors/market/tests/verify_deals_for_activation_test.rs +++ b/actors/market/tests/verify_deals_for_activation_test.rs @@ -3,15 +3,16 @@ use fvm_ipld_encoding::ipld_block::IpldBlock; use fvm_shared::address::Address; -use fvm_shared::bigint::BigInt; use fvm_shared::clock::ChainEpoch; use fvm_shared::econ::TokenAmount; use fvm_shared::error::ExitCode; use fvm_shared::piece::PieceInfo; use fvm_shared::sector::RegisteredSealProof; -use num_traits::Zero; -use fil_actor_market::{Actor as MarketActor, Method, SectorDeals, VerifyDealsForActivationParams}; +use fil_actor_market::{ + ActivatedDeal, Actor as MarketActor, Method, SectorDeals, VerifyDealsForActivationParams, + NO_ALLOCATION_ID, +}; use fil_actors_runtime::runtime::builtins::Type; use fil_actors_runtime::test_utils::{ expect_abort, expect_abort_contains_message, make_piece_cid, ACCOUNT_ACTOR_CODE_ID, @@ -21,7 +22,6 @@ use fil_actors_runtime::EPOCHS_IN_DAY; use harness::*; mod harness; - const START_EPOCH: ChainEpoch = 10; const CURR_EPOCH: ChainEpoch = START_EPOCH; const END_EPOCH: ChainEpoch = 200 * EPOCHS_IN_DAY; @@ -56,8 +56,16 @@ fn verify_deal_and_activate_to_get_deal_space_for_unverified_deal_proposal() { let s_response = a_response.activations.get(0).unwrap(); assert_eq!(1, v_response.unsealed_cids.len()); assert_eq!(Some(make_piece_cid("1".as_bytes())), v_response.unsealed_cids[0]); - assert!(s_response.verified_infos.is_empty()); - assert_eq!(BigInt::from(deal_proposal.piece_size.0), s_response.nonverified_deal_space); + assert_eq!(1, s_response.activated.len()); + assert_eq!( + ActivatedDeal { + client: CLIENT_ADDR.id().unwrap(), + allocation_id: NO_ALLOCATION_ID, + data: deal_proposal.piece_cid, + size: deal_proposal.piece_size + }, + *s_response.activated.get(0).unwrap() + ); check_state(&rt); } @@ -94,14 +102,11 @@ fn verify_deal_and_activate_to_get_deal_space_for_verified_deal_proposal() { assert_eq!(1, response.unsealed_cids.len()); assert_eq!(Some(make_piece_cid("1".as_bytes())), response.unsealed_cids[0]); - assert_eq!(1, s_response.verified_infos.len()); - assert_eq!(deal_proposal.piece_size, s_response.verified_infos[0].size); - assert_eq!(deal_proposal.client.id().unwrap(), s_response.verified_infos[0].client); - assert_eq!(deal_proposal.piece_cid, s_response.verified_infos[0].data); - assert_eq!(next_allocation_id, s_response.verified_infos[0].allocation_id); - - assert_eq!(BigInt::zero(), s_response.nonverified_deal_space); - + assert_eq!(1, s_response.activated.len()); + assert_eq!(deal_proposal.piece_size, s_response.activated[0].size); + assert_eq!(deal_proposal.client.id().unwrap(), s_response.activated[0].client); + assert_eq!(deal_proposal.piece_cid, s_response.activated[0].data); + assert_eq!(next_allocation_id, s_response.activated[0].allocation_id); check_state(&rt); } @@ -129,7 +134,7 @@ fn verification_and_weights_for_verified_and_unverified_deals() { assert_eq!(4, deal_ids.len()); let sector_number = 7; - let response = verify_deals_for_activation( + verify_deals_for_activation( &rt, PROVIDER_ADDR, vec![SectorDeals { @@ -148,19 +153,46 @@ fn verification_and_weights_for_verified_and_unverified_deals() { }, ); - let verified_space = BigInt::from(verified_deal_1.piece_size.0 + verified_deal_2.piece_size.0); - let unverified_space = - BigInt::from(unverified_deal_1.piece_size.0 + unverified_deal_2.piece_size.0); - let a_response = activate_deals(&rt, SECTOR_EXPIRY, PROVIDER_ADDR, CURR_EPOCH, sector_number, &deal_ids); let s_response = a_response.activations.get(0).unwrap(); - - assert_eq!(1, response.unsealed_cids.len()); - let returned_verified_space: BigInt = - s_response.verified_infos.iter().map(|info| BigInt::from(info.size.0)).sum(); - assert_eq!(verified_space, returned_verified_space); - assert_eq!(unverified_space, s_response.nonverified_deal_space); + assert_eq!(4, s_response.activated.len()); + assert_eq!( + &ActivatedDeal { + client: CLIENT_ADDR.id().unwrap(), + allocation_id: 1, + data: verified_deal_1.piece_cid, + size: verified_deal_1.piece_size, + }, + s_response.activated.get(0).unwrap() + ); + assert_eq!( + &ActivatedDeal { + client: CLIENT_ADDR.id().unwrap(), + allocation_id: 2, + data: verified_deal_2.piece_cid, + size: verified_deal_2.piece_size, + }, + s_response.activated.get(1).unwrap() + ); + assert_eq!( + &ActivatedDeal { + client: CLIENT_ADDR.id().unwrap(), + allocation_id: NO_ALLOCATION_ID, + data: unverified_deal_1.piece_cid, + size: unverified_deal_1.piece_size, + }, + s_response.activated.get(2).unwrap() + ); + assert_eq!( + &ActivatedDeal { + client: CLIENT_ADDR.id().unwrap(), + allocation_id: NO_ALLOCATION_ID, + data: unverified_deal_2.piece_cid, + size: unverified_deal_2.piece_size, + }, + s_response.activated.get(3).unwrap() + ); check_state(&rt); } diff --git a/actors/miner/src/emit.rs b/actors/miner/src/emit.rs new file mode 100644 index 000000000..c802285cb --- /dev/null +++ b/actors/miner/src/emit.rs @@ -0,0 +1,74 @@ +use cid::Cid; +use fil_actors_runtime::runtime::Runtime; +use fil_actors_runtime::{ActorError, EventBuilder}; +use fvm_shared::sector::SectorNumber; + +/// Indicates a sector has been pre-committed. +pub fn sector_precommitted(rt: &impl Runtime, sector: SectorNumber) -> Result<(), ActorError> { + rt.emit_event( + &EventBuilder::new().typ("sector-precommitted").field_indexed("sector", §or).build()?, + ) +} + +/// Indicates a sector has been activated. +pub fn sector_activated( + rt: &impl Runtime, + sector: SectorNumber, + unsealed_cid: Option, + pieces: &[(Cid, u64)], +) -> Result<(), ActorError> { + rt.emit_event( + &EventBuilder::new() + .typ("sector-activated") + .with_sector_info(sector, unsealed_cid, pieces) + .build()?, + ) +} + +/// Indicates a sector has been updated. +pub fn sector_updated( + rt: &impl Runtime, + sector: SectorNumber, + unsealed_cid: Option, + pieces: &[(Cid, u64)], +) -> Result<(), ActorError> { + rt.emit_event( + &EventBuilder::new() + .typ("sector-updated") + .with_sector_info(sector, unsealed_cid, pieces) + .build()?, + ) +} + +/// Indicates a sector has been terminated. +pub fn sector_terminated(rt: &impl Runtime, sector: SectorNumber) -> Result<(), ActorError> { + rt.emit_event( + &EventBuilder::new().typ("sector-terminated").field_indexed("sector", §or).build()?, + ) +} + +trait WithSectorInfo { + fn with_sector_info( + self, + sector: SectorNumber, + unsealed_cid: Option, + pieces: &[(Cid, u64)], + ) -> EventBuilder; +} + +impl WithSectorInfo for EventBuilder { + fn with_sector_info( + self, + sector: SectorNumber, + unsealed_cid: Option, + pieces: &[(Cid, u64)], + ) -> EventBuilder { + let mut event = + self.field_indexed("sector", §or).field_indexed("unsealed-cid", &unsealed_cid); + + for piece in pieces { + event = event.field_indexed("piece-cid", &piece.0).field("piece-size", &piece.1); + } + event + } +} diff --git a/actors/miner/src/ext.rs b/actors/miner/src/ext.rs index 63b9002a9..6a3cb3146 100644 --- a/actors/miner/src/ext.rs +++ b/actors/miner/src/ext.rs @@ -25,6 +25,8 @@ pub mod market { pub const BATCH_ACTIVATE_DEALS_METHOD: u64 = 6; pub const ON_MINER_SECTORS_TERMINATE_METHOD: u64 = 7; + pub const NO_ALLOCATION_ID: u64 = 0; + #[derive(Serialize_tuple, Deserialize_tuple)] pub struct SectorDeals { pub sector_number: SectorNumber, @@ -40,29 +42,16 @@ pub mod market { } #[derive(Serialize_tuple, Deserialize_tuple, Clone)] - pub struct VerifiedDealInfo { + pub struct ActivateDeal { pub client: ActorID, pub allocation_id: u64, pub data: Cid, pub size: PaddedPieceSize, } - impl Default for VerifiedDealInfo { - fn default() -> VerifiedDealInfo { - VerifiedDealInfo { - size: PaddedPieceSize(0), - client: 0, - allocation_id: 0, - data: Default::default(), - } - } - } - #[derive(Serialize_tuple, Deserialize_tuple, Clone)] pub struct SectorDealActivation { - #[serde(with = "bigint_ser")] - pub nonverified_deal_space: BigInt, - pub verified_infos: Vec, + pub activated: Vec, pub unsealed_cid: Option, } diff --git a/actors/miner/src/lib.rs b/actors/miner/src/lib.rs index 770c1ed89..ec65c17a2 100644 --- a/actors/miner/src/lib.rs +++ b/actors/miner/src/lib.rs @@ -4,7 +4,7 @@ use std::cmp; use std::cmp::max; use std::collections::btree_map::Entry; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::ops::Neg; use anyhow::{anyhow, Error}; @@ -51,6 +51,7 @@ use fil_actors_runtime::{ }; use fvm_ipld_encoding::ipld_block::IpldBlock; +use crate::ext::market::NO_ALLOCATION_ID; pub use monies::*; pub use partition_state::*; pub use policy::*; @@ -75,6 +76,7 @@ mod deadline_assignment; mod deadline_info; mod deadline_state; mod deadlines; +mod emit; mod expiration_queue; #[doc(hidden)] pub mod ext; @@ -824,7 +826,18 @@ impl Actor { activate_sectors_deals(rt, &data_activation_inputs, compute_commd)?; let activated_precommits = batch_return.successes(&valid_precommits); - activate_new_sector_infos(rt, activated_precommits, activated_data, &pledge_inputs, &info)?; + activate_new_sector_infos( + rt, + activated_precommits.clone(), + activated_data.clone(), + &pledge_inputs, + &info, + )?; + + for (pc, data) in activated_precommits.iter().zip(activated_data.iter()) { + let unsealed_cid = pc.info.unsealed_cid.0; + emit::sector_activated(rt, pc.info.sector_number, unsealed_cid, &data.pieces)?; + } // The aggregate fee is paid on the sectors successfully proven. pay_aggregate_seal_proof_fee(rt, valid_precommits.len())?; @@ -964,6 +977,13 @@ impl Actor { sector_info: usi.sector_info, activated_data, }); + + emit::sector_updated( + rt, + usi.update.sector_number, + data_activation.unsealed_cid, + &data_activation.pieces, + )?; } let (power_delta, pledge_delta) = update_replica_states( @@ -1006,25 +1026,29 @@ impl Actor { .context_code(ExitCode::USR_ILLEGAL_STATE, "failed to load sectors array")?; let mut sector_infos = Vec::with_capacity(params.sector_updates.len()); let mut updates = Vec::with_capacity(params.sector_updates.len()); + let mut sector_commds: HashMap = + HashMap::with_capacity(params.sector_updates.len()); for (i, update) in params.sector_updates.iter().enumerate() { let sector = sectors.must_get(update.sector)?; let sector_type = sector.seal_proof; sector_infos.push(sector); - let computed_commd = - unsealed_cid_from_pieces(rt, &update.pieces, sector_type)?.get_cid(sector_type)?; + + let computed_commd = unsealed_cid_from_pieces(rt, &update.pieces, sector_type)?; updates.push(ReplicaUpdateInner { sector_number: update.sector, deadline: update.deadline, partition: update.partition, new_sealed_cid: update.new_sealed_cid, - new_unsealed_cid: Some(computed_commd), + new_unsealed_cid: Some(computed_commd.get_cid(sector_type)?), deals: vec![], update_proof_type: params.update_proofs_type, // Replica proof may be empty if an aggregate is being proven. // Validation needs to accept this empty proof. replica_proof: params.sector_proofs.get(i).unwrap_or(&RawBytes::default()).clone(), }); + + sector_commds.insert(update.sector, computed_commd); } // Validate inputs. @@ -1158,14 +1182,23 @@ impl Actor { request_update_power(rt, power_delta)?; // Notify data consumers. - let notifications: Vec = successful_manifests - .into_iter() - .map(|(update, sector_info)| ActivationNotifications { + let mut notifications: Vec = vec![]; + for (update, sector_info) in successful_manifests { + notifications.push(ActivationNotifications { sector_number: update.sector, sector_expiration: sector_info.expiration, pieces: &update.pieces, - }) - .collect(); + }); + + let pieces: Vec<(Cid, u64)> = update.pieces.iter().map(|x| (x.cid, x.size.0)).collect(); + + emit::sector_updated( + rt, + update.sector, + sector_commds.get(&update.sector).unwrap().0, + &pieces, + )?; + } notify_data_consumers(rt, ¬ifications, params.require_notification_success)?; let result = util::stack(&[validation_batch, proven_batch, data_batch]); @@ -1655,6 +1688,10 @@ impl Actor { .map_err(|e| { e.downcast_default(ExitCode::USR_ILLEGAL_STATE, "failed to add pre-commit expiry to queue") })?; + + for sector_num in sector_numbers.iter() { + emit::sector_precommitted(rt, sector_num)?; + } // Activate miner cron needs_cron = !state.deadline_cron_active; state.deadline_cron_active = true; @@ -1861,6 +1898,12 @@ impl Actor { sector_expiration: sector.info.expiration, pieces: &activations.pieces, }); + + let pieces: Vec<(Cid, u64)> = + activations.pieces.iter().map(|p| (p.cid, p.size.0)).collect(); + let unsealed_cid = sector.info.unsealed_cid.0; + + emit::sector_activated(rt, sector.info.sector_number, unsealed_cid, &pieces)?; } notify_data_consumers(rt, ¬ifications, params.require_notification_success)?; @@ -1969,11 +2012,18 @@ impl Actor { }; activate_new_sector_infos( rt, - successful_activations, - data_activations, + successful_activations.clone(), + data_activations.clone(), &pledge_inputs, &info, - ) + )?; + + for (pc, data) in successful_activations.iter().zip(data_activations.iter()) { + let unsealed_cid = pc.info.unsealed_cid.0; + emit::sector_activated(rt, pc.info.sector_number, unsealed_cid, &data.pieces)?; + } + + Ok(()) } fn check_sector_proven( @@ -4082,6 +4132,7 @@ fn process_early_terminations( reward_smoothed: &FilterEstimate, quality_adj_power_smoothed: &FilterEstimate, ) -> Result { + let mut terminated_sector_nums = vec![]; let mut sectors_with_data = vec![]; let (result, more, penalty, pledge_delta) = rt.transaction(|state: &mut State, rt| { let store = rt.store(); @@ -4120,6 +4171,7 @@ fn process_early_terminations( for sector in §ors { total_initial_pledge += §or.initial_pledge; let sector_power = qa_power_for_sector(info.sector_size, sector); + terminated_sector_nums.push(sector.sector_number); total_penalty += pledge_penalty_for_termination( §or.expected_day_reward, epoch - sector.power_base_epoch, @@ -4186,6 +4238,10 @@ fn process_early_terminations( .context_code(ExitCode::USR_ILLEGAL_STATE, "invalid sector number")?; request_terminate_deals(rt, rt.curr_epoch(), &terminated_data)?; + for sector in terminated_sector_nums { + emit::sector_terminated(rt, sector)?; + } + // reschedule cron worker, if necessary. Ok(more) } @@ -5316,6 +5372,7 @@ struct DataActivationOutput { pub verified_space: BigInt, // None indicates either no deals or computation was not requested. pub unsealed_cid: Option, + pub pieces: Vec<(Cid, u64)>, } // Track information needed to update a sector info's data during ProveReplicaUpdate @@ -5353,10 +5410,11 @@ fn activate_sectors_pieces( ) -> Result<(BatchReturn, Vec), ActorError> { // Get a flattened list of verified claims for all activated sectors let mut verified_claims = Vec::new(); - let mut unverified_spaces = Vec::new(); - for activation_info in activation_inputs { + let mut sectors_pieces = Vec::new(); + + for activation_info in &activation_inputs { // Check a declared CommD matches that computed from the data. - if let Some(declared_commd) = activation_info.expected_commd { + if let Some(declared_commd) = &activation_info.expected_commd { let computed_commd = unsealed_cid_from_pieces( rt, &activation_info.piece_manifests, @@ -5377,20 +5435,18 @@ fn activate_sectors_pieces( } let mut sector_claims = vec![]; - let mut unverified_space = BigInt::zero(); - for piece in activation_info.piece_manifests { - if let Some(alloc_key) = piece.verified_allocation_key { + sectors_pieces.push(&activation_info.piece_manifests); + + for piece in &activation_info.piece_manifests { + if let Some(alloc_key) = &piece.verified_allocation_key { sector_claims.push(ext::verifreg::AllocationClaim { client: alloc_key.client, allocation_id: alloc_key.id, data: piece.cid, size: piece.size, }); - } else { - unverified_space += piece.size.0 } } - unverified_spaces.push(unverified_space); verified_claims.push(ext::verifreg::SectorAllocationClaims { sector: activation_info.sector_number, expiry: activation_info.sector_expiry, @@ -5409,11 +5465,22 @@ fn activate_sectors_pieces( let activation_outputs = claim_res .sector_claims .iter() - .zip(claim_res.sector_results.successes(&unverified_spaces)) - .map(|(sector_claim, unverified_space)| DataActivationOutput { - unverified_space: unverified_space.clone(), - verified_space: sector_claim.claimed_space.clone(), - unsealed_cid: None, + .zip(claim_res.sector_results.successes(§ors_pieces)) + .map(|(sector_claim, sector_pieces)| { + let mut unverified_space = BigInt::zero(); + let mut pieces = Vec::new(); + for piece in *sector_pieces { + if piece.verified_allocation_key.is_none() { + unverified_space += piece.size.0; + } + pieces.push((piece.cid, piece.size.0)); + } + DataActivationOutput { + unverified_space: unverified_space.clone(), + verified_space: sector_claim.claimed_space.clone(), + unsealed_cid: None, + pieces, + } }) .collect(); @@ -5434,8 +5501,7 @@ fn activate_sectors_deals( // if all sectors are empty of deals, skip calling the market actor activations: vec![ ext::market::SectorDealActivation { - nonverified_deal_space: BigInt::default(), - verified_infos: Vec::default(), + activated: Vec::default(), unsealed_cid: None, }; activation_infos.len() @@ -5480,8 +5546,9 @@ fn activate_sectors_deals( successful_activation_infos.iter().zip(&batch_activation_res.activations) { let sector_claims = activate_res - .verified_infos + .activated .iter() + .filter(|info| info.allocation_id != NO_ALLOCATION_ID) .map(|info| ext::verifreg::AllocationClaim { client: info.client, allocation_id: info.allocation_id, @@ -5510,10 +5577,21 @@ fn activate_sectors_deals( .activations .iter() .zip(claim_res.sector_claims) - .map(|(sector_deals, sector_claim)| DataActivationOutput { - unverified_space: sector_deals.nonverified_deal_space.clone(), - verified_space: sector_claim.claimed_space, - unsealed_cid: sector_deals.unsealed_cid, + .map(|(sector_deals, sector_claim)| { + let mut sector_pieces = Vec::new(); + let mut unverified_deal_space = BigInt::zero(); + for info in §or_deals.activated { + sector_pieces.push((info.data, info.size.0)); + if info.allocation_id == NO_ALLOCATION_ID { + unverified_deal_space += info.size.0; + } + } + DataActivationOutput { + unverified_space: unverified_deal_space, + verified_space: sector_claim.claimed_space, + unsealed_cid: sector_deals.unsealed_cid, + pieces: sector_pieces, + } }) .collect(); diff --git a/actors/miner/tests/aggregate_prove_commit.rs b/actors/miner/tests/aggregate_prove_commit.rs index e4bfd05c1..942eccbc9 100644 --- a/actors/miner/tests/aggregate_prove_commit.rs +++ b/actors/miner/tests/aggregate_prove_commit.rs @@ -57,9 +57,9 @@ fn valid_precommits_then_aggregate_provecommit() { let mut pcc = ProveCommitConfig::empty(); for pc in &precommits { - pcc.add_verified_deals( + pcc.add_activated_deals( pc.info.sector_number, - vec![test_verified_deal(verified_deal_space)], + vec![test_activated_deal(verified_deal_space, 1)], ); } diff --git a/actors/miner/tests/compact_sector_numbers_tests.rs b/actors/miner/tests/compact_sector_numbers_tests.rs index df04f3b33..ccf0e6471 100644 --- a/actors/miner/tests/compact_sector_numbers_tests.rs +++ b/actors/miner/tests/compact_sector_numbers_tests.rs @@ -51,6 +51,7 @@ mod compact_sector_numbers_test { ExitCode::USR_ILLEGAL_ARGUMENT, h.pre_commit_sector(&rt, precommit, util::PreCommitConfig::default(), false), ); + rt.reset(); } { diff --git a/actors/miner/tests/extend_sector_expiration_test.rs b/actors/miner/tests/extend_sector_expiration_test.rs index 3134cea5c..17d15cc25 100644 --- a/actors/miner/tests/extend_sector_expiration_test.rs +++ b/actors/miner/tests/extend_sector_expiration_test.rs @@ -1,4 +1,4 @@ -use fil_actor_market::VerifiedDealInfo; +use fil_actor_market::ActivatedDeal; use fil_actor_miner::ext::verifreg::Claim as FILPlusClaim; use fil_actor_miner::{ power_for_sector, seal_proof_sector_maximum_lifetime, ExpirationExtension, @@ -13,6 +13,7 @@ use fil_actors_runtime::{ EPOCHS_IN_DAY, }; use fvm_ipld_bitfield::BitField; +use fvm_shared::deal::DealID; use fvm_shared::{ address::Address, clock::ChainEpoch, @@ -378,8 +379,8 @@ fn update_expiration2_multiple_claims() { let (mut h, rt) = setup(); // add in verified deal let verified_deals = vec![ - test_verified_deal(h.sector_size as u64 / 2), - test_verified_deal(h.sector_size as u64 / 2), + test_activated_deal(h.sector_size as u64 / 2, 1), + test_activated_deal(h.sector_size as u64 / 2, 2), ]; let old_sector = commit_sector_verified_deals(&verified_deals, &mut h, &rt); h.advance_and_submit_posts(&rt, &vec![old_sector.clone()]); @@ -450,8 +451,8 @@ fn update_expiration2_failure_cases() { let (mut h, rt) = setup(); // add in verified deal let verified_deals = vec![ - test_verified_deal(h.sector_size as u64 / 2), - test_verified_deal(h.sector_size as u64 / 2), + test_activated_deal(h.sector_size as u64 / 2, 1), + test_activated_deal(h.sector_size as u64 / 2, 2), ]; let old_sector = commit_sector_verified_deals(&verified_deals, &mut h, &rt); h.advance_and_submit_posts(&rt, &vec![old_sector.clone()]); @@ -612,8 +613,8 @@ fn extend_expiration2_drop_claims() { let (mut h, rt) = setup(); // add in verified deal let verified_deals = vec![ - test_verified_deal(h.sector_size as u64 / 2), - test_verified_deal(h.sector_size as u64 / 2), + test_activated_deal(h.sector_size as u64 / 2, 1), + test_activated_deal(h.sector_size as u64 / 2, 2), ]; let policy = Policy::default(); let old_sector = commit_sector_verified_deals(&verified_deals, &mut h, &rt); @@ -732,8 +733,8 @@ fn update_expiration_legacy_fails_on_new_sector_with_deals() { let (mut h, rt) = setup(); // add in verified deal let verified_deals = vec![ - test_verified_deal(h.sector_size as u64 / 2), - test_verified_deal(h.sector_size as u64 / 2), + test_activated_deal(h.sector_size as u64 / 2, 1), + test_activated_deal(h.sector_size as u64 / 2, 2), ]; let old_sector = commit_sector_verified_deals(&verified_deals, &mut h, &rt); h.advance_and_submit_posts(&rt, &vec![old_sector.clone()]); @@ -778,8 +779,8 @@ fn update_expiration2_drop_claims_failure_cases() { let policy = Policy::default(); // add in verified deal let verified_deals = vec![ - test_verified_deal(h.sector_size as u64 / 2), - test_verified_deal(h.sector_size as u64 / 2), + test_activated_deal(h.sector_size as u64 / 2, 1), + test_activated_deal(h.sector_size as u64 / 2, 2), ]; let old_sector = commit_sector_verified_deals(&verified_deals, &mut h, &rt); h.advance_and_submit_posts(&rt, &vec![old_sector.clone()]); @@ -889,7 +890,7 @@ fn update_expiration2_drop_claims_failure_cases() { } fn commit_sector_verified_deals( - verified_deals: &Vec, + verified_deals: &Vec, h: &mut ActorHarness, rt: &MockRuntime, ) -> SectorOnChainInfo { @@ -897,13 +898,18 @@ fn commit_sector_verified_deals( assert!(!verified_deals.is_empty()); let mut pcc = ProveCommitConfig::empty(); - pcc.add_verified_deals(h.next_sector_no, verified_deals.clone()); + pcc.add_activated_deals(h.next_sector_no, verified_deals.clone()); + + let mut deal_ids: Vec = vec![]; + for i in 0..verified_deals.len() { + deal_ids.push(i as u64); + } let sector_info = &h.commit_and_prove_sectors_with_cfgs( rt, 1, DEFAULT_SECTOR_EXPIRATION as u64, - vec![vec![42]], + vec![deal_ids], true, pcc, )[0]; @@ -957,7 +963,7 @@ fn make_claim( client: ActorID, provider: ActorID, new_expiration: ChainEpoch, - deal: &VerifiedDealInfo, + deal: &ActivatedDeal, term_min: ChainEpoch, ) -> FILPlusClaim { FILPlusClaim { diff --git a/actors/miner/tests/prove_commit.rs b/actors/miner/tests/prove_commit.rs index 0cdc7b3f2..603c0aefb 100644 --- a/actors/miner/tests/prove_commit.rs +++ b/actors/miner/tests/prove_commit.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use fvm_shared::{ bigint::{BigInt, Zero}, clock::ChainEpoch, @@ -8,7 +6,9 @@ use fvm_shared::{ sector::{StoragePower, MAX_SECTOR_NUMBER}, smooth::FilterEstimate, }; +use std::collections::HashMap; +use fil_actor_miner::ext::market::NO_ALLOCATION_ID; use fil_actor_miner::{ initial_pledge_for_power, max_prove_commit_duration, pre_commit_deposit_for_power, qa_power_for_weight, qa_power_max, PowerPair, PreCommitSectorBatchParams, VestSpec, @@ -47,7 +47,7 @@ fn prove_single_sector() { dl_info.period_end() + DEFAULT_SECTOR_EXPIRATION * rt.policy.wpost_proving_period; // something on deadline boundary but > 180 days // Fill the sector with verified deals let deal_space = BigInt::zero(); - let verified_deal = test_verified_deal(h.sector_size as u64); + let verified_deal = test_activated_deal(h.sector_size as u64, 1); let verified_deal_space = BigInt::from(verified_deal.size.0); // Pre-commit with a deal in order to exercise non-zero deal weights. @@ -72,7 +72,7 @@ fn prove_single_sector() { rt.set_epoch(prove_commit_epoch); rt.balance.replace(TokenAmount::from_whole(1000)); let mut pcc = ProveCommitConfig::empty(); - pcc.add_verified_deals(sector_no, vec![verified_deal]); + pcc.add_activated_deals(sector_no, vec![verified_deal]); let sector = h .prove_commit_sector_and_confirm( @@ -177,9 +177,9 @@ fn prove_sectors_from_batch_pre_commit() { let deal_space: i64 = 32 << 30; let prove_commit_epoch = precommit_epoch + rt.policy.pre_commit_challenge_delay + 1; let deal_lifespan = sector_expiration - prove_commit_epoch; - let verified_deal1 = test_verified_deal(deal_space as u64); - let verified_deal2 = test_verified_deal(deal_space as u64 / 2); - let verified_deal3 = test_verified_deal(deal_space as u64 / 2); + let verified_deal1 = test_activated_deal(deal_space as u64, 1); + let verified_deal2 = test_activated_deal(deal_space as u64 / 2, 2); + let verified_deal3 = test_activated_deal(deal_space as u64 / 2, 3); let deal_weight = DealWeight::zero(); let verified_deal_weight = deal_space * DealWeight::from(deal_lifespan); @@ -256,7 +256,7 @@ fn prove_sectors_from_batch_pre_commit() { { let precommit = &precommits[1]; let mut pcc = ProveCommitConfig::empty(); - pcc.add_verified_deals(precommit.info.sector_number, vec![verified_deal1]); + pcc.add_activated_deals(precommit.info.sector_number, vec![verified_deal1]); let sector = h .prove_commit_sector_and_confirm( &rt, @@ -283,7 +283,7 @@ fn prove_sectors_from_batch_pre_commit() { { let precommit = &precommits[2]; let mut pcc = ProveCommitConfig::empty(); - pcc.add_verified_deals(precommit.info.sector_number, vec![verified_deal2, verified_deal3]); + pcc.add_activated_deals(precommit.info.sector_number, vec![verified_deal2, verified_deal3]); let sector = h .prove_commit_sector_and_confirm( &rt, @@ -508,6 +508,10 @@ fn drop_invalid_prove_commit_while_processing_valid_one() { let conf = ProveCommitConfig { verify_deals_exit: HashMap::from([(sector_no_a, ExitCode::USR_ILLEGAL_ARGUMENT)]), + activated_deals: HashMap::from([( + sector_no_b, + vec![test_activated_deal(100, NO_ALLOCATION_ID)], + )]), ..Default::default() }; h.confirm_sector_proofs_valid(&rt, conf, vec![pre_commit_a, pre_commit_b]).unwrap(); @@ -572,6 +576,7 @@ fn sector_with_non_positive_lifetime_fails_in_confirmation() { h.confirm_sector_proofs_valid(&rt, ProveCommitConfig::empty(), vec![precommit]), ); h.check_state(&rt); + rt.reset(); } #[test] diff --git a/actors/miner/tests/terminate_sectors_test.rs b/actors/miner/tests/terminate_sectors_test.rs index 2c8b0d1d5..6a5888a5f 100644 --- a/actors/miner/tests/terminate_sectors_test.rs +++ b/actors/miner/tests/terminate_sectors_test.rs @@ -7,8 +7,8 @@ use fil_actor_miner::{ use fil_actors_runtime::{ runtime::Runtime, test_utils::{expect_abort_contains_message, MockRuntime, ACCOUNT_ACTOR_CODE_ID}, - DealWeight, BURNT_FUNDS_ACTOR_ADDR, EPOCHS_IN_DAY, STORAGE_MARKET_ACTOR_ADDR, - STORAGE_POWER_ACTOR_ADDR, SYSTEM_ACTOR_ADDR, + BURNT_FUNDS_ACTOR_ADDR, EPOCHS_IN_DAY, STORAGE_MARKET_ACTOR_ADDR, STORAGE_POWER_ACTOR_ADDR, + SYSTEM_ACTOR_ADDR, }; use fvm_ipld_bitfield::BitField; use fvm_shared::{econ::TokenAmount, error::ExitCode, METHOD_SEND}; @@ -16,7 +16,7 @@ use std::collections::HashMap; mod util; -use fil_actor_market::VerifiedDealInfo; +use fil_actor_market::{ActivatedDeal, NO_ALLOCATION_ID}; use fil_actor_miner::ext::market::{ OnMinerSectorsTerminateParams, ON_MINER_SECTORS_TERMINATE_METHOD, }; @@ -96,16 +96,26 @@ fn removes_sector_with_without_deals() { ProveCommitConfig { verify_deals_exit: Default::default(), claim_allocs_exit: Default::default(), - deal_space: HashMap::from_iter(vec![(1, DealWeight::from(1024))]), - verified_deal_infos: HashMap::from_iter(vec![( - 2, - vec![VerifiedDealInfo { - client: 0, - allocation_id: 0, - data: Default::default(), - size: PaddedPieceSize(1024), - }], - )]), + activated_deals: HashMap::from_iter(vec![ + ( + 1, + vec![ActivatedDeal { + client: 0, + allocation_id: NO_ALLOCATION_ID, + data: Default::default(), + size: PaddedPieceSize(1024), + }], + ), + ( + 2, + vec![ActivatedDeal { + client: 0, + allocation_id: 1, + data: Default::default(), + size: PaddedPieceSize(1024), + }], + ), + ]), }, ); let snos: Vec = sectors.iter().map(|s| s.sector_number).collect(); @@ -182,8 +192,15 @@ fn owner_cannot_terminate_if_market_fails() { ProveCommitConfig { verify_deals_exit: Default::default(), claim_allocs_exit: Default::default(), - deal_space: HashMap::from_iter(vec![(0, DealWeight::from(1024))]), - verified_deal_infos: Default::default(), + activated_deals: HashMap::from_iter(vec![( + 0, + vec![ActivatedDeal { + client: 0, + allocation_id: NO_ALLOCATION_ID, + data: Default::default(), + size: PaddedPieceSize(1024), + }], + )]), }, ); diff --git a/actors/miner/tests/util.rs b/actors/miner/tests/util.rs index b4206ddd1..2cc9968b5 100644 --- a/actors/miner/tests/util.rs +++ b/actors/miner/tests/util.rs @@ -45,8 +45,8 @@ use num_traits::Signed; use fil_actor_account::Method as AccountMethod; use fil_actor_market::{ - BatchActivateDealsParams, BatchActivateDealsResult, Method as MarketMethod, - OnMinerSectorsTerminateParams, SectorDealActivation, SectorDeals, VerifiedDealInfo, + ActivatedDeal, BatchActivateDealsParams, BatchActivateDealsResult, Method as MarketMethod, + OnMinerSectorsTerminateParams, SectorDealActivation, SectorDeals, VerifyDealsForActivationParams, VerifyDealsForActivationReturn, NO_ALLOCATION_ID, }; use fil_actor_miner::{ @@ -91,7 +91,7 @@ use fil_actor_power::{ use fil_actor_reward::{Method as RewardMethod, ThisEpochRewardReturn}; use fil_actors_runtime::cbor::serialize; use fil_actors_runtime::runtime::{DomainSeparationTag, Runtime, RuntimePolicy}; -use fil_actors_runtime::{test_utils::*, BatchReturn, BatchReturnGen}; +use fil_actors_runtime::{test_utils::*, BatchReturn, BatchReturnGen, EventBuilder}; use fil_actors_runtime::{ ActorDowncast, ActorError, Array, DealWeight, MessageAccumulator, BURNT_FUNDS_ACTOR_ADDR, INIT_ACTOR_ADDR, REWARD_ACTOR_ADDR, STORAGE_MARKET_ACTOR_ADDR, STORAGE_POWER_ACTOR_ADDR, @@ -661,6 +661,10 @@ impl ActorHarness { ); } + for sn in sectors { + expect_event(rt, "sector-precommitted", &sn.sector_number); + } + let param = PreCommitSectorBatchParams2 { sectors: sectors.into() }; let result = rt.call::( Method::PreCommitSectorBatch2 as u64, @@ -842,6 +846,9 @@ impl ActorHarness { .map(|pc| pc.info.unsealed_cid.get_cid(pc.info.seal_proof).unwrap()) .collect(); + let unsealed_cids: Vec> = + precommits.iter().map(|pc| pc.info.unsealed_cid.0.clone()).collect(); + self.expect_query_network_info(rt); // expect randomness queries for provided precommits @@ -861,7 +868,14 @@ impl ActorHarness { rt.expect_aggregate_verify_seals(svis, params.aggregate_proof.clone().into(), Ok(())); // confirm sector proofs valid - self.confirm_sector_proofs_valid_internal(rt, config, &precommits); + let pieces = self.confirm_sector_proofs_valid_internal(rt, config, &precommits); + + // sector activated event + for (i, sc) in precommits.iter().enumerate() { + let num = &sc.info.sector_number; + let piece_info = pieces.get(&num).unwrap(); + expect_sector_event(rt, "sector-activated", &num, unsealed_cids[i], &piece_info); + } // burn network fee let expected_fee = aggregate_prove_commit_network_fee(precommits.len(), base_fee); @@ -893,10 +907,10 @@ impl ActorHarness { cfg: ProveCommitConfig, pcs: Vec, ) -> Result<(), ActorError> { - self.confirm_sector_proofs_valid_internal(rt, cfg, &pcs); + let pieces = self.confirm_sector_proofs_valid_internal(rt, cfg.clone(), &pcs); let mut all_sector_numbers = Vec::new(); - for pc in pcs { + for pc in pcs.clone() { all_sector_numbers.push(pc.info.sector_number); } @@ -909,6 +923,24 @@ impl ActorHarness { reward_baseline_power: self.baseline_power.clone(), quality_adj_power_smoothed: self.epoch_qa_power_smooth.clone(), }; + + for sc in pcs.iter() { + if cfg.verify_deals_exit.contains_key(&sc.info.sector_number) + || cfg.claim_allocs_exit.contains_key(&sc.info.sector_number) + { + continue; + } + let unsealed_cid = sc.info.unsealed_cid.0; + let num = &sc.info.sector_number; + expect_sector_event( + rt, + "sector-activated", + &num, + unsealed_cid, + pieces.get(&num).unwrap(), + ); + } + rt.call::( Method::ConfirmSectorProofsValid as u64, IpldBlock::serialize_cbor(¶ms).unwrap(), @@ -922,7 +954,7 @@ impl ActorHarness { rt: &MockRuntime, cfg: ProveCommitConfig, pcs: &[SectorPreCommitOnChainInfo], - ) { + ) -> HashMap> { let mut valid_pcs = Vec::new(); // claim FIL+ allocations @@ -932,10 +964,12 @@ impl ActorHarness { let mut sector_activation_params: Vec = Vec::new(); let mut sector_activations: Vec = Vec::new(); let mut sector_activation_results = BatchReturnGen::new(pcs.len()); + let mut pieces: HashMap> = HashMap::new(); for pc in pcs { + pieces.insert(pc.info.sector_number, Vec::new()); + if !pc.info.deal_ids.is_empty() { - let deal_space = cfg.deal_space(pc.info.sector_number); let activate_params = SectorDeals { sector_number: pc.info.sector_number, deal_ids: pc.info.deal_ids.clone(), @@ -945,15 +979,18 @@ impl ActorHarness { sector_activation_params.push(activate_params); let ret = SectorDealActivation { - nonverified_deal_space: deal_space, - verified_infos: cfg - .verified_deal_infos + activated: cfg + .activated_deals .get(&pc.info.sector_number) .cloned() .unwrap_or_default(), unsealed_cid: None, }; + for info in &ret.activated { + pieces.get_mut(&pc.info.sector_number).unwrap().push((info.data, info.size.0)); + } + let mut activate_deals_exit = ExitCode::OK; match cfg.verify_deals_exit.get(&pc.info.sector_number) { Some(exit_code) => { @@ -969,8 +1006,9 @@ impl ActorHarness { if activate_deals_exit == ExitCode::OK { valid_pcs.push(pc); let sector_claims = ret - .verified_infos + .activated .iter() + .filter(|info| info.allocation_id != NO_ALLOCATION_ID) .map(|info| ext::verifreg::AllocationClaim { client: info.client, allocation_id: info.allocation_id, @@ -992,11 +1030,8 @@ impl ActorHarness { sector_expiry: pc.info.expiration, sector_type: RegisteredSealProof::StackedDRG8MiBV1, }); - sector_activations.push(SectorDealActivation { - nonverified_deal_space: BigInt::zero(), - verified_infos: vec![], - unsealed_cid: None, - }); + sector_activations + .push(SectorDealActivation { activated: vec![], unsealed_cid: None }); sector_activation_results.add_success(); sectors_claims.push(ext::verifreg::SectorAllocationClaims { sector: pc.info.sector_number, @@ -1082,6 +1117,8 @@ impl ActorHarness { expect_update_pledge(rt, &expected_pledge); } + + pieces } pub fn prove_commit_sectors2( @@ -1181,11 +1218,17 @@ impl ActorHarness { let mut expected_pledge = TokenAmount::zero(); let mut expected_qa_power = StoragePower::zero(); let mut expected_sector_notifications = Vec::new(); // Assuming all to f05 + let mut unsealed_cids: HashMap> = HashMap::new(); + for (i, sa) in sector_activations.iter().enumerate() { if cfg.validation_failure.contains(&i) || cfg.proof_failure.contains(&i) { continue; } - expect_compute_unsealed_cid_from_pieces(rt, self.seal_proof_type, &sa.pieces); + let comm_d = + expect_compute_unsealed_cid_from_pieces(rt, self.seal_proof_type, &sa.pieces); + let unsealed_cid = comm_d.0; + unsealed_cids.insert(sa.sector_number, unsealed_cid); + let precommit = self.get_precommit(rt, sa.sector_number); sector_allocation_claims.push(SectorAllocationClaims { sector: sa.sector_number, @@ -1304,6 +1347,23 @@ impl ActorHarness { ); } + for (i, sm) in sector_activations.iter().enumerate() { + if cfg.validation_failure.contains(&i) + || cfg.proof_failure.contains(&i) + || cfg.claim_failure.contains(&i) + { + continue; + } + let pieces = sm.pieces.iter().map(|p| (p.cid, p.size.0)).collect(); + expect_sector_event( + rt, + "sector-activated", + &sm.sector_number, + *unsealed_cids.get(&sm.sector_number).unwrap(), + &pieces, + ) + } + let result = rt.call::( MinerMethod::ProveCommitSectors3 as u64, IpldBlock::serialize_cbor(¶ms).unwrap(), @@ -1361,10 +1421,14 @@ impl ActorHarness { let mut expected_pledge = TokenAmount::zero(); let mut expected_qa_power = StoragePower::zero(); let mut expected_sector_notifications = Vec::new(); // Assuming all to f05 + let mut unsealed_cids: HashMap> = HashMap::new(); + for (i, sup) in sector_updates.iter().enumerate() { let sector = self.get_sector(rt, sup.sector); let unsealed_cid = expect_compute_unsealed_cid_from_pieces(rt, self.seal_proof_type, &sup.pieces); + unsealed_cids.insert(sector.sector_number, unsealed_cid.0); + if cfg.validation_failure.contains(&i) { continue; } @@ -1488,6 +1552,24 @@ impl ActorHarness { ); } + for (i, sm) in params.sector_updates.iter().enumerate() { + if cfg.validation_failure.contains(&i) + || cfg.proof_failure.contains(&i) + || cfg.claim_failure.contains(&i) + { + continue; + } + let pieces = sm.pieces.iter().map(|p| (p.cid, p.size.0)).collect(); + + expect_sector_event( + rt, + "sector-updated", + &sm.sector, + *unsealed_cids.get(&sm.sector).unwrap(), + &pieces, + ) + } + let result = rt.call::( MinerMethod::ProveReplicaUpdates3 as u64, IpldBlock::serialize_cbor(¶ms).unwrap(), @@ -2341,6 +2423,12 @@ impl ActorHarness { }); } + for termination in terminations.iter() { + for sector in termination.sectors.iter() { + expect_event(rt, "sector-terminated", §or); + } + } + let params = TerminateSectorsParams { terminations }; rt.call::( @@ -2812,6 +2900,31 @@ impl ActorHarness { } } +pub fn expect_sector_event( + rt: &MockRuntime, + typ: &str, + sector: &SectorNumber, + unsealed_cid: Option, + pieces: &Vec<(Cid, u64)>, +) { + let mut base_event = EventBuilder::new() + .typ(typ) + .field_indexed("sector", §or) + .field_indexed("unsealed-cid", &unsealed_cid); + + for piece in pieces { + base_event = base_event.field_indexed("piece-cid", &piece.0).field("piece-size", &piece.1); + } + + rt.expect_emitted_event(base_event.build().unwrap()); +} + +pub fn expect_event(rt: &MockRuntime, typ: &str, sector: &SectorNumber) { + rt.expect_emitted_event( + EventBuilder::new().typ(typ).field_indexed("sector", sector).build().unwrap(), + ); +} + #[allow(dead_code)] pub struct PoStConfig { pub chain_randomness: Option<[u8; RANDOMNESS_LENGTH]>, @@ -2864,17 +2977,16 @@ impl PreCommitConfig { pub struct ProveCommitConfig { pub verify_deals_exit: HashMap, pub claim_allocs_exit: HashMap, - pub deal_space: HashMap, - pub verified_deal_infos: HashMap>, + pub activated_deals: HashMap>, } #[allow(dead_code)] -pub fn test_verified_deal(space: u64) -> VerifiedDealInfo { +pub fn test_activated_deal(space: u64, alloc: AllocationID) -> ActivatedDeal { // only set size for testing and zero out remaining fields - VerifiedDealInfo { + ActivatedDeal { client: 0, - allocation_id: 0, - data: make_unsealed_cid("test verified deal".as_bytes()), + allocation_id: alloc, + data: make_unsealed_cid("test activated deal".as_bytes()), size: PaddedPieceSize(space), } } @@ -2885,24 +2997,32 @@ impl ProveCommitConfig { ProveCommitConfig { verify_deals_exit: HashMap::new(), claim_allocs_exit: HashMap::new(), - deal_space: HashMap::new(), - verified_deal_infos: HashMap::new(), + activated_deals: HashMap::new(), } } - pub fn add_verified_deals(&mut self, sector: SectorNumber, deals: Vec) { - self.verified_deal_infos.insert(sector, deals); + pub fn add_activated_deals(&mut self, sector: SectorNumber, deals: Vec) { + self.activated_deals.insert(sector, deals); } pub fn deal_space(&self, sector: SectorNumber) -> BigInt { - self.deal_space.get(§or).cloned().unwrap_or_default() + match self.activated_deals.get(§or) { + None => BigInt::zero(), + Some(infos) => infos + .iter() + .filter(|info| info.allocation_id == NO_ALLOCATION_ID) + .map(|info| BigInt::from(info.size.0)) + .reduce(|x, a| x + a) + .unwrap_or_default(), + } } pub fn verified_deal_space(&self, sector: SectorNumber) -> BigInt { - match self.verified_deal_infos.get(§or) { + match self.activated_deals.get(§or) { None => BigInt::zero(), Some(infos) => infos .iter() + .filter(|info| info.allocation_id != NO_ALLOCATION_ID) .map(|info| BigInt::from(info.size.0)) .reduce(|x, a| x + a) .unwrap_or_default(), diff --git a/actors/verifreg/src/emit.rs b/actors/verifreg/src/emit.rs new file mode 100644 index 000000000..c66897108 --- /dev/null +++ b/actors/verifreg/src/emit.rs @@ -0,0 +1,98 @@ +// A namespace for helpers that build and emit verified registry events. + +use crate::{ActorError, AllocationID}; +use crate::{ClaimID, DataCap}; +use fil_actors_runtime::runtime::Runtime; +use fil_actors_runtime::EventBuilder; +use fvm_shared::ActorID; + +/// Indicates a new value for a verifier's datacap balance. +/// Note that receiving this event does not necessarily mean the balance has changed. +/// The value is in datacap whole units (not TokenAmount). +pub fn verifier_balance( + rt: &impl Runtime, + verifier: ActorID, + new_balance: &DataCap, +) -> Result<(), ActorError> { + rt.emit_event( + &EventBuilder::new() + .typ("verifier-balance") + .field_indexed("verifier", &verifier) + .field("balance", new_balance) + .build()?, + ) +} + +/// Indicates a new allocation has been made. +pub fn allocation( + rt: &impl Runtime, + id: AllocationID, + client: ActorID, + provider: ActorID, +) -> Result<(), ActorError> { + rt.emit_event( + &EventBuilder::new().typ("allocation").with_parties(id, client, provider).build()?, + ) +} + +/// Indicates an expired allocation has been removed. +pub fn allocation_removed( + rt: &impl Runtime, + id: AllocationID, + client: ActorID, + provider: ActorID, +) -> Result<(), ActorError> { + rt.emit_event( + &EventBuilder::new() + .typ("allocation-removed") + .with_parties(id, client, provider) + .build()?, + ) +} + +/// Indicates an allocation has been claimed. +pub fn claim( + rt: &impl Runtime, + id: ClaimID, + client: ActorID, + provider: ActorID, +) -> Result<(), ActorError> { + rt.emit_event(&EventBuilder::new().typ("claim").with_parties(id, client, provider).build()?) +} + +/// Indicates an existing claim has been updated (e.g. with a longer term). +pub fn claim_updated( + rt: &impl Runtime, + id: ClaimID, + client: ActorID, + provider: ActorID, +) -> Result<(), ActorError> { + rt.emit_event( + &EventBuilder::new().typ("claim-updated").with_parties(id, client, provider).build()?, + ) +} + +/// Indicates an expired claim has been removed. +pub fn claim_removed( + rt: &impl Runtime, + id: ClaimID, + client: ActorID, + provider: ActorID, +) -> Result<(), ActorError> { + rt.emit_event( + &EventBuilder::new().typ("claim-removed").with_parties(id, client, provider).build()?, + ) +} + +// Private helpers // +trait WithParties { + fn with_parties(self, id: AllocationID, client: ActorID, provider: ActorID) -> EventBuilder; +} + +impl WithParties for EventBuilder { + fn with_parties(self, id: AllocationID, client: ActorID, provider: ActorID) -> EventBuilder { + self.field_indexed("id", &id) + .field_indexed("client", &client) + .field_indexed("provider", &provider) + } +} diff --git a/actors/verifreg/src/lib.rs b/actors/verifreg/src/lib.rs index 18d5f617f..d44cfa974 100644 --- a/actors/verifreg/src/lib.rs +++ b/actors/verifreg/src/lib.rs @@ -42,6 +42,8 @@ pub use self::types::*; #[cfg(feature = "fil-actor")] fil_actors_runtime::wasm_trampoline!(Actor); +mod emit; + pub mod expiration; pub mod ext; pub mod state; @@ -103,31 +105,33 @@ impl Actor { } let verifier = resolve_to_actor_id(rt, ¶ms.address, true)?; - let verifier = Address::new_id(verifier); + let verifier_addr = Address::new_id(verifier); let st: State = rt.state()?; rt.validate_immediate_caller_is(std::iter::once(&st.root_key))?; // Disallow root as a verifier. - if verifier == st.root_key { + if verifier_addr == st.root_key { return Err(actor_error!(illegal_argument, "Rootkey cannot be added as verifier")); } // Disallow existing clients as verifiers. - let token_balance = balance(rt, &verifier)?; + let token_balance = balance(rt, &verifier_addr)?; if token_balance.is_positive() { return Err(actor_error!( illegal_argument, "verified client {} cannot become a verifier", - verifier + verifier_addr )); } // Store the new verifier and allowance (over-writing). rt.transaction(|st: &mut State, rt| { - st.put_verifier(rt.store(), &verifier, ¶ms.allowance) + st.put_verifier(rt.store(), &verifier_addr, ¶ms.allowance) .context("failed to add verifier") - }) + })?; + + emit::verifier_balance(rt, verifier, ¶ms.allowance) } pub fn remove_verifier( @@ -135,12 +139,14 @@ impl Actor { params: RemoveVerifierParams, ) -> Result<(), ActorError> { let verifier = resolve_to_actor_id(rt, ¶ms.verifier, false)?; - let verifier = Address::new_id(verifier); + let verifier_addr = Address::new_id(verifier); rt.transaction(|st: &mut State, rt| { rt.validate_immediate_caller_is(std::iter::once(&st.root_key))?; - st.remove_verifier(rt.store(), &verifier).context("failed to remove verifier") - }) + st.remove_verifier(rt.store(), &verifier_addr).context("failed to remove verifier") + })?; + + emit::verifier_balance(rt, verifier, &DataCap::zero()) } pub fn add_verified_client( @@ -168,10 +174,11 @@ impl Actor { } // Validate caller is one of the verifiers, i.e. has an allowance (even if zero). - let verifier = rt.message().caller(); - let verifier_cap = st - .get_verifier_cap(rt.store(), &verifier)? - .ok_or_else(|| actor_error!(not_found, "caller {} is not a verifier", verifier))?; + let verifier_addr = rt.message().caller(); + let verifier_cap = + st.get_verifier_cap(rt.store(), &verifier_addr)?.ok_or_else(|| { + actor_error!(not_found, "caller {} is not a verifier", verifier_addr) + })?; // Disallow existing verifiers as clients. if st.get_verifier_cap(rt.store(), &client)?.is_some() { @@ -194,8 +201,10 @@ impl Actor { // Reduce verifier's cap. let new_verifier_cap = verifier_cap - ¶ms.allowance; - st.put_verifier(rt.store(), &verifier, &new_verifier_cap) - .context("failed to update verifier allowance") + st.put_verifier(rt.store(), &verifier_addr, &new_verifier_cap) + .context("failed to update verifier allowance")?; + + emit::verifier_balance(rt, verifier_addr.id().unwrap(), &new_verifier_cap) })?; // Credit client token allowance. @@ -328,12 +337,18 @@ impl Actor { } for id in to_remove { - let existing = allocs.remove(params.client, *id).context_code( - ExitCode::USR_ILLEGAL_STATE, - format!("failed to remove allocation {}", id), - )?; + let existing = allocs + .remove(params.client, *id) + .context_code( + ExitCode::USR_ILLEGAL_STATE, + format!("failed to remove allocation {}", id), + )? + .unwrap(); // Unwrapping here as both paths to here should ensure the allocation exists. + + emit::allocation_removed(rt, *id, existing.client, existing.provider)?; + // Unwrapping here as both paths to here should ensure the allocation exists. - recovered_datacap += existing.unwrap().size.0; + recovered_datacap += existing.size.0; } st.save_allocs(&mut allocs)?; @@ -432,6 +447,9 @@ impl Actor { if !inserted { return Err(actor_error!(illegal_argument, "claim {} already exists", id)); } + + emit::claim(rt, id, new_claim.client, new_claim.provider)?; + allocs.remove(new_claim.client, id).context_code( ExitCode::USR_ILLEGAL_STATE, format!("failed to remove allocation {}", id), @@ -542,11 +560,12 @@ impl Actor { } let new_claim = Claim { term_max: term.term_max, ..*claim }; - st_claims.put(term.provider, term.claim_id, new_claim).context_code( + st_claims.put(term.provider, term.claim_id, new_claim.clone()).context_code( ExitCode::USR_ILLEGAL_STATE, "HAMT put failure storing new claims", )?; batch_gen.add_success(); + emit::claim_updated(rt, term.claim_id, new_claim.client, new_claim.provider)?; } else { batch_gen.add_fail(ExitCode::USR_NOT_FOUND); info!("no claim {} for provider {}", term.claim_id, term.provider); @@ -590,10 +609,15 @@ impl Actor { } for id in to_remove { - claims.remove(params.provider, *id).context_code( - ExitCode::USR_ILLEGAL_STATE, - format!("failed to remove claim {}", id), - )?; + let removed = claims + .remove(params.provider, *id) + .context_code( + ExitCode::USR_ILLEGAL_STATE, + format!("failed to remove claim {}", id), + )? + .unwrap(); + + emit::claim_removed(rt, *id, removed.client, removed.provider)?; } st.save_claims(&mut claims)?; @@ -690,8 +714,18 @@ impl Actor { // Save new allocations and updated claims. let ids = rt.transaction(|st: &mut State, rt| { - let ids = st.insert_allocations(rt.store(), client, new_allocs)?; - st.put_claims(rt.store(), updated_claims)?; + let ids = st.insert_allocations(rt.store(), client, new_allocs.clone())?; + + for (id, alloc) in ids.iter().zip(new_allocs.iter()) { + emit::allocation(rt, *id, alloc.client, alloc.provider)?; + } + + st.put_claims(rt.store(), updated_claims.clone())?; + + for (id, claim) in updated_claims { + emit::claim_updated(rt, id, claim.client, claim.provider)?; + } + Ok(ids) })?; diff --git a/actors/verifreg/tests/harness/mod.rs b/actors/verifreg/tests/harness/mod.rs index c5f237305..eac9acfc5 100644 --- a/actors/verifreg/tests/harness/mod.rs +++ b/actors/verifreg/tests/harness/mod.rs @@ -35,7 +35,7 @@ use fil_actors_runtime::runtime::policy_constants::{ use fil_actors_runtime::runtime::Runtime; use fil_actors_runtime::test_utils::*; use fil_actors_runtime::{ - make_empty_map, ActorError, AsActorError, BatchReturn, DATACAP_TOKEN_ACTOR_ADDR, + make_empty_map, ActorError, AsActorError, BatchReturn, EventBuilder, DATACAP_TOKEN_ACTOR_ADDR, STORAGE_MARKET_ACTOR_ADDR, SYSTEM_ACTOR_ADDR, VERIFIED_REGISTRY_ACTOR_ADDR, }; @@ -139,6 +139,14 @@ impl Harness { None, ); + rt.expect_emitted_event( + EventBuilder::new() + .typ("verifier-balance") + .field_indexed("verifier", &verifier_resolved.id().unwrap()) + .field("balance", &allowance) + .build()?, + ); + let params = AddVerifierParams { address: *verifier, allowance: allowance.clone() }; let ret = rt.call::( Method::AddVerifier as MethodNum, @@ -153,6 +161,14 @@ impl Harness { pub fn remove_verifier(&self, rt: &MockRuntime, verifier: &Address) -> Result<(), ActorError> { rt.expect_validate_caller_addr(vec![self.root]); + + rt.expect_emitted_event( + EventBuilder::new() + .typ("verifier-balance") + .field_indexed("verifier", &verifier.id().unwrap()) + .field("balance", &DataCap::zero()) + .build()?, + ); rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, self.root); let ret = rt.call::( Method::RemoveVerifier as MethodNum, @@ -192,6 +208,7 @@ impl Harness { verifier: &Address, client: &Address, allowance: &DataCap, + verifier_balance: &DataCap, ) -> Result<(), ActorError> { rt.expect_validate_caller_any(); rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, *verifier); @@ -213,6 +230,13 @@ impl Harness { ); let params = AddVerifiedClientParams { address: *client, allowance: allowance.clone() }; + rt.expect_emitted_event( + EventBuilder::new() + .typ("verifier-balance") + .field_indexed("verifier", &verifier.id().unwrap()) + .field("balance", &(verifier_balance - allowance)) + .build()?, + ); let ret = rt.call::( Method::AddVerifiedClient as MethodNum, IpldBlock::serialize_cbor(¶ms).unwrap(), @@ -265,10 +289,15 @@ impl Harness { claim_allocs: Vec, datacap_burnt: u64, all_or_nothing: bool, + expect_claimed: Vec<(AllocationID, Allocation)>, ) -> Result { rt.expect_validate_caller_type(vec![Type::Miner]); rt.set_caller(*MINER_ACTOR_CODE_ID, Address::new_id(provider)); + for (id, alloc) in expect_claimed.iter() { + expect_emitted(rt, "claim", id, alloc.client, alloc.provider); + } + if datacap_burnt > 0 { rt.expect_send_simple( DATACAP_TOKEN_ACTOR_ADDR, @@ -302,10 +331,16 @@ impl Harness { rt: &MockRuntime, client: ActorID, allocation_ids: Vec, - expected_datacap: u64, + expect_removed: Vec<(AllocationID, Allocation)>, ) -> Result { rt.expect_validate_caller_any(); + let mut expected_datacap = 0u64; + for (id, alloc) in expect_removed { + expected_datacap += alloc.size.0; + + expect_emitted(rt, "allocation-removed", &id, alloc.client, alloc.provider); + } rt.expect_send_simple( DATACAP_TOKEN_ACTOR_ADDR, ext::datacap::Method::Transfer as MethodNum, @@ -339,9 +374,13 @@ impl Harness { rt: &MockRuntime, provider: ActorID, claim_ids: Vec, + expect_removed: Vec<(ClaimID, Claim)>, ) -> Result { rt.expect_validate_caller_any(); + for (id, claim) in expect_removed { + expect_emitted(rt, "claim-removed", &id, claim.client, claim.provider); + } let params = RemoveExpiredClaimsParams { provider, claim_ids }; let ret = rt .call::( @@ -390,6 +429,16 @@ impl Harness { ); } + let allocs_req: AllocationRequests = payload.operator_data.deserialize().unwrap(); + for (alloc, id) in allocs_req.allocations.iter().zip(expected_alloc_ids.iter()) { + expect_emitted(rt, "allocation", id, payload.from, alloc.provider); + } + + for ext in allocs_req.extensions { + let claim = self.load_claim(rt, ext.provider, ext.claim).unwrap(); + expect_emitted(rt, "claim-updated", &ext.claim, claim.client, claim.provider); + } + rt.expect_validate_caller_addr(vec![DATACAP_TOKEN_ACTOR_ADDR]); let ret = rt.call::( Method::UniversalReceiverHook as MethodNum, @@ -445,7 +494,12 @@ impl Harness { &self, rt: &MockRuntime, params: &ExtendClaimTermsParams, + expected: Vec<(ClaimID, Claim)>, ) -> Result { + for (id, new_claim) in expected.iter() { + expect_emitted(rt, "claim-updated", id, new_claim.client, new_claim.provider); + } + rt.expect_validate_caller_any(); let ret = rt .call::( @@ -484,6 +538,18 @@ pub fn make_alloc_req(rt: &MockRuntime, provider: ActorID, size: u64) -> Allocat } } +pub fn expect_emitted(rt: &MockRuntime, typ: &str, id: &u64, client: ActorID, provider: ActorID) { + rt.expect_emitted_event( + EventBuilder::new() + .typ(typ) + .field_indexed("id", &id) + .field_indexed("client", &client) + .field_indexed("provider", &provider) + .build() + .unwrap(), + ); +} + pub fn make_extension_req( provider: ActorID, claim: ClaimID, diff --git a/actors/verifreg/tests/verifreg_actor_test.rs b/actors/verifreg/tests/verifreg_actor_test.rs index ef81977d7..7b94d87b9 100644 --- a/actors/verifreg/tests/verifreg_actor_test.rs +++ b/actors/verifreg/tests/verifreg_actor_test.rs @@ -140,6 +140,7 @@ mod verifiers { h.add_verifier_with_existing_cap(&rt, &VERIFIER, &allowance, &DataCap::from(1)), ); h.check_state(&rt); + rt.reset(); } #[test] @@ -211,6 +212,7 @@ mod verifiers { let (h, rt) = new_harness(); expect_abort(ExitCode::USR_ILLEGAL_ARGUMENT, h.remove_verifier(&rt, &VERIFIER)); h.check_state(&rt); + rt.reset(); } #[test] @@ -249,7 +251,7 @@ mod clients { ext, Actor as VerifregActor, AddVerifiedClientParams, DataCap, Method, }; use fil_actors_runtime::test_utils::*; - use fil_actors_runtime::{DATACAP_TOKEN_ACTOR_ADDR, STORAGE_MARKET_ACTOR_ADDR}; + use fil_actors_runtime::{EventBuilder, DATACAP_TOKEN_ACTOR_ADDR, STORAGE_MARKET_ACTOR_ADDR}; use harness::*; use util::*; @@ -264,11 +266,25 @@ mod clients { h.add_verifier(&rt, &VERIFIER, &allowance_verifier).unwrap(); h.add_verifier(&rt, &VERIFIER2, &allowance_verifier).unwrap(); - h.add_client(&rt, &VERIFIER, &CLIENT, &allowance_client).unwrap(); - h.add_client(&rt, &VERIFIER, &CLIENT2, &allowance_client).unwrap(); + h.add_client(&rt, &VERIFIER, &CLIENT, &allowance_client, &allowance_verifier).unwrap(); + h.add_client( + &rt, + &VERIFIER, + &CLIENT2, + &allowance_client, + &(&allowance_verifier - &allowance_client), + ) + .unwrap(); - h.add_client(&rt, &VERIFIER2, &CLIENT3, &allowance_client).unwrap(); - h.add_client(&rt, &VERIFIER2, &CLIENT4, &allowance_client).unwrap(); + h.add_client(&rt, &VERIFIER2, &CLIENT3, &allowance_client, &allowance_verifier).unwrap(); + h.add_client( + &rt, + &VERIFIER2, + &CLIENT4, + &allowance_client, + &(&allowance_verifier - &allowance_client), + ) + .unwrap(); // No more allowance left h.assert_verifier_allowance(&rt, &VERIFIER, &DataCap::from(0)); @@ -283,10 +299,10 @@ mod clients { // Verifier only has allowance for one client. h.add_verifier(&rt, &VERIFIER, &allowance).unwrap(); - h.add_client(&rt, &VERIFIER, &CLIENT, &allowance).unwrap(); + h.add_client(&rt, &VERIFIER, &CLIENT, &allowance, &allowance).unwrap(); expect_abort( ExitCode::USR_ILLEGAL_ARGUMENT, - h.add_client(&rt, &VERIFIER, &CLIENT2, &allowance), + h.add_client(&rt, &VERIFIER, &CLIENT2, &allowance, &DataCap::zero()), ); rt.reset(); h.assert_verifier_allowance(&rt, &VERIFIER, &DataCap::zero()); @@ -303,12 +319,13 @@ mod clients { rt.id_addresses.borrow_mut().insert(client_pubkey, *CLIENT); h.add_verifier(&rt, &VERIFIER, &allowance_verifier).unwrap(); - h.add_client(&rt, &VERIFIER, &client_pubkey, &allowance_client).unwrap(); + h.add_client(&rt, &VERIFIER, &client_pubkey, &allowance_client, &allowance_verifier) + .unwrap(); // Adding another client with the same address increments // the data cap which has already been granted. h.add_verifier(&rt, &VERIFIER, &allowance_verifier).unwrap(); - h.add_client(&rt, &VERIFIER, &CLIENT, &allowance_client).unwrap(); + h.add_client(&rt, &VERIFIER, &CLIENT, &allowance_client, &allowance_verifier).unwrap(); h.check_state(&rt); } @@ -319,7 +336,7 @@ mod clients { h.add_verifier(&rt, &VERIFIER, &allowance_verifier).unwrap(); let allowance = &rt.policy.minimum_verified_allocation_size; - h.add_client(&rt, &VERIFIER, &CLIENT, allowance).unwrap(); + h.add_client(&rt, &VERIFIER, &CLIENT, allowance, &allowance_verifier).unwrap(); h.check_state(&rt); } @@ -344,7 +361,7 @@ mod clients { expect_abort( ExitCode::USR_ILLEGAL_ARGUMENT, - h.add_client(&rt, &VERIFIER, &client, &allowance_client), + h.add_client(&rt, &VERIFIER, &client, &allowance_client, &allowance_verifier), ); rt.reset(); h.check_state(&rt); @@ -359,7 +376,7 @@ mod clients { let allowance = rt.policy.minimum_verified_allocation_size.clone() - 1; expect_abort( ExitCode::USR_ILLEGAL_ARGUMENT, - h.add_client(&rt, &VERIFIER, &CLIENT, &allowance), + h.add_client(&rt, &VERIFIER, &CLIENT, &allowance, &allowance_verifier), ); rt.reset(); h.check_state(&rt); @@ -408,8 +425,7 @@ mod clients { IpldBlock::serialize_cbor(¶ms).unwrap(), ), ); - - rt.verify(); + rt.reset(); // can call the exported method num @@ -427,6 +443,15 @@ mod clients { ExitCode::OK, ); + rt.expect_emitted_event( + EventBuilder::new() + .typ("verifier-balance") + .field_indexed("verifier", &VERIFIER.id().unwrap()) + .field("balance", &(allowance_verifier - allowance_client)) + .build() + .unwrap(), + ); + rt.expect_validate_caller_any(); rt.call::( Method::AddVerifiedClientExported as MethodNum, @@ -445,10 +470,10 @@ mod clients { let allowance_verifier = verifier_allowance(&rt); h.add_verifier(&rt, &VERIFIER, &allowance_verifier).unwrap(); - let allowance = allowance_verifier + 1; + let allowance = &allowance_verifier + 1; expect_abort( ExitCode::USR_ILLEGAL_ARGUMENT, - h.add_client(&rt, &VERIFIER, &h.root, &allowance), + h.add_client(&rt, &VERIFIER, &h.root, &allowance, &allowance_verifier), ); rt.reset(); h.check_state(&rt); @@ -462,7 +487,7 @@ mod clients { h.add_verifier(&rt, &VERIFIER, &allowance_verifier).unwrap(); expect_abort( ExitCode::USR_ILLEGAL_ARGUMENT, - h.add_client(&rt, &VERIFIER, &h.root, &allowance_client), + h.add_client(&rt, &VERIFIER, &h.root, &allowance_client, &allowance_verifier), ); rt.reset(); h.check_state(&rt); @@ -476,14 +501,14 @@ mod clients { h.add_verifier(&rt, &VERIFIER, &allowance_verifier).unwrap(); expect_abort( ExitCode::USR_ILLEGAL_ARGUMENT, - h.add_client(&rt, &VERIFIER, &VERIFIER, &allowance_client), + h.add_client(&rt, &VERIFIER, &VERIFIER, &allowance_client, &allowance_verifier), ); rt.reset(); h.add_verifier(&rt, &VERIFIER2, &allowance_verifier).unwrap(); expect_abort( ExitCode::USR_ILLEGAL_ARGUMENT, - h.add_client(&rt, &VERIFIER, &VERIFIER2, &allowance_client), + h.add_client(&rt, &VERIFIER, &VERIFIER2, &allowance_client, &allowance_verifier), ); rt.reset(); h.check_state(&rt); @@ -502,8 +527,8 @@ mod allocs_claims { use num_traits::Zero; use fil_actor_verifreg::{ - Actor, AllocationID, ClaimTerm, DataCap, ExtendClaimTermsParams, GetClaimsParams, - GetClaimsReturn, Method, State, + Actor, AllocationID, ClaimTerm, DataCap, ExtendClaimTermsParams, GetClaimsParams, Method, + State, }; use fil_actor_verifreg::{Claim, ExtendClaimTermsReturn}; use fil_actors_runtime::runtime::policy_constants::{ @@ -538,15 +563,19 @@ mod allocs_claims { let id2 = h.create_alloc(&rt, &alloc2).unwrap(); let state_with_allocs: State = rt.get_state(); + let expect_1 = vec![(id1, alloc1.clone())]; + let expect_2 = vec![(id2, alloc2.clone())]; + let expect_both = vec![(id1, alloc1.clone()), (id2, alloc2.clone())]; + // Can't remove allocations that aren't expired - let ret = h.remove_expired_allocations(&rt, CLIENT1, vec![id1, id2], 0).unwrap(); + let ret = h.remove_expired_allocations(&rt, CLIENT1, vec![id1, id2], vec![]).unwrap(); assert_eq!(vec![1, 2], ret.considered); assert_eq!(vec![ExitCode::USR_FORBIDDEN, ExitCode::USR_FORBIDDEN], ret.results.codes()); assert_eq!(DataCap::zero(), ret.datacap_recovered); // Can't remove with wrong client ID rt.set_epoch(200); - let ret = h.remove_expired_allocations(&rt, CLIENT2, vec![id1, id2], 0).unwrap(); + let ret = h.remove_expired_allocations(&rt, CLIENT2, vec![id1, id2], vec![]).unwrap(); assert_eq!(vec![1, 2], ret.considered); assert_eq!(vec![ExitCode::USR_NOT_FOUND, ExitCode::USR_NOT_FOUND], ret.results.codes()); assert_eq!(DataCap::zero(), ret.datacap_recovered); @@ -554,7 +583,7 @@ mod allocs_claims { // Remove the first alloc, which expired. rt.set_epoch(100); let ret = - h.remove_expired_allocations(&rt, CLIENT1, vec![id1, id2], alloc1.size.0).unwrap(); + h.remove_expired_allocations(&rt, CLIENT1, vec![id1, id2], expect_1.clone()).unwrap(); assert_eq!(vec![1, 2], ret.considered); assert_eq!(vec![ExitCode::OK, ExitCode::USR_FORBIDDEN], ret.results.codes()); assert_eq!(DataCap::from(alloc1.size.0), ret.datacap_recovered); @@ -562,21 +591,21 @@ mod allocs_claims { // Remove the second alloc (the first is no longer found). rt.set_epoch(200); let ret = - h.remove_expired_allocations(&rt, CLIENT1, vec![id1, id2], alloc2.size.0).unwrap(); + h.remove_expired_allocations(&rt, CLIENT1, vec![id1, id2], expect_2.clone()).unwrap(); assert_eq!(vec![1, 2], ret.considered); assert_eq!(vec![ExitCode::USR_NOT_FOUND, ExitCode::OK], ret.results.codes()); assert_eq!(DataCap::from(alloc2.size.0), ret.datacap_recovered); // Reset state and show we can remove two at once. rt.replace_state(&state_with_allocs); - let ret = h.remove_expired_allocations(&rt, CLIENT1, vec![id1, id2], total_size).unwrap(); + let ret = h.remove_expired_allocations(&rt, CLIENT1, vec![id1, id2], expect_both).unwrap(); assert_eq!(vec![1, 2], ret.considered); assert_eq!(vec![ExitCode::OK, ExitCode::OK], ret.results.codes()); assert_eq!(DataCap::from(total_size), ret.datacap_recovered); // Reset state and show that only what was asked for is removed. rt.replace_state(&state_with_allocs); - let ret = h.remove_expired_allocations(&rt, CLIENT1, vec![id1], alloc1.size.0).unwrap(); + let ret = h.remove_expired_allocations(&rt, CLIENT1, vec![id1], expect_1.clone()).unwrap(); assert_eq!(vec![1], ret.considered); assert_eq!(vec![ExitCode::OK], ret.results.codes()); assert_eq!(DataCap::from(alloc1.size.0), ret.datacap_recovered); @@ -584,7 +613,7 @@ mod allocs_claims { // Reset state and show that specifying none removes only expired allocations rt.set_epoch(0); rt.replace_state(&state_with_allocs); - let ret = h.remove_expired_allocations(&rt, CLIENT1, vec![], 0).unwrap(); + let ret = h.remove_expired_allocations(&rt, CLIENT1, vec![], vec![]).unwrap(); assert_eq!(Vec::::new(), ret.considered); assert_eq!(Vec::::new(), ret.results.codes()); assert_eq!(DataCap::zero(), ret.datacap_recovered); @@ -592,7 +621,7 @@ mod allocs_claims { assert!(h.load_alloc(&rt, CLIENT1, id2).is_some()); rt.set_epoch(100); - let ret = h.remove_expired_allocations(&rt, CLIENT1, vec![], alloc1.size.0).unwrap(); + let ret = h.remove_expired_allocations(&rt, CLIENT1, vec![], expect_1).unwrap(); assert_eq!(vec![1], ret.considered); assert_eq!(vec![ExitCode::OK], ret.results.codes()); assert_eq!(DataCap::from(alloc1.size.0), ret.datacap_recovered); @@ -600,7 +629,7 @@ mod allocs_claims { assert!(h.load_alloc(&rt, CLIENT1, id2).is_some()); rt.set_epoch(200); - let ret = h.remove_expired_allocations(&rt, CLIENT1, vec![], alloc2.size.0).unwrap(); + let ret = h.remove_expired_allocations(&rt, CLIENT1, vec![], expect_2).unwrap(); assert_eq!(vec![2], ret.considered); assert_eq!(vec![ExitCode::OK], ret.results.codes()); assert_eq!(DataCap::from(alloc2.size.0), ret.datacap_recovered); @@ -609,7 +638,9 @@ mod allocs_claims { // Reset state and show that specifying none removes *all* expired allocations rt.replace_state(&state_with_allocs); - let ret = h.remove_expired_allocations(&rt, CLIENT1, vec![], total_size).unwrap(); + let ret = h + .remove_expired_allocations(&rt, CLIENT1, vec![], vec![(id1, alloc1), (id2, alloc2)]) + .unwrap(); assert_eq!(vec![1, 2], ret.considered); assert_eq!(vec![ExitCode::OK, ExitCode::OK], ret.results.codes()); assert_eq!(DataCap::from(total_size), ret.datacap_recovered); @@ -641,7 +672,16 @@ mod allocs_claims { { // Claim two for PROVIDER1 in one sector let reqs = vec![make_claim_reqs(sector, expiry, &[(id1, &alloc1), (id2, &alloc2)])]; - let ret = h.claim_allocations(&rt, PROVIDER1, reqs, size * 2, false).unwrap(); + let ret = h + .claim_allocations( + &rt, + PROVIDER1, + reqs, + size * 2, + false, + vec![(id1, alloc1.clone()), (id2, alloc2.clone())], + ) + .unwrap(); assert_eq!(ret.sector_results.codes(), vec![ExitCode::OK]); assert_eq!(ret.sector_claims[0].claimed_space, BigInt::from(2 * size)); @@ -658,7 +698,9 @@ mod allocs_claims { make_claim_reqs(sector, expiry, &[(id2, &alloc2)]), ]; reqs[1].claims[0].client = CLIENT1; - let ret = h.claim_allocations(&rt, PROVIDER1, reqs, size, false).unwrap(); + let ret = h + .claim_allocations(&rt, PROVIDER1, reqs, size, false, vec![(id1, alloc1.clone())]) + .unwrap(); assert_eq!(ret.sector_results.codes(), vec![ExitCode::OK, ExitCode::USR_NOT_FOUND]); assert_eq!(ret.sector_claims[0].claimed_space, BigInt::from(size)); assert_alloc_claimed(&rt, CLIENT1, PROVIDER1, id1, &alloc1, 0, sector); @@ -671,7 +713,7 @@ mod allocs_claims { let reqs = vec![ make_claim_reqs(sector, expiry, &[(id4, &alloc4)]), // Wrong provider ]; - let ret = h.claim_allocations(&rt, PROVIDER1, reqs, 0, false).unwrap(); + let ret = h.claim_allocations(&rt, PROVIDER1, reqs, 0, false, vec![]).unwrap(); assert_eq!(ret.sector_results.codes(), vec![ExitCode::USR_FORBIDDEN]); assert_eq!(ret.sector_claims.len(), 0); assert_allocation(&rt, CLIENT1, id4, &alloc4); @@ -683,7 +725,7 @@ mod allocs_claims { let reqs = vec![make_claim_reqs(sector, expiry, &[(id1, &alloc1), (id1, &alloc1)])]; expect_abort( ExitCode::USR_ILLEGAL_ARGUMENT, - h.claim_allocations(&rt, PROVIDER1, reqs, size, false), + h.claim_allocations(&rt, PROVIDER1, reqs, size, false, vec![(id1, alloc1.clone())]), ); rt.reset(); @@ -692,7 +734,9 @@ mod allocs_claims { make_claim_reqs(sector, expiry, &[(id1, &alloc1)]), make_claim_reqs(sector, expiry, &[(id1, &alloc1)]), ]; - let ret = h.claim_allocations(&rt, PROVIDER1, reqs, size, false).unwrap(); + let ret = h + .claim_allocations(&rt, PROVIDER1, reqs, size, false, vec![(id1, alloc1.clone())]) + .unwrap(); assert_eq!(ret.sector_results.codes(), vec![ExitCode::OK, ExitCode::USR_NOT_FOUND]); assert_eq!(ret.sector_claims[0].claimed_space, BigInt::from(size)); assert_alloc_claimed(&rt, CLIENT1, PROVIDER1, id1, &alloc1, 0, sector); @@ -709,7 +753,7 @@ mod allocs_claims { Cid::from_str("bafyreibjo4xmgaevkgud7mbifn3dzp4v4lyaui4yvqp3f2bqwtxcjrdqg4") .unwrap(); reqs[1].claims[0].size = PaddedPieceSize(size + 1); - let ret = h.claim_allocations(&rt, PROVIDER1, reqs, 0, false).unwrap(); + let ret = h.claim_allocations(&rt, PROVIDER1, reqs, 0, false, vec![]).unwrap(); assert_eq!( ret.sector_results.codes(), vec![ExitCode::USR_FORBIDDEN, ExitCode::USR_FORBIDDEN] @@ -722,7 +766,7 @@ mod allocs_claims { rt.replace_state(&prior_state); let reqs = vec![make_claim_reqs(sector, expiry, &[(id1, &alloc1)])]; rt.set_epoch(alloc1.expiration + 1); - let ret = h.claim_allocations(&rt, PROVIDER1, reqs, 0, false).unwrap(); + let ret = h.claim_allocations(&rt, PROVIDER1, reqs, 0, false, vec![]).unwrap(); assert_eq!(ret.sector_results.codes(), vec![ExitCode::USR_FORBIDDEN]); assert_eq!(ret.sector_claims.len(), 0); h.check_state(&rt); @@ -732,13 +776,13 @@ mod allocs_claims { // Sector expiration too soon rt.replace_state(&prior_state); let reqs = vec![make_claim_reqs(sector, alloc1.term_min - 1, &[(id1, &alloc1)])]; - let ret = h.claim_allocations(&rt, PROVIDER1, reqs, 0, false).unwrap(); + let ret = h.claim_allocations(&rt, PROVIDER1, reqs, 0, false, vec![]).unwrap(); assert_eq!(ret.sector_results.codes(), vec![ExitCode::USR_FORBIDDEN]); assert_eq!(ret.sector_claims.len(), 0); // Sector expiration too late let reqs = vec![make_claim_reqs(sector, alloc1.term_max + 1, &[(id1, &alloc1)])]; - let ret = h.claim_allocations(&rt, PROVIDER1, reqs, 0, false).unwrap(); + let ret = h.claim_allocations(&rt, PROVIDER1, reqs, 0, false, vec![]).unwrap(); assert_eq!(ret.sector_results.codes(), vec![ExitCode::USR_FORBIDDEN]); assert_eq!(ret.sector_claims.len(), 0); h.check_state(&rt); @@ -751,7 +795,9 @@ mod allocs_claims { make_claim_reqs(sector, expiry, &[(id3, &alloc3)]), ]; reqs[0].claims[1].size = PaddedPieceSize(0); - let ret = h.claim_allocations(&rt, PROVIDER1, reqs, size, false).unwrap(); + let ret = h + .claim_allocations(&rt, PROVIDER1, reqs, size, false, vec![(id3, alloc3.clone())]) + .unwrap(); assert_eq!(ret.sector_results.codes(), vec![ExitCode::USR_FORBIDDEN, ExitCode::OK]); assert_eq!(ret.sector_claims[0].claimed_space, BigInt::from(size)); assert_allocation(&rt, CLIENT1, id1, &alloc1); @@ -767,7 +813,7 @@ mod allocs_claims { ]; reqs[0].claims[1].size = PaddedPieceSize(0); reqs[1].claims[0].size = PaddedPieceSize(0); - let ret = h.claim_allocations(&rt, PROVIDER1, reqs, 0, false).unwrap(); + let ret = h.claim_allocations(&rt, PROVIDER1, reqs, 0, false, vec![]).unwrap(); assert_eq!( ret.sector_results.codes(), vec![ExitCode::USR_FORBIDDEN, ExitCode::USR_FORBIDDEN] @@ -784,7 +830,7 @@ mod allocs_claims { reqs[0].claims[1].size = PaddedPieceSize(0); expect_abort( ExitCode::USR_ILLEGAL_ARGUMENT, - h.claim_allocations(&rt, PROVIDER1, reqs, 0, true), + h.claim_allocations(&rt, PROVIDER1, reqs, 0, true, vec![(id3, alloc3)]), ); rt.reset(); } @@ -857,8 +903,12 @@ mod allocs_claims { ClaimTerm { provider: PROVIDER2, claim_id: id3, term_max: max_term + 3 }, ], }; + + let expected_claims = + vec![(id1, claim1.clone()), (id2, claim2.clone()), (id3, claim3.clone())]; + rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, Address::new_id(CLIENT1)); - let ret = h.extend_claim_terms(&rt, ¶ms).unwrap(); + let ret = h.extend_claim_terms(&rt, ¶ms, expected_claims).unwrap(); assert_eq!(ret.codes(), vec![ExitCode::OK, ExitCode::OK, ExitCode::OK]); // Verify state directly. @@ -886,7 +936,7 @@ mod allocs_claims { terms: vec![ClaimTerm { provider: PROVIDER1, claim_id, term_max: max_term }], }; rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, Address::new_id(CLIENT1)); - let ret = h.extend_claim_terms(&rt, ¶ms).unwrap(); + let ret = h.extend_claim_terms(&rt, ¶ms, vec![(claim_id, claim.clone())]).unwrap(); assert_eq!(ret.codes(), vec![ExitCode::OK]); rt.verify() } @@ -897,7 +947,7 @@ mod allocs_claims { terms: vec![ClaimTerm { provider: PROVIDER1, claim_id, term_max: max_term }], }; rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, Address::new_id(CLIENT2)); - let ret = h.extend_claim_terms(&rt, ¶ms).unwrap(); + let ret = h.extend_claim_terms(&rt, ¶ms, vec![]).unwrap(); assert_eq!(ret.codes(), vec![ExitCode::USR_FORBIDDEN]); rt.verify() } @@ -908,7 +958,7 @@ mod allocs_claims { terms: vec![ClaimTerm { provider: PROVIDER2, claim_id, term_max: max_term }], }; rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, Address::new_id(CLIENT1)); - let ret = h.extend_claim_terms(&rt, ¶ms).unwrap(); + let ret = h.extend_claim_terms(&rt, ¶ms, vec![]).unwrap(); assert_eq!(ret.codes(), vec![ExitCode::USR_NOT_FOUND]); rt.verify() } @@ -923,7 +973,7 @@ mod allocs_claims { }], }; rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, Address::new_id(CLIENT1)); - let ret = h.extend_claim_terms(&rt, ¶ms).unwrap(); + let ret = h.extend_claim_terms(&rt, ¶ms, vec![]).unwrap(); assert_eq!(ret.codes(), vec![ExitCode::USR_ILLEGAL_ARGUMENT]); rt.verify() } @@ -934,7 +984,7 @@ mod allocs_claims { terms: vec![ClaimTerm { provider: PROVIDER1, claim_id, term_max: max_term - 1 }], }; rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, Address::new_id(CLIENT1)); - let ret = h.extend_claim_terms(&rt, ¶ms).unwrap(); + let ret = h.extend_claim_terms(&rt, ¶ms, vec![]).unwrap(); assert_eq!(ret.codes(), vec![ExitCode::USR_ILLEGAL_ARGUMENT]); rt.verify() } @@ -950,7 +1000,7 @@ mod allocs_claims { }; rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, Address::new_id(CLIENT1)); rt.set_epoch(max_term + 1); - let ret = h.extend_claim_terms(&rt, ¶ms).unwrap(); + let ret = h.extend_claim_terms(&rt, ¶ms, vec![(claim_id, claim)]).unwrap(); assert_eq!(ret.codes(), vec![ExitCode::OK]); rt.verify() } @@ -995,35 +1045,39 @@ mod allocs_claims { // The full test suite is not duplicated here, simple ones to ensure that the expiration // is correctly computed. + let expect_1 = vec![(id1, claim1.clone())]; + let expect_2 = vec![(id2, claim2.clone())]; + let expect_both = vec![(id1, claim1), (id2, claim2)]; + // None expired yet rt.set_epoch(term_start + term_min + 99); - let ret = h.remove_expired_claims(&rt, PROVIDER1, vec![id1, id2]).unwrap(); + let ret = h.remove_expired_claims(&rt, PROVIDER1, vec![id1, id2], vec![]).unwrap(); assert_eq!(vec![1, 2], ret.considered); assert_eq!(vec![ExitCode::USR_FORBIDDEN, ExitCode::USR_FORBIDDEN], ret.results.codes()); // One expired rt.set_epoch(term_start + term_min + 100); - let ret = h.remove_expired_claims(&rt, PROVIDER1, vec![id1, id2]).unwrap(); + let ret = h.remove_expired_claims(&rt, PROVIDER1, vec![id1, id2], expect_1).unwrap(); assert_eq!(vec![1, 2], ret.considered); assert_eq!(vec![ExitCode::OK, ExitCode::USR_FORBIDDEN], ret.results.codes()); // Both now expired rt.set_epoch(term_start + term_min + 200); - let ret = h.remove_expired_claims(&rt, PROVIDER1, vec![id1, id2]).unwrap(); + let ret = h.remove_expired_claims(&rt, PROVIDER1, vec![id1, id2], expect_2).unwrap(); assert_eq!(vec![1, 2], ret.considered); assert_eq!(vec![ExitCode::USR_NOT_FOUND, ExitCode::OK], ret.results.codes()); // Reset state, and show that specifying none removes only expired allocations rt.set_epoch(term_start + term_min); rt.replace_state(&state_with_allocs); - let ret = h.remove_expired_claims(&rt, PROVIDER1, vec![]).unwrap(); + let ret = h.remove_expired_claims(&rt, PROVIDER1, vec![], vec![]).unwrap(); assert_eq!(Vec::::new(), ret.considered); assert_eq!(Vec::::new(), ret.results.codes()); assert!(h.load_claim(&rt, PROVIDER1, id1).is_some()); assert!(h.load_claim(&rt, PROVIDER1, id2).is_some()); rt.set_epoch(term_start + term_min + 200); - let ret = h.remove_expired_claims(&rt, PROVIDER1, vec![]).unwrap(); + let ret = h.remove_expired_claims(&rt, PROVIDER1, vec![], expect_both).unwrap(); assert_eq!(vec![1, 2], ret.considered); assert_eq!(vec![ExitCode::OK, ExitCode::OK], ret.results.codes()); assert!(h.load_claim(&rt, PROVIDER1, id1).is_none()); // removed @@ -1034,21 +1088,10 @@ mod allocs_claims { #[test] fn claims_restricted_correctly() { let (h, rt) = new_harness(); - let size = MINIMUM_VERIFIED_ALLOCATION_SIZE as u64; - let sector = 0; - let start = 0; - let min_term = MINIMUM_VERIFIED_ALLOCATION_TERM; - let max_term = min_term + 1000; - - let claim1 = make_claim("1", CLIENT1, PROVIDER1, size, min_term, max_term, start, sector); - - let id1 = h.create_claim(&rt, &claim1).unwrap(); // First, let's extend some claims - - let params = ExtendClaimTermsParams { - terms: vec![ClaimTerm { provider: PROVIDER1, claim_id: id1, term_max: max_term + 1 }], - }; + // Empty request to avoid setting expectations for events etc. + let params = ExtendClaimTermsParams { terms: vec![] }; // set caller to not-builtin rt.set_caller(*EVM_ACTOR_CODE_ID, Address::new_id(CLIENT1)); @@ -1057,8 +1100,9 @@ mod allocs_claims { expect_abort_contains_message( ExitCode::USR_FORBIDDEN, "must be built-in", - h.extend_claim_terms(&rt, ¶ms), + h.extend_claim_terms(&rt, ¶ms, vec![]), ); + rt.reset(); // can call the exported method num @@ -1075,41 +1119,31 @@ mod allocs_claims { rt.verify(); - assert_eq!(ret.codes(), vec![ExitCode::OK]); + assert_eq!(ret.codes(), vec![]); // Now let's Get those Claims, and check them - let params = GetClaimsParams { claim_ids: vec![id1], provider: PROVIDER1 }; - + let params = GetClaimsParams { claim_ids: vec![], provider: PROVIDER1 }; // cannot call the unexported extend method num expect_abort_contains_message( ExitCode::USR_FORBIDDEN, "must be built-in", - rt.call::( - Method::GetClaims as MethodNum, - IpldBlock::serialize_cbor(¶ms).unwrap(), - ), + h.get_claims(&rt, PROVIDER1, vec![]), ); - rt.verify(); + rt.reset(); // can call the exported method num rt.expect_validate_caller_any(); - let ret: GetClaimsReturn = rt - .call::( - Method::GetClaimsExported as MethodNum, - IpldBlock::serialize_cbor(¶ms).unwrap(), - ) - .unwrap() - .unwrap() - .deserialize() - .expect("failed to deserialize get claims return"); + rt.call::( + Method::GetClaimsExported as MethodNum, + IpldBlock::serialize_cbor(¶ms).unwrap(), + ) + .unwrap() + .unwrap(); rt.verify(); - assert_eq!(ret.batch_info.codes(), vec![ExitCode::OK]); - assert_eq!(ret.claims, vec![Claim { term_max: max_term + 1, ..claim1 }]); - h.check_state(&rt); } } @@ -1350,6 +1384,7 @@ mod datacap { .as_str(), h.receive_tokens(&rt, payload, BatchReturn::ok(1), BATCH_EMPTY, vec![1], 0), ); + rt.reset(); h.check_state(&rt); } @@ -1469,11 +1504,13 @@ mod datacap { "term_max 5260486 for claim 1 exceeds maximum 5260485 at current epoch 1100", h.receive_tokens(&rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![], 0), ); + rt.reset(); // But just on the limit is allowed let reqs = vec![make_extension_req(PROVIDER1, cid1, max_allowed_term)]; let payload = make_receiver_hook_token_payload(CLIENT1, vec![], reqs, SIZE); h.receive_tokens(&rt, payload, BATCH_EMPTY, BatchReturn::ok(1), vec![], SIZE).unwrap(); h.check_state(&rt); + rt.reset(); } { // Claim already expired @@ -1488,6 +1525,7 @@ mod datacap { "claim 1 expired at 518600, current epoch 518601", h.receive_tokens(&rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![], 0), ); + rt.reset(); // But just at expiration is allowed let epoch = term_start + term_max; let new_term = epoch - term_start + MAXIMUM_VERIFIED_ALLOCATION_TERM; // Can get full max term now @@ -1496,6 +1534,7 @@ mod datacap { let payload = make_receiver_hook_token_payload(CLIENT1, vec![], reqs, SIZE); h.receive_tokens(&rt, payload, BATCH_EMPTY, BatchReturn::ok(1), vec![], SIZE).unwrap(); h.check_state(&rt); + rt.reset(); } { // Extension is zero @@ -1508,6 +1547,7 @@ mod datacap { "term_max 518500 for claim 1 is not larger than existing term max 518500", h.receive_tokens(&rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![], 0), ); + rt.reset(); // Extension is negative let reqs = vec![make_extension_req(PROVIDER1, cid1, term_max - 1)]; let payload = make_receiver_hook_token_payload(CLIENT1, vec![], reqs, SIZE); @@ -1516,11 +1556,13 @@ mod datacap { "term_max 518499 for claim 1 is not larger than existing term max 518500", h.receive_tokens(&rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![], 0), ); + rt.reset(); // But extension by just 1 epoch is allowed let reqs = vec![make_extension_req(PROVIDER1, cid1, term_max + 1)]; let payload = make_receiver_hook_token_payload(CLIENT1, vec![], reqs, SIZE); h.receive_tokens(&rt, payload, BATCH_EMPTY, BatchReturn::ok(1), vec![], SIZE).unwrap(); h.check_state(&rt); + rt.reset(); } } } diff --git a/integration_tests/src/expects.rs b/integration_tests/src/expects.rs index aa248d46e..907d85b08 100644 --- a/integration_tests/src/expects.rs +++ b/integration_tests/src/expects.rs @@ -1,3 +1,4 @@ +use cid::Cid; use frc46_token::receiver::{FRC46TokenReceived, FRC46_TOKEN_TYPE}; use frc46_token::token::types::BurnParams; use fvm_actor_utils::receiver::UniversalReceiverParams; @@ -23,11 +24,13 @@ use fil_actor_miner::{IsControllingAddressParam, PowerPair}; use fil_actor_power::{UpdateClaimedPowerParams, UpdatePledgeTotalParams}; use fil_actor_verifreg::GetClaimsParams; use fil_actors_runtime::{ - BURNT_FUNDS_ACTOR_ADDR, DATACAP_TOKEN_ACTOR_ADDR, DATACAP_TOKEN_ACTOR_ID, REWARD_ACTOR_ADDR, - STORAGE_MARKET_ACTOR_ADDR, STORAGE_MARKET_ACTOR_ID, STORAGE_POWER_ACTOR_ADDR, - STORAGE_POWER_ACTOR_ID, VERIFIED_REGISTRY_ACTOR_ADDR, VERIFIED_REGISTRY_ACTOR_ID, + EventBuilder, BURNT_FUNDS_ACTOR_ADDR, DATACAP_TOKEN_ACTOR_ADDR, DATACAP_TOKEN_ACTOR_ID, + REWARD_ACTOR_ADDR, STORAGE_MARKET_ACTOR_ADDR, STORAGE_MARKET_ACTOR_ID, + STORAGE_POWER_ACTOR_ADDR, STORAGE_POWER_ACTOR_ID, VERIFIED_REGISTRY_ACTOR_ADDR, + VERIFIED_REGISTRY_ACTOR_ID, }; -use vm_api::trace::ExpectInvocation; + +use vm_api::trace::{EmittedEvent, ExpectInvocation}; /// Static helper functions for creating invocation expectations. pub struct Expect {} @@ -42,6 +45,7 @@ impl Expect { pub fn market_activate_deals( from: ActorID, deals: Vec, + client_id: ActorID, sector_number: SectorNumber, sector_expiry: ChainEpoch, sector_type: RegisteredSealProof, @@ -50,13 +54,19 @@ impl Expect { let params = IpldBlock::serialize_cbor(&BatchActivateDealsParams { sectors: vec![SectorDeals { sector_number, - deal_ids: deals, + deal_ids: deals.clone(), sector_expiry, sector_type, }], compute_cid, }) .unwrap(); + + let events: Vec = deals + .iter() + .map(|deal_id| Expect::build_market_event("deal-activated", *deal_id, client_id, from)) + .collect(); + ExpectInvocation { from, to: STORAGE_MARKET_ACTOR_ADDR, @@ -64,6 +74,7 @@ impl Expect { params: Some(params), value: Some(TokenAmount::zero()), subinvocs: Some(vec![]), + events, ..Default::default() } } @@ -71,11 +82,20 @@ impl Expect { from: ActorID, epoch: ChainEpoch, sectors: Vec, + deals: Vec<(DealID, ActorID)>, ) -> ExpectInvocation { let bf = BitField::try_from_bits(sectors).unwrap(); let params = IpldBlock::serialize_cbor(&OnMinerSectorsTerminateParams { epoch, sectors: bf }) .unwrap(); + + let events: Vec = deals + .into_iter() + .map(|(deal_id, client)| { + Expect::build_market_event("deal-terminated", deal_id, client, from) + }) + .collect(); + ExpectInvocation { from, to: STORAGE_MARKET_ACTOR_ADDR, @@ -83,6 +103,7 @@ impl Expect { params: Some(params), value: Some(TokenAmount::zero()), subinvocs: Some(vec![Expect::burn(STORAGE_MARKET_ACTOR_ID, None)]), + events, ..Default::default() } } @@ -208,11 +229,13 @@ impl Expect { ..Default::default() } } + pub fn datacap_transfer_to_verifreg( from: ActorID, amount: TokenAmount, operator_data: RawBytes, burn: bool, + claim_events: Vec, ) -> ExpectInvocation { let payload = IpldBlock::serialize_cbor(&FRC46TokenReceived { from, @@ -244,11 +267,13 @@ impl Expect { .unwrap(), ), subinvocs: Some(burn_invocs), + events: claim_events, ..Default::default() }]), ..Default::default() } } + pub fn verifreg_get_claims( from: ActorID, miner: ActorID, @@ -342,4 +367,75 @@ impl Expect { ..Default::default() } } + + pub fn build_verifreg_event( + typ: &str, + id: u64, + client: ActorID, + provider: ActorID, + ) -> EmittedEvent { + EmittedEvent { + emitter: VERIFIED_REGISTRY_ACTOR_ID, + event: EventBuilder::new() + .typ(typ) + .field_indexed("id", &id) + .field_indexed("client", &client) + .field_indexed("provider", &provider) + .build() + .unwrap(), + } + } + + pub fn build_market_event( + typ: &str, + deal_id: DealID, + client: ActorID, + provider: ActorID, + ) -> EmittedEvent { + EmittedEvent { + emitter: STORAGE_MARKET_ACTOR_ID, + event: EventBuilder::new() + .typ(typ) + .field_indexed("id", &deal_id) + .field_indexed("client", &client) + .field_indexed("provider", &provider) + .build() + .unwrap(), + } + } + + pub fn build_miner_event( + typ: &str, + miner_id: ActorID, + sector_number: SectorNumber, + ) -> EmittedEvent { + EmittedEvent { + emitter: miner_id, + event: EventBuilder::new() + .typ(typ) + .field_indexed("sector", §or_number) + .build() + .unwrap(), + } + } + + pub fn build_sector_activation_event( + typ: &str, + miner_id: ActorID, + sector_number: SectorNumber, + unsealed_cid: Option, + pieces: &Vec<(Cid, u64)>, + ) -> EmittedEvent { + let mut base_event = EventBuilder::new() + .typ(typ) + .field_indexed("sector", §or_number) + .field_indexed("unsealed-cid", &unsealed_cid); + + for piece in pieces { + base_event = + base_event.field_indexed("piece-cid", &piece.0).field("piece-size", &piece.1); + } + + EmittedEvent { emitter: miner_id, event: base_event.build().unwrap() } + } } diff --git a/integration_tests/src/tests/commit_post_test.rs b/integration_tests/src/tests/commit_post_test.rs index 7cf317f28..b714a642b 100644 --- a/integration_tests/src/tests/commit_post_test.rs +++ b/integration_tests/src/tests/commit_post_test.rs @@ -1,3 +1,4 @@ +use cid::Cid; use export_macro::vm_test; use fvm_ipld_bitfield::BitField; use fvm_ipld_encoding::ipld_block::IpldBlock; @@ -29,7 +30,7 @@ use fil_actors_runtime::{ CRON_ACTOR_ADDR, CRON_ACTOR_ID, STORAGE_MARKET_ACTOR_ADDR, STORAGE_POWER_ACTOR_ADDR, STORAGE_POWER_ACTOR_ID, SYSTEM_ACTOR_ADDR, }; -use vm_api::trace::ExpectInvocation; +use vm_api::trace::{EmittedEvent, ExpectInvocation}; use vm_api::util::{apply_code, apply_ok, get_state, DynBlockstore}; use vm_api::VM; @@ -63,7 +64,19 @@ fn setup(v: &dyn VM) -> (MinerInfo, SectorInfo) { // precommit and advance to prove commit time let sector_number: SectorNumber = 100; - precommit_sectors_v2(v, 1, 1, vec![], &worker, &id_addr, seal_proof, sector_number, true, None); + let infos = precommit_sectors_v2( + v, + 1, + 1, + vec![], + &worker, + &id_addr, + seal_proof, + sector_number, + true, + None, + ); + let pc = &infos[0]; let balances = miner_balance(v, &id_addr); assert!(balances.pre_commit_deposit.is_positive()); @@ -71,6 +84,7 @@ fn setup(v: &dyn VM) -> (MinerInfo, SectorInfo) { let prove_time = v.epoch() + Policy::default().pre_commit_challenge_delay + 1; advance_by_deadline_to_epoch(v, &id_addr, prove_time); + let unsealed_cid = pc.info.unsealed_cid.0; // prove commit, cron, advance to post time let prove_params = ProveCommitSectorParams { sector_number, proof: vec![].into() }; let prove_params_ser = IpldBlock::serialize_cbor(&prove_params).unwrap(); @@ -101,6 +115,7 @@ fn setup(v: &dyn VM) -> (MinerInfo, SectorInfo) { ) .unwrap(); assert_eq!(ExitCode::OK, res.code); + let pieces: Vec<(Cid, u64)> = vec![]; ExpectInvocation { to: CRON_ACTOR_ADDR, method: CronMethod::EpochTick as u64, @@ -119,6 +134,13 @@ fn setup(v: &dyn VM) -> (MinerInfo, SectorInfo) { id_addr.id().unwrap(), None, )]), + events: vec![Expect::build_sector_activation_event( + "sector-activated", + id_addr.id().unwrap(), + sector_number, + unsealed_cid, + &pieces, + )], ..Default::default() }, Expect::reward_update_kpi(), @@ -708,7 +730,7 @@ pub fn aggregate_one_precommit_expires_test(v: &dyn VM) { None, ); - let all_precommits = [early_precommits, later_precommits].concat(); + let all_precommits = [early_precommits, later_precommits.clone()].concat(); let sector_nos_bf = BitField::try_from_bits(all_precommits.iter().map(|info| info.info.sector_number)).unwrap(); @@ -747,6 +769,22 @@ pub fn aggregate_one_precommit_expires_test(v: &dyn VM) { MinerMethod::ProveCommitAggregate as u64, Some(prove_params), ); + + let events: Vec = later_precommits + .iter() + .map(|info| { + let pieces: Vec<(Cid, u64)> = vec![]; + let unsealed_cid = info.info.unsealed_cid.0; + Expect::build_sector_activation_event( + "sector-activated", + miner_id, + info.info.sector_number, + unsealed_cid, + &pieces, + ) + }) + .collect(); + ExpectInvocation { from: worker_id, to: miner_addr, @@ -758,6 +796,7 @@ pub fn aggregate_one_precommit_expires_test(v: &dyn VM) { Expect::power_update_pledge(miner_id, None), Expect::burn(miner_id, None), ]), + events, ..Default::default() } .matches(v.take_invocations().last().unwrap()); diff --git a/integration_tests/src/tests/extend_sectors_test.rs b/integration_tests/src/tests/extend_sectors_test.rs index f9acfc4bd..92871a9b0 100644 --- a/integration_tests/src/tests/extend_sectors_test.rs +++ b/integration_tests/src/tests/extend_sectors_test.rs @@ -1,22 +1,23 @@ +use cid::Cid; +use export_macro::vm_test; +use fil_actor_verifreg::Method as VerifregMethod; +use fil_actors_runtime::runtime::Policy; +use fil_actors_runtime::test_utils::{make_piece_cid, make_sealed_cid}; +use fil_actors_runtime::{DealWeight, EPOCHS_IN_DAY, VERIFIED_REGISTRY_ACTOR_ADDR}; use fvm_ipld_bitfield::BitField; use fvm_shared::address::Address; use fvm_shared::bigint::Zero; use fvm_shared::clock::ChainEpoch; use fvm_shared::econ::TokenAmount; -use fvm_shared::piece::PaddedPieceSize; +use fvm_shared::piece::{PaddedPieceSize, PieceInfo}; use fvm_shared::sector::{RegisteredSealProof, SectorNumber, StoragePower}; -use export_macro::vm_test; use fil_actor_miner::{ max_prove_commit_duration, power_for_sector, ExpirationExtension, ExpirationExtension2, ExtendSectorExpiration2Params, ExtendSectorExpirationParams, Method as MinerMethod, PowerPair, ProveReplicaUpdatesParams, ReplicaUpdate, SectorClaim, SectorOnChainInfoFlags, Sectors, State as MinerState, }; -use fil_actor_verifreg::Method as VerifregMethod; -use fil_actors_runtime::runtime::Policy; -use fil_actors_runtime::test_utils::make_sealed_cid; -use fil_actors_runtime::{DealWeight, EPOCHS_IN_DAY, VERIFIED_REGISTRY_ACTOR_ADDR}; use vm_api::trace::ExpectInvocation; use vm_api::util::{apply_ok, get_state, mutate_state, DynBlockstore}; use vm_api::VM; @@ -27,8 +28,9 @@ use crate::util::{ advance_by_deadline_to_index, advance_to_proving_deadline, bf_all, create_accounts, create_miner, cron_tick, expect_invariants, invariant_failure_patterns, market_add_balance, market_pending_deal_allocations, market_publish_deal, miner_precommit_one_sector_v2, - miner_prove_sector, precommit_meta_data_from_deals, sector_deadline, submit_windowed_post, - verifreg_add_client, verifreg_add_verifier, PrecommitMetadata, + miner_prove_sector, override_compute_unsealed_sector_cid, precommit_meta_data_from_deals, + sector_deadline, submit_windowed_post, verifreg_add_client, verifreg_add_verifier, + PrecommitMetadata, }; #[allow(clippy::too_many_arguments)] @@ -520,12 +522,17 @@ pub fn extend_sector_up_to_max_relative_extension_test(v: &dyn VM) { #[vm_test] pub fn extend_updated_sector_with_claims_test(v: &dyn VM) { + override_compute_unsealed_sector_cid(v); + let addrs = create_accounts(v, 3, &TokenAmount::from_whole(10_000)); let seal_proof = RegisteredSealProof::StackedDRG32GiBV1P1; let (owner, worker, verifier, verified_client) = (addrs[0], addrs[0], addrs[1], addrs[2]); let worker_id = worker.id().unwrap(); let sector_number: SectorNumber = 100; let policy = Policy::default(); + let deal_label = "deal1".to_string(); + let piece_cid = make_piece_cid(deal_label.as_bytes()); + let piece_size = PaddedPieceSize(32u64 << 30); // create miner let miner_addr = create_miner( @@ -615,8 +622,8 @@ pub fn extend_updated_sector_with_claims_test(v: &dyn VM) { &worker, &verified_client, &miner_addr, - "deal1".to_string(), - PaddedPieceSize(32u64 << 30), + deal_label, + piece_size, true, deal_start, 340 * EPOCHS_IN_DAY, @@ -651,6 +658,11 @@ pub fn extend_updated_sector_with_claims_test(v: &dyn VM) { assert_eq!(vec![sector_number], bf_all(updated_sectors)); let old_power = power_for_sector(seal_proof.sector_size().unwrap(), &initial_sector_info); + + let pieces: Vec<(Cid, u64)> = vec![(piece_cid, piece_size.0)]; + let pis: Vec = vec![PieceInfo { cid: piece_cid, size: piece_size }]; + let unsealed_cid = v.primitives().compute_unsealed_sector_cid(seal_proof, &pis).unwrap(); + // check for the expected subcalls ExpectInvocation { from: worker_id, @@ -660,6 +672,7 @@ pub fn extend_updated_sector_with_claims_test(v: &dyn VM) { Expect::market_activate_deals( miner_id, deal_ids, + verified_client.id().unwrap(), sector_number, initial_sector_info.expiration, initial_sector_info.seal_proof, @@ -669,6 +682,12 @@ pub fn extend_updated_sector_with_claims_test(v: &dyn VM) { from: miner_id, to: VERIFIED_REGISTRY_ACTOR_ADDR, method: VerifregMethod::ClaimAllocations as u64, + events: vec![Expect::build_verifreg_event( + "claim", + claim_id, + verified_client.id().unwrap(), + miner_id, + )], ..Default::default() }, Expect::reward_this_epoch(miner_id), @@ -679,6 +698,13 @@ pub fn extend_updated_sector_with_claims_test(v: &dyn VM) { PowerPair { raw: StoragePower::zero(), qa: 9 * old_power.qa }, ), ]), + events: vec![Expect::build_sector_activation_event( + "sector-updated", + miner_id, + sector_number, + Some(unsealed_cid), + &pieces, + )], ..Default::default() } .matches(v.take_invocations().last().unwrap()); diff --git a/integration_tests/src/tests/prove_commit2_test.rs b/integration_tests/src/tests/prove_commit2_test.rs index f84c5abc8..22ffd12b4 100644 --- a/integration_tests/src/tests/prove_commit2_test.rs +++ b/integration_tests/src/tests/prove_commit2_test.rs @@ -1,3 +1,4 @@ +use cid::Cid; use export_macro::vm_test; use fvm_ipld_encoding::ipld_block::IpldBlock; use fvm_ipld_encoding::RawBytes; @@ -9,7 +10,6 @@ use fvm_shared::piece::{PaddedPieceSize, PieceInfo}; use fvm_shared::sector::{ RegisteredAggregateProof, RegisteredSealProof, SectorNumber, StoragePower, }; -use integer_encoding::VarInt; use num_traits::Zero; use fil_actor_market::Method as MarketMethod; @@ -29,7 +29,7 @@ use fil_actors_runtime::test_utils::make_piece_cid; use fil_actors_runtime::{ EPOCHS_IN_DAY, EPOCHS_IN_YEAR, STORAGE_MARKET_ACTOR_ADDR, VERIFIED_REGISTRY_ACTOR_ADDR, }; -use vm_api::trace::ExpectInvocation; +use vm_api::trace::{EmittedEvent, ExpectInvocation}; use vm_api::util::apply_ok; use vm_api::VM; @@ -37,31 +37,15 @@ use crate::deals::{DealBatcher, DealOptions}; use crate::expects::Expect; use crate::util::{ advance_by_deadline_to_epoch, create_accounts, create_miner, datacap_create_allocations, - market_add_balance, market_list_deals, market_list_sectors_deals, precommit_sectors_v2, - sector_info, verifreg_add_client, verifreg_add_verifier, verifreg_list_claims, - PrecommitMetadata, + market_add_balance, market_list_deals, market_list_sectors_deals, + override_compute_unsealed_sector_cid, precommit_sectors_v2, sector_info, verifreg_add_client, + verifreg_add_verifier, verifreg_list_claims, PrecommitMetadata, }; #[vm_test] pub fn prove_commit_sectors2_test(v: &dyn VM) { // Expectations depend on the correct unsealed CID for empty sector. - // TODO: move this code somewhere more accessible. - // This change was made during a long rebase during which structural change was not practical. - v.mut_primitives().override_compute_unsealed_sector_cid( - |proof_type: RegisteredSealProof, pis: &[PieceInfo]| { - if pis.is_empty() { - return Ok(CompactCommD::empty().get_cid(proof_type).unwrap()); - } - let mut buf: Vec = Vec::new(); - let ptv: i64 = proof_type.into(); - buf.extend(ptv.encode_var_vec()); - for p in pis { - buf.extend(&p.cid.to_bytes()); - buf.extend(p.size.0.encode_var_vec()) - } - Ok(make_piece_cid(&buf)) - }, - ); + override_compute_unsealed_sector_cid(v); let policy = Policy::default(); let addrs = create_accounts(v, 3, &TokenAmount::from_whole(10_000)); let seal_proof = RegisteredSealProof::StackedDRG32GiBV1P1; @@ -209,7 +193,7 @@ pub fn prove_commit_sectors2_test(v: &dyn VM) { v, meta.len(), meta.len(), - meta, + meta.clone(), &worker, &maddr, seal_proof, @@ -239,6 +223,24 @@ pub fn prove_commit_sectors2_test(v: &dyn VM) { MinerMethod::ProveCommitSectors3 as u64, Some(params.clone()), ); + + let events: Vec = manifests + .iter() + .enumerate() + .map(|(i, sa)| { + let unsealed_cid = meta.get(i).unwrap().commd.0; + + let pieces: Vec<(Cid, u64)> = sa.pieces.iter().map(|p| (p.cid, p.size.0)).collect(); + Expect::build_sector_activation_event( + "sector-activated", + miner_id, + sa.sector_number, + unsealed_cid, + &pieces, + ) + }) + .collect(); + ExpectInvocation { from: worker_id, to: maddr, @@ -289,6 +291,11 @@ pub fn prove_commit_sectors2_test(v: &dyn VM) { }) .unwrap(), ), + events: vec![ + Expect::build_verifreg_event("claim", alloc_ids_s2[0], client_id, miner_id), + Expect::build_verifreg_event("claim", alloc_ids_s2[1], client_id, miner_id), + Expect::build_verifreg_event("claim", alloc_ids_s4[0], client_id, miner_id), + ], ..Default::default() }, Expect::reward_this_epoch(miner_id), @@ -321,6 +328,7 @@ pub fn prove_commit_sectors2_test(v: &dyn VM) { ..Default::default() }, ]), + events, ..Default::default() } .matches(v.take_invocations().last().unwrap()); diff --git a/integration_tests/src/tests/replica_update2_test.rs b/integration_tests/src/tests/replica_update2_test.rs index 0d1cdda17..219e90716 100644 --- a/integration_tests/src/tests/replica_update2_test.rs +++ b/integration_tests/src/tests/replica_update2_test.rs @@ -1,3 +1,4 @@ +use cid::Cid; use export_macro::vm_test; use fvm_ipld_encoding::ipld_block::IpldBlock; use fvm_ipld_encoding::RawBytes; @@ -5,7 +6,7 @@ use fvm_shared::bigint::BigInt; use fvm_shared::clock::ChainEpoch; use fvm_shared::deal::DealID; use fvm_shared::econ::TokenAmount; -use fvm_shared::piece::PaddedPieceSize; +use fvm_shared::piece::{PaddedPieceSize, PieceInfo}; use fvm_shared::sector::{ RegisteredAggregateProof, RegisteredSealProof, SectorNumber, StoragePower, }; @@ -29,7 +30,7 @@ use fil_actors_runtime::test_utils::{make_piece_cid, make_sealed_cid}; use fil_actors_runtime::{ EPOCHS_IN_DAY, EPOCHS_IN_YEAR, STORAGE_MARKET_ACTOR_ADDR, VERIFIED_REGISTRY_ACTOR_ADDR, }; -use vm_api::trace::ExpectInvocation; +use vm_api::trace::{EmittedEvent, ExpectInvocation}; use vm_api::util::apply_ok; use vm_api::VM; @@ -38,13 +39,15 @@ use crate::expects::Expect; use crate::util::{ advance_by_deadline_to_epoch, advance_by_deadline_to_index, advance_to_proving_deadline, create_accounts, create_miner, datacap_create_allocations, market_add_balance, - market_list_deals, market_list_sectors_deals, precommit_sectors_v2, sector_info, - submit_windowed_post, verifreg_add_client, verifreg_add_verifier, verifreg_list_claims, - PrecommitMetadata, + market_list_deals, market_list_sectors_deals, override_compute_unsealed_sector_cid, + precommit_sectors_v2, sector_info, submit_windowed_post, verifreg_add_client, + verifreg_add_verifier, verifreg_list_claims, PrecommitMetadata, }; #[vm_test] pub fn prove_replica_update2_test(v: &dyn VM) { + override_compute_unsealed_sector_cid(v); + let policy = Policy::default(); let addrs = create_accounts(v, 3, &TokenAmount::from_whole(10_000)); let seal_proof = RegisteredSealProof::StackedDRG32GiBV1P1; @@ -276,6 +279,31 @@ pub fn prove_replica_update2_test(v: &dyn VM) { .map(|p| p.size.0 * 9) .sum::(), ); + + let events: Vec = manifests + .iter() + .map(|m| { + let pieces: Vec<(Cid, u64)> = m.pieces.iter().map(|p| (p.cid, p.size.0)).collect(); + + let pis: Vec = + m.pieces.iter().map(|p| PieceInfo { cid: p.cid, size: p.size }).collect(); + + let unsealed_cid: Option = if pis.is_empty() { + None + } else { + Some(v.primitives().compute_unsealed_sector_cid(seal_proof, &pis).unwrap()) + }; + + Expect::build_sector_activation_event( + "sector-updated", + miner_id, + m.sector, + unsealed_cid, + &pieces, + ) + }) + .collect(); + ExpectInvocation { from: worker_id, to: maddr, @@ -326,6 +354,11 @@ pub fn prove_replica_update2_test(v: &dyn VM) { }) .unwrap(), ), + events: vec![ + Expect::build_verifreg_event("claim", alloc_ids_s2[0], client_id, miner_id), + Expect::build_verifreg_event("claim", alloc_ids_s2[1], client_id, miner_id), + Expect::build_verifreg_event("claim", alloc_ids_s4[0], client_id, miner_id), + ], ..Default::default() }, Expect::reward_this_epoch(miner_id), @@ -359,6 +392,7 @@ pub fn prove_replica_update2_test(v: &dyn VM) { ..Default::default() }, ]), + events, ..Default::default() } .matches(v.take_invocations().last().unwrap()); diff --git a/integration_tests/src/tests/replica_update_test.rs b/integration_tests/src/tests/replica_update_test.rs index f3ac465b2..e30283296 100644 --- a/integration_tests/src/tests/replica_update_test.rs +++ b/integration_tests/src/tests/replica_update_test.rs @@ -1,3 +1,4 @@ +use cid::Cid; use fvm_ipld_bitfield::BitField; use fvm_ipld_encoding::RawBytes; use fvm_shared::address::Address; @@ -6,7 +7,7 @@ use fvm_shared::clock::ChainEpoch; use fvm_shared::deal::DealID; use fvm_shared::econ::TokenAmount; use fvm_shared::error::ExitCode; -use fvm_shared::piece::PaddedPieceSize; +use fvm_shared::piece::{PaddedPieceSize, PieceInfo}; use fvm_shared::sector::SectorSize; use fvm_shared::sector::StoragePower; use fvm_shared::sector::{RegisteredSealProof, SectorNumber}; @@ -14,6 +15,7 @@ use fvm_shared::sector::{RegisteredSealProof, SectorNumber}; use export_macro::vm_test; use fil_actor_cron::Method as CronMethod; use fil_actor_market::Method as MarketMethod; +use fil_actor_market::State as MarketState; use fil_actor_miner::{ power_for_sector, DisputeWindowedPoStParams, ExpirationExtension, ExtendSectorExpirationParams, Method as MinerMethod, PowerPair, ProveCommitSectorParams, ProveReplicaUpdatesParams, @@ -38,8 +40,9 @@ use crate::util::{ assert_invariants, bf_all, check_sector_active, check_sector_faulty, create_accounts, create_miner, deadline_state, declare_recovery, expect_invariants, get_deal_weights, get_network_stats, invariant_failure_patterns, make_bitfield, market_publish_deal, - miner_balance, miner_power, precommit_sectors_v2, prove_commit_sectors, sector_info, - submit_invalid_post, submit_windowed_post, verifreg_add_client, verifreg_add_verifier, + miner_balance, miner_power, override_compute_unsealed_sector_cid, precommit_sectors_v2, + prove_commit_sectors, sector_info, submit_invalid_post, submit_windowed_post, + verifreg_add_client, verifreg_add_verifier, }; #[vm_test] @@ -973,6 +976,7 @@ pub fn deal_included_in_multiple_sectors_failure_test(v: &dyn VM) { #[vm_test] pub fn replica_update_verified_deal_test(v: &dyn VM) { + override_compute_unsealed_sector_cid(v); let addrs = create_accounts(v, 3, &TokenAmount::from_whole(100_000)); let (worker, owner, client, verifier) = (addrs[0], addrs[0], addrs[1], addrs[2]); let worker_id = worker.id().unwrap(); @@ -1010,6 +1014,10 @@ pub fn replica_update_verified_deal_test(v: &dyn VM) { old_sector_info.expiration - v.epoch() - policy.market_default_allocation_term_buffer, ); + let st: MarketState = get_state(v, &STORAGE_MARKET_ACTOR_ADDR).unwrap(); + let store = DynBlockstore::wrap(v.blockstore()); + let proposal = st.get_proposal(&store, deal_ids[0]).unwrap(); + // replica update let new_sealed_cid = make_sealed_cid(b"replica1"); @@ -1034,7 +1042,16 @@ pub fn replica_update_verified_deal_test(v: &dyn VM) { .unwrap(); assert_eq!(vec![100], bf_all(updated_sectors)); + let claim_id = 1_u64; + let claim_event = + Expect::build_verifreg_event("claim", claim_id, client.id().unwrap(), maddr.id().unwrap()); let old_power = power_for_sector(seal_proof.sector_size().unwrap(), &old_sector_info); + + let pieces: Vec<(Cid, u64)> = vec![(proposal.piece_cid, proposal.piece_size.0)]; + let pis: Vec = + vec![PieceInfo { cid: proposal.piece_cid, size: proposal.piece_size }]; + let unsealed_cid = v.primitives().compute_unsealed_sector_cid(seal_proof, &pis).unwrap(); + // check for the expected subcalls ExpectInvocation { from: worker_id, @@ -1044,6 +1061,7 @@ pub fn replica_update_verified_deal_test(v: &dyn VM) { Expect::market_activate_deals( miner_id, deal_ids.clone(), + client.id().unwrap(), sector_number, old_sector_info.expiration, old_sector_info.seal_proof, @@ -1053,6 +1071,7 @@ pub fn replica_update_verified_deal_test(v: &dyn VM) { from: miner_id, to: VERIFIED_REGISTRY_ACTOR_ADDR, method: VerifregMethod::ClaimAllocations as u64, + events: vec![claim_event], ..Default::default() }, Expect::reward_this_epoch(miner_id), @@ -1064,6 +1083,13 @@ pub fn replica_update_verified_deal_test(v: &dyn VM) { PowerPair { raw: StoragePower::zero(), qa: 9 * old_power.qa }, ), ]), + events: vec![Expect::build_sector_activation_event( + "sector-updated", + miner_id, + sector_number, + Some(unsealed_cid), + &pieces, + )], ..Default::default() } .matches(v.take_invocations().last().unwrap()); diff --git a/integration_tests/src/tests/terminate_test.rs b/integration_tests/src/tests/terminate_test.rs index ec3d18797..e60449d73 100644 --- a/integration_tests/src/tests/terminate_test.rs +++ b/integration_tests/src/tests/terminate_test.rs @@ -24,6 +24,8 @@ use fil_actors_runtime::{ CRON_ACTOR_ADDR, STORAGE_MARKET_ACTOR_ADDR, STORAGE_MARKET_ACTOR_ID, STORAGE_POWER_ACTOR_ADDR, SYSTEM_ACTOR_ADDR, VERIFIED_REGISTRY_ACTOR_ADDR, }; +use fvm_shared::deal::DealID; +use fvm_shared::ActorID; use vm_api::trace::ExpectInvocation; use vm_api::util::{apply_ok, get_state, DynBlockstore}; use vm_api::VM; @@ -243,6 +245,13 @@ pub fn terminate_sectors_test(v: &dyn VM) { } let epoch = v.epoch(); + let expect_event = Expect::build_miner_event("sector-terminated", miner_id, sector_number); + let deal_clients: Vec<(DealID, ActorID)> = vec![ + (deal_ids[0], verified_client_id), + (deal_ids[1], verified_client_id), + (deal_ids[2], unverified_client.id().unwrap()), + ]; + // Terminate Sector apply_ok( v, @@ -267,9 +276,15 @@ pub fn terminate_sectors_test(v: &dyn VM) { Expect::power_current_total(miner_id), Expect::burn(miner_id, None), Expect::power_update_pledge(miner_id, None), - Expect::market_sectors_terminate(miner_id, epoch, [sector_number].to_vec()), + Expect::market_sectors_terminate( + miner_id, + epoch, + [sector_number].to_vec(), + deal_clients, + ), Expect::power_update_claim(miner_id, sector_power.neg()), ]), + events: vec![expect_event], ..Default::default() } .matches(v.take_invocations().last().unwrap()); diff --git a/integration_tests/src/tests/verified_claim_test.rs b/integration_tests/src/tests/verified_claim_test.rs index 122ea0cb6..684618e2b 100644 --- a/integration_tests/src/tests/verified_claim_test.rs +++ b/integration_tests/src/tests/verified_claim_test.rs @@ -246,7 +246,15 @@ pub fn verified_claim_scenario_test(v: &dyn VM) { let new_max_term = new_claim_expiry_epoch - claim.term_start; assert!(new_max_term > original_max_term); - datacap_extend_claim(v, &verified_client2, &miner_id, claim_id, deal_size, new_max_term); + datacap_extend_claim( + v, + &verified_client2, + &miner_id, + claim_id, + deal_size, + new_max_term, + verified_client.id().unwrap(), + ); // The miner extends the sector into the second year. let extended_expiration_2 = extended_expiration_1 + 60 * EPOCHS_IN_DAY; @@ -448,7 +456,14 @@ pub fn expired_allocations_test(v: &dyn VM) { let mut allocs = verifreg_state.load_allocs(&store).unwrap(); assert!(allocs.get(verified_client.id().unwrap(), alloc_id).unwrap().is_some()); - verifreg_remove_expired_allocations(v, &worker, &verified_client, vec![], deal_size); + verifreg_remove_expired_allocations( + v, + &worker, + &verified_client, + vec![], + deal_size, + vec![alloc_id], + ); // Allocation is gone let verifreg_state: VerifregState = get_state(v, &VERIFIED_REGISTRY_ACTOR_ADDR).unwrap(); diff --git a/integration_tests/src/tests/verifreg_remove_datacap_test.rs b/integration_tests/src/tests/verifreg_remove_datacap_test.rs index f889e0168..599d8d4d6 100644 --- a/integration_tests/src/tests/verifreg_remove_datacap_test.rs +++ b/integration_tests/src/tests/verifreg_remove_datacap_test.rs @@ -31,7 +31,9 @@ use vm_api::VM; use crate::expects::Expect; -use crate::util::{assert_invariants, create_accounts, verifreg_add_verifier}; +use crate::util::{ + assert_invariants, create_accounts, verifier_balance_event, verifreg_add_verifier, +}; use crate::{TEST_VERIFREG_ROOT_ADDR, TEST_VERIFREG_ROOT_ID}; #[vm_test] @@ -67,6 +69,8 @@ pub fn remove_datacap_simple_successful_path_test(v: &dyn VM) { Some(add_verified_client_params.clone()), ); + let verifier_datacap = DataCap::from(0); + ExpectInvocation { from: verifier1_id, to: VERIFIED_REGISTRY_ACTOR_ADDR, @@ -80,6 +84,7 @@ pub fn remove_datacap_simple_successful_path_test(v: &dyn VM) { subinvocs: None, ..Default::default() }]), + events: vec![verifier_balance_event(verifier1.id().unwrap(), verifier_datacap)], ..Default::default() } .matches(v.take_invocations().last().unwrap()); diff --git a/integration_tests/src/util/mod.rs b/integration_tests/src/util/mod.rs index 9f414017d..30c80711c 100644 --- a/integration_tests/src/util/mod.rs +++ b/integration_tests/src/util/mod.rs @@ -1,32 +1,34 @@ -use std::collections::HashMap; - use cid::multihash::{Code, MultihashDigest}; use cid::Cid; -use fvm_ipld_bitfield::BitField; -use fvm_ipld_encoding::{CborStore, RawBytes, DAG_CBOR}; -use fvm_shared::address::Address; -use fvm_shared::deal::DealID; -use fvm_shared::econ::TokenAmount; -use fvm_shared::sector::SectorNumber; -use fvm_shared::{ActorID, METHOD_SEND}; -use num_traits::Zero; -use regex::Regex; - use fil_actor_market::{load_provider_sector_deals, DealProposal, DealState, State as MarketState}; use fil_actor_miner::ext::verifreg::AllocationID; use fil_actor_miner::{ - new_deadline_info_from_offset_and_epoch, Deadline, DeadlineInfo, GetBeneficiaryReturn, - Method as MinerMethod, MinerInfo, PowerPair, SectorOnChainInfo, State as MinerState, + new_deadline_info_from_offset_and_epoch, CompactCommD, Deadline, DeadlineInfo, + GetBeneficiaryReturn, Method as MinerMethod, MinerInfo, PowerPair, SectorOnChainInfo, + State as MinerState, }; use fil_actor_power::State as PowerState; use fil_actor_reward::State as RewardState; use fil_actor_verifreg::{Claim, ClaimID, State as VerifregState}; use fil_actors_runtime::cbor::serialize; +use fil_actors_runtime::test_utils::make_piece_cid; use fil_actors_runtime::{ parse_uint_key, runtime::Policy, MessageAccumulator, REWARD_ACTOR_ADDR, STORAGE_MARKET_ACTOR_ADDR, STORAGE_POWER_ACTOR_ADDR, VERIFIED_REGISTRY_ACTOR_ADDR, }; use fil_builtin_actors_state::check::check_state_invariants; +use fvm_ipld_bitfield::BitField; +use fvm_ipld_encoding::{CborStore, RawBytes, DAG_CBOR}; +use fvm_shared::address::Address; +use fvm_shared::deal::DealID; +use fvm_shared::econ::TokenAmount; +use fvm_shared::piece::PieceInfo; +use fvm_shared::sector::{RegisteredSealProof, SectorNumber}; +use fvm_shared::{ActorID, METHOD_SEND}; +use integer_encoding::VarInt; +use num_traits::Zero; +use regex::Regex; +use std::collections::HashMap; use vm_api::{ util::{apply_ok, get_state, pk_addrs_from, DynBlockstore}, VM, @@ -264,6 +266,24 @@ pub fn get_network_stats(vm: &dyn VM) -> NetworkStats { } } +pub fn override_compute_unsealed_sector_cid(v: &dyn VM) { + v.mut_primitives().override_compute_unsealed_sector_cid( + |proof_type: RegisteredSealProof, pis: &[PieceInfo]| { + if pis.is_empty() { + return Ok(CompactCommD::empty().get_cid(proof_type).unwrap()); + } + let mut buf: Vec = Vec::new(); + let ptv: i64 = proof_type.into(); + buf.extend(ptv.encode_var_vec()); + for p in pis { + buf.extend(&p.cid.to_bytes()); + buf.extend(p.size.0.encode_var_vec()) + } + Ok(make_piece_cid(&buf)) + }, + ); +} + /// Compute a deal CID directly. pub fn deal_cid_for_testing(proposal: &DealProposal) -> Cid { const DIGEST_SIZE: u32 = 32; diff --git a/integration_tests/src/util/workflows.rs b/integration_tests/src/util/workflows.rs index 07229f882..beaffbc21 100644 --- a/integration_tests/src/util/workflows.rs +++ b/integration_tests/src/util/workflows.rs @@ -1,3 +1,4 @@ +use cid::Cid; use std::cmp::min; use fil_actor_market::SettleDealPaymentsParams; @@ -48,13 +49,14 @@ use fil_actor_multisig::Method as MultisigMethod; use fil_actor_multisig::ProposeParams; use fil_actor_power::{CreateMinerParams, CreateMinerReturn, Method as PowerMethod}; use fil_actor_verifreg::ext::datacap::MintParams; -use fil_actor_verifreg::AllocationRequest; use fil_actor_verifreg::AllocationRequests; use fil_actor_verifreg::ClaimExtensionRequest; use fil_actor_verifreg::{ AddVerifiedClientParams, AllocationID, ClaimID, ClaimTerm, ExtendClaimTermsParams, - Method as VerifregMethod, RemoveExpiredAllocationsParams, VerifierParams, + Method as VerifregMethod, RemoveExpiredAllocationsParams, State as VerifregState, + VerifierParams, }; +use fil_actor_verifreg::{AllocationRequest, DataCap}; use fil_actors_runtime::cbor::deserialize; use fil_actors_runtime::cbor::serialize; use fil_actors_runtime::runtime::policy_constants::{ @@ -64,6 +66,7 @@ use fil_actors_runtime::runtime::Policy; use fil_actors_runtime::test_utils::make_piece_cid; use fil_actors_runtime::test_utils::make_sealed_cid; use fil_actors_runtime::DealWeight; +use fil_actors_runtime::EventBuilder; use fil_actors_runtime::CRON_ACTOR_ADDR; use fil_actors_runtime::DATACAP_TOKEN_ACTOR_ADDR; use fil_actors_runtime::STORAGE_MARKET_ACTOR_ADDR; @@ -72,7 +75,7 @@ use fil_actors_runtime::STORAGE_POWER_ACTOR_ADDR; use fil_actors_runtime::SYSTEM_ACTOR_ADDR; use fil_actors_runtime::VERIFIED_REGISTRY_ACTOR_ADDR; use fil_actors_runtime::{DATACAP_TOKEN_ACTOR_ID, VERIFIED_REGISTRY_ACTOR_ID}; -use vm_api::trace::ExpectInvocation; +use vm_api::trace::{EmittedEvent, ExpectInvocation}; use vm_api::util::get_state; use vm_api::util::DynBlockstore; use vm_api::util::{apply_code, apply_ok, apply_ok_implicit}; @@ -189,7 +192,7 @@ pub fn miner_prove_sector( .matches(v.take_invocations().last().unwrap()); } -#[derive(Default)] +#[derive(Default, Clone)] pub struct PrecommitMetadata { pub deals: Vec, pub commd: CompactCommD, @@ -253,6 +256,12 @@ pub fn precommit_sectors_v2_expect_code( sector_idx += 1; j += 1; } + + let events: Vec = param_sectors + .iter() + .map(|ps| Expect::build_miner_event("sector-precommitted", miner_id, ps.sector_number)) + .collect(); + if !sectors_with_deals.is_empty() { invocs.push(Expect::market_verify_deals(miner_id, sectors_with_deals.clone())); } @@ -287,6 +296,7 @@ pub fn precommit_sectors_v2_expect_code( .unwrap(), ), subinvocs: Some(invocs), + events, ..Default::default() }; expect.matches(v.take_invocations().last().unwrap()); @@ -392,6 +402,28 @@ pub fn prove_commit_sectors( Some(prove_commit_aggregate_params), ); + let st: MarketState = get_state(v, &STORAGE_MARKET_ACTOR_ADDR).unwrap(); + let store = DynBlockstore::wrap(v.blockstore()); + let events: Vec = to_prove + .iter() + .map(|ps| { + let mut pieces: Vec<(Cid, u64)> = vec![]; + for deal_id in &ps.info.deal_ids { + let proposal = st.get_proposal(&store, *deal_id).unwrap(); + pieces.push((proposal.piece_cid, proposal.piece_size.0)); + } + + let unsealed_cid = ps.info.unsealed_cid.0; + Expect::build_sector_activation_event( + "sector-activated", + miner_id, + ps.info.sector_number, + unsealed_cid, + &pieces, + ) + }) + .collect(); + let expected_fee = aggregate_prove_commit_network_fee(to_prove.len(), &TokenAmount::zero()); ExpectInvocation { from: worker_id, @@ -404,6 +436,7 @@ pub fn prove_commit_sectors( Expect::power_update_pledge(miner_id, None), Expect::burn(miner_id, Some(expected_fee)), ]), + events, ..Default::default() } .matches(v.take_invocations().last().unwrap()); @@ -725,8 +758,20 @@ pub fn submit_invalid_post( ); } +pub fn verifier_balance_event(verifier: ActorID, data_cap: DataCap) -> EmittedEvent { + EmittedEvent { + emitter: VERIFIED_REGISTRY_ACTOR_ID, + event: EventBuilder::new() + .typ("verifier-balance") + .field_indexed("verifier", &verifier) + .field("balance", &data_cap) + .build() + .unwrap(), + } +} + pub fn verifreg_add_verifier(v: &dyn VM, verifier: &Address, data_cap: StoragePower) { - let add_verifier_params = VerifierParams { address: *verifier, allowance: data_cap }; + let add_verifier_params = VerifierParams { address: *verifier, allowance: data_cap.clone() }; // root address is msig, send proposal from root key let proposal = ProposeParams { to: VERIFIED_REGISTRY_ACTOR_ADDR, @@ -757,6 +802,7 @@ pub fn verifreg_add_verifier(v: &dyn VM, verifier: &Address, data_cap: StoragePo DATACAP_TOKEN_ACTOR_ADDR, *verifier, )]), + events: vec![verifier_balance_event(verifier.id().unwrap(), data_cap)], ..Default::default() }]), ..Default::default() @@ -770,6 +816,13 @@ pub fn verifreg_add_client( client: &Address, allowance: StoragePower, ) { + let v_st: VerifregState = get_state(v, &VERIFIED_REGISTRY_ACTOR_ADDR).unwrap(); + let store = DynBlockstore::wrap(v.blockstore()); + + let verifier_cap = v_st.get_verifier_cap(&store, verifier).unwrap().unwrap(); + + let updated_verifier_balance = verifier_cap - allowance.clone(); + let verifier_id = v.resolve_id_address(verifier).unwrap().id().unwrap(); let add_client_params = AddVerifiedClientParams { address: *client, allowance: allowance.clone() }; @@ -809,6 +862,7 @@ pub fn verifreg_add_client( )]), ..Default::default() }]), + events: vec![verifier_balance_event(verifier.id().unwrap(), updated_verifier_balance)], ..Default::default() } .matches(v.take_invocations().last().unwrap()); @@ -844,7 +898,24 @@ pub fn verifreg_remove_expired_allocations( client: &Address, ids: Vec, datacap_refund: u64, + expected_expirations: Vec, ) { + let v_st: VerifregState = get_state(v, &VERIFIED_REGISTRY_ACTOR_ADDR).unwrap(); + let store = DynBlockstore::wrap(v.blockstore()); + let mut allocs = v_st.load_allocs(&store).unwrap(); + let expected_events: Vec = expected_expirations + .iter() + .map(|id| { + let alloc = allocs.get(client.id().unwrap(), *id).unwrap().unwrap(); + Expect::build_verifreg_event( + "allocation-removed", + *id, + client.id().unwrap(), + alloc.provider, + ) + }) + .collect(); + let caller_id = v.resolve_id_address(caller).unwrap().id().unwrap(); let params = RemoveExpiredAllocationsParams { client: client.id().unwrap(), allocation_ids: ids }; @@ -883,6 +954,7 @@ pub fn verifreg_remove_expired_allocations( )]), ..Default::default() }]), + events: expected_events, ..Default::default() } .matches(v.take_invocations().last().unwrap()); @@ -927,11 +999,21 @@ pub fn datacap_create_allocations( .unwrap(); let alloc_response: AllocationsResponse = tfer_result.recipient_data.deserialize().unwrap(); + let events: Vec = alloc_response + .new_allocations + .iter() + .enumerate() + .map(|(i, alloc_id)| { + Expect::build_verifreg_event("allocation", *alloc_id, client_id, reqs[i].provider) + }) + .collect::>(); + Expect::datacap_transfer_to_verifreg( client_id, token_amount, operator_data, false, // No burn + events, ) .matches(v.take_invocations().last().unwrap()); @@ -945,6 +1027,7 @@ pub fn datacap_extend_claim( claim: ClaimID, size: u64, new_term: ChainEpoch, + claim_client: ActorID, ) { let payload = AllocationRequests { allocations: vec![], @@ -962,7 +1045,6 @@ pub fn datacap_extend_claim( operator_data: operator_data.clone(), }; - let client_id = v.resolve_id_address(client).unwrap().id().unwrap(); apply_ok( v, client, @@ -972,11 +1054,18 @@ pub fn datacap_extend_claim( Some(transfer_params), ); + let client_id = v.resolve_id_address(client).unwrap().id().unwrap(); Expect::datacap_transfer_to_verifreg( client_id, token_amount, operator_data, true, // Burn + vec![Expect::build_verifreg_event( + "claim-updated", + claim, + claim_client, + provider.id().unwrap(), + )], ) .matches(v.take_invocations().last().unwrap()); } @@ -1088,6 +1177,10 @@ pub fn market_publish_deal( }], extensions: vec![], }; + + let v_st: fil_actor_verifreg::State = get_state(v, &VERIFIED_REGISTRY_ACTOR_ADDR).unwrap(); + let alloc_id = v_st.next_allocation_id - 1; + expect_publish_invocs.push(ExpectInvocation { from: STORAGE_MARKET_ACTOR_ID, to: DATACAP_TOKEN_ACTOR_ADDR, @@ -1123,6 +1216,12 @@ pub fn market_publish_deal( }) .unwrap(), ), + events: vec![Expect::build_verifreg_event( + "allocation", + alloc_id, + deal_client.id().unwrap(), + miner_id.id().unwrap(), + )], ..Default::default() }]), ..Default::default() @@ -1139,6 +1238,12 @@ pub fn market_publish_deal( to: STORAGE_MARKET_ACTOR_ADDR, method: MarketMethod::PublishStorageDeals as u64, subinvocs: Some(expect_publish_invocs), + events: vec![Expect::build_market_event( + "deal-published", + ret.ids[0], + deal_client.id().unwrap(), + miner_id.id().unwrap(), + )], ..Default::default() } .matches(v.take_invocations().last().unwrap()); diff --git a/runtime/src/test_utils.rs b/runtime/src/test_utils.rs index af48c6eb1..4c10379ce 100644 --- a/runtime/src/test_utils.rs +++ b/runtime/src/test_utils.rs @@ -1285,7 +1285,7 @@ impl Runtime for MockRuntime { .borrow_mut() .expect_emitted_events .pop_front() - .expect("unexpected call to emit_evit"); + .expect("unexpected call to emit_event"); assert_eq!(*event, expected); diff --git a/runtime/src/util/events.rs b/runtime/src/util/events.rs new file mode 100644 index 000000000..0d39b1d22 --- /dev/null +++ b/runtime/src/util/events.rs @@ -0,0 +1,127 @@ +use crate::cbor::serialize_vec; +use crate::ActorError; +use fvm_shared::event::{ActorEvent, Entry, Flags}; +use serde::ser; + +// Codec identifier for CBOR-encoded data. +const IPLD_CBOR: u64 = 0x51; + +const EVENT_TYPE_KEY: &str = "$type"; + +/// Builder for ActorEvent objects, accumulating key/value pairs. +pub struct EventBuilder { + entries: Result, ActorError>, +} + +impl EventBuilder { + /// Creates a new builder with no values. + pub fn new() -> Self { + Self { entries: Ok(Vec::new()) } + } + + /// Initialise the "type" of the event i.e. Actor event type. + pub fn typ(self, _type: &str) -> Self { + self.push_entry(EVENT_TYPE_KEY, _type, Flags::FLAG_INDEXED_ALL) + } + + /// Pushes an entry with an indexed key and an un-indexed, IPLD-CBOR-serialized value. + pub fn field(self, name: &str, value: &T) -> Self { + self.push_entry(name, value, Flags::FLAG_INDEXED_KEY) + } + + /// Pushes an entry with an indexed key and indexed, IPLD-CBOR-serialized value. + pub fn field_indexed(self, name: &str, value: &T) -> Self { + self.push_entry(name, value, Flags::FLAG_INDEXED_ALL) + } + + /// Returns an actor event ready to emit (consuming self). + pub fn build(self) -> Result { + Ok(ActorEvent { entries: self.entries? }) + } + + /// Pushes an entry with an IPLD-CBOR-serialized value. + fn push_entry( + mut self, + key: &str, + value: &T, + flags: Flags, + ) -> Self { + if let Ok(ref mut entries) = self.entries { + match serialize_vec(&value, "event value") { + Ok(value) => { + entries.push(Entry { flags, key: key.to_string(), codec: IPLD_CBOR, value }) + } + Err(e) => { + self.entries = Err(e); + } + } + } + self + } +} + +impl Default for EventBuilder { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod test { + use crate::cbor::serialize_vec; + use crate::util::events::{EVENT_TYPE_KEY, IPLD_CBOR}; + use crate::EventBuilder; + use fvm_shared::event::{ActorEvent, Entry, Flags}; + + #[test] + fn event_type() { + let e = EventBuilder::new().typ("l1").field_indexed("v1", "abc").build().unwrap(); + + let l1_cbor = serialize_vec("l1", "event value").unwrap(); + let v_cbor = serialize_vec("abc", "event value").unwrap(); + + assert_eq!( + ActorEvent { + entries: vec![ + Entry { + flags: Flags::FLAG_INDEXED_ALL, + key: EVENT_TYPE_KEY.to_string(), + codec: IPLD_CBOR, + value: l1_cbor, // CBOR for "l1" + }, + Entry { + flags: Flags::FLAG_INDEXED_ALL, + key: "v1".to_string(), + codec: IPLD_CBOR, + value: v_cbor, // CBOR for "abc" + }, + ] + }, + e + ) + } + + #[test] + fn values() { + let e = EventBuilder::new().field("v1", &3).field_indexed("v2", "abc").build().unwrap(); + assert_eq!( + ActorEvent { + entries: vec![ + Entry { + flags: Flags::FLAG_INDEXED_KEY, + key: "v1".to_string(), + codec: IPLD_CBOR, + value: vec![0x03], + }, + Entry { + flags: Flags::FLAG_INDEXED_ALL, + key: "v2".to_string(), + codec: IPLD_CBOR, + value: vec![0x63, 0x61, 0x62, 0x63], // CBOR for "abc" + }, + ] + }, + e + ); + } +} diff --git a/runtime/src/util/mod.rs b/runtime/src/util/mod.rs index 0afe49b7f..9202f6acf 100644 --- a/runtime/src/util/mod.rs +++ b/runtime/src/util/mod.rs @@ -3,6 +3,7 @@ pub use self::batch_return::*; pub use self::downcast::*; +pub use self::events::*; pub use self::map::*; pub use self::mapmap::MapMap; pub use self::message_accumulator::MessageAccumulator; @@ -13,6 +14,7 @@ pub use self::set_multimap::SetMultimap; mod batch_return; pub mod cbor; mod downcast; +mod events; mod map; mod mapmap; mod message_accumulator; diff --git a/test_vm/src/lib.rs b/test_vm/src/lib.rs index 007f2ebaf..50b61b617 100644 --- a/test_vm/src/lib.rs +++ b/test_vm/src/lib.rs @@ -320,6 +320,7 @@ impl VM for TestVM { read_only: false, policy: &Policy::default(), subinvocations: RefCell::new(vec![]), + events: RefCell::new(vec![]), }; let res = new_ctx.invoke(); diff --git a/test_vm/src/messaging.rs b/test_vm/src/messaging.rs index c7439b817..70521f9c3 100644 --- a/test_vm/src/messaging.rs +++ b/test_vm/src/messaging.rs @@ -56,7 +56,7 @@ use fvm_shared::{ActorID, MethodNum, Response, IPLD_RAW, METHOD_CONSTRUCTOR, MET use serde::de::DeserializeOwned; use serde::Serialize; use std::cell::{RefCell, RefMut}; -use vm_api::trace::InvocationTrace; +use vm_api::trace::{EmittedEvent, InvocationTrace}; use vm_api::util::get_state; use vm_api::{new_actor, ActorState, VM}; @@ -113,6 +113,7 @@ pub struct InvocationCtx<'invocation> { pub read_only: bool, pub policy: &'invocation Policy, pub subinvocations: RefCell>, + pub events: RefCell>, } impl<'invocation> InvocationCtx<'invocation> { @@ -177,6 +178,7 @@ impl<'invocation> InvocationCtx<'invocation> { read_only: false, policy: self.policy, subinvocations: RefCell::new(vec![]), + events: RefCell::new(vec![]), }; if is_account { new_ctx.create_actor(*ACCOUNT_ACTOR_CODE_ID, target_id, None).unwrap(); @@ -218,6 +220,7 @@ impl<'invocation> InvocationCtx<'invocation> { return_value: ret, exit_code: code, subinvocations: self.subinvocations.take(), + events: self.events.take(), } } @@ -255,17 +258,18 @@ impl<'invocation> InvocationCtx<'invocation> { from_actor.balance -= &self.msg.value; self.v.set_actor(&Address::new_id(self.msg.from), from_actor); - let (mut to_actor, ref to_addr) = self.resolve_target(&self.msg.to)?; + let (mut to_actor, to_addr) = self.resolve_target(&self.msg.to)?; to_actor.balance = to_actor.balance.add(&self.msg.value); - self.v.set_actor(to_addr, to_actor); + self.v.set_actor(&to_addr, to_actor); // Exit early on send if self.msg.method == METHOD_SEND { return Ok(None); } + self.msg.to = to_addr; // call target actor - let to_actor = self.v.actor(to_addr).unwrap(); + let to_actor = self.v.actor(&to_addr).unwrap(); let params = self.msg.params.clone(); let mut res = match ACTOR_TYPES.get(&to_actor.code).expect("Target actor is not a builtin") { @@ -512,6 +516,7 @@ impl<'invocation> Runtime for InvocationCtx<'invocation> { read_only: send_flags.read_only(), policy: self.policy, subinvocations: RefCell::new(vec![]), + events: RefCell::new(vec![]), }; let res = new_ctx.invoke(); let invoc = new_ctx.gather_trace(res.clone()); @@ -636,9 +641,11 @@ impl<'invocation> Runtime for InvocationCtx<'invocation> { Ok(Cid::new_v1(IPLD_RAW, Multihash::wrap(0, b"faketipset").unwrap())) } - // TODO No support for events yet. - fn emit_event(&self, _event: &ActorEvent) -> Result<(), ActorError> { - unimplemented!() + fn emit_event(&self, event: &ActorEvent) -> Result<(), ActorError> { + self.events + .borrow_mut() + .push(EmittedEvent { emitter: self.msg.to.id().unwrap(), event: event.clone() }); + Ok(()) } fn read_only(&self) -> bool { diff --git a/vm_api/src/trace.rs b/vm_api/src/trace.rs index 65a927694..8ea020b0f 100644 --- a/vm_api/src/trace.rs +++ b/vm_api/src/trace.rs @@ -2,10 +2,17 @@ use fvm_ipld_encoding::ipld_block::IpldBlock; use fvm_shared::address::Address; use fvm_shared::econ::TokenAmount; use fvm_shared::error::{ErrorNumber, ExitCode}; +use fvm_shared::event::ActorEvent; use fvm_shared::{ActorID, MethodNum}; type ReturnValue = Option; +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct EmittedEvent { + pub emitter: ActorID, + pub event: ActorEvent, +} + /// A trace of an actor method invocation. #[derive(Clone, Debug)] pub struct InvocationTrace { @@ -20,6 +27,7 @@ pub struct InvocationTrace { pub exit_code: ExitCode, pub return_value: ReturnValue, pub subinvocations: Vec, + pub events: Vec, } /// An expectation for a method invocation trace. @@ -44,6 +52,7 @@ pub struct ExpectInvocation { pub exit_code: ExitCode, pub return_value: Option, pub subinvocs: Option>, + pub events: Vec, } impl ExpectInvocation { @@ -105,6 +114,29 @@ impl ExpectInvocation { id, p, invoc.params ); } + + // match emitted events + let emitted_events = &invoc.events; + let expected_events = &self.events; + assert_eq!( + emitted_events.len(), + expected_events.len(), + "{} {} emitted={}, expected={}, {:?}, {:?}", + id, + "length of expected and emitted events do not match", + emitted_events.len(), + expected_events.len(), + emitted_events, + expected_events + ); + + // use the zip method to iterate over the emitted events and expected_events + // vectors at the same time + for (emitted, expected) in emitted_events.iter().zip(expected_events.iter()) { + // only try to match if required fields match + assert_eq!(*emitted, *expected); + } + if let Some(expect_subinvocs) = &self.subinvocs { let subinvocs = &invoc.subinvocations; @@ -174,6 +206,7 @@ impl Default for ExpectInvocation { exit_code: ExitCode::OK, return_value: None, subinvocs: None, + events: vec![], } } }