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 #[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 #[cfg(feature = "cargo-test")]
361 use crate::chain_sync::tipset_syncer::validate_tipset;
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 #[cfg(feature = "cargo-test")]
381 #[tokio::test(flavor = "multi_thread")]
382 #[fickle::fickle]
383 async fn cargo_test_state_validate_mainnet_5688000() {
384 let chain = NetworkChain::Mainnet;
385 let snapshot = get_state_validate_snapshot(&chain, 5688000).await.unwrap();
386 let (sm, fts) = prepare_state_validate(&chain, &snapshot).await.unwrap();
387 validate_tipset(&sm, fts, None).await.unwrap();
388 }
389
390 #[cfg(feature = "cargo-test")]
391 #[tokio::test(flavor = "multi_thread")]
392 #[fickle::fickle]
393 async fn cargo_test_state_replay_uses_trace_cache_calibnet_3408952() {
394 let chain = NetworkChain::Calibnet;
395 let snapshot = get_state_compute_snapshot(&chain, 3408952).await.unwrap();
396 let (sm, ts, _) = prepare_state_compute(&chain, &snapshot).await.unwrap();
397 let msg_cid = sm
398 .chain_store()
399 .messages_for_tipset(&ts)
400 .unwrap()
401 .first()
402 .expect("test tipset must contain messages")
403 .cid();
404
405 let replayed = sm.replay(ts.clone(), msg_cid).await.unwrap();
406 assert_eq!(replayed.msg_cid, msg_cid);
407
408 let (_, trace) = sm
409 .trace_cache
410 .get(ts.key())
411 .expect("replay must populate the tipset trace cache");
412 let from_cache = trace
413 .iter()
414 .find(|r| r.msg_cid == msg_cid)
415 .expect("cached trace must contain the replayed message");
416 assert_eq!(**from_cache, replayed);
417
418 let misses = sm.trace_cache.misses();
420 let replayed_again = sm.replay(ts.clone(), msg_cid).await.unwrap();
421 assert_eq!(replayed_again, replayed);
422 assert_eq!(sm.trace_cache.misses(), misses);
423 }
424
425 #[cfg(feature = "cargo-test")]
427 #[tokio::test(flavor = "multi_thread")]
428 #[fickle::fickle]
429 async fn cargo_test_state_validate_calibnet_16802() {
430 let chain = NetworkChain::Calibnet;
431 let snapshot = get_state_validate_snapshot(&chain, 16802).await.unwrap();
432 let (sm, fts) = prepare_state_validate(&chain, &snapshot).await.unwrap();
433 validate_tipset(&sm, fts, None).await.unwrap();
434 }
435
436 #[cfg(feature = "cargo-test")]
438 #[tokio::test(flavor = "multi_thread")]
439 #[fickle::fickle]
440 async fn cargo_test_state_validate_calibnet_322356() {
441 let chain = NetworkChain::Calibnet;
442 let snapshot = get_state_validate_snapshot(&chain, 322356).await.unwrap();
443 let (sm, fts) = prepare_state_validate(&chain, &snapshot).await.unwrap();
444 validate_tipset(&sm, fts, None).await.unwrap();
445 }
446 }
447}
448
449#[cfg(test)]
450mod test {
451 use crate::shim::{address::Address, econ::TokenAmount, state_tree::ActorState};
452 use cid::Cid;
453
454 use super::*;
455
456 #[test]
457 fn is_valid_for_sending_test() {
458 let create_actor = |code: &Cid, sequence: u64, delegated_address: Option<Address>| {
459 ActorState::new(
460 code.to_owned(),
461 Cid::try_from("bafk2bzaceavfgpiw6whqigmskk74z4blm22nwjfnzxb4unlqz2e4wgcthulhu")
463 .unwrap(),
464 TokenAmount::default(),
465 sequence,
466 delegated_address,
467 )
468 };
469
470 let account_actor_cid =
472 Cid::try_from("bafk2bzaceavfgpiw6whqigmskk74z4blm22nwjfnzxb4unlqz2e4wg3c5ujpw")
473 .unwrap();
474 let ethaccount_actor_cid =
475 Cid::try_from("bafk2bzacebiyrhz32xwxi6xql67aaq5nrzeelzas472kuwjqmdmgwotpkj35e")
476 .unwrap();
477 let placeholder_actor_cid =
478 Cid::try_from("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro")
479 .unwrap();
480
481 let actor = create_actor(&account_actor_cid, 0, None);
483 assert!(is_valid_for_sending(NetworkVersion::V17, &actor));
484
485 let actor = create_actor(ðaccount_actor_cid, 0, None);
487 assert!(!is_valid_for_sending(NetworkVersion::V17, &actor));
488
489 assert!(is_valid_for_sending(NetworkVersion::V18, &actor));
491
492 let actor = create_actor(&placeholder_actor_cid, 0, None);
494 assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
495
496 let delegated_address = Address::new_delegated(
498 Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR.id().unwrap(),
499 &[0; 20],
500 )
501 .ok();
502 let actor = create_actor(&placeholder_actor_cid, 0, delegated_address);
503 assert!(is_valid_for_sending(NetworkVersion::V18, &actor));
504
505 let actor = create_actor(&placeholder_actor_cid, 1, delegated_address);
507 assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
508
509 let delegated_address =
511 Address::new_delegated(Address::CHAOS_ACTOR.id().unwrap(), &[0; 20]).ok();
512 let actor = create_actor(&placeholder_actor_cid, 0, delegated_address);
513 assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
514 }
515}
516
517pub mod structured {
519 use crate::{
520 rpc::state::{ActorTrace, ExecutionTrace, GasTrace, MessageTrace, ReturnTrace, TraceIpld},
521 shim::kernel::ErrorNumber,
522 };
523 use std::collections::VecDeque;
524
525 use crate::shim::{
526 address::Address,
527 error::ExitCode,
528 gas::GasCharge,
529 kernel::SyscallError,
530 trace::{Call, CallReturn, ExecutionEvent},
531 };
532 use fvm_ipld_encoding::{RawBytes, ipld_block::IpldBlock};
533 use itertools::Either;
534
535 enum CallTreeReturn {
536 Return(CallReturn),
537 Abort(ExitCode),
538 Error(SyscallError),
539 }
540
541 #[derive(Debug, thiserror::Error)]
542 pub enum BuildExecutionTraceError {
543 #[error(
544 "every ExecutionEvent::Return | ExecutionEvent::CallError should be preceded by an ExecutionEvent::Call, but this one wasn't"
545 )]
546 UnexpectedReturn,
547 #[error(
548 "every ExecutionEvent::Call should have a corresponding ExecutionEvent::Return, but this one didn't"
549 )]
550 NoReturn,
551 #[error("unrecognised ExecutionEvent variant: {0:?}")]
552 UnrecognisedEvent(Box<dyn std::fmt::Debug + Send + Sync + 'static>),
553 }
554
555 pub fn parse_events(
584 events: Vec<ExecutionEvent>,
585 ) -> anyhow::Result<Option<ExecutionTrace>, BuildExecutionTraceError> {
586 let mut events = VecDeque::from(events);
587 let mut front_load_me = vec![];
588 let mut call_trees = vec![];
589
590 while let Some(event) = events.pop_front() {
592 match event {
593 ExecutionEvent::GasCharge(gc) => front_load_me.push(gc),
594 ExecutionEvent::Call(call) => call_trees.push(ExecutionTrace::parse(call, {
595 for gc in front_load_me.drain(..).rev() {
600 events.push_front(ExecutionEvent::GasCharge(gc))
601 }
602 &mut events
603 })?),
604 ExecutionEvent::CallReturn(_)
605 | ExecutionEvent::CallAbort(_)
606 | ExecutionEvent::CallError(_) => {
607 return Err(BuildExecutionTraceError::UnexpectedReturn);
608 }
609 ExecutionEvent::Log(_ignored) => {}
610 ExecutionEvent::InvokeActor(_cid) => {}
611 ExecutionEvent::Ipld { .. } => {}
612 ExecutionEvent::Unknown(u) => {
613 return Err(BuildExecutionTraceError::UnrecognisedEvent(Box::new(u)));
614 }
615 }
616 }
617
618 if !front_load_me.is_empty() {
619 tracing::warn!(
620 "vm tracing: ignoring {} trailing gas charges",
621 front_load_me.len()
622 );
623 }
624
625 match call_trees.len() {
626 0 => Ok(None),
627 1 => Ok(Some(call_trees.remove(0))),
628 many => {
629 tracing::warn!(
630 "vm tracing: ignoring {} call trees at the root level",
631 many - 1
632 );
633 Ok(Some(call_trees.remove(0)))
634 }
635 }
636 }
637
638 impl ExecutionTrace {
639 fn parse(
651 call: Call,
652 events: &mut VecDeque<ExecutionEvent>,
653 ) -> Result<ExecutionTrace, BuildExecutionTraceError> {
654 let mut gas_charges = vec![];
655 let mut subcalls = vec![];
656 let mut actor_trace = None;
657 let mut logs = vec![];
658 let mut ipld_ops = vec![];
659
660 while let Some(event) = events.pop_front() {
662 let found_return = match event {
663 ExecutionEvent::GasCharge(gc) => {
664 gas_charges.push(to_gas_trace(gc));
665 None
666 }
667 ExecutionEvent::Call(call) => {
668 subcalls.push(Self::parse(call, events)?);
669 None
670 }
671 ExecutionEvent::CallReturn(ret) => Some(CallTreeReturn::Return(ret)),
672 ExecutionEvent::CallAbort(ab) => Some(CallTreeReturn::Abort(ab)),
673 ExecutionEvent::CallError(e) => Some(CallTreeReturn::Error(e)),
674 ExecutionEvent::Log(log) => {
675 logs.push(log);
676 None
677 }
678 ExecutionEvent::InvokeActor(cid) => {
679 actor_trace = match cid {
680 Either::Left(_cid) => None,
681 Either::Right(actor) => Some(ActorTrace {
682 id: actor.id,
683 state: actor.state,
684 }),
685 };
686 None
687 }
688 ExecutionEvent::Ipld { op, cid, size } => {
689 ipld_ops.push(TraceIpld {
690 op: op.into(),
691 cid,
692 size: size as u64,
693 });
694 None
695 }
696 ExecutionEvent::Unknown(u) => {
700 return Err(BuildExecutionTraceError::UnrecognisedEvent(Box::new(u)));
701 }
702 };
703
704 if let Some(ret) = found_return {
706 return Ok(ExecutionTrace {
707 msg: to_message_trace(call),
708 msg_rct: to_return_trace(ret),
709 gas_charges,
710 subcalls,
711 invoked_actor: actor_trace,
712 logs,
713 ipld_ops,
714 });
715 }
716 }
717
718 Err(BuildExecutionTraceError::NoReturn)
719 }
720 }
721
722 fn to_message_trace(call: Call) -> MessageTrace {
723 let (bytes, codec) = to_bytes_codec(call.params);
724 MessageTrace {
725 from: Address::new_id(call.from),
726 to: call.to,
727 value: call.value,
728 method: call.method_num,
729 params: bytes,
730 params_codec: codec,
731 gas_limit: call.gas_limit,
732 read_only: call.read_only,
733 }
734 }
735
736 fn to_return_trace(ret: CallTreeReturn) -> ReturnTrace {
737 match ret {
738 CallTreeReturn::Return(return_code) => {
739 let exit_code = return_code.exit_code.unwrap_or(0.into());
740 let (bytes, codec) = to_bytes_codec(return_code.data);
741 ReturnTrace {
742 exit_code,
743 r#return: bytes,
744 return_codec: codec,
745 }
746 }
747 CallTreeReturn::Abort(exit_code) => ReturnTrace {
748 exit_code,
749 r#return: RawBytes::default(),
750 return_codec: 0,
751 },
752 CallTreeReturn::Error(syscall_error) => match syscall_error.number {
753 ErrorNumber::InsufficientFunds => ReturnTrace {
754 exit_code: ExitCode::from(6),
755 r#return: RawBytes::default(),
756 return_codec: 0,
757 },
758 _ => ReturnTrace {
759 exit_code: ExitCode::from(0),
760 r#return: RawBytes::default(),
761 return_codec: 0,
762 },
763 },
764 }
765 }
766
767 fn to_bytes_codec(data: Either<RawBytes, Option<IpldBlock>>) -> (RawBytes, u64) {
768 match data {
769 Either::Left(l) => (l, 0),
770 Either::Right(r) => match r {
771 Some(b) => (RawBytes::from(b.data), b.codec),
772 None => (RawBytes::default(), 0),
773 },
774 }
775 }
776
777 fn to_gas_trace(gc: GasCharge) -> GasTrace {
778 GasTrace {
779 name: gc.name().into(),
780 total_gas: gc.total().round_up(),
781 compute_gas: gc.compute_gas().round_up(),
782 storage_gas: gc.other_gas().round_up(),
783 time_taken: gc.elapsed().as_nanos(),
784 }
785 }
786}