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 ) -> Result<Vec<ExtendedSectorInfo>, anyhow::Error> {
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) -> Result<Vec<u64>, anyhow::Error> {
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::{StateManager, StateOutput},
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 use digest::Digest as _;
264 println!(
265 "snapshot: {file}, sha256sum: {}",
266 hex::encode(sha2::Sha256::digest(std::fs::read(&path)?))
267 );
268 }
269 Ok(path)
270 }
271
272 pub async fn prepare_state_compute(
273 chain: &NetworkChain,
274 snapshot: &Path,
275 ) -> anyhow::Result<(Arc<StateManager<ManyCar>>, Tipset, Tipset)> {
276 let snap_car = AnyCar::try_from(snapshot)?;
277 let ts_next = snap_car.heaviest_tipset()?;
278 let db = Arc::new(ManyCar::new(MemoryDB::default()).with_read_only(snap_car)?);
279 let ts = Tipset::load_required(&db, ts_next.parents())?;
280 let chain_config = Arc::new(ChainConfig::from_chain(chain));
281 let genesis_header =
282 read_genesis_header(None, chain_config.genesis_bytes(&db).await?.as_deref(), &db)
283 .await?;
284 let chain_store = Arc::new(ChainStore::new(
285 db.clone(),
286 db.clone(),
287 db.clone(),
288 chain_config,
289 genesis_header,
290 )?);
291 let state_manager = Arc::new(StateManager::new(chain_store.clone())?);
292 Ok((state_manager, ts, ts_next))
293 }
294
295 pub async fn prepare_state_validate(
296 chain: &NetworkChain,
297 snapshot: &Path,
298 ) -> anyhow::Result<(Arc<StateManager<ManyCar>>, FullTipset)> {
299 let (sm, _, ts) = prepare_state_compute(chain, snapshot).await?;
300 let fts = load_full_tipset(sm.chain_store(), ts.key())?;
301 Ok((sm, fts))
302 }
303
304 pub async fn state_compute(
305 state_manager: &Arc<StateManager<ManyCar>>,
306 ts: Tipset,
307 ts_next: &Tipset,
308 ) -> anyhow::Result<()> {
309 let epoch = ts.epoch();
310 let expected_state_root = *ts_next.parent_state();
311 let expected_receipt_root = *ts_next.parent_message_receipts();
312 let start = Instant::now();
313 let StateOutput {
314 state_root,
315 receipt_root,
316 ..
317 } = state_manager
318 .compute_tipset_state(ts, crate::state_manager::NO_CALLBACK, VMTrace::NotTraced)
319 .await?;
320 println!(
321 "epoch: {epoch}, state_root: {state_root}, receipt_root: {receipt_root}, took {}.",
322 humantime::format_duration(start.elapsed())
323 );
324 anyhow::ensure!(
325 state_root == expected_state_root,
326 "state root mismatch, state_root: {state_root}, expected_state_root: {expected_state_root}"
327 );
328 anyhow::ensure!(
329 receipt_root == expected_receipt_root,
330 "receipt root mismatch, receipt_root: {receipt_root}, expected_receipt_root: {expected_receipt_root}"
331 );
332 Ok(())
333 }
334
335 pub async fn list_state_snapshot_files() -> anyhow::Result<Vec<String>> {
336 let url = Url::parse(&format!("{DO_SPACE_ROOT}?format=json&prefix=state_"))?;
337 let mut json_str = String::new();
338 crate::utils::net::reader(url.as_str(), DownloadFileOption::NonResumable, None)
339 .await?
340 .read_to_string(&mut json_str)
341 .await?;
342 let obj: sonic_rs::Object = sonic_rs::from_str(&json_str)?;
343 let files = obj
344 .iter()
345 .filter_map(|(k, v)| {
346 if k == "Contents"
347 && let sonic_rs::ValueRef::Array(arr) = v.as_ref()
348 && let Some(first) = arr.first()
349 && let Some(file) = first.as_str()
350 && file.ends_with(".car.zst")
351 {
352 Some(file.to_string())
353 } else {
354 None
355 }
356 })
357 .collect();
358 Ok(files)
359 }
360
361 #[cfg(test)]
362 mod tests {
363 use super::*;
368 #[cfg(feature = "cargo-test")]
369 use crate::chain_sync::tipset_syncer::validate_tipset;
370
371 #[tokio::test(flavor = "multi_thread")]
372 async fn test_list_state_snapshot_files() {
373 let files = list_state_snapshot_files().await.unwrap();
374 println!("{files:?}");
375 assert!(files.len() > 1);
376 get_state_snapshot_file(&files[0]).await.unwrap();
377 }
378
379 include!(concat!(env!("OUT_DIR"), "/__state_compute_tests_gen.rs"));
380
381 #[allow(dead_code)]
382 async fn state_compute_test_run(chain: NetworkChain, epoch: i64) {
383 let snapshot = get_state_compute_snapshot(&chain, epoch).await.unwrap();
384 let (sm, ts, ts_next) = prepare_state_compute(&chain, &snapshot).await.unwrap();
385 state_compute(&sm, ts, &ts_next).await.unwrap();
386 }
387
388 #[cfg(feature = "cargo-test")]
389 #[tokio::test(flavor = "multi_thread")]
390 #[fickle::fickle]
391 async fn cargo_test_state_validate_mainnet_5688000() {
392 let chain = NetworkChain::Mainnet;
393 let snapshot = get_state_validate_snapshot(&chain, 5688000).await.unwrap();
394 let (sm, fts) = prepare_state_validate(&chain, &snapshot).await.unwrap();
395 validate_tipset(&sm, fts, None).await.unwrap();
396 }
397
398 #[cfg(feature = "cargo-test")]
400 #[tokio::test(flavor = "multi_thread")]
401 #[fickle::fickle]
402 async fn cargo_test_state_validate_calibnet_16802() {
403 let chain = NetworkChain::Calibnet;
404 let snapshot = get_state_validate_snapshot(&chain, 16802).await.unwrap();
405 let (sm, fts) = prepare_state_validate(&chain, &snapshot).await.unwrap();
406 validate_tipset(&sm, fts, None).await.unwrap();
407 }
408
409 #[cfg(feature = "cargo-test")]
411 #[tokio::test(flavor = "multi_thread")]
412 #[fickle::fickle]
413 async fn cargo_test_state_validate_calibnet_322356() {
414 let chain = NetworkChain::Calibnet;
415 let snapshot = get_state_validate_snapshot(&chain, 322356).await.unwrap();
416 let (sm, fts) = prepare_state_validate(&chain, &snapshot).await.unwrap();
417 validate_tipset(&sm, fts, None).await.unwrap();
418 }
419 }
420}
421
422#[cfg(test)]
423mod test {
424 use crate::shim::{address::Address, econ::TokenAmount, state_tree::ActorState};
425 use cid::Cid;
426
427 use super::*;
428
429 #[test]
430 fn is_valid_for_sending_test() {
431 let create_actor = |code: &Cid, sequence: u64, delegated_address: Option<Address>| {
432 ActorState::new(
433 code.to_owned(),
434 Cid::try_from("bafk2bzaceavfgpiw6whqigmskk74z4blm22nwjfnzxb4unlqz2e4wgcthulhu")
436 .unwrap(),
437 TokenAmount::default(),
438 sequence,
439 delegated_address,
440 )
441 };
442
443 let account_actor_cid =
445 Cid::try_from("bafk2bzaceavfgpiw6whqigmskk74z4blm22nwjfnzxb4unlqz2e4wg3c5ujpw")
446 .unwrap();
447 let ethaccount_actor_cid =
448 Cid::try_from("bafk2bzacebiyrhz32xwxi6xql67aaq5nrzeelzas472kuwjqmdmgwotpkj35e")
449 .unwrap();
450 let placeholder_actor_cid =
451 Cid::try_from("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro")
452 .unwrap();
453
454 let actor = create_actor(&account_actor_cid, 0, None);
456 assert!(is_valid_for_sending(NetworkVersion::V17, &actor));
457
458 let actor = create_actor(ðaccount_actor_cid, 0, None);
460 assert!(!is_valid_for_sending(NetworkVersion::V17, &actor));
461
462 assert!(is_valid_for_sending(NetworkVersion::V18, &actor));
464
465 let actor = create_actor(&placeholder_actor_cid, 0, None);
467 assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
468
469 let delegated_address = Address::new_delegated(
471 Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR.id().unwrap(),
472 &[0; 20],
473 )
474 .ok();
475 let actor = create_actor(&placeholder_actor_cid, 0, delegated_address);
476 assert!(is_valid_for_sending(NetworkVersion::V18, &actor));
477
478 let actor = create_actor(&placeholder_actor_cid, 1, delegated_address);
480 assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
481
482 let delegated_address =
484 Address::new_delegated(Address::CHAOS_ACTOR.id().unwrap(), &[0; 20]).ok();
485 let actor = create_actor(&placeholder_actor_cid, 0, delegated_address);
486 assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
487 }
488}
489
490pub mod structured {
492 use crate::{
493 rpc::state::{ActorTrace, ExecutionTrace, GasTrace, MessageTrace, ReturnTrace},
494 shim::kernel::ErrorNumber,
495 };
496 use std::collections::VecDeque;
497
498 use crate::shim::{
499 address::Address,
500 error::ExitCode,
501 gas::GasCharge,
502 kernel::SyscallError,
503 trace::{Call, CallReturn, ExecutionEvent},
504 };
505 use fvm_ipld_encoding::{RawBytes, ipld_block::IpldBlock};
506 use itertools::Either;
507
508 enum CallTreeReturn {
509 Return(CallReturn),
510 Abort(ExitCode),
511 Error(SyscallError),
512 }
513
514 #[derive(Debug, thiserror::Error)]
515 pub enum BuildExecutionTraceError {
516 #[error(
517 "every ExecutionEvent::Return | ExecutionEvent::CallError should be preceded by an ExecutionEvent::Call, but this one wasn't"
518 )]
519 UnexpectedReturn,
520 #[error(
521 "every ExecutionEvent::Call should have a corresponding ExecutionEvent::Return, but this one didn't"
522 )]
523 NoReturn,
524 #[error("unrecognised ExecutionEvent variant: {0:?}")]
525 UnrecognisedEvent(Box<dyn std::fmt::Debug + Send + Sync + 'static>),
526 }
527
528 pub fn parse_events(
557 events: Vec<ExecutionEvent>,
558 ) -> anyhow::Result<Option<ExecutionTrace>, BuildExecutionTraceError> {
559 let mut events = VecDeque::from(events);
560 let mut front_load_me = vec![];
561 let mut call_trees = vec![];
562
563 while let Some(event) = events.pop_front() {
565 match event {
566 ExecutionEvent::GasCharge(gc) => front_load_me.push(gc),
567 ExecutionEvent::Call(call) => call_trees.push(ExecutionTrace::parse(call, {
568 for gc in front_load_me.drain(..).rev() {
573 events.push_front(ExecutionEvent::GasCharge(gc))
574 }
575 &mut events
576 })?),
577 ExecutionEvent::CallReturn(_)
578 | ExecutionEvent::CallAbort(_)
579 | ExecutionEvent::CallError(_) => {
580 return Err(BuildExecutionTraceError::UnexpectedReturn);
581 }
582 ExecutionEvent::Log(_ignored) => {}
583 ExecutionEvent::InvokeActor(_cid) => {}
584 ExecutionEvent::Ipld { .. } => {}
585 ExecutionEvent::Unknown(u) => {
586 return Err(BuildExecutionTraceError::UnrecognisedEvent(Box::new(u)));
587 }
588 }
589 }
590
591 if !front_load_me.is_empty() {
592 tracing::warn!(
593 "vm tracing: ignoring {} trailing gas charges",
594 front_load_me.len()
595 );
596 }
597
598 match call_trees.len() {
599 0 => Ok(None),
600 1 => Ok(Some(call_trees.remove(0))),
601 many => {
602 tracing::warn!(
603 "vm tracing: ignoring {} call trees at the root level",
604 many - 1
605 );
606 Ok(Some(call_trees.remove(0)))
607 }
608 }
609 }
610
611 impl ExecutionTrace {
612 fn parse(
624 call: Call,
625 events: &mut VecDeque<ExecutionEvent>,
626 ) -> Result<ExecutionTrace, BuildExecutionTraceError> {
627 let mut gas_charges = vec![];
628 let mut subcalls = vec![];
629 let mut actor_trace = None;
630
631 while let Some(event) = events.pop_front() {
633 let found_return = match event {
634 ExecutionEvent::GasCharge(gc) => {
635 gas_charges.push(to_gas_trace(gc));
636 None
637 }
638 ExecutionEvent::Call(call) => {
639 subcalls.push(Self::parse(call, events)?);
640 None
641 }
642 ExecutionEvent::CallReturn(ret) => Some(CallTreeReturn::Return(ret)),
643 ExecutionEvent::CallAbort(ab) => Some(CallTreeReturn::Abort(ab)),
644 ExecutionEvent::CallError(e) => Some(CallTreeReturn::Error(e)),
645 ExecutionEvent::Log(_ignored) => None,
646 ExecutionEvent::InvokeActor(cid) => {
647 actor_trace = match cid {
648 Either::Left(_cid) => None,
649 Either::Right(actor) => Some(ActorTrace {
650 id: actor.id,
651 state: actor.state,
652 }),
653 };
654 None
655 }
656 ExecutionEvent::Ipld { .. } => None,
657 ExecutionEvent::Unknown(u) => {
661 return Err(BuildExecutionTraceError::UnrecognisedEvent(Box::new(u)));
662 }
663 };
664
665 if let Some(ret) = found_return {
667 return Ok(ExecutionTrace {
668 msg: to_message_trace(call),
669 msg_rct: to_return_trace(ret),
670 gas_charges,
671 subcalls,
672 invoked_actor: actor_trace,
673 });
674 }
675 }
676
677 Err(BuildExecutionTraceError::NoReturn)
678 }
679 }
680
681 fn to_message_trace(call: Call) -> MessageTrace {
682 let (bytes, codec) = to_bytes_codec(call.params);
683 MessageTrace {
684 from: Address::new_id(call.from),
685 to: call.to,
686 value: call.value,
687 method: call.method_num,
688 params: bytes,
689 params_codec: codec,
690 gas_limit: call.gas_limit,
691 read_only: call.read_only,
692 }
693 }
694
695 fn to_return_trace(ret: CallTreeReturn) -> ReturnTrace {
696 match ret {
697 CallTreeReturn::Return(return_code) => {
698 let exit_code = return_code.exit_code.unwrap_or(0.into());
699 let (bytes, codec) = to_bytes_codec(return_code.data);
700 ReturnTrace {
701 exit_code,
702 r#return: bytes,
703 return_codec: codec,
704 }
705 }
706 CallTreeReturn::Abort(exit_code) => ReturnTrace {
707 exit_code,
708 r#return: RawBytes::default(),
709 return_codec: 0,
710 },
711 CallTreeReturn::Error(syscall_error) => match syscall_error.number {
712 ErrorNumber::InsufficientFunds => ReturnTrace {
713 exit_code: ExitCode::from(6),
714 r#return: RawBytes::default(),
715 return_codec: 0,
716 },
717 _ => ReturnTrace {
718 exit_code: ExitCode::from(0),
719 r#return: RawBytes::default(),
720 return_codec: 0,
721 },
722 },
723 }
724 }
725
726 fn to_bytes_codec(data: Either<RawBytes, Option<IpldBlock>>) -> (RawBytes, u64) {
727 match data {
728 Either::Left(l) => (l, 0),
729 Either::Right(r) => match r {
730 Some(b) => (RawBytes::from(b.data), b.codec),
731 None => (RawBytes::default(), 0),
732 },
733 }
734 }
735
736 fn to_gas_trace(gc: GasCharge) -> GasTrace {
737 GasTrace {
738 name: gc.name().into(),
739 total_gas: gc.total().round_up(),
740 compute_gas: gc.compute_gas().round_up(),
741 storage_gas: gc.other_gas().round_up(),
742 time_taken: gc.elapsed().as_nanos(),
743 }
744 }
745}