use crate::{
traits::{BlockSource, ReceiptProvider},
AccountReader, BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, BlockReaderIdExt,
ChainSpecProvider, ChangeSetReader, DatabaseProvider, EvmEnvProvider, HeaderProvider,
ReceiptProviderIdExt, StateProvider, StateProviderBox, StateProviderFactory, StateReader,
StateRootProvider, TransactionVariant, TransactionsProvider, WithdrawalsProvider,
};
use alloy_consensus::{constants::EMPTY_ROOT_HASH, Header};
use alloy_eips::{
eip4895::{Withdrawal, Withdrawals},
BlockHashOrNumber, BlockId, BlockNumberOrTag,
};
use alloy_primitives::{
keccak256,
map::{HashMap, HashSet},
Address, BlockHash, BlockNumber, Bytes, StorageKey, StorageValue, TxHash, TxNumber, B256, U256,
};
use parking_lot::Mutex;
use reth_chainspec::{ChainInfo, ChainSpec};
use reth_db::mock::{DatabaseMock, TxMock};
use reth_db_api::models::{AccountBeforeTx, StoredBlockBodyIndices};
use reth_evm::ConfigureEvmEnv;
use reth_execution_types::ExecutionOutcome;
use reth_node_types::NodeTypes;
use reth_primitives::{
Account, Block, BlockWithSenders, Bytecode, GotExpected, Receipt, SealedBlock,
SealedBlockWithSenders, SealedHeader, TransactionMeta, TransactionSigned,
TransactionSignedNoHash,
};
use reth_stages_types::{StageCheckpoint, StageId};
use reth_storage_api::{
DatabaseProviderFactory, StageCheckpointReader, StateProofProvider, StorageRootProvider,
};
use reth_storage_errors::provider::{ConsistentViewError, ProviderError, ProviderResult};
use reth_trie::{
updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, StorageProof,
TrieInput,
};
use reth_trie_db::MerklePatriciaTrie;
use revm::primitives::{BlockEnv, CfgEnvWithHandlerCfg};
use std::{
collections::BTreeMap,
ops::{RangeBounds, RangeInclusive},
sync::Arc,
};
#[derive(Debug, Clone)]
pub struct MockEthProvider {
pub blocks: Arc<Mutex<HashMap<B256, Block>>>,
pub headers: Arc<Mutex<HashMap<B256, Header>>>,
pub accounts: Arc<Mutex<HashMap<Address, ExtendedAccount>>>,
pub chain_spec: Arc<ChainSpec>,
pub state_roots: Arc<Mutex<Vec<B256>>>,
}
impl Default for MockEthProvider {
fn default() -> Self {
Self {
blocks: Default::default(),
headers: Default::default(),
accounts: Default::default(),
chain_spec: Arc::new(reth_chainspec::ChainSpecBuilder::mainnet().build()),
state_roots: Default::default(),
}
}
}
#[derive(Debug, Clone)]
pub struct ExtendedAccount {
account: Account,
bytecode: Option<Bytecode>,
storage: HashMap<StorageKey, StorageValue>,
}
impl ExtendedAccount {
pub fn new(nonce: u64, balance: U256) -> Self {
Self {
account: Account { nonce, balance, bytecode_hash: None },
bytecode: None,
storage: Default::default(),
}
}
pub fn with_bytecode(mut self, bytecode: Bytes) -> Self {
let hash = keccak256(&bytecode);
self.account.bytecode_hash = Some(hash);
self.bytecode = Some(Bytecode::new_raw(bytecode));
self
}
pub fn extend_storage(
mut self,
storage: impl IntoIterator<Item = (StorageKey, StorageValue)>,
) -> Self {
self.storage.extend(storage);
self
}
}
impl MockEthProvider {
pub fn add_block(&self, hash: B256, block: Block) {
self.add_header(hash, block.header.clone());
self.blocks.lock().insert(hash, block);
}
pub fn extend_blocks(&self, iter: impl IntoIterator<Item = (B256, Block)>) {
for (hash, block) in iter {
self.add_header(hash, block.header.clone());
self.add_block(hash, block)
}
}
pub fn add_header(&self, hash: B256, header: Header) {
self.headers.lock().insert(hash, header);
}
pub fn extend_headers(&self, iter: impl IntoIterator<Item = (B256, Header)>) {
for (hash, header) in iter {
self.add_header(hash, header)
}
}
pub fn add_account(&self, address: Address, account: ExtendedAccount) {
self.accounts.lock().insert(address, account);
}
pub fn extend_accounts(&self, iter: impl IntoIterator<Item = (Address, ExtendedAccount)>) {
for (address, account) in iter {
self.add_account(address, account)
}
}
pub fn add_state_root(&self, state_root: B256) {
self.state_roots.lock().push(state_root);
}
}
#[derive(Debug)]
pub struct MockNode;
impl NodeTypes for MockNode {
type Primitives = ();
type ChainSpec = ChainSpec;
type StateCommitment = MerklePatriciaTrie;
}
impl DatabaseProviderFactory for MockEthProvider {
type DB = DatabaseMock;
type Provider = DatabaseProvider<TxMock, MockNode>;
type ProviderRW = DatabaseProvider<TxMock, MockNode>;
fn database_provider_ro(&self) -> ProviderResult<Self::Provider> {
Err(ConsistentViewError::Syncing { best_block: GotExpected::new(0, 0) }.into())
}
fn database_provider_rw(&self) -> ProviderResult<Self::ProviderRW> {
Err(ConsistentViewError::Syncing { best_block: GotExpected::new(0, 0) }.into())
}
}
impl HeaderProvider for MockEthProvider {
fn header(&self, block_hash: &BlockHash) -> ProviderResult<Option<Header>> {
let lock = self.headers.lock();
Ok(lock.get(block_hash).cloned())
}
fn header_by_number(&self, num: u64) -> ProviderResult<Option<Header>> {
let lock = self.headers.lock();
Ok(lock.values().find(|h| h.number == num).cloned())
}
fn header_td(&self, hash: &BlockHash) -> ProviderResult<Option<U256>> {
let lock = self.headers.lock();
Ok(lock.get(hash).map(|target| {
lock.values()
.filter(|h| h.number < target.number)
.fold(target.difficulty, |td, h| td + h.difficulty)
}))
}
fn header_td_by_number(&self, number: BlockNumber) -> ProviderResult<Option<U256>> {
let lock = self.headers.lock();
let sum = lock
.values()
.filter(|h| h.number <= number)
.fold(U256::ZERO, |td, h| td + h.difficulty);
Ok(Some(sum))
}
fn headers_range(&self, range: impl RangeBounds<BlockNumber>) -> ProviderResult<Vec<Header>> {
let lock = self.headers.lock();
let mut headers: Vec<_> =
lock.values().filter(|header| range.contains(&header.number)).cloned().collect();
headers.sort_by_key(|header| header.number);
Ok(headers)
}
fn sealed_header(&self, number: BlockNumber) -> ProviderResult<Option<SealedHeader>> {
Ok(self.header_by_number(number)?.map(SealedHeader::seal))
}
fn sealed_headers_while(
&self,
range: impl RangeBounds<BlockNumber>,
mut predicate: impl FnMut(&SealedHeader) -> bool,
) -> ProviderResult<Vec<SealedHeader>> {
Ok(self
.headers_range(range)?
.into_iter()
.map(SealedHeader::seal)
.take_while(|h| predicate(h))
.collect())
}
}
impl ChainSpecProvider for MockEthProvider {
type ChainSpec = ChainSpec;
fn chain_spec(&self) -> Arc<ChainSpec> {
self.chain_spec.clone()
}
}
impl TransactionsProvider for MockEthProvider {
fn transaction_id(&self, tx_hash: TxHash) -> ProviderResult<Option<TxNumber>> {
let lock = self.blocks.lock();
let tx_number = lock
.values()
.flat_map(|block| &block.body.transactions)
.position(|tx| tx.hash() == tx_hash)
.map(|pos| pos as TxNumber);
Ok(tx_number)
}
fn transaction_by_id(&self, id: TxNumber) -> ProviderResult<Option<TransactionSigned>> {
let lock = self.blocks.lock();
let transaction =
lock.values().flat_map(|block| &block.body.transactions).nth(id as usize).cloned();
Ok(transaction)
}
fn transaction_by_id_no_hash(
&self,
id: TxNumber,
) -> ProviderResult<Option<TransactionSignedNoHash>> {
let lock = self.blocks.lock();
let transaction = lock
.values()
.flat_map(|block| &block.body.transactions)
.nth(id as usize)
.map(|tx| Into::<TransactionSignedNoHash>::into(tx.clone()));
Ok(transaction)
}
fn transaction_by_hash(&self, hash: TxHash) -> ProviderResult<Option<TransactionSigned>> {
Ok(self.blocks.lock().iter().find_map(|(_, block)| {
block.body.transactions.iter().find(|tx| tx.hash() == hash).cloned()
}))
}
fn transaction_by_hash_with_meta(
&self,
hash: TxHash,
) -> ProviderResult<Option<(TransactionSigned, TransactionMeta)>> {
let lock = self.blocks.lock();
for (block_hash, block) in lock.iter() {
for (index, tx) in block.body.transactions.iter().enumerate() {
if tx.hash() == hash {
let meta = TransactionMeta {
tx_hash: hash,
index: index as u64,
block_hash: *block_hash,
block_number: block.header.number,
base_fee: block.header.base_fee_per_gas,
excess_blob_gas: block.header.excess_blob_gas,
timestamp: block.header.timestamp,
};
return Ok(Some((tx.clone(), meta)))
}
}
}
Ok(None)
}
fn transaction_block(&self, id: TxNumber) -> ProviderResult<Option<BlockNumber>> {
let lock = self.blocks.lock();
let mut current_tx_number: TxNumber = 0;
for block in lock.values() {
if current_tx_number + (block.body.transactions.len() as TxNumber) > id {
return Ok(Some(block.header.number))
}
current_tx_number += block.body.transactions.len() as TxNumber;
}
Ok(None)
}
fn transactions_by_block(
&self,
id: BlockHashOrNumber,
) -> ProviderResult<Option<Vec<TransactionSigned>>> {
Ok(self.block(id)?.map(|b| b.body.transactions))
}
fn transactions_by_block_range(
&self,
range: impl RangeBounds<alloy_primitives::BlockNumber>,
) -> ProviderResult<Vec<Vec<TransactionSigned>>> {
let mut map = BTreeMap::new();
for (_, block) in self.blocks.lock().iter() {
if range.contains(&block.number) {
map.insert(block.number, block.body.transactions.clone());
}
}
Ok(map.into_values().collect())
}
fn transactions_by_tx_range(
&self,
range: impl RangeBounds<TxNumber>,
) -> ProviderResult<Vec<reth_primitives::TransactionSignedNoHash>> {
let lock = self.blocks.lock();
let transactions = lock
.values()
.flat_map(|block| &block.body.transactions)
.enumerate()
.filter(|&(tx_number, _)| range.contains(&(tx_number as TxNumber)))
.map(|(_, tx)| tx.clone().into())
.collect();
Ok(transactions)
}
fn senders_by_tx_range(
&self,
range: impl RangeBounds<TxNumber>,
) -> ProviderResult<Vec<Address>> {
let lock = self.blocks.lock();
let transactions = lock
.values()
.flat_map(|block| &block.body.transactions)
.enumerate()
.filter_map(|(tx_number, tx)| {
range.contains(&(tx_number as TxNumber)).then(|| tx.recover_signer()).flatten()
})
.collect();
Ok(transactions)
}
fn transaction_sender(&self, id: TxNumber) -> ProviderResult<Option<Address>> {
self.transaction_by_id(id).map(|tx_option| tx_option.map(|tx| tx.recover_signer().unwrap()))
}
}
impl ReceiptProvider for MockEthProvider {
fn receipt(&self, _id: TxNumber) -> ProviderResult<Option<Receipt>> {
Ok(None)
}
fn receipt_by_hash(&self, _hash: TxHash) -> ProviderResult<Option<Receipt>> {
Ok(None)
}
fn receipts_by_block(&self, _block: BlockHashOrNumber) -> ProviderResult<Option<Vec<Receipt>>> {
Ok(None)
}
fn receipts_by_tx_range(
&self,
_range: impl RangeBounds<TxNumber>,
) -> ProviderResult<Vec<Receipt>> {
Ok(vec![])
}
}
impl ReceiptProviderIdExt for MockEthProvider {}
impl BlockHashReader for MockEthProvider {
fn block_hash(&self, number: u64) -> ProviderResult<Option<B256>> {
let lock = self.blocks.lock();
let hash = lock.iter().find_map(|(hash, b)| (b.number == number).then_some(*hash));
Ok(hash)
}
fn canonical_hashes_range(
&self,
start: BlockNumber,
end: BlockNumber,
) -> ProviderResult<Vec<B256>> {
let range = start..end;
let lock = self.blocks.lock();
let mut hashes: Vec<_> =
lock.iter().filter(|(_, block)| range.contains(&block.number)).collect();
hashes.sort_by_key(|(_, block)| block.number);
Ok(hashes.into_iter().map(|(hash, _)| *hash).collect())
}
}
impl BlockNumReader for MockEthProvider {
fn chain_info(&self) -> ProviderResult<ChainInfo> {
let best_block_number = self.best_block_number()?;
let lock = self.headers.lock();
Ok(lock
.iter()
.find(|(_, header)| header.number == best_block_number)
.map(|(hash, header)| ChainInfo { best_hash: *hash, best_number: header.number })
.unwrap_or_default())
}
fn best_block_number(&self) -> ProviderResult<BlockNumber> {
let lock = self.headers.lock();
lock.iter()
.max_by_key(|h| h.1.number)
.map(|(_, header)| header.number)
.ok_or(ProviderError::BestBlockNotFound)
}
fn last_block_number(&self) -> ProviderResult<BlockNumber> {
self.best_block_number()
}
fn block_number(&self, hash: B256) -> ProviderResult<Option<alloy_primitives::BlockNumber>> {
let lock = self.blocks.lock();
let num = lock.iter().find_map(|(h, b)| (*h == hash).then_some(b.number));
Ok(num)
}
}
impl BlockIdReader for MockEthProvider {
fn pending_block_num_hash(&self) -> ProviderResult<Option<alloy_eips::BlockNumHash>> {
Ok(None)
}
fn safe_block_num_hash(&self) -> ProviderResult<Option<alloy_eips::BlockNumHash>> {
Ok(None)
}
fn finalized_block_num_hash(&self) -> ProviderResult<Option<alloy_eips::BlockNumHash>> {
Ok(None)
}
}
impl BlockReader for MockEthProvider {
fn find_block_by_hash(
&self,
hash: B256,
_source: BlockSource,
) -> ProviderResult<Option<Block>> {
self.block(hash.into())
}
fn block(&self, id: BlockHashOrNumber) -> ProviderResult<Option<Block>> {
let lock = self.blocks.lock();
match id {
BlockHashOrNumber::Hash(hash) => Ok(lock.get(&hash).cloned()),
BlockHashOrNumber::Number(num) => Ok(lock.values().find(|b| b.number == num).cloned()),
}
}
fn pending_block(&self) -> ProviderResult<Option<SealedBlock>> {
Ok(None)
}
fn pending_block_with_senders(&self) -> ProviderResult<Option<SealedBlockWithSenders>> {
Ok(None)
}
fn pending_block_and_receipts(&self) -> ProviderResult<Option<(SealedBlock, Vec<Receipt>)>> {
Ok(None)
}
fn ommers(&self, _id: BlockHashOrNumber) -> ProviderResult<Option<Vec<Header>>> {
Ok(None)
}
fn block_body_indices(&self, _num: u64) -> ProviderResult<Option<StoredBlockBodyIndices>> {
Ok(None)
}
fn block_with_senders(
&self,
_id: BlockHashOrNumber,
_transaction_kind: TransactionVariant,
) -> ProviderResult<Option<BlockWithSenders>> {
Ok(None)
}
fn sealed_block_with_senders(
&self,
_id: BlockHashOrNumber,
_transaction_kind: TransactionVariant,
) -> ProviderResult<Option<SealedBlockWithSenders>> {
Ok(None)
}
fn block_range(&self, range: RangeInclusive<BlockNumber>) -> ProviderResult<Vec<Block>> {
let lock = self.blocks.lock();
let mut blocks: Vec<_> =
lock.values().filter(|block| range.contains(&block.number)).cloned().collect();
blocks.sort_by_key(|block| block.number);
Ok(blocks)
}
fn block_with_senders_range(
&self,
_range: RangeInclusive<BlockNumber>,
) -> ProviderResult<Vec<BlockWithSenders>> {
Ok(vec![])
}
fn sealed_block_with_senders_range(
&self,
_range: RangeInclusive<BlockNumber>,
) -> ProviderResult<Vec<SealedBlockWithSenders>> {
Ok(vec![])
}
}
impl BlockReaderIdExt for MockEthProvider {
fn block_by_id(&self, id: BlockId) -> ProviderResult<Option<Block>> {
match id {
BlockId::Number(num) => self.block_by_number_or_tag(num),
BlockId::Hash(hash) => self.block_by_hash(hash.block_hash),
}
}
fn sealed_header_by_id(&self, id: BlockId) -> ProviderResult<Option<SealedHeader>> {
self.header_by_id(id)?.map_or_else(|| Ok(None), |h| Ok(Some(SealedHeader::seal(h))))
}
fn header_by_id(&self, id: BlockId) -> ProviderResult<Option<Header>> {
match self.block_by_id(id)? {
None => Ok(None),
Some(block) => Ok(Some(block.header)),
}
}
fn ommers_by_id(&self, id: BlockId) -> ProviderResult<Option<Vec<Header>>> {
match id {
BlockId::Number(num) => self.ommers_by_number_or_tag(num),
BlockId::Hash(hash) => self.ommers(BlockHashOrNumber::Hash(hash.block_hash)),
}
}
}
impl AccountReader for MockEthProvider {
fn basic_account(&self, address: Address) -> ProviderResult<Option<Account>> {
Ok(self.accounts.lock().get(&address).cloned().map(|a| a.account))
}
}
impl StageCheckpointReader for MockEthProvider {
fn get_stage_checkpoint(&self, _id: StageId) -> ProviderResult<Option<StageCheckpoint>> {
Ok(None)
}
fn get_stage_checkpoint_progress(&self, _id: StageId) -> ProviderResult<Option<Vec<u8>>> {
Ok(None)
}
fn get_all_checkpoints(&self) -> ProviderResult<Vec<(String, StageCheckpoint)>> {
Ok(vec![])
}
}
impl StateRootProvider for MockEthProvider {
fn state_root(&self, _state: HashedPostState) -> ProviderResult<B256> {
Ok(self.state_roots.lock().pop().unwrap_or_default())
}
fn state_root_from_nodes(&self, _input: TrieInput) -> ProviderResult<B256> {
Ok(self.state_roots.lock().pop().unwrap_or_default())
}
fn state_root_with_updates(
&self,
_state: HashedPostState,
) -> ProviderResult<(B256, TrieUpdates)> {
let state_root = self.state_roots.lock().pop().unwrap_or_default();
Ok((state_root, Default::default()))
}
fn state_root_from_nodes_with_updates(
&self,
_input: TrieInput,
) -> ProviderResult<(B256, TrieUpdates)> {
let state_root = self.state_roots.lock().pop().unwrap_or_default();
Ok((state_root, Default::default()))
}
}
impl StorageRootProvider for MockEthProvider {
fn storage_root(
&self,
_address: Address,
_hashed_storage: HashedStorage,
) -> ProviderResult<B256> {
Ok(EMPTY_ROOT_HASH)
}
fn storage_proof(
&self,
_address: Address,
slot: B256,
_hashed_storage: HashedStorage,
) -> ProviderResult<reth_trie::StorageProof> {
Ok(StorageProof::new(slot))
}
}
impl StateProofProvider for MockEthProvider {
fn proof(
&self,
_input: TrieInput,
address: Address,
_slots: &[B256],
) -> ProviderResult<AccountProof> {
Ok(AccountProof::new(address))
}
fn multiproof(
&self,
_input: TrieInput,
_targets: HashMap<B256, HashSet<B256>>,
) -> ProviderResult<MultiProof> {
Ok(MultiProof::default())
}
fn witness(
&self,
_input: TrieInput,
_target: HashedPostState,
) -> ProviderResult<HashMap<B256, Bytes>> {
Ok(HashMap::default())
}
}
impl StateProvider for MockEthProvider {
fn storage(
&self,
account: Address,
storage_key: StorageKey,
) -> ProviderResult<Option<StorageValue>> {
let lock = self.accounts.lock();
Ok(lock.get(&account).and_then(|account| account.storage.get(&storage_key)).copied())
}
fn bytecode_by_hash(&self, code_hash: B256) -> ProviderResult<Option<Bytecode>> {
let lock = self.accounts.lock();
Ok(lock.values().find_map(|account| {
match (account.account.bytecode_hash.as_ref(), account.bytecode.as_ref()) {
(Some(bytecode_hash), Some(bytecode)) if *bytecode_hash == code_hash => {
Some(bytecode.clone())
}
_ => None,
}
}))
}
}
impl EvmEnvProvider for MockEthProvider {
fn fill_env_at<EvmConfig>(
&self,
_cfg: &mut CfgEnvWithHandlerCfg,
_block_env: &mut BlockEnv,
_at: BlockHashOrNumber,
_evm_config: EvmConfig,
) -> ProviderResult<()>
where
EvmConfig: ConfigureEvmEnv<Header = Header>,
{
Ok(())
}
fn fill_env_with_header<EvmConfig>(
&self,
_cfg: &mut CfgEnvWithHandlerCfg,
_block_env: &mut BlockEnv,
_header: &Header,
_evm_config: EvmConfig,
) -> ProviderResult<()>
where
EvmConfig: ConfigureEvmEnv<Header = Header>,
{
Ok(())
}
fn fill_cfg_env_at<EvmConfig>(
&self,
_cfg: &mut CfgEnvWithHandlerCfg,
_at: BlockHashOrNumber,
_evm_config: EvmConfig,
) -> ProviderResult<()>
where
EvmConfig: ConfigureEvmEnv<Header = Header>,
{
Ok(())
}
fn fill_cfg_env_with_header<EvmConfig>(
&self,
_cfg: &mut CfgEnvWithHandlerCfg,
_header: &Header,
_evm_config: EvmConfig,
) -> ProviderResult<()>
where
EvmConfig: ConfigureEvmEnv<Header = Header>,
{
Ok(())
}
}
impl StateProviderFactory for MockEthProvider {
fn latest(&self) -> ProviderResult<StateProviderBox> {
Ok(Box::new(self.clone()))
}
fn state_by_block_number_or_tag(
&self,
number_or_tag: BlockNumberOrTag,
) -> ProviderResult<StateProviderBox> {
match number_or_tag {
BlockNumberOrTag::Latest => self.latest(),
BlockNumberOrTag::Finalized => {
let hash =
self.finalized_block_hash()?.ok_or(ProviderError::FinalizedBlockNotFound)?;
self.history_by_block_hash(hash)
}
BlockNumberOrTag::Safe => {
let hash = self.safe_block_hash()?.ok_or(ProviderError::SafeBlockNotFound)?;
self.history_by_block_hash(hash)
}
BlockNumberOrTag::Earliest => self.history_by_block_number(0),
BlockNumberOrTag::Pending => self.pending(),
BlockNumberOrTag::Number(num) => self.history_by_block_number(num),
}
}
fn history_by_block_number(&self, _block: BlockNumber) -> ProviderResult<StateProviderBox> {
Ok(Box::new(self.clone()))
}
fn history_by_block_hash(&self, _block: BlockHash) -> ProviderResult<StateProviderBox> {
Ok(Box::new(self.clone()))
}
fn state_by_block_hash(&self, _block: BlockHash) -> ProviderResult<StateProviderBox> {
Ok(Box::new(self.clone()))
}
fn pending(&self) -> ProviderResult<StateProviderBox> {
Ok(Box::new(self.clone()))
}
fn pending_state_by_hash(&self, _block_hash: B256) -> ProviderResult<Option<StateProviderBox>> {
Ok(Some(Box::new(self.clone())))
}
}
impl WithdrawalsProvider for MockEthProvider {
fn withdrawals_by_block(
&self,
_id: BlockHashOrNumber,
_timestamp: u64,
) -> ProviderResult<Option<Withdrawals>> {
Ok(None)
}
fn latest_withdrawal(&self) -> ProviderResult<Option<Withdrawal>> {
Ok(None)
}
}
impl ChangeSetReader for MockEthProvider {
fn account_block_changeset(
&self,
_block_number: BlockNumber,
) -> ProviderResult<Vec<AccountBeforeTx>> {
Ok(Vec::default())
}
}
impl StateReader for MockEthProvider {
fn get_state(&self, _block: BlockNumber) -> ProviderResult<Option<ExecutionOutcome>> {
Ok(None)
}
}