Skip to main content

reth_rpc/
reth.rs

1use std::{future::Future, sync::Arc};
2
3use alloy_consensus::BlockHeader;
4use alloy_eips::BlockId;
5use alloy_primitives::{map::AddressMap, U256, U64};
6use futures::{Stream, StreamExt};
7use jsonrpsee::{core::RpcResult, PendingSubscriptionSink, SubscriptionMessage, SubscriptionSink};
8use reth_chain_state::{
9    CanonStateNotification, CanonStateSubscriptions, ForkChoiceSubscriptions,
10    PersistedBlockSubscriptions,
11};
12use reth_errors::{RethError, RethResult};
13use reth_evm::{database::StateProviderDatabase, execute::Executor, ConfigureEvm};
14use reth_execution_types::{Chain, ExecutionOutcome};
15use reth_primitives_traits::{NodePrimitives, SealedHeader};
16use reth_rpc_api::{RethApiServer, RethJitAction};
17use reth_rpc_eth_types::{EthApiError, EthResult};
18use reth_storage_api::{
19    BlockReader, BlockReaderIdExt, ChangeSetReader, StateProvider, StateProviderFactory,
20    TransactionVariant,
21};
22use reth_tasks::{pool::BlockingTaskGuard, CancelOnDrop, Runtime};
23use serde::Serialize;
24use serde_json::value::RawValue;
25use tokio::sync::oneshot;
26
27/// `reth` API implementation.
28///
29/// This type provides the functionality for handling `reth` prototype RPC requests.
30pub struct RethApi<Provider, EvmConfig> {
31    inner: Arc<RethApiInner<Provider, EvmConfig>>,
32}
33
34// === impl RethApi ===
35
36impl<Provider, EvmConfig> RethApi<Provider, EvmConfig> {
37    /// The provider that can interact with the chain.
38    pub fn provider(&self) -> &Provider {
39        &self.inner.provider
40    }
41
42    /// The evm config.
43    pub fn evm_config(&self) -> &EvmConfig {
44        &self.inner.evm_config
45    }
46
47    /// Create a new instance of the [`RethApi`]
48    pub fn new(
49        provider: Provider,
50        evm_config: EvmConfig,
51        blocking_task_guard: BlockingTaskGuard,
52        task_spawner: Runtime,
53    ) -> Self {
54        let inner =
55            Arc::new(RethApiInner { provider, evm_config, blocking_task_guard, task_spawner });
56        Self { inner }
57    }
58}
59
60impl<Provider, EvmConfig> RethApi<Provider, EvmConfig>
61where
62    Provider: BlockReaderIdExt + ChangeSetReader + StateProviderFactory + 'static,
63    EvmConfig: Send + Sync + 'static,
64{
65    /// Executes the future on a new blocking task.
66    async fn on_blocking_task<C, F, R>(&self, c: C) -> EthResult<R>
67    where
68        C: FnOnce(Self) -> F,
69        F: Future<Output = EthResult<R>> + Send + 'static,
70        R: Send + 'static,
71    {
72        let (tx, rx) = oneshot::channel();
73        let this = self.clone();
74        let f = c(this);
75        self.inner.task_spawner.spawn_blocking_task(async move {
76            let res = f.await;
77            let _ = tx.send(res);
78        });
79        rx.await.map_err(|_| EthApiError::InternalEthError)?
80    }
81
82    /// Returns post-block balances for accounts whose balance changed in the block.
83    ///
84    /// Missing accounts are treated as having zero balance.
85    pub async fn balance_changes_in_block(&self, block_id: BlockId) -> EthResult<AddressMap<U256>> {
86        self.on_blocking_task(async move |this| this.try_balance_changes_in_block(block_id)).await
87    }
88
89    fn try_balance_changes_in_block(&self, block_id: BlockId) -> EthResult<AddressMap<U256>> {
90        let Some(block_number) = self.provider().block_number_for_id(block_id)? else {
91            return Err(EthApiError::HeaderNotFound(block_id))
92        };
93
94        let state = self.provider().state_by_block_id(block_id)?;
95        let accounts_before = self.provider().account_block_changeset(block_number)?;
96        let hash_map = accounts_before.iter().try_fold(
97            AddressMap::default(),
98            |mut hash_map, account_before| -> RethResult<_> {
99                let current_balance =
100                    state.account_balance(&account_before.address)?.unwrap_or_default();
101                let prev_balance =
102                    account_before.info.as_ref().map(|info| info.balance).unwrap_or_default();
103                if current_balance != prev_balance {
104                    hash_map.insert(account_before.address, current_balance);
105                }
106                Ok(hash_map)
107            },
108        )?;
109        Ok(hash_map)
110    }
111}
112
113impl<N, Provider, EvmConfig> RethApi<Provider, EvmConfig>
114where
115    N: NodePrimitives,
116    Provider: BlockReaderIdExt
117        + ChangeSetReader
118        + StateProviderFactory
119        + BlockReader<Block = N::Block>
120        + CanonStateSubscriptions<Primitives = N>
121        + 'static,
122    EvmConfig: ConfigureEvm<Primitives = N> + 'static,
123{
124    /// Re-executes one or more consecutive blocks and returns the execution outcome.
125    pub async fn block_execution_outcome(
126        &self,
127        block_id: BlockId,
128        count: Option<U64>,
129    ) -> EthResult<Option<ExecutionOutcome<N::Receipt>>> {
130        const MAX_BLOCK_COUNT: u64 = 128;
131
132        let block_count = count.map(|c| c.to::<u64>()).unwrap_or(1);
133        if block_count == 0 || block_count > MAX_BLOCK_COUNT {
134            return Err(EthApiError::InvalidParams(format!(
135                "block count must be between 1 and {MAX_BLOCK_COUNT}, got {block_count}"
136            )))
137        }
138
139        let permit = self
140            .inner
141            .blocking_task_guard
142            .clone()
143            .acquire_owned()
144            .await
145            .map_err(|_| EthApiError::InternalEthError)?;
146        let guard = CancelOnDrop::default();
147        let cancel = guard.clone();
148        let outcome = self
149            .on_blocking_task(async move |this| {
150                let _permit = permit;
151                this.try_block_execution_outcome(block_id, block_count, &cancel)
152            })
153            .await;
154        drop(guard);
155        outcome
156    }
157
158    fn try_block_execution_outcome(
159        &self,
160        block_id: BlockId,
161        block_count: u64,
162        cancel: &CancelOnDrop,
163    ) -> EthResult<Option<ExecutionOutcome<N::Receipt>>> {
164        let Some(start_block) = self.provider().block_number_for_id(block_id)? else {
165            return Ok(None)
166        };
167
168        if start_block == 0 {
169            return Ok(Some(ExecutionOutcome::default()))
170        }
171
172        let state_provider = self.provider().history_by_block_number(start_block - 1)?;
173        let db = StateProviderDatabase::new((&state_provider).into_evm_state_provider());
174
175        let mut blocks = Vec::with_capacity(block_count as usize);
176        for block_number in start_block..start_block + block_count {
177            let Some(block) = self
178                .provider()
179                .recovered_block(block_number.into(), TransactionVariant::WithHash)?
180            else {
181                if block_number == start_block {
182                    return Ok(None)
183                }
184                break;
185            };
186            blocks.push(block);
187        }
188
189        // stop between blocks once the request is dropped
190        let blocks = blocks.iter().take_while(|_| !cancel.is_cancelled());
191        let outcome = self.evm_config().executor(db).execute_batch(blocks).map_err(
192            |e: reth_evm::execute::BlockExecutionError| {
193                EthApiError::Internal(reth_errors::RethError::Other(e.into()))
194            },
195        )?;
196        if cancel.is_cancelled() {
197            return Err(EthApiError::InternalEthError)
198        }
199
200        Ok(Some(outcome))
201    }
202}
203
204impl<Provider, EvmConfig> RethApiServer for RethApi<Provider, EvmConfig>
205where
206    Provider: BlockReaderIdExt
207        + ChangeSetReader
208        + StateProviderFactory
209        + BlockReader<
210            Block = <<Provider as CanonStateSubscriptions>::Primitives as NodePrimitives>::Block,
211        > + CanonStateSubscriptions
212        + ForkChoiceSubscriptions<
213            Header = <<Provider as CanonStateSubscriptions>::Primitives as NodePrimitives>::BlockHeader,
214        >
215        + PersistedBlockSubscriptions
216        + 'static,
217    EvmConfig: ConfigureEvm<Primitives = <Provider as CanonStateSubscriptions>::Primitives> + 'static,
218{
219    /// Handler for `reth_getBalanceChangesInBlock`
220    async fn reth_get_balance_changes_in_block(
221        &self,
222        block_id: BlockId,
223    ) -> RpcResult<AddressMap<U256>> {
224        Ok(Self::balance_changes_in_block(self, block_id).await?)
225    }
226
227    /// Handler for `reth_getBlockExecutionOutcome`
228    async fn reth_get_block_execution_outcome(
229        &self,
230        block_id: BlockId,
231        count: Option<U64>,
232    ) -> RpcResult<Option<Box<RawValue>>> {
233        let outcome = Self::block_execution_outcome(self, block_id, count).await?;
234        match outcome {
235            Some(outcome) => {
236                let raw = serde_json::value::to_raw_value(&outcome).map_err(|e| {
237                    EthApiError::Internal(reth_errors::RethError::msg(e.to_string()))
238                })?;
239                Ok(Some(raw))
240            }
241            None => Ok(None),
242        }
243    }
244
245    /// Handler for `reth_jit`
246    async fn reth_jit(&self, action: RethJitAction) -> RpcResult<()> {
247        let Some(jit_backend) = self.evm_config().jit_backend() else {
248            return Ok(());
249        };
250
251        match action {
252            RethJitAction::Enable => jit_backend
253                .set_enabled(true)
254                .map_err(|err| EthApiError::Internal(RethError::msg(err)))?,
255            RethJitAction::Disable => jit_backend
256                .set_enabled(false)
257                .map_err(|err| EthApiError::Internal(RethError::msg(err)))?,
258            RethJitAction::Pause => jit_backend.pause(),
259            RethJitAction::Unpause => jit_backend.resume(),
260            RethJitAction::Clear => jit_backend.clear(),
261        }
262
263        Ok(())
264    }
265
266    /// Handler for `reth_subscribeChainNotifications`
267    async fn reth_subscribe_chain_notifications(
268        &self,
269        pending: PendingSubscriptionSink,
270    ) -> jsonrpsee::core::SubscriptionResult {
271        let sink = pending.accept().await?;
272        let stream = self.provider().canonical_state_stream();
273        self.inner.task_spawner.spawn_task(pipe_from_stream(sink, stream));
274
275        Ok(())
276    }
277
278    /// Handler for `reth_subscribePersistedBlock`
279    async fn reth_subscribe_persisted_block(
280        &self,
281        pending: PendingSubscriptionSink,
282    ) -> jsonrpsee::core::SubscriptionResult {
283        let sink = pending.accept().await?;
284        let stream = self.provider().persisted_block_stream();
285        self.inner.task_spawner.spawn_task(pipe_from_stream(sink, stream));
286
287        Ok(())
288    }
289
290    /// Handler for `reth_subscribeFinalizedChainNotifications`
291    async fn reth_subscribe_finalized_chain_notifications(
292        &self,
293        pending: PendingSubscriptionSink,
294    ) -> jsonrpsee::core::SubscriptionResult {
295        let sink = pending.accept().await?;
296        let canon_stream = self.provider().canonical_state_stream();
297        let finalized_stream = self.provider().finalized_block_stream();
298        self.inner.task_spawner.spawn_task(finalized_chain_notifications(
299            sink,
300            canon_stream,
301            finalized_stream,
302        ));
303
304        Ok(())
305    }
306}
307
308/// Pipes all stream items to the subscription sink.
309async fn pipe_from_stream<S, T>(sink: SubscriptionSink, mut stream: S)
310where
311    S: Stream<Item = T> + Unpin,
312    T: Serialize,
313{
314    loop {
315        tokio::select! {
316            _ = sink.closed() => {
317                break
318            }
319            maybe_item = stream.next() => {
320                let Some(item) = maybe_item else {
321                    break
322                };
323                let msg = match SubscriptionMessage::new(sink.method_name(), sink.subscription_id(), &item) {
324                    Ok(msg) => msg,
325                    Err(err) => {
326                        tracing::error!(target: "rpc::reth", %err, "Failed to serialize subscription message");
327                        break
328                    }
329                };
330                if sink.send(msg).await.is_err() {
331                    break;
332                }
333            }
334        }
335    }
336}
337
338/// Buffers committed chain notifications and emits them when a new finalized block is received.
339async fn finalized_chain_notifications<N>(
340    sink: SubscriptionSink,
341    mut canon_stream: reth_chain_state::CanonStateNotificationStream<N>,
342    mut finalized_stream: reth_chain_state::ForkChoiceStream<SealedHeader<N::BlockHeader>>,
343) where
344    N: NodePrimitives,
345{
346    let mut buffered: Vec<CanonStateNotification<N>> = Vec::new();
347
348    loop {
349        tokio::select! {
350            _ = sink.closed() => {
351                break
352            }
353            maybe_canon = canon_stream.next() => {
354                let Some(notification) = maybe_canon else { break };
355                match &notification {
356                    CanonStateNotification::Commit { .. } => {
357                        buffered.push(notification);
358                    }
359                    CanonStateNotification::Reorg { old, new } => {
360                        let first_reverted = old.first().number();
361                        buffered.retain_mut(|notification| {
362                            let chain = notification.committed();
363                            if chain.first().number() >= first_reverted {
364                                return false
365                            }
366                            // Preserve the canonical prefix of a segment crossing the fork.
367                            if chain.tip().number() >= first_reverted {
368                                let (blocks, mut outcome, mut trie_data) = (*chain).clone().into_inner();
369                                outcome.revert_to(first_reverted - 1);
370                                trie_data.split_off(&first_reverted);
371                                *notification = CanonStateNotification::Commit {
372                                    new: Arc::new(Chain::new(
373                                        blocks.into_blocks().take_while(|b| b.number() < first_reverted),
374                                        outcome,
375                                        trie_data,
376                                    )),
377                                };
378                            }
379                            true
380                        });
381                        if !new.is_empty() {
382                            buffered.push(CanonStateNotification::Commit { new: new.clone() });
383                        }
384                    }
385                }
386            }
387            maybe_finalized = finalized_stream.next() => {
388                let Some(finalized_header) = maybe_finalized else { break };
389                let finalized_num = finalized_header.number();
390
391                let mut committed = Vec::new();
392                buffered.retain(|n| {
393                    if *n.committed().range().end() <= finalized_num {
394                        committed.push(n.clone());
395                        false
396                    } else {
397                        true
398                    }
399                });
400
401                if committed.is_empty() {
402                    continue;
403                }
404
405                committed.sort_by_key(|n| *n.committed().range().start());
406
407                let msg = match SubscriptionMessage::new(
408                    sink.method_name(),
409                    sink.subscription_id(),
410                    &committed,
411                ) {
412                    Ok(msg) => msg,
413                    Err(err) => {
414                        tracing::error!(target: "rpc::reth", %err, "Failed to serialize finalized chain notification");
415                        break
416                    }
417                };
418                if sink.send(msg).await.is_err() {
419                    break;
420                }
421            }
422        }
423    }
424}
425
426impl<Provider, EvmConfig> std::fmt::Debug for RethApi<Provider, EvmConfig> {
427    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
428        f.debug_struct("RethApi").finish_non_exhaustive()
429    }
430}
431
432impl<Provider, EvmConfig> Clone for RethApi<Provider, EvmConfig> {
433    fn clone(&self) -> Self {
434        Self { inner: Arc::clone(&self.inner) }
435    }
436}
437
438struct RethApiInner<Provider, EvmConfig> {
439    /// The provider that can interact with the chain.
440    provider: Provider,
441    /// The EVM configuration used to create block executors.
442    evm_config: EvmConfig,
443    /// Guard to restrict the number of concurrent block re-execution requests.
444    blocking_task_guard: BlockingTaskGuard,
445    /// The type that can spawn tasks which would otherwise block.
446    task_spawner: Runtime,
447}
448
449#[cfg(test)]
450mod tests {
451    use super::*;
452    use alloy_consensus::{Block, Header};
453    use alloy_genesis::{Genesis, GenesisAccount};
454    use alloy_primitives::Address;
455    use evm2::evm::AccountInfo;
456    use reth_chainspec::ChainSpecBuilder;
457    use reth_db_common::init::init_genesis;
458    use reth_primitives_traits::Block as _;
459    use reth_provider::{
460        providers::BlockchainProvider, test_utils::create_test_provider_factory_with_chain_spec,
461        BlockWriter,
462    };
463    use reth_trie_common::{bundle_state::BundleState, HashedPostState, KeccakKeyHasher};
464
465    #[test]
466    fn balance_changes_ignore_account_existence() {
467        let transitions = [
468            (1, None, Some(0)),      // Zero-balance creation.
469            (2, Some(0), None),      // Zero-balance deletion.
470            (3, None, Some(10)),     // Funded creation.
471            (4, Some(10), None),     // Funded deletion.
472            (5, Some(10), Some(20)), // Balance increase.
473            (6, Some(10), Some(0)),  // Existing account emptied.
474            (7, Some(10), Some(10)), // Nonce-only change.
475        ]
476        .map(|(id, before, after)| {
477            (
478                Address::with_last_byte(id),
479                before.map(|balance| AccountInfo {
480                    balance: U256::from(balance),
481                    nonce: 1,
482                    ..Default::default()
483                }),
484                after.map(|balance| AccountInfo {
485                    balance: U256::from(balance),
486                    nonce: 2,
487                    ..Default::default()
488                }),
489            )
490        });
491        let genesis = Genesis::default().extend_accounts(transitions.iter().filter_map(
492            |(address, before, _)| {
493                before.as_ref().map(|account| {
494                    (
495                        *address,
496                        GenesisAccount::default()
497                            .with_balance(account.balance)
498                            .with_nonce(Some(account.nonce)),
499                    )
500                })
501            },
502        ));
503        let factory = create_test_provider_factory_with_chain_spec(Arc::new(
504            ChainSpecBuilder::mainnet().genesis(genesis).build(),
505        ));
506        let genesis_hash = init_genesis(&factory).unwrap();
507        let block = Block {
508            header: Header { parent_hash: genesis_hash, number: 1, ..Default::default() },
509            body: Default::default(),
510        };
511        let outcome = ExecutionOutcome {
512            bundle: BundleState::new(
513                transitions.iter().map(|(address, before, after)| {
514                    (*address, before.clone(), after.clone(), Default::default())
515                }),
516                [transitions
517                    .iter()
518                    .map(|(address, before, _)| (*address, Some(before.clone()), []))],
519                [],
520            ),
521            first_block: 1,
522            receipts: vec![vec![]],
523            ..Default::default()
524        };
525        let provider_rw = factory.provider_rw().unwrap();
526        provider_rw
527            .append_blocks_with_state(
528                vec![block.seal_slow().try_recover().unwrap()],
529                &outcome,
530                HashedPostState::from_bundle_state::<KeccakKeyHasher>(&outcome.bundle.state)
531                    .into_sorted(),
532            )
533            .unwrap();
534        provider_rw.commit().unwrap();
535        let api = RethApi::new(
536            BlockchainProvider::new(factory).unwrap(),
537            (),
538            BlockingTaskGuard::new(1),
539            Runtime::test(),
540        );
541
542        assert_eq!(
543            api.try_balance_changes_in_block(1.into()).unwrap(),
544            AddressMap::from_iter([
545                (Address::with_last_byte(3), U256::from(10)),
546                (Address::with_last_byte(4), U256::ZERO),
547                (Address::with_last_byte(5), U256::from(20)),
548                (Address::with_last_byte(6), U256::ZERO),
549            ])
550        );
551    }
552}