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