1use super::MinerActorStateLoad as _;
5use crate::shim::actors::miner;
6use crate::shim::{
7 actors::{is_account_actor, is_ethaccount_actor, is_placeholder_actor},
8 address::{Address, Payload},
9 randomness::Randomness,
10 sector::{ExtendedSectorInfo, RegisteredPoStProof, RegisteredSealProof},
11 state_tree::ActorState,
12 version::NetworkVersion,
13};
14use crate::state_manager::{StateManager, errors::*};
15use crate::utils::encoding::prover_id_from_u64;
16use cid::Cid;
17use fil_actors_shared::filecoin_proofs_api::post;
18use fil_actors_shared::fvm_ipld_bitfield::BitField;
19use fvm_ipld_blockstore::Blockstore;
20use fvm_ipld_encoding::bytes_32;
21
22impl<DB> StateManager<DB>
23where
24 DB: Blockstore,
25{
26 pub fn get_sectors_for_winning_post(
29 &self,
30 st: &Cid,
31 nv: NetworkVersion,
32 miner_address: &Address,
33 rand: Randomness,
34 ) -> anyhow::Result<Vec<ExtendedSectorInfo>> {
35 let store = self.blockstore();
36
37 let actor = self
38 .get_actor(miner_address, *st)?
39 .ok_or_else(|| Error::state("Miner actor address could not be resolved"))?;
40 let mas = miner::State::load(self.blockstore(), actor.code, actor.state)?;
41
42 let proving_sectors = {
43 let mut proving_sectors = BitField::new();
44
45 if nv < NetworkVersion::V7 {
46 mas.for_each_deadline(&self.chain_config().policy, store, |_, deadline| {
47 let mut fault_sectors = BitField::new();
48 deadline.for_each(store, |_, partition: miner::Partition| {
49 proving_sectors |= partition.all_sectors();
50 fault_sectors |= partition.faulty_sectors();
51 Ok(())
52 })?;
53
54 proving_sectors -= &fault_sectors;
55 Ok(())
56 })?;
57 } else {
58 mas.for_each_deadline(&self.chain_config().policy, store, |_, deadline| {
59 deadline.for_each(store, |_, partition: miner::Partition| {
60 proving_sectors |= &partition.active_sectors();
61 Ok(())
62 })?;
63 Ok(())
64 })?;
65 }
66 proving_sectors
67 };
68
69 let num_prov_sect = proving_sectors.len();
70
71 if num_prov_sect == 0 {
72 return Ok(Vec::new());
73 }
74
75 let info = mas.info(store)?;
76 let spt = RegisteredSealProof::from_sector_size(info.sector_size(), nv);
77
78 let wpt = spt.registered_winning_post_proof()?;
79
80 let m_id = miner_address.id()?;
81
82 let ids = generate_winning_post_sector_challenge(wpt.into(), m_id, rand, num_prov_sect)?;
83
84 let mut iter = proving_sectors.iter();
85
86 let mut selected_sectors = BitField::new();
87 for n in ids {
88 let sno = iter.nth(n as usize).ok_or_else(|| {
89 anyhow::anyhow!(
90 "Error iterating over proving sectors, id {} does not exist",
91 n
92 )
93 })?;
94 selected_sectors.set(sno);
95 }
96
97 let sectors = mas.load_sectors(store, Some(&selected_sectors))?;
98
99 let out = sectors
100 .into_iter()
101 .map(|s_info| ExtendedSectorInfo {
102 proof: s_info.seal_proof.into(),
103 sector_number: s_info.sector_number,
104 sector_key: s_info.sector_key_cid,
105 sealed_cid: s_info.sealed_cid,
106 })
107 .collect();
108
109 Ok(out)
110 }
111}
112
113pub fn is_valid_for_sending(network_version: NetworkVersion, actor: &ActorState) -> bool {
114 if network_version < NetworkVersion::V18 {
128 return is_account_actor(&actor.code);
129 }
130
131 if is_account_actor(&actor.code) || is_ethaccount_actor(&actor.code) {
133 return true;
134 }
135
136 if !is_placeholder_actor(&actor.code)
139 || actor.sequence != 0
140 || actor.delegated_address.is_none()
141 {
142 return false;
143 }
144
145 if let Payload::Delegated(address) = actor
148 .delegated_address
149 .as_ref()
150 .expect("unfallible")
151 .payload()
152 {
153 address.namespace() == Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR.id().unwrap()
154 } else {
155 false
156 }
157}
158
159fn generate_winning_post_sector_challenge(
161 proof: RegisteredPoStProof,
162 prover_id: u64,
163 mut rand: Randomness,
164 eligible_sector_count: u64,
165) -> anyhow::Result<Vec<u64>> {
166 if let Some(b31) = rand.0.get_mut(31) {
168 *b31 &= 0x3f;
169 } else {
170 anyhow::bail!("rand should have at least 32 bytes");
171 }
172
173 post::generate_winning_post_sector_challenge(
174 proof.try_into()?,
175 &bytes_32(&rand.0),
176 eligible_sector_count,
177 prover_id_from_u64(prover_id),
178 )
179}
180
181pub mod state_compute {
182 use crate::{
183 blocks::{FullTipset, Tipset},
184 chain::store::ChainStore,
185 chain_sync::load_full_tipset,
186 db::{
187 MemoryDB,
188 car::{AnyCar, ManyCar},
189 },
190 genesis::read_genesis_header,
191 interpreter::VMTrace,
192 networks::{ChainConfig, NetworkChain},
193 state_manager::{ExecutedTipset, StateManager},
194 utils::net::{DownloadFileOption, download_file_with_cache},
195 };
196 use directories::ProjectDirs;
197 use sonic_rs::JsonValueTrait;
198 use std::{
199 path::{Path, PathBuf},
200 sync::{Arc, LazyLock},
201 time::{Duration, Instant},
202 };
203 use tokio::io::AsyncReadExt;
204 use url::Url;
205
206 const DO_SPACE_ROOT: &str = "https://forest-snapshots.fra1.cdn.digitaloceanspaces.com/";
207
208 #[allow(dead_code)]
209 pub async fn get_state_compute_snapshot(
210 chain: &NetworkChain,
211 epoch: i64,
212 ) -> anyhow::Result<PathBuf> {
213 get_state_snapshot(chain, "state_compute", epoch).await
214 }
215
216 #[allow(dead_code)]
217 async fn get_state_validate_snapshot(
218 chain: &NetworkChain,
219 epoch: i64,
220 ) -> anyhow::Result<PathBuf> {
221 get_state_snapshot(chain, "state_validate", epoch).await
222 }
223
224 #[allow(dead_code)]
225 pub async fn get_state_snapshot(
226 chain: &NetworkChain,
227 bucket: &str,
228 epoch: i64,
229 ) -> anyhow::Result<PathBuf> {
230 let file = format!("{bucket}/{chain}_{epoch}.forest.car.zst");
231 get_state_snapshot_file(&file).await
232 }
233
234 pub async fn get_state_snapshot_file(file: &str) -> anyhow::Result<PathBuf> {
235 static SNAPSHOT_CACHE_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
236 let project_dir = ProjectDirs::from("com", "ChainSafe", "Forest");
237 project_dir
238 .map(|d| d.cache_dir().to_path_buf())
239 .unwrap_or_else(std::env::temp_dir)
240 .join("state_compute_snapshots")
241 });
242
243 let url = Url::parse(&format!("{DO_SPACE_ROOT}{file}"))?;
244 let path = crate::utils::retry(
245 crate::utils::RetryArgs {
246 timeout: Some(Duration::from_secs(30)),
247 max_retries: Some(5),
248 delay: Some(Duration::from_secs(1)),
249 },
250 || {
251 download_file_with_cache(
252 &url,
253 &SNAPSHOT_CACHE_DIR,
254 DownloadFileOption::NonResumable,
255 )
256 },
257 )
258 .await?
259 .path;
260 #[cfg(test)]
261 {
262 println!(
264 "snapshot: {file}, sha256sum: {}",
265 hex::encode(crate::utils::hash::digest_file::<sha2::Sha256>(&path)?)
266 );
267 }
268 Ok(path)
269 }
270
271 pub async fn prepare_state_compute(
272 chain: &NetworkChain,
273 snapshot: &Path,
274 ) -> anyhow::Result<(Arc<StateManager<ManyCar>>, Tipset, Tipset)> {
275 let snap_car = AnyCar::try_from(snapshot)?;
276 let ts_next = snap_car.heaviest_tipset()?;
277 let db = Arc::new(ManyCar::new(MemoryDB::default()).with_read_only(snap_car)?);
278 let ts = Tipset::load_required(&db, ts_next.parents())?;
279 let chain_config = Arc::new(ChainConfig::from_chain(chain));
280 let genesis_header =
281 read_genesis_header(None, chain_config.genesis_bytes(&db).await?.as_deref(), &db)
282 .await?;
283 let chain_store = Arc::new(ChainStore::new(
284 db.clone(),
285 db.clone(),
286 db.clone(),
287 chain_config,
288 genesis_header,
289 )?);
290 let state_manager = Arc::new(StateManager::new(chain_store.clone())?);
291 Ok((state_manager, ts, ts_next))
292 }
293
294 pub async fn prepare_state_validate(
295 chain: &NetworkChain,
296 snapshot: &Path,
297 ) -> anyhow::Result<(Arc<StateManager<ManyCar>>, FullTipset)> {
298 let (sm, _, ts) = prepare_state_compute(chain, snapshot).await?;
299 let fts = load_full_tipset(sm.chain_store(), ts.key())?;
300 Ok((sm, fts))
301 }
302
303 pub async fn state_compute(
304 state_manager: &Arc<StateManager<ManyCar>>,
305 ts: Tipset,
306 ts_next: &Tipset,
307 ) -> anyhow::Result<()> {
308 let epoch = ts.epoch();
309 let expected_state_root = *ts_next.parent_state();
310 let expected_receipt_root = *ts_next.parent_message_receipts();
311 let start = Instant::now();
312 let ExecutedTipset {
313 state_root,
314 receipt_root,
315 ..
316 } = state_manager
317 .compute_tipset_state(ts, crate::state_manager::NO_CALLBACK, VMTrace::NotTraced)
318 .await?;
319 tracing::info!(
320 "epoch: {epoch}, state_root: {state_root}, receipt_root: {receipt_root}, took {}.",
321 humantime::format_duration(start.elapsed())
322 );
323 anyhow::ensure!(
324 state_root == expected_state_root,
325 "state root mismatch, state_root: {state_root}, expected_state_root: {expected_state_root}"
326 );
327 anyhow::ensure!(
328 receipt_root == expected_receipt_root,
329 "receipt root mismatch, receipt_root: {receipt_root}, expected_receipt_root: {expected_receipt_root}"
330 );
331 Ok(())
332 }
333
334 pub async fn list_state_snapshot_files() -> anyhow::Result<Vec<String>> {
335 let url = Url::parse(&format!("{DO_SPACE_ROOT}?format=json&prefix=state_"))?;
336 let mut json_str = String::new();
337 crate::utils::net::reader(url.as_str(), DownloadFileOption::NonResumable, None)
338 .await?
339 .read_to_string(&mut json_str)
340 .await?;
341 let obj: sonic_rs::Object = sonic_rs::from_str(&json_str)?;
342 let files = obj
343 .iter()
344 .filter_map(|(k, v)| {
345 if k == "Contents"
346 && let sonic_rs::ValueRef::Array(arr) = v.as_ref()
347 && let Some(first) = arr.first()
348 && let Some(file) = first.as_str()
349 && file.ends_with(".car.zst")
350 {
351 Some(file.to_string())
352 } else {
353 None
354 }
355 })
356 .collect();
357 Ok(files)
358 }
359
360 #[cfg(test)]
361 mod tests {
362 use super::*;
367 #[cfg(feature = "cargo-test")]
368 use crate::chain_sync::tipset_syncer::validate_tipset;
369
370 #[tokio::test(flavor = "multi_thread")]
371 async fn test_list_state_snapshot_files() {
372 let files = list_state_snapshot_files().await.unwrap();
373 println!("{files:?}");
374 assert!(files.len() > 1);
375 get_state_snapshot_file(&files[0]).await.unwrap();
376 }
377
378 include!(concat!(env!("OUT_DIR"), "/__state_compute_tests_gen.rs"));
379
380 #[allow(dead_code)]
381 async fn state_compute_test_run(chain: NetworkChain, epoch: i64) {
382 let snapshot = get_state_compute_snapshot(&chain, epoch).await.unwrap();
383 let (sm, ts, ts_next) = prepare_state_compute(&chain, &snapshot).await.unwrap();
384 state_compute(&sm, ts, &ts_next).await.unwrap();
385 }
386
387 #[cfg(feature = "cargo-test")]
388 #[tokio::test(flavor = "multi_thread")]
389 #[fickle::fickle]
390 async fn cargo_test_state_validate_mainnet_5688000() {
391 let chain = NetworkChain::Mainnet;
392 let snapshot = get_state_validate_snapshot(&chain, 5688000).await.unwrap();
393 let (sm, fts) = prepare_state_validate(&chain, &snapshot).await.unwrap();
394 validate_tipset(&sm, fts, None).await.unwrap();
395 }
396
397 #[cfg(feature = "cargo-test")]
399 #[tokio::test(flavor = "multi_thread")]
400 #[fickle::fickle]
401 async fn cargo_test_state_validate_calibnet_16802() {
402 let chain = NetworkChain::Calibnet;
403 let snapshot = get_state_validate_snapshot(&chain, 16802).await.unwrap();
404 let (sm, fts) = prepare_state_validate(&chain, &snapshot).await.unwrap();
405 validate_tipset(&sm, fts, None).await.unwrap();
406 }
407
408 #[cfg(feature = "cargo-test")]
410 #[tokio::test(flavor = "multi_thread")]
411 #[fickle::fickle]
412 async fn cargo_test_state_validate_calibnet_322356() {
413 let chain = NetworkChain::Calibnet;
414 let snapshot = get_state_validate_snapshot(&chain, 322356).await.unwrap();
415 let (sm, fts) = prepare_state_validate(&chain, &snapshot).await.unwrap();
416 validate_tipset(&sm, fts, None).await.unwrap();
417 }
418 }
419}
420
421#[cfg(test)]
422mod test {
423 use crate::shim::{address::Address, econ::TokenAmount, state_tree::ActorState};
424 use cid::Cid;
425
426 use super::*;
427
428 #[test]
429 fn is_valid_for_sending_test() {
430 let create_actor = |code: &Cid, sequence: u64, delegated_address: Option<Address>| {
431 ActorState::new(
432 code.to_owned(),
433 Cid::try_from("bafk2bzaceavfgpiw6whqigmskk74z4blm22nwjfnzxb4unlqz2e4wgcthulhu")
435 .unwrap(),
436 TokenAmount::default(),
437 sequence,
438 delegated_address,
439 )
440 };
441
442 let account_actor_cid =
444 Cid::try_from("bafk2bzaceavfgpiw6whqigmskk74z4blm22nwjfnzxb4unlqz2e4wg3c5ujpw")
445 .unwrap();
446 let ethaccount_actor_cid =
447 Cid::try_from("bafk2bzacebiyrhz32xwxi6xql67aaq5nrzeelzas472kuwjqmdmgwotpkj35e")
448 .unwrap();
449 let placeholder_actor_cid =
450 Cid::try_from("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro")
451 .unwrap();
452
453 let actor = create_actor(&account_actor_cid, 0, None);
455 assert!(is_valid_for_sending(NetworkVersion::V17, &actor));
456
457 let actor = create_actor(ðaccount_actor_cid, 0, None);
459 assert!(!is_valid_for_sending(NetworkVersion::V17, &actor));
460
461 assert!(is_valid_for_sending(NetworkVersion::V18, &actor));
463
464 let actor = create_actor(&placeholder_actor_cid, 0, None);
466 assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
467
468 let delegated_address = Address::new_delegated(
470 Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR.id().unwrap(),
471 &[0; 20],
472 )
473 .ok();
474 let actor = create_actor(&placeholder_actor_cid, 0, delegated_address);
475 assert!(is_valid_for_sending(NetworkVersion::V18, &actor));
476
477 let actor = create_actor(&placeholder_actor_cid, 1, delegated_address);
479 assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
480
481 let delegated_address =
483 Address::new_delegated(Address::CHAOS_ACTOR.id().unwrap(), &[0; 20]).ok();
484 let actor = create_actor(&placeholder_actor_cid, 0, delegated_address);
485 assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
486 }
487}
488
489pub mod structured {
491 use crate::{
492 rpc::state::{ActorTrace, ExecutionTrace, GasTrace, MessageTrace, ReturnTrace},
493 shim::kernel::ErrorNumber,
494 };
495 use std::collections::VecDeque;
496
497 use crate::shim::{
498 address::Address,
499 error::ExitCode,
500 gas::GasCharge,
501 kernel::SyscallError,
502 trace::{Call, CallReturn, ExecutionEvent},
503 };
504 use fvm_ipld_encoding::{RawBytes, ipld_block::IpldBlock};
505 use itertools::Either;
506
507 enum CallTreeReturn {
508 Return(CallReturn),
509 Abort(ExitCode),
510 Error(SyscallError),
511 }
512
513 #[derive(Debug, thiserror::Error)]
514 pub enum BuildExecutionTraceError {
515 #[error(
516 "every ExecutionEvent::Return | ExecutionEvent::CallError should be preceded by an ExecutionEvent::Call, but this one wasn't"
517 )]
518 UnexpectedReturn,
519 #[error(
520 "every ExecutionEvent::Call should have a corresponding ExecutionEvent::Return, but this one didn't"
521 )]
522 NoReturn,
523 #[error("unrecognised ExecutionEvent variant: {0:?}")]
524 UnrecognisedEvent(Box<dyn std::fmt::Debug + Send + Sync + 'static>),
525 }
526
527 pub fn parse_events(
556 events: Vec<ExecutionEvent>,
557 ) -> anyhow::Result<Option<ExecutionTrace>, BuildExecutionTraceError> {
558 let mut events = VecDeque::from(events);
559 let mut front_load_me = vec![];
560 let mut call_trees = vec![];
561
562 while let Some(event) = events.pop_front() {
564 match event {
565 ExecutionEvent::GasCharge(gc) => front_load_me.push(gc),
566 ExecutionEvent::Call(call) => call_trees.push(ExecutionTrace::parse(call, {
567 for gc in front_load_me.drain(..).rev() {
572 events.push_front(ExecutionEvent::GasCharge(gc))
573 }
574 &mut events
575 })?),
576 ExecutionEvent::CallReturn(_)
577 | ExecutionEvent::CallAbort(_)
578 | ExecutionEvent::CallError(_) => {
579 return Err(BuildExecutionTraceError::UnexpectedReturn);
580 }
581 ExecutionEvent::Log(_ignored) => {}
582 ExecutionEvent::InvokeActor(_cid) => {}
583 ExecutionEvent::Ipld { .. } => {}
584 ExecutionEvent::Unknown(u) => {
585 return Err(BuildExecutionTraceError::UnrecognisedEvent(Box::new(u)));
586 }
587 }
588 }
589
590 if !front_load_me.is_empty() {
591 tracing::warn!(
592 "vm tracing: ignoring {} trailing gas charges",
593 front_load_me.len()
594 );
595 }
596
597 match call_trees.len() {
598 0 => Ok(None),
599 1 => Ok(Some(call_trees.remove(0))),
600 many => {
601 tracing::warn!(
602 "vm tracing: ignoring {} call trees at the root level",
603 many - 1
604 );
605 Ok(Some(call_trees.remove(0)))
606 }
607 }
608 }
609
610 impl ExecutionTrace {
611 fn parse(
623 call: Call,
624 events: &mut VecDeque<ExecutionEvent>,
625 ) -> Result<ExecutionTrace, BuildExecutionTraceError> {
626 let mut gas_charges = vec![];
627 let mut subcalls = vec![];
628 let mut actor_trace = None;
629
630 while let Some(event) = events.pop_front() {
632 let found_return = match event {
633 ExecutionEvent::GasCharge(gc) => {
634 gas_charges.push(to_gas_trace(gc));
635 None
636 }
637 ExecutionEvent::Call(call) => {
638 subcalls.push(Self::parse(call, events)?);
639 None
640 }
641 ExecutionEvent::CallReturn(ret) => Some(CallTreeReturn::Return(ret)),
642 ExecutionEvent::CallAbort(ab) => Some(CallTreeReturn::Abort(ab)),
643 ExecutionEvent::CallError(e) => Some(CallTreeReturn::Error(e)),
644 ExecutionEvent::Log(_ignored) => None,
645 ExecutionEvent::InvokeActor(cid) => {
646 actor_trace = match cid {
647 Either::Left(_cid) => None,
648 Either::Right(actor) => Some(ActorTrace {
649 id: actor.id,
650 state: actor.state,
651 }),
652 };
653 None
654 }
655 ExecutionEvent::Ipld { .. } => None,
656 ExecutionEvent::Unknown(u) => {
660 return Err(BuildExecutionTraceError::UnrecognisedEvent(Box::new(u)));
661 }
662 };
663
664 if let Some(ret) = found_return {
666 return Ok(ExecutionTrace {
667 msg: to_message_trace(call),
668 msg_rct: to_return_trace(ret),
669 gas_charges,
670 subcalls,
671 invoked_actor: actor_trace,
672 });
673 }
674 }
675
676 Err(BuildExecutionTraceError::NoReturn)
677 }
678 }
679
680 fn to_message_trace(call: Call) -> MessageTrace {
681 let (bytes, codec) = to_bytes_codec(call.params);
682 MessageTrace {
683 from: Address::new_id(call.from),
684 to: call.to,
685 value: call.value,
686 method: call.method_num,
687 params: bytes,
688 params_codec: codec,
689 gas_limit: call.gas_limit,
690 read_only: call.read_only,
691 }
692 }
693
694 fn to_return_trace(ret: CallTreeReturn) -> ReturnTrace {
695 match ret {
696 CallTreeReturn::Return(return_code) => {
697 let exit_code = return_code.exit_code.unwrap_or(0.into());
698 let (bytes, codec) = to_bytes_codec(return_code.data);
699 ReturnTrace {
700 exit_code,
701 r#return: bytes,
702 return_codec: codec,
703 }
704 }
705 CallTreeReturn::Abort(exit_code) => ReturnTrace {
706 exit_code,
707 r#return: RawBytes::default(),
708 return_codec: 0,
709 },
710 CallTreeReturn::Error(syscall_error) => match syscall_error.number {
711 ErrorNumber::InsufficientFunds => ReturnTrace {
712 exit_code: ExitCode::from(6),
713 r#return: RawBytes::default(),
714 return_codec: 0,
715 },
716 _ => ReturnTrace {
717 exit_code: ExitCode::from(0),
718 r#return: RawBytes::default(),
719 return_codec: 0,
720 },
721 },
722 }
723 }
724
725 fn to_bytes_codec(data: Either<RawBytes, Option<IpldBlock>>) -> (RawBytes, u64) {
726 match data {
727 Either::Left(l) => (l, 0),
728 Either::Right(r) => match r {
729 Some(b) => (RawBytes::from(b.data), b.codec),
730 None => (RawBytes::default(), 0),
731 },
732 }
733 }
734
735 fn to_gas_trace(gc: GasCharge) -> GasTrace {
736 GasTrace {
737 name: gc.name().into(),
738 total_gas: gc.total().round_up(),
739 compute_gas: gc.compute_gas().round_up(),
740 storage_gas: gc.other_gas().round_up(),
741 time_taken: gc.elapsed().as_nanos(),
742 }
743 }
744}