Skip to content

Commit

Permalink
Exported API for market deal activation state (#819)
Browse files Browse the repository at this point in the history
  • Loading branch information
anorth authored and arajasek committed Nov 16, 2022
1 parent e244218 commit 3bcd854
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 16 deletions.
55 changes: 55 additions & 0 deletions actors/market/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ fil_actors_runtime::wasm_trampoline!(Actor);

pub const NO_ALLOCATION_ID: u64 = 0;

// An exit code indicating that information about a past deal is no longer available.
pub const EX_DEAL_EXPIRED: ExitCode = ExitCode::new(32);

/// Market actor methods available
#[derive(FromPrimitive)]
#[repr(u64)]
Expand All @@ -85,6 +88,7 @@ pub enum Method {
GetDealClientCollateralExported = frc42_dispatch::method_hash!("GetDealClientCollateral"),
GetDealProviderCollateralExported = frc42_dispatch::method_hash!("GetDealProviderCollateral"),
GetDealVerifiedExported = frc42_dispatch::method_hash!("GetDealVerified"),
GetDealActivationExported = frc42_dispatch::method_hash!("GetDealActivation"),
}

/// Market Actor
Expand Down Expand Up @@ -1148,6 +1152,53 @@ impl Actor {
let found = rt.state::<State>()?.get_proposal(rt.store(), params.id)?;
Ok(GetDealVerifiedReturn { verified: found.verified_deal })
}

/// Fetches activation state for a deal.
/// This will be available from when the proposal is published until an undefined period after
/// the deal finishes (either normally or by termination).
/// Returns USR_NOT_FOUND if the deal doesn't exist (yet), or EX_DEAL_EXPIRED if the deal
/// has been removed from state.
fn get_deal_activation(
rt: &mut impl Runtime,
params: GetDealActivationParams,
) -> Result<GetDealActivationReturn, ActorError> {
rt.validate_immediate_caller_accept_any()?;
let st = rt.state::<State>()?;
let found = st.find_deal_state(rt.store(), params.id)?;
match found {
Some(state) => Ok(GetDealActivationReturn {
// If we have state, the deal has been activated.
// It may also have completed normally, or been terminated,
// but not yet been cleaned up.
activated: state.sector_start_epoch,
terminated: state.slash_epoch,
}),
None => {
// State::get_proposal will fail with USR_NOT_FOUND in either case.
let maybe_proposal = st.find_proposal(rt.store(), params.id)?;
match maybe_proposal {
Some(_) => Ok(GetDealActivationReturn {
// The proposal has been published, but not activated.
activated: EPOCH_UNDEFINED,
terminated: EPOCH_UNDEFINED,
}),
None => {
if params.id < st.next_id {
// If the deal ID has been used, it must have been cleaned up.
Err(ActorError::unchecked(
EX_DEAL_EXPIRED,
format!("deal {} expired", params.id),
))
} else {
// We can't distinguish between failing to activate, or having been
// cleaned up after completion/termination.
Err(ActorError::not_found(format!("no such deal {}", params.id)))
}
}
}
}
}
}
}

fn compute_data_commitment<BS: Blockstore>(
Expand Down Expand Up @@ -1600,6 +1651,10 @@ impl ActorCode for Actor {
let res = Self::get_deal_verified(rt, cbor::deserialize_params(params)?)?;
Ok(RawBytes::serialize(res)?)
}
Some(Method::GetDealActivationExported) => {
let res = Self::get_deal_activation(rt, cbor::deserialize_params(params)?)?;
Ok(RawBytes::serialize(res)?)
}
None => Err(actor_error!(unhandled_message, "Invalid method")),
}
}
Expand Down
35 changes: 28 additions & 7 deletions actors/market/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,36 @@ impl State {
store: &BS,
id: DealID,
) -> Result<DealProposal, ActorError> {
let found = self
.find_proposal(store, id)?
.with_context_code(ExitCode::USR_NOT_FOUND, || format!("no such deal {}", id))?;
Ok(found)
}

pub fn find_proposal<BS: Blockstore>(
&self,
store: &BS,
id: DealID,
) -> Result<Option<DealProposal>, ActorError> {
let proposals = DealArray::load(&self.proposals, store)
.context_code(ExitCode::USR_ILLEGAL_STATE, "failed to load deal proposals")?;
let found = proposals
.get(id)
.with_context_code(ExitCode::USR_ILLEGAL_STATE, || {
format!("failed to load deal proposal {}", id)
})?
.with_context_code(ExitCode::USR_NOT_FOUND, || format!("no such deal {}", id))?;
Ok(found.clone())
let maybe = proposals.get(id).with_context_code(ExitCode::USR_ILLEGAL_STATE, || {
format!("failed to load deal proposal {}", id)
})?;
Ok(maybe.cloned())
}

pub fn find_deal_state<BS: Blockstore>(
&self,
store: &BS,
id: DealID,
) -> Result<Option<DealState>, ActorError> {
let states = DealMetaArray::load(&self.states, store)
.context_code(ExitCode::USR_ILLEGAL_STATE, "failed to load deal states")?;
let found = states.get(id).with_context_code(ExitCode::USR_ILLEGAL_STATE, || {
format!("failed to load deal state {}", id)
})?;
Ok(found.cloned())
}

pub(super) fn mutator<'bs, BS: Blockstore>(
Expand Down
10 changes: 10 additions & 0 deletions actors/market/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,13 @@ pub type GetDealVerifiedParams = DealQueryParams;
pub struct GetDealVerifiedReturn {
pub verified: bool,
}

