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
27pub struct RethApi<Provider, EvmConfig> {
31 inner: Arc<RethApiInner<Provider, EvmConfig>>,
32}
33
34impl<Provider, EvmConfig> RethApi<Provider, EvmConfig> {
37 pub fn provider(&self) -> &Provider {
39 &self.inner.provider
40 }
41
42 pub fn evm_config(&self) -> &EvmConfig {
44 &self.inner.evm_config
45 }
46
47 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 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 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 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 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 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 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 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 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 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 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
308async 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
338async 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 ¬ification {
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 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 provider: Provider,
441 evm_config: EvmConfig,
443 blocking_task_guard: BlockingTaskGuard,
445 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)), (2, Some(0), None), (3, None, Some(10)), (4, Some(10), None), (5, Some(10), Some(20)), (6, Some(10), Some(0)), (7, Some(10), Some(10)), ]
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}