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!("Error iterating over proving sectors, id {n} does not exist")
86 })?;
87 selected_sectors.set(sno);
88 }
89
90 let sectors = mas.load_sectors(store, Some(&selected_sectors))?;
91
92 let out = sectors
93 .into_iter()
94 .map(|s_info| ExtendedSectorInfo {
95 proof: s_info.seal_proof.into(),
96 sector_number: s_info.sector_number,
97 sector_key: s_info.sector_key_cid,
98 sealed_cid: s_info.sealed_cid,
99 })
100 .collect();
101
102 Ok(out)
103 }
104}
105
106pub fn is_valid_for_sending(network_version: NetworkVersion, actor: &ActorState) -> bool {
107 if network_version < NetworkVersion::V18 {
121 return is_account_actor(&actor.code);
122 }
123
124 if is_account_actor(&actor.code) || is_ethaccount_actor(&actor.code) {
126 return true;
127 }
128
129 if !is_placeholder_actor(&actor.code)
132 || actor.sequence != 0
133 || actor.delegated_address.is_none()
134 {
135 return false;
136 }
137
138 if let Payload::Delegated(address) = actor
141 .delegated_address
142 .as_ref()
143 .expect("unfallible")
144 .payload()
145 {
146 address.namespace()
147 == Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR
148 .id()
149 .expect("EAM actor address is constructed with new_id, so id() is infallible")
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 #[cfg(test)]
179 use crate::utils::encoding::hex;
180 use crate::{
181 blocks::{FullTipset, Tipset},
182 chain::store::ChainStore,
183 chain_sync::load_full_tipset,
184 db::{
185 MemoryDB,
186 car::{AnyCar, ManyCar},
187 },
188 genesis::read_genesis_header,
189 interpreter::VMTrace,
190 networks::{ChainConfig, NetworkChain},
191 prelude::*,
192 state_manager::{ExecutedTipset, StateManager},
193 utils::net::{DownloadFileOption, download_file_with_cache},
194 };
195 use directories::ProjectDirs;
196 use sonic_rs::JsonValueTrait;
197 use std::{
198 path::{Path, PathBuf},
199 sync::LazyLock,
200 time::{Duration, Instant},
201 };
202 use tokio::io::AsyncReadExt;
203 use url::Url;
204
205 const DO_SPACE_ROOT: &str = "https://forest-snapshots.fra1.cdn.digitaloceanspaces.com/";
206
207 #[allow(dead_code)]
208 pub async fn get_state_compute_snapshot(
209 chain: &NetworkChain,
210 epoch: ChainEpoch,
211 ) -> anyhow::Result<PathBuf> {
212 get_state_snapshot(chain, "state_compute", epoch).await
213 }
214
215 #[allow(dead_code)]
216 async fn get_state_validate_snapshot(
217 chain: &NetworkChain,
218 epoch: ChainEpoch,
219 ) -> anyhow::Result<PathBuf> {
220 get_state_snapshot(chain, "state_validate", epoch).await
221 }
222
223 #[allow(dead_code)]
224 pub async fn get_state_snapshot(
225 chain: &NetworkChain,
226 bucket: &str,
227 epoch: ChainEpoch,
228 ) -> anyhow::Result<PathBuf> {
229 let file = format!("{bucket}/{chain}_{epoch}.forest.car.zst");
230 get_state_snapshot_file(&file).await
231 }
232
233 pub async fn get_state_snapshot_file(file: &str) -> anyhow::Result<PathBuf> {
234 static SNAPSHOT_CACHE_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
235 let project_dir = ProjectDirs::from("com", "ChainSafe", "Forest");
236 project_dir
237 .map(|d| d.cache_dir().to_path_buf())
238 .unwrap_or_else(std::env::temp_dir)
239 .join("state_compute_snapshots")
240 });
241
242 let url = Url::parse(&format!("{DO_SPACE_ROOT}{file}"))?;
243 let path = crate::utils::retry(
244 crate::utils::RetryArgs {
245 timeout: Some(Duration::from_secs(30)),
246 max_retries: Some(5),
247 delay: Some(Duration::from_secs(1)),
248 },
249 || {
250 download_file_with_cache(
251 &url,
252 &SNAPSHOT_CACHE_DIR,
253 DownloadFileOption::NonResumable,
254 )
255 },
256 )
257 .await?
258 .path;
259 #[cfg(test)]
260 {
261 println!(
263 "snapshot: {file}, sha256sum: {}",
264 hex::encode(crate::utils::hash::digest_file::<sha2::Sha256>(&path)?)
265 );
266 }
267 Ok(path)
268 }
269
270 pub async fn prepare_state_compute(
271 chain: &NetworkChain,
272 snapshot: &Path,
273 ) -> anyhow::Result<(StateManager, Tipset, Tipset)> {
274 let snap_car = AnyCar::try_from(snapshot)?;
275 let ts_next = snap_car.heaviest_tipset()?;
276 let db = Arc::new(ManyCar::new(MemoryDB::default()).with_read_only(snap_car)?);
277 let ts = Tipset::load_required(&db, ts_next.parents())?;
278 let chain_config = Arc::new(ChainConfig::from_chain(chain));
279 let genesis_header =
280 read_genesis_header(None, chain_config.genesis_bytes(&db).await?.as_deref(), &db)
281 .await?;
282 let chain_store = ChainStore::new(db, chain_config, genesis_header)?;
283 let state_manager = StateManager::new(chain_store)?;
284 Ok((state_manager, ts, ts_next))
285 }
286
287 pub async fn prepare_state_validate(
288 chain: &NetworkChain,
289 snapshot: &Path,
290 ) -> anyhow::Result<(StateManager, FullTipset)> {
291 let (sm, _, ts) = prepare_state_compute(chain, snapshot).await?;
292 let fts = load_full_tipset(sm.chain_store(), ts.key())?;
293 Ok((sm, fts))
294 }
295
296 pub async fn state_compute(
297 state_manager: &StateManager,
298 ts: Tipset,
299 ts_next: &Tipset,
300 ) -> anyhow::Result<()> {
301 let epoch = ts.epoch();
302 let expected_state_root = *ts_next.parent_state();
303 let expected_receipt_root = *ts_next.parent_message_receipts();
304 let start = Instant::now();
305 let ExecutedTipset {
306 state_root,
307 receipt_root,
308 ..
309 } = state_manager
310 .compute_tipset_state(ts, crate::state_manager::NO_CALLBACK, VMTrace::NotTraced)
311 .await?;
312 tracing::info!(
313 "epoch: {epoch}, state_root: {state_root}, receipt_root: {receipt_root}, took {}.",
314 humantime::format_duration(start.elapsed())
315 );
316 anyhow::ensure!(
317 state_root == expected_state_root,
318 "state root mismatch, state_root: {state_root}, expected_state_root: {expected_state_root}"
319 );
320 anyhow::ensure!(
321 receipt_root == expected_receipt_root,
322 "receipt root mismatch, receipt_root: {receipt_root}, expected_receipt_root: {expected_receipt_root}"
323 );
324 Ok(())
325 }
326
327 pub async fn list_state_snapshot_files() -> anyhow::Result<Vec<String>> {
328 let url = Url::parse(&format!("{DO_SPACE_ROOT}?format=json&prefix=state_"))?;
329 let mut json_str = String::new();
330 crate::utils::net::reader(url.as_str(), DownloadFileOption::NonResumable, None)
331 .await?
332 .read_to_string(&mut json_str)
333 .await?;
334 let obj: sonic_rs::Object = sonic_rs::from_str(&json_str)?;
335 let files = obj
336 .iter()
337 .filter_map(|(k, v)| {
338 if k == "Contents"
339 && let sonic_rs::ValueRef::Array(arr) = v.as_ref()
340 && let Some(first) = arr.first()
341 && let Some(file) = first.as_str()
342 && file.ends_with(".car.zst")
343 {
344 Some(file.to_string())
345 } else {
346 None
347 }
348 })
349 .collect();
350 Ok(files)
351 }
352
353 #[cfg(test)]
354 mod tests {
355 use super::*;
360 use crate::chain_sync::tipset_syncer::validate_tipset;
361 use crate::rpc::eth::types::CallSource;
362
363 #[tokio::test(flavor = "multi_thread")]
364 async fn test_list_state_snapshot_files() {
365 let files = list_state_snapshot_files().await.unwrap();
366 println!("{files:?}");
367 assert!(files.len() > 1);
368 get_state_snapshot_file(&files[0]).await.unwrap();
369 }
370
371 include!(concat!(env!("OUT_DIR"), "/__state_compute_tests_gen.rs"));
372
373 #[allow(dead_code)]
374 async fn state_compute_test_run(chain: NetworkChain, epoch: ChainEpoch) {
375 let snapshot = get_state_compute_snapshot(&chain, epoch).await.unwrap();
376 let (sm, ts, ts_next) = prepare_state_compute(&chain, &snapshot).await.unwrap();
377 state_compute(&sm, ts, &ts_next).await.unwrap();
378 }
379
380 #[tokio::test(flavor = "multi_thread")]
381 #[fickle::fickle]
382 async fn cargo_test_state_validate_mainnet_5688000() {
383 let chain = NetworkChain::Mainnet;
384 let snapshot = get_state_validate_snapshot(&chain, 5688000).await.unwrap();
385 let (sm, fts) = prepare_state_validate(&chain, &snapshot).await.unwrap();
386 validate_tipset(&sm, fts, None).await.unwrap();
387 }
388
389 #[tokio::test(flavor = "multi_thread")]
390 #[fickle::fickle]
391 async fn cargo_test_state_replay_uses_trace_cache_calibnet_3408952() {
392 let chain = NetworkChain::Calibnet;
393 let snapshot = get_state_compute_snapshot(&chain, 3408952).await.unwrap();
394 let (sm, ts, _) = prepare_state_compute(&chain, &snapshot).await.unwrap();
395 let msg_cid = sm
396 .chain_store()
397 .messages_for_tipset(&ts)
398 .unwrap()
399 .first()
400 .expect("test tipset must contain messages")
401 .cid();
402
403 let replayed = sm
404 .replay(ts.clone(), msg_cid, CallSource::External)
405 .await
406 .unwrap();
407 assert_eq!(replayed.msg_cid, msg_cid);
408
409 let (_, trace) = sm
410 .trace_cache
411 .get(ts.key())
412 .expect("replay must populate the tipset trace cache");
413 let from_cache = trace
414 .iter()
415 .find(|r| r.msg_cid == msg_cid)
416 .expect("cached trace must contain the replayed message");
417 assert_eq!(**from_cache, replayed);
418
419 let misses = sm.trace_cache.misses();
421 let replayed_again = sm
422 .replay(ts.clone(), msg_cid, CallSource::External)
423 .await
424 .unwrap();
425 assert_eq!(replayed_again, replayed);
426 assert_eq!(sm.trace_cache.misses(), misses);
427 }
428
429 #[tokio::test(flavor = "multi_thread")]
431 #[fickle::fickle]
432 async fn cargo_test_state_validate_calibnet_16802() {
433 let chain = NetworkChain::Calibnet;
434 let snapshot = get_state_validate_snapshot(&chain, 16802).await.unwrap();
435 let (sm, fts) = prepare_state_validate(&chain, &snapshot).await.unwrap();
436 validate_tipset(&sm, fts, None).await.unwrap();
437 }
438
439 #[tokio::test(flavor = "multi_thread")]
441 #[fickle::fickle]
442 async fn cargo_test_state_validate_calibnet_322356() {
443 let chain = NetworkChain::Calibnet;
444 let snapshot = get_state_validate_snapshot(&chain, 322356).await.unwrap();
445 let (sm, fts) = prepare_state_validate(&chain, &snapshot).await.unwrap();
446 validate_tipset(&sm, fts, None).await.unwrap();
447 }
448 }
449}
450
451#[cfg(test)]
452mod test {
453 use crate::shim::{address::Address, econ::TokenAmount, state_tree::ActorState};
454 use cid::Cid;
455
456 use super::*;
457
458 #[test]
459 fn is_valid_for_sending_test() {
460 let create_actor = |code: &Cid, sequence: u64, delegated_address: Option<Address>| {
461 ActorState::new(
462 code.to_owned(),
463 Cid::try_from("bafk2bzaceavfgpiw6whqigmskk74z4blm22nwjfnzxb4unlqz2e4wgcthulhu")
465 .unwrap(),
466 TokenAmount::default(),
467 sequence,
468 delegated_address,
469 )
470 };
471
472 let account_actor_cid =
474 Cid::try_from("bafk2bzaceavfgpiw6whqigmskk74z4blm22nwjfnzxb4unlqz2e4wg3c5ujpw")
475 .unwrap();
476 let ethaccount_actor_cid =
477 Cid::try_from("bafk2bzacebiyrhz32xwxi6xql67aaq5nrzeelzas472kuwjqmdmgwotpkj35e")
478 .unwrap();
479 let placeholder_actor_cid =
480 Cid::try_from("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro")
481 .unwrap();
482
483 let actor = create_actor(&account_actor_cid, 0, None);
485 assert!(is_valid_for_sending(NetworkVersion::V17, &actor));
486
487 let actor = create_actor(ðaccount_actor_cid, 0, None);
489 assert!(!is_valid_for_sending(NetworkVersion::V17, &actor));
490
491 assert!(is_valid_for_sending(NetworkVersion::V18, &actor));
493
494 let actor = create_actor(&placeholder_actor_cid, 0, None);
496 assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
497
498 let delegated_address = Address::new_delegated(
500 Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR.id().unwrap(),
501 &[0; 20],
502 )
503 .ok();
504 let actor = create_actor(&placeholder_actor_cid, 0, delegated_address);
505 assert!(is_valid_for_sending(NetworkVersion::V18, &actor));
506
507 let actor = create_actor(&placeholder_actor_cid, 1, delegated_address);
509 assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
510
511 let delegated_address =
513 Address::new_delegated(Address::CHAOS_ACTOR.id().unwrap(), &[0; 20]).ok();
514 let actor = create_actor(&placeholder_actor_cid, 0, delegated_address);
515 assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
516 }
517}
518
519pub mod structured {
521 use crate::{
522 rpc::state::{ActorTrace, ExecutionTrace, GasTrace, MessageTrace, ReturnTrace, TraceIpld},
523 shim::kernel::ErrorNumber,
524 };
525 use std::collections::VecDeque;
526
527 use crate::shim::{
528 address::Address,
529 error::ExitCode,
530 gas::GasCharge,
531 kernel::SyscallError,
532 trace::{Call, CallReturn, ExecutionEvent},
533 };
534 use fvm_ipld_encoding::{RawBytes, ipld_block::IpldBlock};
535 use itertools::Either;
536
537 enum CallTreeReturn {
538 Return(CallReturn),
539 Abort(ExitCode),
540 Error(SyscallError),
541 }
542
543 #[derive(Debug, thiserror::Error)]
544 pub enum BuildExecutionTraceError {
545 #[error(
546 "every ExecutionEvent::Return | ExecutionEvent::CallError should be preceded by an ExecutionEvent::Call, but this one wasn't"
547 )]
548 UnexpectedReturn,
549 #[error(
550 "every ExecutionEvent::Call should have a corresponding ExecutionEvent::Return, but this one didn't"
551 )]
552 NoReturn,
553 #[error("unrecognised ExecutionEvent variant: {0:?}")]
554 UnrecognisedEvent(Box<dyn std::fmt::Debug + Send + Sync + 'static>),
555 }
556
557 pub fn parse_events(
586 events: Vec<ExecutionEvent>,
587 ) -> anyhow::Result<Option<ExecutionTrace>, BuildExecutionTraceError> {
588 let mut events = VecDeque::from(events);
589 let mut front_load_me = vec![];
590 let mut call_trees = vec![];
591
592 while let Some(event) = events.pop_front() {
594 match event {
595 ExecutionEvent::GasCharge(gc) => front_load_me.push(gc),
596 ExecutionEvent::Call(call) => call_trees.push(ExecutionTrace::parse(call, {
597 for gc in front_load_me.drain(..).rev() {
602 events.push_front(ExecutionEvent::GasCharge(gc))
603 }
604 &mut events
605 })?),
606 ExecutionEvent::CallReturn(_)
607 | ExecutionEvent::CallAbort(_)
608 | ExecutionEvent::CallError(_) => {
609 return Err(BuildExecutionTraceError::UnexpectedReturn);
610 }
611 ExecutionEvent::Log(_ignored) => {}
612 ExecutionEvent::InvokeActor(_cid) => {}
613 ExecutionEvent::Ipld { .. } => {}
614 ExecutionEvent::Unknown(u) => {
615 return Err(BuildExecutionTraceError::UnrecognisedEvent(Box::new(u)));
616 }
617 }
618 }
619
620 if !front_load_me.is_empty() {
621 tracing::warn!(
622 "vm tracing: ignoring {} trailing gas charges",
623 front_load_me.len()
624 );
625 }
626
627 match call_trees.len() {
628 0 => Ok(None),
629 1 => Ok(Some(call_trees.remove(0))),
630 many => {
631 tracing::warn!(
632 "vm tracing: ignoring {} call trees at the root level",
633 many - 1
634 );
635 Ok(Some(call_trees.remove(0)))
636 }
637 }
638 }
639
640 impl ExecutionTrace {
641 fn parse(
653 call: Call,
654 events: &mut VecDeque<ExecutionEvent>,
655 ) -> Result<ExecutionTrace, BuildExecutionTraceError> {
656 let mut gas_charges = vec![];
657 let mut subcalls = vec![];
658 let mut actor_trace = None;
659 let mut logs = vec![];
660 let mut ipld_ops = vec![];
661
662 while let Some(event) = events.pop_front() {
664 let found_return = match event {
665 ExecutionEvent::GasCharge(gc) => {
666 gas_charges.push(to_gas_trace(gc));
667 None
668 }
669 ExecutionEvent::Call(call) => {
670 subcalls.push(Self::parse(call, events)?);
671 None
672 }
673 ExecutionEvent::CallReturn(ret) => Some(CallTreeReturn::Return(ret)),
674 ExecutionEvent::CallAbort(ab) => Some(CallTreeReturn::Abort(ab)),
675 ExecutionEvent::CallError(e) => Some(CallTreeReturn::Error(e)),
676 ExecutionEvent::Log(log) => {
677 logs.push(log);
678 None
679 }
680 ExecutionEvent::InvokeActor(cid) => {
681 actor_trace = match cid {
682 Either::Left(_cid) => None,
683 Either::Right(actor) => Some(ActorTrace {
684 id: actor.id,
685 state: actor.state,
686 }),
687 };
688 None
689 }
690 ExecutionEvent::Ipld { op, cid, size } => {
691 ipld_ops.push(TraceIpld {
692 op: op.into(),
693 cid,
694 size: size as u64,
695 });
696 None
697 }
698 ExecutionEvent::Unknown(u) => {
702 return Err(BuildExecutionTraceError::UnrecognisedEvent(Box::new(u)));
703 }
704 };
705
706 if let Some(ret) = found_return {
708 return Ok(ExecutionTrace {
709 msg: to_message_trace(call),
710 msg_rct: to_return_trace(ret),
711 gas_charges,
712 subcalls,
713 invoked_actor: actor_trace,
714 logs,
715 ipld_ops,
716 });
717 }
718 }
719
720 Err(BuildExecutionTraceError::NoReturn)
721 }
722 }
723
724 fn to_message_trace(call: Call) -> MessageTrace {
725 let (bytes, codec) = to_bytes_codec(call.params);
726 MessageTrace {
727 from: Address::new_id(call.from),
728 to: call.to,
729 value: call.value,
730 method: call.method_num,
731 params: bytes,
732 params_codec: codec,
733 gas_limit: call.gas_limit,
734 read_only: call.read_only,
735 }
736 }
737
738 fn to_return_trace(ret: CallTreeReturn) -> ReturnTrace {
739 match ret {
740 CallTreeReturn::Return(return_code) => {
741 let exit_code = return_code.exit_code.unwrap_or_else(|| 0.into());
742 let (bytes, codec) = to_bytes_codec(return_code.data);
743 ReturnTrace {
744 exit_code,
745 r#return: bytes,
746 return_codec: codec,
747 }
748 }
749 CallTreeReturn::Abort(exit_code) => ReturnTrace {
750 exit_code,
751 r#return: RawBytes::default(),
752 return_codec: 0,
753 },
754 CallTreeReturn::Error(syscall_error) => match syscall_error.number {
755 ErrorNumber::InsufficientFunds => ReturnTrace {
756 exit_code: ExitCode::from(6),
757 r#return: RawBytes::default(),
758 return_codec: 0,
759 },
760 _ => ReturnTrace {
761 exit_code: ExitCode::from(0),
762 r#return: RawBytes::default(),
763 return_codec: 0,
764 },
765 },
766 }
767 }
768
769 fn to_bytes_codec(data: Either<RawBytes, Option<IpldBlock>>) -> (RawBytes, u64) {
770 match data {
771 Either::Left(l) => (l, 0),
772 Either::Right(r) => match r {
773 Some(b) => (RawBytes::from(b.data), b.codec),
774 None => (RawBytes::default(), 0),
775 },
776 }
777 }
778
779 fn to_gas_trace(gc: GasCharge) -> GasTrace {
780 GasTrace {
781 name: gc.name().into(),
782 total_gas: gc.total().round_up(),
783 compute_gas: gc.compute_gas().round_up(),
784 storage_gas: gc.other_gas().round_up(),
785 time_taken: gc.elapsed().as_nanos(),
786 }
787 }
788}