Created
February 12, 2022 17:10
-
-
Save dtynn/20165faca8a611cc0e160603cfb0da85 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1| |// Copyright 2019-2022 ChainSafe Systems | |
2| |// SPDX-License-Identifier: Apache-2.0, MIT | |
3| | | |
4| |use std::collections::{BTreeMap, BTreeSet}; | |
5| | | |
6| |use actors_runtime::runtime::{ActorCode, Runtime}; | |
7| |use actors_runtime::{ | |
8| | actor_error, wasm_trampoline, ActorDowncast, ActorError, BURNT_FUNDS_ACTOR_ADDR, | |
9| | CALLER_TYPES_SIGNABLE, CRON_ACTOR_ADDR, MINER_ACTOR_CODE_ID, REWARD_ACTOR_ADDR, | |
10| | STORAGE_POWER_ACTOR_ADDR, SYSTEM_ACTOR_ADDR, VERIFIED_REGISTRY_ACTOR_ADDR, | |
11| |}; | |
12| |use bitfield::BitField; | |
13| |use fvm_shared::address::Address; | |
14| |use fvm_shared::bigint::BigInt; | |
15| |use fvm_shared::blockstore::Blockstore; | |
16| |use fvm_shared::clock::{ChainEpoch, QuantSpec, EPOCH_UNDEFINED}; | |
17| |use fvm_shared::deal::DealID; | |
18| |use fvm_shared::econ::TokenAmount; | |
19| |use fvm_shared::encoding::{to_vec, Cbor, RawBytes}; | |
20| |use fvm_shared::error::ExitCode; | |
21| |use fvm_shared::piece::PieceInfo; | |
22| |use fvm_shared::reward::ThisEpochRewardReturn; | |
23| |use fvm_shared::sector::StoragePower; | |
24| |use fvm_shared::{ActorID, MethodNum, METHOD_CONSTRUCTOR, METHOD_SEND}; | |
25| |use log::info; | |
26| |use num_derive::FromPrimitive; | |
27| |use num_traits::{FromPrimitive, Signed, Zero}; | |
28| | | |
29| |pub use self::deal::*; | |
30| |use self::policy::*; | |
31| |pub use self::state::*; | |
32| |pub use self::types::*; | |
33| |use crate::ext::verifreg::UseBytesParams; | |
34| | | |
35| |// export for testing | |
36| |mod deal; | |
37| |#[doc(hidden)] | |
38| |pub mod ext; | |
39| |mod policy; | |
40| |mod state; | |
41| |mod types; | |
42| | | |
43| |/// Export the wasm binary | |
44| |#[cfg(not(feature = "runtime-wasm"))] | |
45| |pub mod wasm { | |
46| | include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); | |
47| | | |
48| | #[cfg(test)] | |
49| | mod tests { | |
50| | use super::*; | |
51| | | |
52| | #[test] | |
53| | fn test_wasm_binaries() { | |
54| | assert!(!WASM_BINARY.unwrap().is_empty()); | |
55| | assert!(!WASM_BINARY_BLOATY.unwrap().is_empty()); | |
56| | } | |
57| | } | |
58| |} | |
59| | | |
60| |wasm_trampoline!(Actor); | |
61| | | |
62| 1|fn request_miner_control_addrs<BS, RT>( | |
63| 1| rt: &mut RT, | |
64| 1| miner_addr: Address, | |
65| 1|) -> Result<(Address, Address, Vec<Address>), ActorError> | |
66| 1|where | |
67| 1| BS: Blockstore, | |
68| 1| RT: Runtime<BS>, | |
69| 1|{ | |
70| 1| let ret = rt.send( | |
71| 1| miner_addr, | |
72| 1| ext::miner::CONTROL_ADDRESSES_METHOD, | |
73| 1| RawBytes::default(), | |
74| 1| TokenAmount::zero(), | |
75| 1| )?; | |
^0 | |
76| 1| let addrs: ext::miner::GetControlAddressesReturnParams = ret.deserialize()?; | |
^0 | |
77| | | |
78| 1| Ok((addrs.owner, addrs.worker, addrs.control_addresses)) | |
79| 1|} | |
80| | | |
81| |// * Updated to specs-actors commit: e195950ba98adb8ce362030356bf4a3809b7ec77 (v2.3.2) | |
82| | | |
83| |/// Market actor methods available | |
84| 1|#[derive(FromPrimitive)] | |
------------------ | |
| _RNvXNvCsh0AsSxgjHCW_16fvm_actor_market34__IMPL_NUM_FromPrimitive_FOR_MethodNtB4_6MethodNtNtCs9AeTcfGaH9B_10num_traits4cast13FromPrimitive8from_i64B4_: | |
| 84| 1|#[derive(FromPrimitive)] | |
------------------ | |
| _RNvXNvCsh0AsSxgjHCW_16fvm_actor_market34__IMPL_NUM_FromPrimitive_FOR_MethodNtB4_6MethodNtNtCs9AeTcfGaH9B_10num_traits4cast13FromPrimitive8from_u64B4_: | |
| 84| 1|#[derive(FromPrimitive)] | |
------------------ | |
85| |#[repr(u64)] | |
86| |pub enum Method { | |
87| | Constructor = METHOD_CONSTRUCTOR, | |
88| | AddBalance = 2, | |
89| | WithdrawBalance = 3, | |
90| | PublishStorageDeals = 4, | |
91| | VerifyDealsForActivation = 5, | |
92| | ActivateDeals = 6, | |
93| | OnMinerSectorsTerminate = 7, | |
94| | ComputeDataCommitment = 8, | |
95| | CronTick = 9, | |
96| |} | |
97| | | |
98| |/// Market Actor | |
99| |pub struct Actor; | |
100| |impl Actor { | |
101| | pub fn constructor<BS, RT>(rt: &mut RT) -> Result<(), ActorError> | |
102| | where | |
103| | BS: Blockstore, | |
104| | RT: Runtime<BS>, | |
105| | { | |
106| 0| rt.validate_immediate_caller_is(std::iter::once(&*SYSTEM_ACTOR_ADDR))?; | |
107| | | |
108| 0| let st = State::new(rt.store()).map_err(|e| { | |
109| 0| e.downcast_default(ExitCode::ErrIllegalState, "Failed to create market state") | |
110| 0| })?; | |
111| 0| rt.create(&st)?; | |
112| 0| Ok(()) | |
113| 0| } | |
114| | | |
115| | /// Deposits the received value into the balance held in escrow. | |
116| 0| fn add_balance<BS, RT>(rt: &mut RT, provider_or_client: Address) -> Result<(), ActorError> | |
117| 0| where | |
118| 0| BS: Blockstore, | |
119| 0| RT: Runtime<BS>, | |
120| 0| { | |
121| 0| let msg_value = rt.message().value_received(); | |
122| 0| | |
123| 0| if msg_value <= TokenAmount::from(0) { | |
124| 0| return Err(actor_error!( | |
125| 0| ErrIllegalArgument, | |
126| 0| "balance to add must be greater than zero was: {}", | |
127| 0| msg_value | |
128| 0| )); | |
129| 0| } | |
130| 0| | |
131| 0| // only signing parties can add balance for client AND provider. | |
132| 0| rt.validate_immediate_caller_type(CALLER_TYPES_SIGNABLE.iter())?; | |
133| | | |
134| 0| let (nominal, _, _) = escrow_address(rt, &provider_or_client)?; | |
135| | | |
136| 0| rt.transaction(|st: &mut State, rt| { | |
137| 0| let mut msm = st.mutator(rt.store()); | |
138| 0| msm.with_escrow_table(Permission::Write) | |
139| 0| .with_locked_table(Permission::Write) | |
140| 0| .build() | |
141| 0| .map_err(|e| { | |
142| 0| e.downcast_default(ExitCode::ErrIllegalState, "failed to load state") | |
143| 0| })?; | |
144| | | |
145| 0| msm.escrow_table | |
146| 0| .as_mut() | |
147| 0| .unwrap() | |
148| 0| .add(&nominal, &msg_value) | |
149| 0| .map_err(|e| { | |
150| 0| e.downcast_default( | |
151| 0| ExitCode::ErrIllegalState, | |
152| 0| "failed to add balance to escrow table", | |
153| 0| ) | |
154| 0| })?; | |
155| | | |
156| 0| msm.commit_state().map_err(|e| { | |
157| 0| e.downcast_default(ExitCode::ErrIllegalState, "failed to flush state") | |
158| 0| })?; | |
159| | | |
160| 0| Ok(()) | |
161| 0| })?; | |
162| | | |
163| 0| Ok(()) | |
164| 0| } | |
165| | | |
166| | /// Attempt to withdraw the specified amount from the balance held in escrow. | |
167| | /// If less than the specified amount is available, yields the entire available balance. | |
168| 1| fn withdraw_balance<BS, RT>( | |
169| 1| rt: &mut RT, | |
170| 1| params: WithdrawBalanceParams, | |
171| 1| ) -> Result<WithdrawBalanceReturn, ActorError> | |
172| 1| where | |
173| 1| BS: Blockstore, | |
174| 1| RT: Runtime<BS>, | |
175| 1| { | |
176| 1| if params.amount < TokenAmount::from(0) { | |
177| 0| return Err(actor_error!( | |
178| 0| ErrIllegalArgument, | |
179| 0| "negative amount: {}", | |
180| 0| params.amount | |
181| 0| )); | |
182| 1| } | |
183| | | |
184| 1| let (nominal, recipient, approved) = escrow_address(rt, ¶ms.provider_or_client)?; | |
^0 | |
185| | // for providers -> only corresponding owner or worker can withdraw | |
186| | // for clients -> only the client i.e the recipient can withdraw | |
187| 1| rt.validate_immediate_caller_is(&approved)?; | |
^0 | |
188| | | |
189| 1| let amount_extracted = rt.transaction(|st: &mut State, rt| { | |
190| 1| let mut msm = st.mutator(rt.store()); | |
191| 1| msm.with_escrow_table(Permission::Write) | |
192| 1| .with_locked_table(Permission::Write) | |
193| 1| .build() | |
194| 1| .map_err(|e| { | |
195| 0| e.downcast_default(ExitCode::ErrIllegalState, "failed to load state") | |
196| 1| })?; | |
^0 | |
197| | | |
198| | // The withdrawable amount might be slightly less than nominal | |
199| | // depending on whether or not all relevant entries have been processed | |
200| | // by cron | |
201| 1| let min_balance = msm | |
202| 1| .locked_table | |
203| 1| .as_ref() | |
204| 1| .unwrap() | |
205| 1| .get(&nominal) | |
206| 1| .map_err(|e| { | |
207| 0| e.downcast_default(ExitCode::ErrIllegalState, "failed to get locked balance") | |
208| 1| })?; | |
^0 | |
209| | | |
210| 1| let ex = msm | |
211| 1| .escrow_table | |
212| 1| .as_mut() | |
213| 1| .unwrap() | |
214| 1| .subtract_with_minimum(&nominal, ¶ms.amount, &min_balance) | |
215| 1| .map_err(|e| { | |
216| 0| e.downcast_default( | |
217| 0| ExitCode::ErrIllegalState, | |
218| 0| "failed to subtract from escrow table", | |
219| 0| ) | |
220| 1| })?; | |
^0 | |
221| | | |
222| 1| msm.commit_state().map_err(|e| { | |
223| 0| e.downcast_default(ExitCode::ErrIllegalState, "failed to flush state") | |
224| 1| })?; | |
^0 | |
225| | | |
226| 1| Ok(ex) | |
227| 1| })?; | |
^0 | |
228| | | |
229| 1| rt.send( | |
230| 1| recipient, | |
231| 1| METHOD_SEND, | |
232| 1| RawBytes::default(), | |
233| 1| amount_extracted.clone(), | |
234| 1| )?; | |
^0 | |
235| | | |
236| 1| Ok(WithdrawBalanceReturn { | |
237| 1| amount_withdrawn: amount_extracted, | |
238| 1| }) | |
239| 1| } | |
240| | | |
241| | /// Publish a new set of storage deals (not yet included in a sector). | |
242| | fn publish_storage_deals<BS, RT>( | |
243| | rt: &mut RT, | |
244| | params: PublishStorageDealsParams, | |
245| | ) -> Result<PublishStorageDealsReturn, ActorError> | |
246| | where | |
247| | BS: Blockstore, | |
248| | RT: Runtime<BS>, | |
249| | { | |
250| | // Deal message must have a From field identical to the provider of all the deals. | |
251| | // This allows us to retain and verify only the client's signature in each deal proposal itself. | |
252| 0| rt.validate_immediate_caller_type(CALLER_TYPES_SIGNABLE.iter())?; | |
253| 0| if params.deals.is_empty() { | |
254| 0| return Err(actor_error!(ErrIllegalArgument, "Empty deals parameter")); | |
255| 0| } | |
256| 0| | |
257| 0| // All deals should have the same provider so get worker once | |
258| 0| let provider_raw = params.deals[0].proposal.provider; | |
259| 0| let provider = rt.resolve_address(&provider_raw).ok_or_else(|| { | |
260| 0| actor_error!( | |
261| 0| ErrNotFound, | |
262| 0| "failed to resolve provider address {}", | |
263| 0| provider_raw | |
264| 0| ) | |
265| 0| })?; | |
266| | | |
267| 0| let code_id = rt.get_actor_code_cid(&provider).ok_or_else(|| { | |
268| 0| actor_error!(ErrIllegalArgument, "no code ID for address {}", provider) | |
269| 0| })?; | |
270| 0| if code_id != *MINER_ACTOR_CODE_ID { | |
271| 0| return Err(actor_error!( | |
272| 0| ErrIllegalArgument, | |
273| 0| "deal provider is not a storage miner actor" | |
274| 0| )); | |
275| 0| } | |
276| | | |
277| 0| let (_, worker, controllers) = request_miner_control_addrs(rt, provider)?; | |
278| 0| let caller = rt.message().caller(); | |
279| 0| let mut caller_ok = caller == worker; | |
280| 0| for controller in controllers.iter() { | |
281| 0| if caller_ok { | |
282| 0| break; | |
283| 0| } | |
284| 0| caller_ok = caller == *controller; | |
285| | } | |
286| 0| if !caller_ok { | |
287| 0| return Err(actor_error!( | |
288| 0| ErrForbidden, | |
289| 0| "caller {} is not worker or control address of provider {}", | |
290| 0| caller, | |
291| 0| provider | |
292| 0| )); | |
293| 0| } | |
294| | | |
295| 0| let baseline_power = request_current_baseline_power(rt)?; | |
296| 0| let (network_raw_power, _) = request_current_network_power(rt)?; | |
297| | | |
298| | // Drop invalid deals | |
299| 0| let mut proposal_cid_lookup = BTreeSet::new(); | |
300| 0| let mut valid_proposal_cids = Vec::new(); | |
301| 0| let mut valid_deals = Vec::with_capacity(params.deals.len()); | |
302| 0| let mut total_client_lockup: BTreeMap<ActorID, TokenAmount> = BTreeMap::new(); | |
303| 0| let mut total_provider_lockup = TokenAmount::zero(); | |
304| 0| | |
305| 0| let mut valid_input_bf = BitField::default(); | |
306| 0| let mut state: State = rt.state::<State>()?; | |
307| | | |
308| 0| let store = rt.store(); | |
309| 0| let mut msm = state.mutator(store); | |
310| 0| msm.with_pending_proposals(Permission::ReadOnly) | |
311| 0| .with_escrow_table(Permission::ReadOnly) | |
312| 0| .with_locked_table(Permission::ReadOnly) | |
313| 0| .build() | |
314| 0| .map_err(|e| e.downcast_default(ExitCode::ErrIllegalState, "failed to load msm"))?; | |
315| | | |
316| 0| for (di, mut deal) in params.deals.into_iter().enumerate() { | |
317| | // drop malformed deals | |
318| 0| if let Err(e) = validate_deal(rt, &deal, &network_raw_power, &baseline_power) { | |
319| 0| info!("invalid deal {}: {}", di, e); | |
320| 0| continue; | |
321| 0| } | |
322| 0| | |
323| 0| if deal.proposal.provider != provider && deal.proposal.provider != provider_raw { | |
324| 0| info!( | |
325| 0| "invalid deal {}: cannot publish deals from multiple providers in one batch", | |
326| | di | |
327| | ); | |
328| 0| continue; | |
329| 0| } | |
330| 0| let client = match rt.resolve_address(&deal.proposal.client) { | |
331| 0| Some(client) => client, | |
332| | _ => { | |
333| 0| info!( | |
334| 0| "invalid deal {}: failed to resolve proposal.client address {} for deal", | |
335| | di, deal.proposal.client | |
336| | ); | |
337| 0| continue; | |
338| | } | |
339| | }; | |
340| | | |
341| | // drop deals with insufficient lock up to cover costs | |
342| 0| let client_id = client | |
343| 0| .id() | |
344| 0| .expect("resolved address should be an ID address"); | |
345| 0| let lockup = total_client_lockup.entry(client_id).or_default(); | |
346| 0| *lockup += deal.proposal.client_balance_requirement(); | |
347| | | |
348| 0| let client_balance_ok = msm.balance_covered(client, lockup).map_err(|e| { | |
349| 0| e.downcast_default( | |
350| 0| ExitCode::ErrIllegalState, | |
351| 0| "failed to check client balance coverage", | |
352| 0| ) | |
353| 0| })?; | |
354| | | |
355| 0| if !client_balance_ok { | |
356| 0| info!( | |
357| 0| "invalid deal: {}: insufficient client funds to cover proposal cost", | |
358| | di | |
359| | ); | |
360| 0| continue; | |
361| 0| } | |
362| 0| total_provider_lockup += &deal.proposal.provider_collateral; | |
363| 0| let provider_balance_ok = msm | |
364| 0| .balance_covered(provider, &total_provider_lockup) | |
365| 0| .map_err(|e| { | |
366| 0| e.downcast_default( | |
367| 0| ExitCode::ErrIllegalState, | |
368| 0| "failed to check provider balance coverage", | |
369| 0| ) | |
370| 0| })?; | |
371| | | |
372| 0| if !provider_balance_ok { | |
373| 0| info!( | |
374| 0| "invalid deal: {}: insufficient provider funds to cover proposal cost", | |
375| | di | |
376| | ); | |
377| 0| continue; | |
378| 0| } | |
379| 0| | |
380| 0| // drop duplicate deals | |
381| 0| // Normalise provider and client addresses in the proposal stored on chain. | |
382| 0| // Must happen after signature verification and before taking cid. | |
383| 0| | |
384| 0| deal.proposal.provider = provider; | |
385| 0| deal.proposal.client = client; | |
386| 0| let pcid = deal.proposal.cid().map_err(|e| { | |
387| 0| actor_error!(ErrIllegalArgument; "failed to take cid of proposal {}: {}", di, e) | |
388| 0| })?; | |
389| | | |
390| | // check proposalCids for duplication within message batch | |
391| | // check state PendingProposals for duplication across messages | |
392| 0| let duplicate_in_state = msm | |
393| 0| .pending_deals | |
394| 0| .as_ref() | |
395| 0| .unwrap() | |
396| 0| .has(&pcid.to_bytes()) | |
397| 0| .map_err(|e| { | |
398| 0| e.downcast_default( | |
399| 0| ExitCode::ErrIllegalState, | |
400| 0| "failed to check for existence of deal proposal", | |
401| 0| ) | |
402| 0| })?; | |
403| 0| let duplicate_in_message = proposal_cid_lookup.contains(&pcid); | |
404| 0| if duplicate_in_state || duplicate_in_message { | |
405| 0| info!( | |
406| 0| "invalid deal {}: cannot publish duplicate deal proposal", | |
407| | di | |
408| | ); | |
409| 0| continue; | |
410| 0| } | |
411| 0| | |
412| 0| // check VerifiedClient allowed cap and deduct PieceSize from cap | |
413| 0| // drop deals with a DealSize that cannot be fully covered by VerifiedClient's available DataCap | |
414| 0| if deal.proposal.verified_deal { | |
415| 0| if let Err(e) = rt.send( | |
416| 0| *VERIFIED_REGISTRY_ACTOR_ADDR, | |
417| 0| crate::ext::verifreg::USE_BYTES_METHOD as u64, | |
418| 0| RawBytes::serialize(UseBytesParams { | |
419| 0| address: client, | |
420| 0| deal_size: BigInt::from(deal.proposal.piece_size.0), | |
421| 0| })?, | |
422| 0| TokenAmount::zero(), | |
423| | ) { | |
424| 0| info!( | |
425| 0| "invalid deal {}: failed to acquire datacap exitcode: {}", | |
426| | di, e | |
427| | ); | |
428| 0| continue; | |
429| 0| } | |
430| 0| } | |
431| | | |
432| 0| proposal_cid_lookup.insert(pcid); | |
433| 0| valid_proposal_cids.push(pcid); | |
434| 0| valid_deals.push(deal); | |
435| 0| valid_input_bf.set(di as u64) | |
436| | } | |
437| | | |
438| 0| let valid_deal_count = valid_input_bf.len(); | |
439| 0| if valid_deals.len() != valid_proposal_cids.len() { | |
440| 0| return Err(actor_error!( | |
441| 0| ErrIllegalState, | |
442| 0| "{} valid deals but {} valid proposal cids", | |
443| 0| valid_deals.len(), | |
444| 0| valid_proposal_cids.len() | |
445| 0| )); | |
446| 0| } | |
447| 0| if valid_deal_count != valid_deals.len() as u64 { | |
448| 0| return Err(actor_error!( | |
449| 0| ErrIllegalState, | |
450| 0| "{} valid deals but valid_deal_count {}", | |
451| 0| valid_deals.len(), | |
452| 0| valid_deal_count | |
453| 0| )); | |
454| 0| } | |
455| 0| if valid_deal_count == 0 { | |
456| 0| return Err(actor_error!( | |
457| 0| ErrIllegalArgument, | |
458| 0| "All deal proposals invalid" | |
459| 0| )); | |
460| 0| } | |
461| 0| | |
462| 0| let mut new_deal_ids = Vec::with_capacity(valid_deals.len()); | |
463| 0| rt.transaction(|st: &mut State, rt| { | |
464| 0| let mut msm = st.mutator(rt.store()); | |
465| 0| msm.with_pending_proposals(Permission::Write) | |
466| 0| .with_deal_proposals(Permission::Write) | |
467| 0| .with_deals_by_epoch(Permission::Write) | |
468| 0| .with_escrow_table(Permission::Write) | |
469| 0| .with_locked_table(Permission::Write) | |
470| 0| .build() | |
471| 0| .map_err(|e| { | |
472| 0| e.downcast_default(ExitCode::ErrIllegalState, "failed to load state") | |
473| 0| })?; | |
474| | // All storage dealProposals will be added in an atomic transaction; this operation will be unrolled if any of them fails. | |
475| | // This should only fail on programmer error because all expected invalid conditions should be filtered in the first set of checks. | |
476| 0| for (vid, valid_deal) in valid_deals.iter().enumerate() { | |
477| 0| msm.lock_client_and_provider_balances(&valid_deal.proposal)?; | |
478| | | |
479| 0| let id = msm.generate_storage_deal_id(); | |
480| 0| | |
481| 0| let pcid = valid_proposal_cids[vid]; | |
482| 0| | |
483| 0| msm.pending_deals | |
484| 0| .as_mut() | |
485| 0| .unwrap() | |
486| 0| .put(pcid.to_bytes().into()) | |
487| 0| .map_err(|e| { | |
488| 0| e.downcast_default(ExitCode::ErrIllegalState, "failed to set pending deal") | |
489| 0| })?; | |
490| 0| msm.deal_proposals | |
491| 0| .as_mut() | |
492| 0| .unwrap() | |
493| 0| .set(id, valid_deal.proposal.clone()) | |
494| 0| .map_err(|e| { | |
495| 0| e.downcast_default(ExitCode::ErrIllegalState, "failed to set deal") | |
496| 0| })?; | |
497| | | |
498| | // We randomize the first epoch for when the deal will be processed so an attacker isn't able to | |
499| | // schedule too many deals for the same tick. | |
500| 0| let process_epoch = gen_rand_next_epoch(valid_deal.proposal.start_epoch, id); | |
501| 0| | |
502| 0| msm.deals_by_epoch | |
503| 0| .as_mut() | |
504| 0| .unwrap() | |
505| 0| .put(process_epoch, id) | |
506| 0| .map_err(|e| { | |
507| 0| e.downcast_default( | |
508| 0| ExitCode::ErrIllegalState, | |
509| 0| "failed to set deal ops by epoch", | |
510| 0| ) | |
511| 0| })?; | |
512| | | |
513| 0| new_deal_ids.push(id); | |
514| | } | |
515| | | |
516| 0| msm.commit_state().map_err(|e| { | |
517| 0| e.downcast_default(ExitCode::ErrIllegalState, "failed to flush state") | |
518| 0| })?; | |
519| 0| Ok(()) | |
520| 0| })?; | |
521| | | |
522| 0| Ok(PublishStorageDealsReturn { | |
523| 0| ids: new_deal_ids, | |
524| 0| valid_deals: valid_input_bf, | |
525| 0| }) | |
526| 0| } | |
527| | | |
528| | /// Verify that a given set of storage deals is valid for a sector currently being PreCommitted | |
529| | /// and return DealWeight of the set of storage deals given. | |
530| | /// The weight is defined as the sum, over all deals in the set, of the product of deal size | |
531| | /// and duration. | |
532| | fn verify_deals_for_activation<BS, RT>( | |
533| | rt: &mut RT, | |
534| | params: VerifyDealsForActivationParams, | |
535| | ) -> Result<VerifyDealsForActivationReturn, ActorError> | |
536| | where | |
537| | BS: Blockstore, | |
538| | RT: Runtime<BS>, | |
539| | { | |
540| 0| rt.validate_immediate_caller_type(std::iter::once(&*MINER_ACTOR_CODE_ID))?; | |
541| 0| let miner_addr = rt.message().caller(); | |
542| 0| let curr_epoch = rt.curr_epoch(); | |
543| | | |
544| 0| let st: State = rt.state()?; | |
545| 0| let proposals = DealArray::load(&st.proposals, rt.store()).map_err(|e| { | |
546| 0| e.downcast_default(ExitCode::ErrIllegalState, "failed to load deal proposals") | |
547| 0| })?; | |
548| | | |
549| 0| let mut weights = Vec::with_capacity(params.sectors.len()); | |
550| 0| for sector in params.sectors.iter() { | |
551| 0| let (deal_weight, verified_deal_weight, deal_space) = validate_and_compute_deal_weight( | |
552| 0| &proposals, | |
553| 0| §or.deal_ids, | |
554| 0| &miner_addr, | |
555| 0| sector.sector_expiry, | |
556| 0| curr_epoch, | |
557| 0| ) | |
558| 0| .map_err(|e| { | |
559| 0| e.downcast_default( | |
560| 0| ExitCode::ErrIllegalState, | |
561| 0| "failed to validate deal proposals for activation", | |
562| 0| ) | |
563| 0| })?; | |
564| 0| weights.push(SectorWeights { | |
565| 0| deal_space, | |
566| 0| deal_weight, | |
567| 0| verified_deal_weight, | |
568| 0| }); | |
569| | } | |
570| | | |
571| 0| Ok(VerifyDealsForActivationReturn { sectors: weights }) | |
572| 0| } | |
573| | | |
574| | /// Verify that a given set of storage deals is valid for a sector currently being ProveCommitted, | |
575| | /// update the market's internal state accordingly. | |
576| | fn activate_deals<BS, RT>(rt: &mut RT, params: ActivateDealsParams) -> Result<(), ActorError> | |
577| | where | |
578| | BS: Blockstore, | |
579| | RT: Runtime<BS>, | |
580| | { | |
581| 0| rt.validate_immediate_caller_type(std::iter::once(&*MINER_ACTOR_CODE_ID))?; | |
582| 0| let miner_addr = rt.message().caller(); | |
583| 0| let curr_epoch = rt.curr_epoch(); | |
584| 0| | |
585| 0| // Update deal states | |
586| 0| rt.transaction(|st: &mut State, rt| { | |
587| 0| validate_deals_for_activation( | |
588| 0| st, | |
589| 0| rt.store(), | |
590| 0| ¶ms.deal_ids, | |
591| 0| &miner_addr, | |
592| 0| params.sector_expiry, | |
593| 0| curr_epoch, | |
594| 0| ) | |
595| 0| .map_err(|e| { | |
596| 0| e.downcast_default( | |
597| 0| ExitCode::ErrIllegalState, | |
598| 0| "failed to validate deal proposals for activation", | |
599| 0| ) | |
600| 0| })?; | |
601| | | |
602| 0| let mut msm = st.mutator(rt.store()); | |
603| 0| msm.with_deal_states(Permission::Write) | |
604| 0| .with_pending_proposals(Permission::ReadOnly) | |
605| 0| .with_deal_proposals(Permission::ReadOnly) | |
606| 0| .build() | |
607| 0| .map_err(|e| { | |
608| 0| e.downcast_default(ExitCode::ErrIllegalState, "failed to load state") | |
609| 0| })?; | |
610| | | |
611| 0| for deal_id in params.deal_ids { | |
612| | // This construction could be replaced with a single "update deal state" | |
613| | // state method, possibly batched over all deal ids at once. | |
614| 0| let s = msm | |
615| 0| .deal_states | |
616| 0| .as_ref() | |
617| 0| .unwrap() | |
618| 0| .get(deal_id) | |
619| 0| .map_err(|e| { | |
620| 0| e.downcast_default( | |
621| 0| ExitCode::ErrIllegalState, | |
622| 0| format!("failed to get state for deal_id ({})", deal_id), | |
623| 0| ) | |
624| 0| })?; | |
625| 0| if s.is_some() { | |
626| 0| return Err(actor_error!( | |
627| 0| ErrIllegalArgument, | |
628| 0| "deal {} already included in another sector", | |
629| 0| deal_id | |
630| 0| )); | |
631| 0| } | |
632| | | |
633| 0| let proposal = msm | |
634| 0| .deal_proposals | |
635| 0| .as_ref() | |
636| 0| .unwrap() | |
637| 0| .get(deal_id) | |
638| 0| .map_err(|e| { | |
639| 0| e.downcast_default( | |
640| 0| ExitCode::ErrIllegalState, | |
641| 0| format!("failed to get deal_id ({})", deal_id), | |
642| 0| ) | |
643| 0| })? | |
644| 0| .ok_or_else(|| actor_error!(ErrNotFound, "no such deal_id: {}", deal_id))?; | |
645| | | |
646| 0| let propc = proposal | |
647| 0| .cid() | |
648| 0| .map_err(|e| ActorError::from(e).wrap("failed to calculate proposal Cid"))?; | |
649| | | |
650| 0| let has = msm | |
651| 0| .pending_deals | |
652| 0| .as_ref() | |
653| 0| .unwrap() | |
654| 0| .has(&propc.to_bytes()) | |
655| 0| .map_err(|e| { | |
656| 0| e.downcast_default( | |
657| 0| ExitCode::ErrIllegalState, | |
658| 0| format!("failed to get pending proposal ({})", propc), | |
659| 0| ) | |
660| 0| })?; | |
661| | | |
662| 0| if !has { | |
663| 0| return Err(actor_error!( | |
664| 0| ErrIllegalState, | |
665| 0| "tried to activate deal that was not in the pending set ({})", | |
666| 0| propc | |
667| 0| )); | |
668| 0| } | |
669| 0| | |
670| 0| msm.deal_states | |
671| 0| .as_mut() | |
672| 0| .unwrap() | |
673| 0| .set( | |
674| 0| deal_id, | |
675| 0| DealState { | |
676| 0| sector_start_epoch: curr_epoch, | |
677| 0| last_updated_epoch: EPOCH_UNDEFINED, | |
678| 0| slash_epoch: EPOCH_UNDEFINED, | |
679| 0| }, | |
680| 0| ) | |
681| 0| .map_err(|e| { | |
682| 0| e.downcast_default( | |
683| 0| ExitCode::ErrIllegalState, | |
684| 0| format!("failed to set deal state {}", deal_id), | |
685| 0| ) | |
686| 0| })?; | |
687| | } | |
688| | | |
689| 0| msm.commit_state().map_err(|e| { | |
690| 0| e.downcast_default(ExitCode::ErrIllegalState, "failed to flush state") | |
691| 0| })?; | |
692| 0| Ok(()) | |
693| 0| })?; | |
694| | | |
695| 0| Ok(()) | |
696| 0| } | |
697| | | |
698| | /// Terminate a set of deals in response to their containing sector being terminated. | |
699| | /// Slash provider collateral, refund client collateral, and refund partial unpaid escrow | |
700| | /// amount to client. | |
701| | fn on_miner_sectors_terminate<BS, RT>( | |
702| | rt: &mut RT, | |
703| | params: OnMinerSectorsTerminateParams, | |
704| | ) -> Result<(), ActorError> | |
705| | where | |
706| | BS: Blockstore, | |
707| | RT: Runtime<BS>, | |
708| | { | |
709| 0| rt.validate_immediate_caller_type(std::iter::once(&*MINER_ACTOR_CODE_ID))?; | |
710| 0| let miner_addr = rt.message().caller(); | |
711| 0| | |
712| 0| rt.transaction(|st: &mut State, rt| { | |
713| 0| let mut msm = st.mutator(rt.store()); | |
714| 0| msm.with_deal_states(Permission::Write) | |
715| 0| .with_deal_proposals(Permission::ReadOnly) | |
716| 0| .build() | |
717| 0| .map_err(|e| { | |
718| 0| e.downcast_default(ExitCode::ErrIllegalState, "failed to load state") | |
719| 0| })?; | |
720| | | |
721| 0| for id in params.deal_ids { | |
722| 0| let deal = msm.deal_proposals.as_ref().unwrap().get(id).map_err(|e| { | |
723| 0| e.downcast_default(ExitCode::ErrIllegalState, "failed to get deal proposal") | |
724| 0| })?; | |
725| | // The deal may have expired and been deleted before the sector is terminated. | |
726| | // Nothing to do, but continue execution for the other deals. | |
727| 0| if deal.is_none() { | |
728| 0| info!("couldn't find deal {}", id); | |
729| 0| continue; | |
730| 0| } | |
731| 0| let deal = deal.unwrap(); | |
732| 0| | |
733| 0| if deal.provider != miner_addr { | |
734| 0| return Err(actor_error!( | |
735| 0| ErrIllegalState, | |
736| 0| "caller {} is not the provider {} of deal {}", | |
737| 0| miner_addr, | |
738| 0| deal.provider, | |
739| 0| id | |
740| 0| )); | |
741| 0| } | |
742| 0| | |
743| 0| // do not slash expired deals | |
744| 0| if deal.end_epoch <= params.epoch { | |
745| 0| info!("deal {} expired, not slashing", id); | |
746| 0| continue; | |
747| 0| } | |
748| | | |
749| 0| let mut state: DealState = *msm | |
750| 0| .deal_states | |
751| 0| .as_ref() | |
752| 0| .unwrap() | |
753| 0| .get(id) | |
754| 0| .map_err(|e| { | |
755| 0| e.downcast_default(ExitCode::ErrIllegalState, "failed to get deal state") | |
756| 0| })? | |
757| | // A deal with a proposal but no state is not activated, but then it should not be | |
758| | // part of a sector that is terminating. | |
759| 0| .ok_or_else(|| actor_error!(ErrIllegalArgument, "no state for deal {}", id))?; | |
760| | | |
761| | // If a deal is already slashed, don't need to do anything | |
762| 0| if state.slash_epoch != EPOCH_UNDEFINED { | |
763| 0| info!("deal {}, already slashed", id); | |
764| 0| continue; | |
765| 0| } | |
766| 0| | |
767| 0| // mark the deal for slashing here. Actual releasing of locked funds for the client | |
768| 0| // and slashing of provider collateral happens in cron_tick. | |
769| 0| state.slash_epoch = params.epoch; | |
770| 0| | |
771| 0| msm.deal_states | |
772| 0| .as_mut() | |
773| 0| .unwrap() | |
774| 0| .set(id, state) | |
775| 0| .map_err(|e| { | |
776| 0| e.downcast_default( | |
777| 0| ExitCode::ErrIllegalState, | |
778| 0| format!("failed to set deal state ({})", id), | |
779| 0| ) | |
780| 0| })?; | |
781| | } | |
782| | | |
783| 0| msm.commit_state().map_err(|e| { | |
784| 0| e.downcast_default(ExitCode::ErrIllegalState, "failed to flush state") | |
785| 0| })?; | |
786| 0| Ok(()) | |
787| 0| })?; | |
788| 0| Ok(()) | |
789| 0| } | |
790| | | |
791| | fn compute_data_commitment<BS, RT>( | |
792| | rt: &mut RT, | |
793| | params: ComputeDataCommitmentParams, | |
794| | ) -> Result<ComputeDataCommitmentReturn, ActorError> | |
795| | where | |
796| | BS: Blockstore, | |
797| | RT: Runtime<BS>, | |
798| | { | |
799| 0| rt.validate_immediate_caller_type(std::iter::once(&*MINER_ACTOR_CODE_ID))?; | |
800| | | |
801| 0| let st: State = rt.state()?; | |
802| | | |
803| 0| let proposals = DealArray::load(&st.proposals, rt.store()).map_err(|e| { | |
804| 0| e.downcast_default(ExitCode::ErrIllegalState, "failed to load deal proposals") | |
805| 0| })?; | |
806| 0| let mut commds = Vec::with_capacity(params.inputs.len()); | |
807| 0| for comm_input in params.inputs.iter() { | |
808| 0| let mut pieces: Vec<PieceInfo> = Vec::with_capacity(comm_input.deal_ids.len()); | |
809| 0| for deal_id in &comm_input.deal_ids { | |
810| 0| let deal = proposals | |
811| 0| .get(*deal_id) | |
812| 0| .map_err(|e| { | |
813| 0| e.downcast_default( | |
814| 0| ExitCode::ErrIllegalState, | |
815| 0| format!("failed to get deal_id ({})", deal_id), | |
816| 0| ) | |
817| 0| })? | |
818| 0| .ok_or_else(|| { | |
819| 0| actor_error!(ErrNotFound, "proposal doesn't exist ({})", deal_id) | |
820| 0| })?; | |
821| 0| pieces.push(PieceInfo { | |
822| 0| cid: deal.piece_cid, | |
823| 0| size: deal.piece_size, | |
824| 0| }); | |
825| | } | |
826| 0| let commd = rt | |
827| 0| .compute_unsealed_sector_cid(comm_input.sector_type, &pieces) | |
828| 0| .map_err(|e| { | |
829| 0| e.downcast_default( | |
830| 0| ExitCode::ErrIllegalArgument, | |
831| 0| "failed to compute unsealed sector CID", | |
832| 0| ) | |
833| 0| })?; | |
834| 0| commds.push(commd); | |
835| | } | |
836| | | |
837| 0| Ok(ComputeDataCommitmentReturn { commds }) | |
838| 0| } | |
839| | | |
840| | fn cron_tick<BS, RT>(rt: &mut RT) -> Result<(), ActorError> | |
841| | where | |
842| | BS: Blockstore, | |
843| | RT: Runtime<BS>, | |
844| | { | |
845| 0| rt.validate_immediate_caller_is(std::iter::once(&*CRON_ACTOR_ADDR))?; | |
846| | | |
847| 0| let mut amount_slashed = BigInt::zero(); | |
848| 0| let curr_epoch = rt.curr_epoch(); | |
849| 0| let mut timed_out_verified_deals: Vec<DealProposal> = Vec::new(); | |
850| 0| | |
851| 0| rt.transaction(|st: &mut State, rt| { | |
852| 0| let last_cron = st.last_cron; | |
853| 0| let mut updates_needed: BTreeMap<ChainEpoch, Vec<DealID>> = BTreeMap::new(); | |
854| 0| let mut msm = st.mutator(rt.store()); | |
855| 0| msm.with_deal_states(Permission::Write) | |
856| 0| .with_locked_table(Permission::Write) | |
857| 0| .with_escrow_table(Permission::Write) | |
858| 0| .with_deals_by_epoch(Permission::Write) | |
859| 0| .with_deal_proposals(Permission::Write) | |
860| 0| .with_pending_proposals(Permission::Write) | |
861| 0| .build() | |
862| 0| .map_err(|e| { | |
863| 0| e.downcast_default(ExitCode::ErrIllegalState, "failed to load state") | |
864| 0| })?; | |
865| | | |
866| 0| for i in (last_cron + 1)..=rt.curr_epoch() { | |
867| | // TODO specs-actors modifies msm as it's iterated through, which is memory unsafe | |
868| | // for now the deal ids are being collected and then iterated on, which could | |
869| | // cause a potential inconsistency in exit code returned if a deal_id fails | |
870| | // to be pulled from storage where it wouldn't be triggered otherwise. | |
871| | // Workaround a better solution (seperating msm or fixing go impl) | |
872| 0| let mut deal_ids = Vec::new(); | |
873| 0| msm.deals_by_epoch | |
874| 0| .as_ref() | |
875| 0| .unwrap() | |
876| 0| .for_each(i, |deal_id| { | |
877| 0| deal_ids.push(deal_id); | |
878| 0| Ok(()) | |
879| 0| }) | |
880| 0| .map_err(|e| { | |
881| 0| e.downcast_default(ExitCode::ErrIllegalState, "failed to set deal state") | |
882| 0| })?; | |
883| | | |
884| 0| for deal_id in deal_ids { | |
885| 0| let deal = msm | |
886| 0| .deal_proposals | |
887| 0| .as_ref() | |
888| 0| .unwrap() | |
889| 0| .get(deal_id) | |
890| 0| .map_err(|e| { | |
891| 0| e.downcast_default( | |
892| 0| ExitCode::ErrIllegalState, | |
893| 0| format!("failed to get deal_id ({})", deal_id), | |
894| 0| ) | |
895| 0| })? | |
896| 0| .ok_or_else(|| { | |
897| 0| actor_error!(ErrNotFound, "proposal doesn't exist ({})", deal_id) | |
898| 0| })? | |
899| 0| .clone(); | |
900| | | |
901| 0| let dcid = deal.cid().map_err(|e| { | |
902| 0| ActorError::from(e) | |
903| 0| .wrap(format!("failed to calculate cid for proposal {}", deal_id)) | |
904| 0| })?; | |
905| | | |
906| 0| let state = msm | |
907| 0| .deal_states | |
908| 0| .as_ref() | |
909| 0| .unwrap() | |
910| 0| .get(deal_id) | |
911| 0| .map_err(|e| { | |
912| 0| e.downcast_default( | |
913| 0| ExitCode::ErrIllegalState, | |
914| 0| "failed to get deal state", | |
915| 0| ) | |
916| 0| })? | |
917| 0| .cloned(); | |
918| 0| | |
919| 0| // deal has been published but not activated yet -> terminate it | |
920| 0| // as it has timed out | |
921| 0| if state.is_none() { | |
922| | // Not yet appeared in proven sector; check for timeout. | |
923| 0| if curr_epoch < deal.start_epoch { | |
924| 0| return Err(actor_error!( | |
925| 0| ErrIllegalState, | |
926| 0| "deal {} processed before start epoch {}", | |
927| 0| deal_id, | |
928| 0| deal.start_epoch | |
929| 0| )); | |
930| 0| } | |
931| | | |
932| 0| let slashed = msm.process_deal_init_timed_out(&deal)?; | |
933| 0| if !slashed.is_zero() { | |
934| 0| amount_slashed += slashed; | |
935| 0| } | |
936| 0| if deal.verified_deal { | |
937| 0| timed_out_verified_deals.push(deal); | |
938| 0| } | |
939| | | |
940| | // Delete the proposal (but not state, which doesn't exist). | |
941| 0| let deleted = msm | |
942| 0| .deal_proposals | |
943| 0| .as_mut() | |
944| 0| .unwrap() | |
945| 0| .delete(deal_id) | |
946| 0| .map_err(|e| { | |
947| 0| e.downcast_default( | |
948| 0| ExitCode::ErrIllegalState, | |
949| 0| format!("failed to delete deal proposal {}", deal_id), | |
950| 0| ) | |
951| 0| })?; | |
952| 0| if deleted.is_none() { | |
953| 0| return Err(actor_error!( | |
954| 0| ErrIllegalState, | |
955| 0| format!( | |
956| 0| "failed to delete deal {} proposal {}: does not exist", | |
957| 0| deal_id, dcid | |
958| 0| ) | |
959| 0| )); | |
960| 0| } | |
961| 0| msm.pending_deals | |
962| 0| .as_mut() | |
963| 0| .unwrap() | |
964| 0| .delete(&dcid.to_bytes()) | |
965| 0| .map_err(|e| { | |
966| 0| e.downcast_default( | |
967| 0| ExitCode::ErrIllegalState, | |
968| 0| format!("failed to delete pending proposal {}", deal_id), | |
969| 0| ) | |
970| 0| })? | |
971| 0| .ok_or_else(|| { | |
972| 0| actor_error!( | |
973| 0| ErrIllegalState, | |
974| 0| "failed to delete pending proposal: does not exist" | |
975| 0| ) | |
976| 0| })?; | |
977| | | |
978| 0| continue; | |
979| 0| } | |
980| 0| let mut state = state.unwrap(); | |
981| 0| | |
982| 0| if state.last_updated_epoch == EPOCH_UNDEFINED { | |
983| 0| msm.pending_deals | |
984| 0| .as_mut() | |
985| 0| .unwrap() | |
986| 0| .delete(&dcid.to_bytes()) | |
987| 0| .map_err(|e| { | |
988| 0| e.downcast_default( | |
989| 0| ExitCode::ErrIllegalState, | |
990| 0| format!("failed to delete pending proposal {}", dcid), | |
991| 0| ) | |
992| 0| })? | |
993| 0| .ok_or_else(|| { | |
994| 0| actor_error!( | |
995| 0| ErrIllegalState, | |
996| 0| "failed to delete pending proposal: does not exist" | |
997| 0| ) | |
998| 0| })?; | |
999| 0| } | |
1000| | | |
1001| 0| let (slash_amount, next_epoch, remove_deal) = | |
1002| 0| msm.update_pending_deal_state(&state, &deal, curr_epoch)?; | |
1003| 0| if slash_amount.is_negative() { | |
1004| 0| return Err(actor_error!( | |
1005| 0| ErrIllegalState, | |
1006| 0| format!( | |
1007| 0| "computed negative slash amount {} for deal {}", | |
1008| 0| slash_amount, deal_id | |
1009| 0| ) | |
1010| 0| )); | |
1011| 0| } | |
1012| 0| | |
1013| 0| if remove_deal { | |
1014| 0| if next_epoch != EPOCH_UNDEFINED { | |
1015| 0| return Err(actor_error!( | |
1016| 0| ErrIllegalState, | |
1017| 0| format!( | |
1018| 0| "removed deal {} should have no scheduled epoch (got {})", | |
1019| 0| deal_id, next_epoch | |
1020| 0| ) | |
1021| 0| )); | |
1022| 0| } | |
1023| 0| | |
1024| 0| amount_slashed += slash_amount; | |
1025| | | |
1026| | // Delete proposal and state simultaneously. | |
1027| 0| let deleted = | |
1028| 0| msm.deal_states | |
1029| 0| .as_mut() | |
1030| 0| .unwrap() | |
1031| 0| .delete(deal_id) | |
1032| 0| .map_err(|e| { | |
1033| 0| e.downcast_default( | |
1034| 0| ExitCode::ErrIllegalState, | |
1035| 0| "failed to delete deal state", | |
1036| 0| ) | |
1037| 0| })?; | |
1038| 0| if deleted.is_none() { | |
1039| 0| return Err(actor_error!( | |
1040| 0| ErrIllegalState, | |
1041| 0| "failed to delete deal state: does not exist" | |
1042| 0| )); | |
1043| 0| } | |
1044| | | |
1045| 0| let deleted = msm | |
1046| 0| .deal_proposals | |
1047| 0| .as_mut() | |
1048| 0| .unwrap() | |
1049| 0| .delete(deal_id) | |
1050| 0| .map_err(|e| { | |
1051| 0| e.downcast_default( | |
1052| 0| ExitCode::ErrIllegalState, | |
1053| 0| "failed to delete deal proposal", | |
1054| 0| ) | |
1055| 0| })?; | |
1056| 0| if deleted.is_none() { | |
1057| 0| return Err(actor_error!( | |
1058| 0| ErrIllegalState, | |
1059| 0| "failed to delete deal proposal: does not exist" | |
1060| 0| )); | |
1061| 0| } | |
1062| | } else { | |
1063| 0| if next_epoch <= rt.curr_epoch() { | |
1064| 0| return Err(actor_error!( | |
1065| 0| ErrIllegalState, | |
1066| 0| "continuing deal {} next epoch {} should be in the future", | |
1067| 0| deal_id, | |
1068| 0| next_epoch | |
1069| 0| )); | |
1070| 0| } | |
1071| 0| if !slash_amount.is_zero() { | |
1072| 0| return Err(actor_error!( | |
1073| 0| ErrIllegalState, | |
1074| 0| "continuing deal {} should not be slashed", | |
1075| 0| deal_id | |
1076| 0| )); | |
1077| 0| } | |
1078| 0| | |
1079| 0| state.last_updated_epoch = curr_epoch; | |
1080| 0| msm.deal_states | |
1081| 0| .as_mut() | |
1082| 0| .unwrap() | |
1083| 0| .set(deal_id, state) | |
1084| 0| .map_err(|e| { | |
1085| 0| e.downcast_default( | |
1086| 0| ExitCode::ErrIllegalState, | |
1087| 0| "failed to set deal state", | |
1088| 0| ) | |
1089| 0| })?; | |
1090| | | |
1091| 0| if let Some(ev) = updates_needed.get_mut(&next_epoch) { | |
1092| 0| ev.push(deal_id); | |
1093| 0| } else { | |
1094| 0| updates_needed.insert(next_epoch, vec![deal_id]); | |
1095| 0| } | |
1096| | } | |
1097| | } | |
1098| 0| msm.deals_by_epoch | |
1099| 0| .as_mut() | |
1100| 0| .unwrap() | |
1101| 0| .remove_all(i) | |
1102| 0| .map_err(|e| { | |
1103| 0| e.downcast_default( | |
1104| 0| ExitCode::ErrIllegalState, | |
1105| 0| format!("failed to delete deal ops for epoch {}", i), | |
1106| 0| ) | |
1107| 0| })?; | |
1108| | } | |
1109| | | |
1110| | // updates_needed is already sorted by epoch. | |
1111| 0| for (epoch, deals) in updates_needed { | |
1112| 0| msm.deals_by_epoch | |
1113| 0| .as_mut() | |
1114| 0| .unwrap() | |
1115| 0| .put_many(epoch, &deals) | |
1116| 0| .map_err(|e| { | |
1117| 0| e.downcast_default( | |
1118| 0| ExitCode::ErrIllegalState, | |
1119| 0| format!("failed to reinsert deal IDs for epoch {}", epoch), | |
1120| 0| ) | |
1121| 0| })?; | |
1122| | } | |
1123| | | |
1124| 0| msm.st.last_cron = rt.curr_epoch(); | |
1125| 0| | |
1126| 0| msm.commit_state().map_err(|e| { | |
1127| 0| e.downcast_default(ExitCode::ErrIllegalState, "failed to flush state") | |
1128| 0| })?; | |
1129| 0| Ok(()) | |
1130| 0| })?; | |
1131| | | |
1132| 0| for d in timed_out_verified_deals { | |
1133| 0| let res = rt.send( | |
1134| 0| *VERIFIED_REGISTRY_ACTOR_ADDR, | |
1135| 0| ext::verifreg::RESTORE_BYTES_METHOD, | |
1136| 0| RawBytes::serialize(ext::verifreg::RestoreBytesParams { | |
1137| 0| address: d.client, | |
1138| 0| deal_size: BigInt::from(d.piece_size.0), | |
1139| 0| })?, | |
1140| 0| TokenAmount::zero(), | |
1141| | ); | |
1142| 0| if let Err(e) = res { | |
1143| 0| log::error!( | |
1144| 0| "failed to send RestoreBytes call to the verifreg actor for timed \ | |
1145| 0| out verified deal, client: {}, deal_size: {}, provider: {}, got code: {:?}. {}", | |
1146| 0| d.client, | |
1147| 0| d.piece_size.0, | |
1148| 0| d.provider, | |
1149| 0| e.exit_code(), | |
1150| 0| e.msg() | |
1151| | ); | |
1152| 0| } | |
1153| | } | |
1154| | | |
1155| 0| if !amount_slashed.is_zero() { | |
1156| 0| rt.send( | |
1157| 0| *BURNT_FUNDS_ACTOR_ADDR, | |
1158| 0| METHOD_SEND, | |
1159| 0| RawBytes::default(), | |
1160| 0| amount_slashed, | |
1161| 0| )?; | |
1162| 0| } | |
1163| 0| Ok(()) | |
1164| 0| } | |
1165| |} | |
1166| | | |
1167| |/// Validates a collection of deal dealProposals for activation, and returns their combined weight, | |
1168| |/// split into regular deal weight and verified deal weight. | |
1169| 0|pub fn validate_deals_for_activation<BS>( | |
1170| 0| st: &State, | |
1171| 0| store: &BS, | |
1172| 0| deal_ids: &[DealID], | |
1173| 0| miner_addr: &Address, | |
1174| 0| sector_expiry: ChainEpoch, | |
1175| 0| curr_epoch: ChainEpoch, | |
1176| 0|) -> anyhow::Result<(BigInt, BigInt, u64)> | |
1177| 0|where | |
1178| 0| BS: Blockstore, | |
1179| 0|{ | |
1180| 0| let proposals = DealArray::load(&st.proposals, store)?; | |
1181| | | |
1182| 0| validate_and_compute_deal_weight(&proposals, deal_ids, miner_addr, sector_expiry, curr_epoch) | |
1183| 0|} | |
1184| | | |
1185| 0|pub fn validate_and_compute_deal_weight<BS>( | |
1186| 0| proposals: &DealArray<BS>, | |
1187| 0| deal_ids: &[DealID], | |
1188| 0| miner_addr: &Address, | |
1189| 0| sector_expiry: ChainEpoch, | |
1190| 0| sector_activation: ChainEpoch, | |
1191| 0|) -> anyhow::Result<(BigInt, BigInt, u64)> | |
1192| 0|where | |
1193| 0| BS: Blockstore, | |
1194| 0|{ | |
1195| 0| let mut seen_deal_ids = BTreeSet::new(); | |
1196| 0| let mut total_deal_space = 0; | |
1197| 0| let mut total_deal_space_time = BigInt::zero(); | |
1198| 0| let mut total_verified_space_time = BigInt::zero(); | |
1199| 0| for deal_id in deal_ids { | |
1200| 0| if !seen_deal_ids.insert(deal_id) { | |
1201| 0| return Err(actor_error!( | |
1202| 0| ErrIllegalArgument, | |
1203| 0| "deal id {} present multiple times", | |
1204| 0| deal_id | |
1205| 0| ) | |
1206| 0| .into()); | |
1207| 0| } | |
1208| 0| let proposal = proposals | |
1209| 0| .get(*deal_id)? | |
1210| 0| .ok_or_else(|| actor_error!(ErrNotFound, "no such deal {}", deal_id))?; | |
1211| | | |
1212| 0| validate_deal_can_activate(proposal, miner_addr, sector_expiry, sector_activation) | |
1213| 0| .map_err(|e| e.wrap(&format!("cannot activate deal {}", deal_id)))?; | |
1214| | | |
1215| 0| total_deal_space += proposal.piece_size.0; | |
1216| 0| let deal_space_time = deal_weight(proposal); | |
1217| 0| if proposal.verified_deal { | |
1218| 0| total_verified_space_time += deal_space_time; | |
1219| 0| } else { | |
1220| 0| total_deal_space_time += deal_space_time; | |
1221| 0| } | |
1222| | } | |
1223| | | |
1224| 0| Ok(( | |
1225| 0| total_deal_space_time, | |
1226| 0| total_verified_space_time, | |
1227| 0| total_deal_space, | |
1228| 0| )) | |
1229| 0|} | |
1230| | | |
1231| 0|fn gen_rand_next_epoch(start_epoch: ChainEpoch, deal_id: DealID) -> ChainEpoch { | |
1232| 0| let offset = deal_id as i64 % DEAL_UPDATES_INTERVAL; | |
1233| 0| let q = QuantSpec { | |
1234| 0| unit: DEAL_UPDATES_INTERVAL, | |
1235| 0| offset: 0, | |
1236| 0| }; | |
1237| 0| let prev_day = q.quantize_down(start_epoch); | |
1238| 0| if prev_day + offset >= start_epoch { | |
1239| 0| return prev_day + offset; | |
1240| 0| } | |
1241| 0| let next_day = q.quantize_up(start_epoch); | |
1242| 0| next_day + offset | |
1243| 0|} | |
1244| |//////////////////////////////////////////////////////////////////////////////// | |
1245| |// Checks | |
1246| |//////////////////////////////////////////////////////////////////////////////// | |
1247| 0|fn validate_deal_can_activate( | |
1248| 0| proposal: &DealProposal, | |
1249| 0| miner_addr: &Address, | |
1250| 0| sector_expiration: ChainEpoch, | |
1251| 0| curr_epoch: ChainEpoch, | |
1252| 0|) -> Result<(), ActorError> { | |
1253| 0| if &proposal.provider != miner_addr { | |
1254| 0| return Err(actor_error!( | |
1255| 0| ErrForbidden, | |
1256| 0| "proposal has provider {}, must be {}", | |
1257| 0| proposal.provider, | |
1258| 0| miner_addr | |
1259| 0| )); | |
1260| 0| }; | |
1261| 0| | |
1262| 0| if curr_epoch > proposal.start_epoch { | |
1263| 0| return Err(actor_error!( | |
1264| 0| ErrIllegalArgument, | |
1265| 0| "proposal start epoch {} has already elapsed at {}", | |
1266| 0| proposal.start_epoch, | |
1267| 0| curr_epoch | |
1268| 0| )); | |
1269| 0| }; | |
1270| 0| | |
1271| 0| if proposal.end_epoch > sector_expiration { | |
1272| 0| return Err(actor_error!( | |
1273| 0| ErrIllegalArgument, | |
1274| 0| "proposal expiration {} exceeds sector expiration {}", | |
1275| 0| proposal.end_epoch, | |
1276| 0| sector_expiration | |
1277| 0| )); | |
1278| 0| }; | |
1279| 0| | |
1280| 0| Ok(()) | |
1281| 0|} | |
1282| | | |
1283| |fn validate_deal<BS, RT>( | |
1284| | rt: &RT, | |
1285| | deal: &ClientDealProposal, | |
1286| | network_raw_power: &StoragePower, | |
1287| | baseline_power: &StoragePower, | |
1288| |) -> Result<(), ActorError> | |
1289| |where | |
1290| | BS: Blockstore, | |
1291| | RT: Runtime<BS>, | |
1292| |{ | |
1293| 0| deal_proposal_is_internally_valid(rt, deal)?; | |
1294| | | |
1295| 0| let proposal = &deal.proposal; | |
1296| 0| | |
1297| 0| if proposal.label.len() > DEAL_MAX_LABEL_SIZE { | |
1298| 0| return Err(actor_error!( | |
1299| 0| ErrIllegalArgument, | |
1300| 0| "deal label can be at most {} bytes, is {}", | |
1301| 0| DEAL_MAX_LABEL_SIZE, | |
1302| 0| proposal.label.len() | |
1303| 0| )); | |
1304| 0| } | |
1305| 0| | |
1306| 0| proposal | |
1307| 0| .piece_size | |
1308| 0| .validate() | |
1309| 0| .map_err(|e| actor_error!(ErrIllegalArgument, "proposal piece size is invalid: {}", e))?; | |
1310| | | |
1311| | // * we are skipping the check for if Cid is defined, but this shouldn't be possible | |
1312| | | |
1313| 0| if !is_piece_cid(&proposal.piece_cid) { | |
1314| 0| return Err(actor_error!( | |
1315| 0| ErrIllegalArgument, | |
1316| 0| "proposal PieceCID undefined" | |
1317| 0| )); | |
1318| 0| } | |
1319| 0| | |
1320| 0| if proposal.end_epoch <= proposal.start_epoch { | |
1321| 0| return Err(actor_error!( | |
1322| 0| ErrIllegalArgument, | |
1323| 0| "proposal end before proposal start" | |
1324| 0| )); | |
1325| 0| } | |
1326| 0| | |
1327| 0| if rt.curr_epoch() > proposal.start_epoch { | |
1328| 0| return Err(actor_error!( | |
1329| 0| ErrIllegalArgument, | |
1330| 0| "Deal start epoch has already elapsed." | |
1331| 0| )); | |
1332| 0| }; | |
1333| 0| | |
1334| 0| let (min_dur, max_dur) = deal_duration_bounds(proposal.piece_size); | |
1335| 0| if proposal.duration() < min_dur || proposal.duration() > max_dur { | |
1336| 0| return Err(actor_error!( | |
1337| 0| ErrIllegalArgument, | |
1338| 0| "Deal duration out of bounds." | |
1339| 0| )); | |
1340| 0| }; | |
1341| 0| | |
1342| 0| let (min_price, max_price) = | |
1343| 0| deal_price_per_epoch_bounds(proposal.piece_size, proposal.duration()); | |
1344| 0| if proposal.storage_price_per_epoch < min_price || &proposal.storage_price_per_epoch > max_price | |
1345| | { | |
1346| 0| return Err(actor_error!( | |
1347| 0| ErrIllegalArgument, | |
1348| 0| "Storage price out of bounds." | |
1349| 0| )); | |
1350| 0| }; | |
1351| 0| | |
1352| 0| let (min_provider_collateral, max_provider_collateral) = deal_provider_collateral_bounds( | |
1353| 0| proposal.piece_size, | |
1354| 0| network_raw_power, | |
1355| 0| baseline_power, | |
1356| 0| &rt.total_fil_circ_supply(), | |
1357| 0| ); | |
1358| 0| if proposal.provider_collateral < min_provider_collateral | |
1359| 0| || proposal.provider_collateral > max_provider_collateral | |
1360| | { | |
1361| 0| return Err(actor_error!( | |
1362| 0| ErrIllegalArgument, | |
1363| 0| "Provider collateral out of bounds." | |
1364| 0| )); | |
1365| 0| }; | |
1366| 0| | |
1367| 0| let (min_client_collateral, max_client_collateral) = | |
1368| 0| deal_client_collateral_bounds(proposal.piece_size, proposal.duration()); | |
1369| 0| if proposal.client_collateral < min_client_collateral | |
1370| 0| || proposal.client_collateral > max_client_collateral | |
1371| | { | |
1372| 0| return Err(actor_error!( | |
1373| 0| ErrIllegalArgument, | |
1374| 0| "Client collateral out of bounds." | |
1375| 0| )); | |
1376| 0| }; | |
1377| 0| | |
1378| 0| Ok(()) | |
1379| 0|} | |
1380| | | |
1381| 0|fn deal_proposal_is_internally_valid<BS, RT>( | |
1382| 0| rt: &RT, | |
1383| 0| proposal: &ClientDealProposal, | |
1384| 0|) -> Result<(), ActorError> | |
1385| 0|where | |
1386| 0| BS: Blockstore, | |
1387| 0| RT: Runtime<BS>, | |
1388| 0|{ | |
1389| | // Generate unsigned bytes | |
1390| 0| let sv_bz = to_vec(&proposal.proposal) | |
1391| 0| .map_err(|e| ActorError::from(e).wrap("failed to serialize DealProposal"))?; | |
1392| | | |
1393| 0| rt.verify_signature( | |
1394| 0| &proposal.client_signature, | |
1395| 0| &proposal.proposal.client, | |
1396| 0| &sv_bz, | |
1397| 0| ) | |
1398| 0| .map_err(|e| e.downcast_default(ExitCode::ErrIllegalArgument, "signature proposal invalid"))?; | |
1399| | | |
1400| 0| Ok(()) | |
1401| 0|} | |
1402| | | |
1403| |/// Resolves a provider or client address to the canonical form against which a balance should be held, and | |
1404| |/// the designated recipient address of withdrawals (which is the same, for simple account parties). | |
1405| 1|fn escrow_address<BS, RT>( | |
1406| 1| rt: &mut RT, | |
1407| 1| addr: &Address, | |
1408| 1|) -> Result<(Address, Address, Vec<Address>), ActorError> | |
1409| 1|where | |
1410| 1| BS: Blockstore, | |
1411| 1| RT: Runtime<BS>, | |
1412| 1|{ | |
1413| | // Resolve the provided address to the canonical form against which the balance is held. | |
1414| 1| let nominal = rt | |
1415| 1| .resolve_address(addr) | |
1416| 1| .ok_or_else(|| actor_error!(ErrIllegalArgument, "failed to resolve address {}", addr))?; | |
^0 ^0 | |
1417| | | |
1418| 1| let code_id = rt | |
1419| 1| .get_actor_code_cid(&nominal) | |
1420| 1| .ok_or_else(|| actor_error!(ErrIllegalArgument, "no code for address {}", nominal))?; | |
^0 ^0 | |
1421| | | |
1422| 1| if code_id == *MINER_ACTOR_CODE_ID { | |
1423| | // Storage miner actor entry; implied funds recipient is the associated owner address. | |
1424| 1| let (owner_addr, worker_addr, _) = request_miner_control_addrs(rt, nominal)?; | |
^0 | |
1425| 1| return Ok((nominal, owner_addr, vec![owner_addr, worker_addr])); | |
1426| 0| } | |
1427| 0| | |
1428| 0| Ok((nominal, nominal, vec![nominal])) | |
1429| 1|} | |
1430| | | |
1431| |/// Requests the current epoch target block reward from the reward actor. | |
1432| 0|fn request_current_baseline_power<BS, RT>(rt: &mut RT) -> Result<StoragePower, ActorError> | |
1433| 0|where | |
1434| 0| BS: Blockstore, | |
1435| 0| RT: Runtime<BS>, | |
1436| 0|{ | |
1437| 0| let rwret = rt.send( | |
1438| 0| *REWARD_ACTOR_ADDR, | |
1439| 0| ext::reward::THIS_EPOCH_REWARD_METHOD, | |
1440| 0| RawBytes::default(), | |
1441| 0| 0.into(), | |
1442| 0| )?; | |
1443| 0| let ret: ThisEpochRewardReturn = rwret.deserialize()?; | |
1444| 0| Ok(ret.this_epoch_baseline_power) | |
1445| 0|} | |
1446| | | |
1447| |/// Requests the current network total power and pledge from the power actor. | |
1448| |/// Returns a tuple of (raw_power, qa_power). | |
1449| 0|fn request_current_network_power<BS, RT>( | |
1450| 0| rt: &mut RT, | |
1451| 0|) -> Result<(StoragePower, StoragePower), ActorError> | |
1452| 0|where | |
1453| 0| BS: Blockstore, | |
1454| 0| RT: Runtime<BS>, | |
1455| 0|{ | |
1456| 0| let rwret = rt.send( | |
1457| 0| *STORAGE_POWER_ACTOR_ADDR, | |
1458| 0| ext::power::CURRENT_TOTAL_POWER_METHOD, | |
1459| 0| RawBytes::default(), | |
1460| 0| 0.into(), | |
1461| 0| )?; | |
1462| 0| let ret: ext::power::CurrentTotalPowerReturnParams = rwret.deserialize()?; | |
1463| 0| Ok((ret.raw_byte_power, ret.quality_adj_power)) | |
1464| 0|} | |
1465| | | |
1466| |impl ActorCode for Actor { | |
1467| | fn invoke_method<BS, RT>( | |
1468| | rt: &mut RT, | |
1469| | method: MethodNum, | |
1470| | params: &RawBytes, | |
1471| | ) -> Result<RawBytes, ActorError> | |
1472| | where | |
1473| | BS: Blockstore, | |
1474| | RT: Runtime<BS>, | |
1475| | { | |
1476| 1| match FromPrimitive::from_u64(method) { | |
1477| | Some(Method::Constructor) => { | |
1478| 0| Self::constructor(rt)?; | |
1479| 0| Ok(RawBytes::default()) | |
1480| | } | |
1481| | Some(Method::AddBalance) => { | |
1482| 0| Self::add_balance(rt, rt.deserialize_params(params)?)?; | |
1483| 0| Ok(RawBytes::default()) | |
1484| | } | |
1485| | Some(Method::WithdrawBalance) => { | |
1486| 1| let res = Self::withdraw_balance(rt, rt.deserialize_params(params)?)?; | |
^0^0 | |
1487| 1| Ok(RawBytes::serialize(res)?) | |
^0 | |
1488| | } | |
1489| | Some(Method::PublishStorageDeals) => { | |
1490| 0| let res = Self::publish_storage_deals(rt, rt.deserialize_params(params)?)?; | |
1491| 0| Ok(RawBytes::serialize(res)?) | |
1492| | } | |
1493| | Some(Method::VerifyDealsForActivation) => { | |
1494| 0| let res = Self::verify_deals_for_activation(rt, rt.deserialize_params(params)?)?; | |
1495| 0| Ok(RawBytes::serialize(res)?) | |
1496| | } | |
1497| | Some(Method::ActivateDeals) => { | |
1498| 0| Self::activate_deals(rt, rt.deserialize_params(params)?)?; | |
1499| 0| Ok(RawBytes::default()) | |
1500| | } | |
1501| | Some(Method::OnMinerSectorsTerminate) => { | |
1502| 0| Self::on_miner_sectors_terminate(rt, rt.deserialize_params(params)?)?; | |
1503| 0| Ok(RawBytes::default()) | |
1504| | } | |
1505| | Some(Method::ComputeDataCommitment) => { | |
1506| 0| let res = Self::compute_data_commitment(rt, rt.deserialize_params(params)?)?; | |
1507| 0| Ok(RawBytes::serialize(res)?) | |
1508| | } | |
1509| | Some(Method::CronTick) => { | |
1510| 0| Self::cron_tick(rt)?; | |
1511| 0| Ok(RawBytes::default()) | |
1512| | } | |
1513| 0| None => Err(actor_error!(SysErrInvalidMethod, "Invalid method")), | |
1514| | } | |
1515| 1| } | |
1516| |} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment