1use crate::ExecutionOutcome;
4use alloc::{borrow::Cow, collections::BTreeMap, vec::Vec};
5use alloy_consensus::{transaction::Recovered, BlockHeader, TxReceipt};
6use alloy_eips::{eip1898::ForkBlock, eip2718::Encodable2718, BlockNumHash};
7use alloy_primitives::{Address, BlockHash, BlockNumber, Log, TxHash};
8use core::{fmt, ops::RangeInclusive};
9use reth_primitives_traits::{
10 transaction::signed::SignedTransaction, Block, BlockBody, IndexedTx, NodePrimitives,
11 RecoveredBlock, SealedHeader,
12};
13use reth_trie_common::LazyTrieData;
14
15#[derive(Clone, Debug, PartialEq, Eq)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27pub struct Chain<N: NodePrimitives = reth_ethereum_primitives::EthPrimitives> {
28 blocks: BTreeMap<BlockNumber, RecoveredBlock<N::Block>>,
30 execution_outcome: ExecutionOutcome<N::Receipt>,
37 trie_data: BTreeMap<BlockNumber, LazyTrieData>,
41}
42
43type ChainTxReceiptMeta<'a, N> = (
44 &'a RecoveredBlock<<N as NodePrimitives>::Block>,
45 IndexedTx<'a, <N as NodePrimitives>::Block>,
46 &'a <N as NodePrimitives>::Receipt,
47 &'a [<N as NodePrimitives>::Receipt],
48);
49
50impl<N: NodePrimitives> Default for Chain<N> {
51 fn default() -> Self {
52 Self {
53 blocks: Default::default(),
54 execution_outcome: Default::default(),
55 trie_data: Default::default(),
56 }
57 }
58}
59
60impl<N: NodePrimitives> Chain<N> {
61 pub fn new(
67 blocks: impl IntoIterator<Item = RecoveredBlock<N::Block>>,
68 execution_outcome: ExecutionOutcome<N::Receipt>,
69 trie_data: BTreeMap<BlockNumber, LazyTrieData>,
70 ) -> Self {
71 let blocks =
72 blocks.into_iter().map(|b| (b.header().number(), b)).collect::<BTreeMap<_, _>>();
73 debug_assert!(!blocks.is_empty(), "Chain should have at least one block");
74
75 Self { blocks, execution_outcome, trie_data }
76 }
77
78 pub fn from_block(
80 block: RecoveredBlock<N::Block>,
81 execution_outcome: ExecutionOutcome<N::Receipt>,
82 trie_data: LazyTrieData,
83 ) -> Self {
84 let block_number = block.header().number();
85 Self::new([block], execution_outcome, BTreeMap::from([(block_number, trie_data)]))
86 }
87
88 pub const fn blocks(&self) -> &BTreeMap<BlockNumber, RecoveredBlock<N::Block>> {
90 &self.blocks
91 }
92
93 pub fn into_blocks(self) -> BTreeMap<BlockNumber, RecoveredBlock<N::Block>> {
95 self.blocks
96 }
97
98 pub fn headers(&self) -> impl Iterator<Item = SealedHeader<N::BlockHeader>> + '_ {
100 self.blocks.values().map(|block| block.clone_sealed_header())
101 }
102
103 pub const fn trie_data(&self) -> &BTreeMap<BlockNumber, LazyTrieData> {
105 &self.trie_data
106 }
107
108 pub fn trie_data_at(&self, block_number: BlockNumber) -> Option<&LazyTrieData> {
110 self.trie_data.get(&block_number)
111 }
112
113 pub fn clear_trie_data(&mut self) {
115 self.trie_data.clear();
116 }
117
118 pub const fn execution_outcome(&self) -> &ExecutionOutcome<N::Receipt> {
120 &self.execution_outcome
121 }
122
123 pub const fn execution_outcome_mut(&mut self) -> &mut ExecutionOutcome<N::Receipt> {
125 &mut self.execution_outcome
126 }
127
128 pub fn is_empty(&self) -> bool {
130 self.blocks.is_empty()
131 }
132
133 pub fn block_number(&self, block_hash: BlockHash) -> Option<BlockNumber> {
135 self.blocks.iter().find_map(|(num, block)| (block.hash() == block_hash).then_some(*num))
136 }
137
138 pub fn recovered_block(&self, block_hash: BlockHash) -> Option<&RecoveredBlock<N::Block>> {
140 self.blocks.iter().find_map(|(_num, block)| (block.hash() == block_hash).then_some(block))
141 }
142
143 pub fn execution_outcome_at_block(
145 &self,
146 block_number: BlockNumber,
147 ) -> Option<ExecutionOutcome<N::Receipt>> {
148 if self.tip().number() == block_number {
149 return Some(self.execution_outcome.clone())
150 }
151
152 if self.blocks.contains_key(&block_number) {
153 let mut execution_outcome = self.execution_outcome.clone();
154 execution_outcome.revert_to(block_number);
155 return Some(execution_outcome)
156 }
157 None
158 }
159
160 #[allow(clippy::type_complexity)]
165 pub fn into_inner(
166 self,
167 ) -> (
168 ChainBlocks<'static, N::Block>,
169 ExecutionOutcome<N::Receipt>,
170 BTreeMap<BlockNumber, LazyTrieData>,
171 ) {
172 (ChainBlocks { blocks: Cow::Owned(self.blocks) }, self.execution_outcome, self.trie_data)
173 }
174
175 pub const fn inner(&self) -> (ChainBlocks<'_, N::Block>, &ExecutionOutcome<N::Receipt>) {
179 (ChainBlocks { blocks: Cow::Borrowed(&self.blocks) }, &self.execution_outcome)
180 }
181
182 pub fn block_receipts_iter(&self) -> impl Iterator<Item = &Vec<N::Receipt>> + '_ {
184 self.execution_outcome.receipts().iter()
185 }
186
187 pub fn receipts_iter(&self) -> impl Iterator<Item = &N::Receipt> + '_ {
189 self.block_receipts_iter().flatten()
190 }
191
192 pub fn logs_iter(&self) -> impl Iterator<Item = &Log> + '_
194 where
195 N::Receipt: TxReceipt<Log = Log>,
196 {
197 self.receipts_iter().flat_map(|receipt| receipt.logs())
198 }
199
200 pub fn blocks_iter(&self) -> impl Iterator<Item = &RecoveredBlock<N::Block>> + '_ {
202 self.blocks().iter().map(|block| block.1)
203 }
204
205 pub fn transactions_iter(&self) -> impl Iterator<Item = &N::SignedTx> + '_ {
207 self.blocks_iter().flat_map(|block| block.body().transactions())
208 }
209
210 pub fn transactions_recovered_iter(
212 &self,
213 ) -> impl Iterator<Item = Recovered<&N::SignedTx>> + '_ {
214 self.blocks_iter().flat_map(|block| block.transactions_recovered())
215 }
216
217 pub fn blocks_and_receipts(
219 &self,
220 ) -> impl Iterator<Item = (&RecoveredBlock<N::Block>, &Vec<N::Receipt>)> + '_ {
221 self.blocks_iter().zip(self.block_receipts_iter())
222 }
223
224 pub fn find_transaction_and_receipt_by_hash(
228 &self,
229 tx_hash: TxHash,
230 ) -> Option<ChainTxReceiptMeta<'_, N>> {
231 for (block, receipts) in self.blocks_and_receipts() {
232 let Some(indexed_tx) = block.find_indexed(tx_hash) else {
233 continue;
234 };
235 let receipt = receipts.get(indexed_tx.index())?;
236 return Some((block, indexed_tx, receipt, receipts.as_slice()));
237 }
238
239 None
240 }
241
242 pub fn fork_block(&self) -> ForkBlock {
244 let first = self.first();
245 ForkBlock {
246 number: first.header().number().saturating_sub(1),
247 hash: first.header().parent_hash(),
248 }
249 }
250
251 #[track_caller]
257 pub fn first(&self) -> &RecoveredBlock<N::Block> {
258 self.blocks.first_key_value().expect("Chain should have at least one block").1
259 }
260
261 #[track_caller]
267 pub fn tip(&self) -> &RecoveredBlock<N::Block> {
268 self.blocks.last_key_value().expect("Chain should have at least one block").1
269 }
270
271 pub fn len(&self) -> usize {
273 self.blocks.len()
274 }
275
276 pub fn range(&self) -> RangeInclusive<BlockNumber> {
282 self.first().header().number()..=self.tip().header().number()
283 }
284
285 pub fn receipts_by_block_hash(&self, block_hash: BlockHash) -> Option<Vec<&N::Receipt>> {
287 let num = self.block_number(block_hash)?;
288 Some(self.execution_outcome.receipts_by_block(num).iter().collect())
289 }
290
291 pub fn receipts_with_attachment(&self) -> Vec<BlockReceipts<N::Receipt>>
295 where
296 N::SignedTx: Encodable2718,
297 {
298 let mut receipt_attach = Vec::with_capacity(self.blocks().len());
299
300 self.blocks_and_receipts().for_each(|(block, receipts)| {
301 let block_num_hash = BlockNumHash::new(block.number(), block.hash());
302
303 let tx_receipts = block
304 .body()
305 .transactions()
306 .iter()
307 .zip(receipts)
308 .map(|(tx, receipt)| (tx.trie_hash(), receipt.clone()))
309 .collect();
310
311 receipt_attach.push(BlockReceipts {
312 block: block_num_hash,
313 tx_receipts,
314 timestamp: block.timestamp(),
315 });
316 });
317
318 receipt_attach
319 }
320
321 pub fn append_block(
324 &mut self,
325 block: RecoveredBlock<N::Block>,
326 execution_outcome: ExecutionOutcome<N::Receipt>,
327 trie_data: LazyTrieData,
328 ) {
329 let block_number = block.header().number();
330 self.blocks.insert(block_number, block);
331 self.execution_outcome.extend(execution_outcome);
332 self.trie_data.insert(block_number, trie_data);
333 }
334
335 pub fn append_chain(&mut self, other: Self) -> Result<(), Self> {
342 let chain_tip = self.tip();
343 let other_fork_block = other.fork_block();
344 if chain_tip.hash() != other_fork_block.hash {
345 return Err(other)
346 }
347
348 self.blocks.extend(other.blocks);
350 self.execution_outcome.extend(other.execution_outcome);
351 self.trie_data.extend(other.trie_data);
352
353 Ok(())
354 }
355}
356
357#[derive(Debug)]
359pub struct DisplayBlocksChain<'a, B: reth_primitives_traits::Block>(
360 pub &'a BTreeMap<BlockNumber, RecoveredBlock<B>>,
361);
362
363impl<B: reth_primitives_traits::Block> fmt::Display for DisplayBlocksChain<'_, B> {
364 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365 let mut list = f.debug_list();
366 let mut values = self.0.values().map(|block| block.num_hash());
367 if values.len() <= 3 {
368 list.entries(values);
369 } else {
370 list.entry(&values.next().unwrap());
371 list.entry(&format_args!("..."));
372 list.entry(&values.next_back().unwrap());
373 }
374 list.finish()
375 }
376}
377
378#[derive(Clone, Debug, Default, PartialEq, Eq)]
380pub struct ChainBlocks<'a, B: Block> {
381 blocks: Cow<'a, BTreeMap<BlockNumber, RecoveredBlock<B>>>,
382}
383
384impl<B: Block<Body: BlockBody<Transaction: SignedTransaction>>> ChainBlocks<'_, B> {
385 #[inline]
389 pub fn into_blocks(self) -> impl Iterator<Item = RecoveredBlock<B>> {
390 self.blocks.into_owned().into_values()
391 }
392
393 #[inline]
395 pub fn iter(&self) -> impl Iterator<Item = (&BlockNumber, &RecoveredBlock<B>)> {
396 self.blocks.iter()
397 }
398
399 #[inline]
405 pub fn tip(&self) -> &RecoveredBlock<B> {
406 self.blocks.last_key_value().expect("Chain should have at least one block").1
407 }
408
409 #[inline]
415 pub fn first(&self) -> &RecoveredBlock<B> {
416 self.blocks.first_key_value().expect("Chain should have at least one block").1
417 }
418
419 #[inline]
421 pub fn transactions(&self) -> impl Iterator<Item = &<B::Body as BlockBody>::Transaction> + '_ {
422 self.blocks.values().flat_map(|block| block.body().transactions_iter())
423 }
424
425 #[inline]
427 pub fn transactions_with_sender(
428 &self,
429 ) -> impl Iterator<Item = (&Address, &<B::Body as BlockBody>::Transaction)> + '_ {
430 self.blocks.values().flat_map(|block| block.transactions_with_sender())
431 }
432
433 #[inline]
437 pub fn transactions_ecrecovered(
438 &self,
439 ) -> impl Iterator<Item = Recovered<<B::Body as BlockBody>::Transaction>> + '_ {
440 self.transactions_with_sender().map(|(signer, tx)| tx.clone().with_signer(*signer))
441 }
442
443 #[inline]
445 pub fn transaction_hashes(&self) -> impl Iterator<Item = TxHash> + '_ {
446 self.blocks
447 .values()
448 .flat_map(|block| block.body().transactions_iter().map(|tx| tx.trie_hash()))
449 }
450}
451
452impl<B: Block> IntoIterator for ChainBlocks<'_, B> {
453 type Item = (BlockNumber, RecoveredBlock<B>);
454 type IntoIter = alloc::collections::btree_map::IntoIter<BlockNumber, RecoveredBlock<B>>;
455
456 fn into_iter(self) -> Self::IntoIter {
457 self.blocks.into_owned().into_iter()
458 }
459}
460
461#[derive(Default, Clone, Debug, PartialEq, Eq)]
463pub struct BlockReceipts<T = reth_ethereum_primitives::Receipt> {
464 pub block: BlockNumHash,
466 pub tx_receipts: Vec<(TxHash, T)>,
468 pub timestamp: u64,
470}
471
472#[cfg(feature = "serde-bincode-compat")]
474pub(super) mod serde_bincode_compat {
475 use crate::serde_bincode_compat;
476 use alloc::{collections::BTreeMap, sync::Arc, vec::Vec};
477 use alloy_primitives::{Address, BlockNumber, Bytes};
478 use alloy_rlp::Decodable;
479 use core::marker::PhantomData;
480 use reth_ethereum_primitives::EthPrimitives;
481 use reth_primitives_traits::{NodePrimitives, SealedBlock};
482 use serde::{Deserialize, Deserializer, Serialize, Serializer};
483 use serde_with::{DeserializeAs, SerializeAs};
484
485 #[derive(Debug, Serialize, Deserialize)]
501 #[serde(bound = "")]
502 pub struct Chain<'a, N = EthPrimitives>
503 where
504 N: NodePrimitives,
505 {
506 #[serde(skip)]
507 _phantom: PhantomData<N>,
508 blocks: BTreeMap<BlockNumber, RecoveredBlockRepr>,
509 execution_outcome: serde_bincode_compat::ExecutionOutcome<'a>,
510 #[serde(default)]
511 trie_updates: BTreeMap<
512 BlockNumber,
513 reth_trie_common::serde_bincode_compat::updates::TrieUpdatesSorted<'a>,
514 >,
515 #[serde(default)]
516 hashed_state: BTreeMap<
517 BlockNumber,
518 reth_trie_common::serde_bincode_compat::hashed_state::HashedPostStateSorted<'a>,
519 >,
520 }
521
522 #[derive(Debug, Serialize, Deserialize)]
523 struct RecoveredBlockRepr {
524 rlp: Bytes,
525 senders: Vec<Address>,
526 }
527
528 impl<'a, N> From<&'a super::Chain<N>> for Chain<'a, N>
529 where
530 N: NodePrimitives,
531 {
532 fn from(value: &'a super::Chain<N>) -> Self {
533 Self {
534 _phantom: PhantomData,
535 blocks: value
536 .blocks
537 .iter()
538 .map(|(num, recovered)| {
539 let senders = recovered.senders().to_vec();
540 let rlp = Bytes::from(alloy_rlp::encode(recovered.sealed_block()));
541 (*num, RecoveredBlockRepr { rlp, senders })
542 })
543 .collect(),
544 execution_outcome: (&value.execution_outcome).into(),
545 trie_updates: value
546 .trie_data
547 .iter()
548 .map(|(k, v)| (*k, v.get().trie_updates.as_ref().into()))
549 .collect(),
550 hashed_state: value
551 .trie_data
552 .iter()
553 .map(|(k, v)| (*k, v.get().hashed_state.as_ref().into()))
554 .collect(),
555 }
556 }
557 }
558
559 impl<'a, N> From<Chain<'a, N>> for super::Chain<N>
560 where
561 N: NodePrimitives,
562 {
563 fn from(value: Chain<'a, N>) -> Self {
564 use reth_primitives_traits::RecoveredBlock;
565 use reth_trie_common::LazyTrieData;
566
567 let hashed_state_map: BTreeMap<_, _> =
568 value.hashed_state.into_iter().map(|(k, v)| (k, Arc::new(v.into()))).collect();
569
570 let trie_data: BTreeMap<BlockNumber, LazyTrieData> = value
571 .trie_updates
572 .into_iter()
573 .map(|(k, v)| {
574 let hashed_state = hashed_state_map.get(&k).cloned().unwrap_or_default();
575 (k, LazyTrieData::ready(hashed_state, Arc::new(v.into())))
576 })
577 .collect();
578
579 let blocks = value
580 .blocks
581 .into_iter()
582 .map(|(num, repr)| {
583 let block = N::Block::decode(&mut repr.rlp.as_ref())
584 .expect("invalid RLP for block in serde_bincode_compat");
585 let sealed = SealedBlock::new_unhashed(block);
586 (num, RecoveredBlock::new_sealed(sealed, repr.senders))
587 })
588 .collect();
589
590 Self { blocks, execution_outcome: value.execution_outcome.into(), trie_data }
591 }
592 }
593
594 impl<N> SerializeAs<super::Chain<N>> for Chain<'_, N>
595 where
596 N: NodePrimitives,
597 {
598 fn serialize_as<S>(source: &super::Chain<N>, serializer: S) -> Result<S::Ok, S::Error>
599 where
600 S: Serializer,
601 {
602 Chain::from(source).serialize(serializer)
603 }
604 }
605
606 impl<'de, N> DeserializeAs<'de, super::Chain<N>> for Chain<'de, N>
607 where
608 N: NodePrimitives,
609 {
610 fn deserialize_as<D>(deserializer: D) -> Result<super::Chain<N>, D::Error>
611 where
612 D: Deserializer<'de>,
613 {
614 Chain::deserialize(deserializer).map(Into::into)
615 }
616 }
617
618 #[cfg(test)]
619 mod tests {
620 use super::super::{serde_bincode_compat, Chain};
621 use arbitrary::Arbitrary;
622 use rand::Rng;
623 use reth_primitives_traits::RecoveredBlock;
624 use serde::{Deserialize, Serialize};
625 use serde_with::serde_as;
626
627 #[test]
628 fn test_chain_bincode_roundtrip() {
629 use alloc::collections::BTreeMap;
630
631 #[serde_as]
632 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
633 struct Data {
634 #[serde_as(as = "serde_bincode_compat::Chain")]
635 chain: Chain,
636 }
637
638 let mut bytes = [0u8; 1024];
639 rand::rng().fill(bytes.as_mut_slice());
640 let data = Data {
641 chain: Chain::new(
642 vec![RecoveredBlock::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
643 .unwrap()],
644 Default::default(),
645 BTreeMap::new(),
646 ),
647 };
648
649 let encoded = bincode::serialize(&data).unwrap();
650 let decoded: Data = bincode::deserialize(&encoded).unwrap();
651 assert_eq!(decoded, data);
652 }
653 }
654}
655
656#[cfg(test)]
657mod tests {
658 use super::*;
659 use alloy_consensus::TxType;
660 use alloy_primitives::{Address, B256};
661 use reth_ethereum_primitives::Receipt;
662 use revm::{database::BundleState, primitives::HashMap, state::AccountInfo};
663
664 #[test]
665 fn chain_append() {
666 let block: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
667 let block1_hash = B256::new([0x01; 32]);
668 let block2_hash = B256::new([0x02; 32]);
669 let block3_hash = B256::new([0x03; 32]);
670 let block4_hash = B256::new([0x04; 32]);
671
672 let mut block1 = block.clone();
673 let mut block2 = block.clone();
674 let mut block3 = block.clone();
675 let mut block4 = block;
676
677 block1.set_hash(block1_hash);
678 block2.set_hash(block2_hash);
679 block3.set_hash(block3_hash);
680 block4.set_hash(block4_hash);
681
682 block3.set_parent_hash(block2_hash);
683
684 let mut chain1: Chain =
685 Chain { blocks: BTreeMap::from([(1, block1), (2, block2)]), ..Default::default() };
686
687 let chain2 =
688 Chain { blocks: BTreeMap::from([(3, block3), (4, block4)]), ..Default::default() };
689
690 assert!(chain1.append_chain(chain2.clone()).is_ok());
691
692 assert!(chain1.append_chain(chain2).is_err());
694 }
695
696 #[test]
697 fn test_number_split() {
698 let execution_outcome1: ExecutionOutcome = ExecutionOutcome::new(
699 BundleState::new(
700 vec![(
701 Address::new([2; 20]),
702 None,
703 Some(AccountInfo::default()),
704 HashMap::default(),
705 )],
706 vec![vec![(Address::new([2; 20]), None, vec![])]],
707 vec![],
708 ),
709 vec![vec![]],
710 1,
711 vec![],
712 );
713
714 let execution_outcome2 = ExecutionOutcome::new(
715 BundleState::new(
716 vec![(
717 Address::new([3; 20]),
718 None,
719 Some(AccountInfo::default()),
720 HashMap::default(),
721 )],
722 vec![vec![(Address::new([3; 20]), None, vec![])]],
723 vec![],
724 ),
725 vec![vec![]],
726 2,
727 vec![],
728 );
729
730 let mut block1: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
731 let block1_hash = B256::new([15; 32]);
732 block1.set_block_number(1);
733 block1.set_hash(block1_hash);
734 block1.push_sender(Address::new([4; 20]));
735
736 let mut block2: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
737 let block2_hash = B256::new([16; 32]);
738 block2.set_block_number(2);
739 block2.set_hash(block2_hash);
740 block2.push_sender(Address::new([4; 20]));
741
742 let mut block_state_extended = execution_outcome1;
743 block_state_extended.extend(execution_outcome2);
744
745 let chain: Chain =
746 Chain::new(vec![block1.clone(), block2.clone()], block_state_extended, BTreeMap::new());
747
748 assert_eq!(
750 chain.execution_outcome_at_block(block2.number),
751 Some(chain.execution_outcome.clone())
752 );
753 assert_eq!(chain.execution_outcome_at_block(100), None);
755 }
756
757 #[test]
758 fn receipts_by_block_hash() {
759 let block: RecoveredBlock<reth_ethereum_primitives::Block> = Default::default();
761
762 let block1_hash = B256::new([0x01; 32]);
764 let block2_hash = B256::new([0x02; 32]);
765
766 let mut block1 = block.clone();
768 let mut block2 = block;
769
770 block1.set_hash(block1_hash);
772 block2.set_hash(block2_hash);
773
774 let receipt1 = Receipt {
776 tx_type: TxType::Legacy,
777 cumulative_gas_used: 46913,
778 logs: vec![],
779 success: true,
780 };
781
782 let receipt2 = Receipt {
784 tx_type: TxType::Legacy,
785 cumulative_gas_used: 1325345,
786 logs: vec![],
787 success: true,
788 };
789
790 let receipts = vec![vec![receipt1.clone()], vec![receipt2]];
792
793 let execution_outcome = ExecutionOutcome {
796 bundle: Default::default(),
797 receipts,
798 requests: vec![],
799 first_block: 10,
800 };
801
802 let chain: Chain = Chain {
805 blocks: BTreeMap::from([(10, block1), (11, block2)]),
806 execution_outcome: execution_outcome.clone(),
807 ..Default::default()
808 };
809
810 assert_eq!(chain.receipts_by_block_hash(block1_hash), Some(vec![&receipt1]));
812
813 let execution_outcome1 = ExecutionOutcome {
815 bundle: Default::default(),
816 receipts: vec![vec![receipt1]],
817 requests: vec![],
818 first_block: 10,
819 };
820
821 assert_eq!(chain.execution_outcome_at_block(10), Some(execution_outcome1));
823
824 assert_eq!(chain.execution_outcome_at_block(11), Some(execution_outcome));
826 }
827}