pub type GetDealActivationParams = DealQueryParams;
#[derive(Serialize_tuple, Deserialize_tuple, Debug, Clone, Eq, PartialEq)]
pub struct GetDealActivationReturn {
/// Epoch at which the deal was activated, or -1.
/// This may be before the proposed start epoch.
pub activated: ChainEpoch,
/// Epoch at which the deal was terminated abnormally, or -1.
pub terminated: ChainEpoch,
}
95 changes: 87 additions & 8 deletions actors/market/tests/deal_api_test.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
use fvm_ipld_encoding::RawBytes;
use fvm_shared::clock::ChainEpoch;
use fvm_shared::error::ExitCode;
use fvm_shared::METHOD_SEND;
use serde::de::DeserializeOwned;

use fil_actor_market::{
Actor as MarketActor, DealQueryParams, GetDealClientCollateralReturn, GetDealClientReturn,
GetDealDataCommitmentReturn, GetDealLabelReturn, GetDealProviderCollateralReturn,
GetDealProviderReturn, GetDealTermReturn, GetDealTotalPriceReturn, GetDealVerifiedReturn,
Method,
Actor as MarketActor, DealQueryParams, GetDealActivationReturn, GetDealClientCollateralReturn,
GetDealClientReturn, GetDealDataCommitmentReturn, GetDealLabelReturn,
GetDealProviderCollateralReturn, GetDealProviderReturn, GetDealTermReturn,
GetDealTotalPriceReturn, GetDealVerifiedReturn, Method, EX_DEAL_EXPIRED,
};
use fil_actors_runtime::network::EPOCHS_IN_DAY;
use fil_actors_runtime::test_utils::{MockRuntime, ACCOUNT_ACTOR_CODE_ID};
use fil_actors_runtime::runtime::policy_constants::DEAL_UPDATES_INTERVAL;
use fil_actors_runtime::test_utils::{
expect_abort_contains_message, MockRuntime, ACCOUNT_ACTOR_CODE_ID,
};
use fil_actors_runtime::ActorError;
use fil_actors_runtime::BURNT_FUNDS_ACTOR_ADDR;
use harness::*;

mod harness;
Expand Down Expand Up @@ -71,11 +78,83 @@ fn proposal_data() {
check_state(&rt);
}

#[test]
fn activation() {
let start_epoch = 10;
let end_epoch = start_epoch + 180 * EPOCHS_IN_DAY;
let publish_epoch = ChainEpoch::from(1);

let mut rt = setup();
rt.set_epoch(publish_epoch);
let next_allocation_id = 1;

let proposal = generate_deal_and_add_funds(
&mut rt,
CLIENT_ADDR,
&MinerAddresses::default(),
start_epoch,
end_epoch,
);
rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, WORKER_ADDR);
let id =
publish_deals(&mut rt, &MinerAddresses::default(), &[proposal.clone()], next_allocation_id)
[0];

let activation: GetDealActivationReturn =
query_deal(&mut rt, Method::GetDealActivationExported, id);
assert_eq!(-1, activation.activated);
assert_eq!(-1, activation.terminated);

// activate the deal
let activate_epoch = start_epoch - 2;
rt.set_epoch(activate_epoch);
activate_deals(&mut rt, end_epoch + 1, PROVIDER_ADDR, activate_epoch, &[id]);
let activation: GetDealActivationReturn =
query_deal(&mut rt, Method::GetDealActivationExported, id);
assert_eq!(activate_epoch, activation.activated);
assert_eq!(-1, activation.terminated);

// terminate early
let terminate_epoch = activate_epoch + 100;
rt.set_epoch(terminate_epoch);
terminate_deals(&mut rt, PROVIDER_ADDR, &[id]);
let activation: GetDealActivationReturn =
query_deal(&mut rt, Method::GetDealActivationExported, id);
assert_eq!(activate_epoch, activation.activated);
assert_eq!(terminate_epoch, activation.terminated);

// Clean up state
let clean_epoch = terminate_epoch + DEAL_UPDATES_INTERVAL;
rt.set_epoch(clean_epoch);
rt.expect_send(
BURNT_FUNDS_ACTOR_ADDR,
METHOD_SEND,
RawBytes::default(),
proposal.provider_collateral,
RawBytes::default(),
ExitCode::OK,
);
cron_tick(&mut rt);
expect_abort_contains_message(
EX_DEAL_EXPIRED,
"expired",
query_deal_raw(&mut rt, Method::GetDealActivationExported, id),
);

// Non-existent deal is NOT FOUND
expect_abort_contains_message(
ExitCode::USR_NOT_FOUND,
"no such deal",
query_deal_raw(&mut rt, Method::GetDealActivationExported, id + 1),
);
}

fn query_deal<T: DeserializeOwned>(rt: &mut MockRuntime, method: Method, id: u64) -> T {
query_deal_raw(rt, method, id).unwrap().deserialize().unwrap()
}

fn query_deal_raw(rt: &mut MockRuntime, method: Method, id: u64) -> Result<RawBytes, ActorError> {
let params = DealQueryParams { id };
rt.expect_validate_caller_any();
rt.call::<MarketActor>(method as u64, &RawBytes::serialize(params).unwrap())
.unwrap()
.deserialize()
.unwrap()
}
2 changes: 1 addition & 1 deletion runtime/src/actor_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use thiserror::Error;
#[error("ActorError(exit_code: {exit_code:?}, msg: {msg})")]
pub struct ActorError {
/// The exit code for this invocation.
/// Codes less than `FIRST_ACTOR_EXIT_CODE` are prohibited and will be overwritten by the VM.
/// Codes less than `FIRST_USER_EXIT_CODE` are prohibited and will be overwritten by the VM.
exit_code: ExitCode,
/// Message for debugging purposes,
msg: String,
Expand Down

0 comments on commit 3bcd854

Please sign in to comment.