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