1#![cfg_attr(docsrs, feature(doc_cfg))]
19
20pub use alloy_rpc_types::{BlockId, BlockNumberOrTag, Filter, FilterBlockOption};
21use client::ClientError;
22use futures::{Stream, StreamExt, TryStreamExt};
23use jsonrpsee::{
24 PendingSubscriptionSink, SubscriptionMessage, SubscriptionSink,
25 core::{RpcResult, async_trait},
26 types::{ErrorCode, ErrorObjectOwned},
27};
28use pallet_revive::evm::*;
29use pallet_revive_types::runtime_api::{
30 BlockV1, ExecutionTracerConfigV1, GenericTransactionV1, ReceiptGasInfoV1, StateOverrideSetV1,
31 SyntheticTransactionV1, TraceV1, TracerTypeV1, TransactionInfoV1,
32};
33use sp_core::{H160, H256, U256};
34use sp_crypto_hashing::keccak_256;
35use std::pin::Pin;
36use subxt::rpcs::methods::legacy::TransactionStatus;
37use thiserror::Error;
38use tokio_stream::wrappers::{BroadcastStream, errors::BroadcastStreamRecvError};
39
40mod block_sync;
41pub(crate) use block_sync::{ChainMetadata, SyncLabel, SyncStateKey};
42pub mod cli;
43pub mod client;
44pub mod example;
45pub mod subxt_client;
46
47#[cfg(test)]
48mod tests;
49
50mod block_info_provider;
51pub use block_info_provider::*;
52
53mod receipt_provider;
54pub use receipt_provider::*;
55
56mod fee_history_provider;
57pub use fee_history_provider::*;
58
59mod receipt_extractor;
60pub use receipt_extractor::*;
61
62mod apis;
63pub use apis::*;
64
65mod types;
66pub use types::*;
67
68async fn chain_id(
70 api: &subxt::OnlineClient<subxt_client::SrcChainConfig>,
71) -> Result<u64, ClientError> {
72 let query = subxt_client::constants().revive().chain_id().unvalidated();
73 let at_block = api.at_current_block().await?;
74 at_block.constants().entry(query).map_err(|err| err.into())
75}
76
77pub const LOG_TARGET: &str = "eth-rpc";
78
79pub struct EthRpcServerImpl {
81 client: client::Client,
83
84 accounts: Vec<Account>,
86
87 allow_unprotected_txs: bool,
89
90 use_pending_for_estimate_gas: bool,
92}
93
94impl EthRpcServerImpl {
95 pub fn new(client: client::Client) -> Self {
97 Self {
98 client,
99 accounts: vec![],
100 allow_unprotected_txs: false,
101 use_pending_for_estimate_gas: false,
102 }
103 }
104
105 pub fn with_accounts(mut self, accounts: Vec<Account>) -> Self {
107 self.accounts = accounts;
108 self
109 }
110
111 pub fn with_allow_unprotected_txs(mut self, allow_unprotected_txs: bool) -> Self {
113 self.allow_unprotected_txs = allow_unprotected_txs;
114 self
115 }
116
117 pub fn with_use_pending_for_estimate_gas(mut self, use_pending_for_estimate_gas: bool) -> Self {
119 self.use_pending_for_estimate_gas = use_pending_for_estimate_gas;
120 self
121 }
122}
123
124#[derive(Error, Debug)]
126pub enum EthRpcError {
127 #[error("Client error: {0}")]
129 ClientError(#[from] ClientError),
130 #[error("Decoding error: {0}")]
132 RlpError(#[from] rlp::DecoderError),
133 #[error("Conversion error")]
135 ConversionError,
136 #[error("Invalid signature")]
138 InvalidSignature,
139 #[error("Account not found for address {0:?}")]
141 AccountNotFound(H160),
142 #[error("Invalid transaction")]
144 InvalidTransaction,
145 #[error("Invalid transaction {0:?}")]
147 TransactionTypeNotSupported(Byte),
148 #[error("{0}")]
150 InvalidRewardPercentiles(String),
151}
152
153impl From<EthRpcError> for ErrorObjectOwned {
154 fn from(value: EthRpcError) -> Self {
155 use jsonrpsee::types::error::CALL_EXECUTION_FAILED_CODE;
156 let message = value.to_string();
157 let code = match value {
158 EthRpcError::ClientError(err) => return Self::from(err),
160 EthRpcError::ConversionError => ErrorCode::InvalidParams.code(),
161 EthRpcError::RlpError(_) |
163 EthRpcError::InvalidSignature |
164 EthRpcError::AccountNotFound(_) |
165 EthRpcError::InvalidTransaction |
166 EthRpcError::InvalidRewardPercentiles(_) |
167 EthRpcError::TransactionTypeNotSupported(_) => CALL_EXECUTION_FAILED_CODE,
168 };
169 Self::owned::<String>(code, message, None)
170 }
171}
172
173#[async_trait]
174impl EthRpcServer for EthRpcServerImpl {
175 async fn net_version(&self) -> RpcResult<String> {
176 Ok(self.client.chain_id().to_string())
177 }
178
179 async fn net_listening(&self) -> RpcResult<bool> {
180 let syncing = self.client.syncing().await?;
181 let listening = matches!(syncing, SyncingStatus::Bool(false));
182 Ok(listening)
183 }
184
185 async fn syncing(&self) -> RpcResult<SyncingStatus> {
186 Ok(self.client.syncing().await?)
187 }
188
189 async fn block_number(&self) -> RpcResult<U256> {
190 let number = self.client.block_number().await?;
191 Ok(number.into())
192 }
193
194 async fn get_transaction_receipt(
195 &self,
196 transaction_hash: H256,
197 ) -> RpcResult<Option<ReceiptInfo>> {
198 let receipt = self.client.receipt(&transaction_hash).await;
199 Ok(receipt)
200 }
201
202 async fn estimate_gas(
207 &self,
208 transaction: GenericTransactionV1,
209 block: Option<BlockNumberOrTag>,
210 ) -> RpcResult<U256> {
211 log::trace!(target: LOG_TARGET, "estimate_gas transaction={transaction:?} block={block:?}");
212
213 let block = block.unwrap_or_else(|| {
214 if self.use_pending_for_estimate_gas {
215 BlockNumberOrTag::Pending
216 } else {
217 Default::default()
218 }
219 });
220 let block = BlockId::from(block);
221 let hash = self.client.block_hash_for_tag(block).await?;
222 let gas_estimate = self
223 .client
224 .runtime_api(hash)
225 .await?
226 .estimate_gas(transaction, block)
227 .ok_or(ClientError::UnsupportedRuntimeApiMethod("eth_estimate_gas"))?
228 .await?;
229
230 log::trace!(
231 target: LOG_TARGET,
232 "estimate_gas result={gas_estimate:?}",
233 );
234 Ok(gas_estimate)
235 }
236
237 async fn call(
238 &self,
239 transaction: GenericTransactionV1,
240 block: Option<BlockId>,
241 state_overrides: Option<StateOverrideSetV1>,
242 ) -> RpcResult<Bytes> {
243 let block = block.unwrap_or_default();
244 let hash = self.client.block_hash_for_tag(block).await?;
245 let runtime_api = self.client.runtime_api(hash).await?;
246 let dry_run = runtime_api
247 .dry_run(transaction, block, state_overrides)
248 .ok_or(ClientError::UnsupportedRuntimeApiMethod("eth_transact"))?
249 .await?;
250 Ok(dry_run.data.into())
251 }
252
253 async fn send_raw_transaction(&self, transaction: Bytes) -> RpcResult<H256> {
254 let hash = H256(keccak_256(&transaction.0));
255 log::trace!(target: LOG_TARGET, "send_raw_transaction transaction: {transaction:?} ethereum_hash: {hash:?}");
256
257 if !self.allow_unprotected_txs {
258 let signed_transaction = TransactionSigned::decode(transaction.0.as_slice())
259 .map_err(|err| {
260 log::trace!(target: LOG_TARGET, "Transaction decoding failed. ethereum_hash: {hash:?}, error: {err:?}");
261 EthRpcError::InvalidTransaction
262 })?;
263
264 let is_chain_id_provided = match signed_transaction {
265 TransactionSigned::Transaction7702Signed(tx) => {
266 tx.transaction_7702_unsigned.chain_id != U256::zero()
267 },
268 TransactionSigned::Transaction4844Signed(tx) => {
269 tx.transaction_4844_unsigned.chain_id != U256::zero()
270 },
271 TransactionSigned::Transaction1559Signed(tx) => {
272 tx.transaction_1559_unsigned.chain_id != U256::zero()
273 },
274 TransactionSigned::Transaction2930Signed(tx) => {
275 tx.transaction_2930_unsigned.chain_id != U256::zero()
276 },
277 TransactionSigned::TransactionLegacySigned(tx) => {
278 tx.transaction_legacy_unsigned.chain_id.is_some()
279 },
280 };
281
282 if !is_chain_id_provided {
283 log::trace!(target: LOG_TARGET, "Invalid Transaction: transaction doesn't include a chain-id. ethereum_hash: {hash:?}");
284 Err(EthRpcError::InvalidTransaction)?;
285 }
286 }
287
288 let call = subxt_client::tx().revive().eth_transact(transaction.0);
289
290 let receiver = self.client.block_notifier().map(|sender| sender.subscribe());
292
293 let tx_status = self.client.submit(call).await.map_err(|err| {
295 log::trace!(target: LOG_TARGET, "send_raw_transaction ethereum_hash: {hash:?} failed: {err:?}");
296 err
297 })?;
298
299 if matches!(tx_status, TransactionStatus::Future) {
300 return Ok(hash);
301 }
302
303 if let Some(mut receiver) = receiver {
305 loop {
306 if let Ok(block_hash) = receiver.recv().await {
307 let Ok(Some(block)) = self.client.block_by_hash(&block_hash).await else {
308 log::debug!(target: LOG_TARGET, "Could not find the block with the received hash: {hash:?}.");
309 continue;
310 };
311 let Some(evm_block) = self.client.evm_block(block, false).await else {
312 log::debug!(target: LOG_TARGET, "Failed to get the EVM block for substrate block with hash: {hash:?}");
313 continue;
314 };
315 if evm_block.transactions.contains_tx(hash) {
316 log::debug!(target: LOG_TARGET, "{hash:} was included in a block");
317 break;
318 }
319 }
320 }
321 }
322
323 log::debug!(target: LOG_TARGET, "send_raw_transaction hash: {hash:?}");
324 Ok(hash)
325 }
326
327 async fn send_transaction(&self, mut transaction: GenericTransactionV1) -> RpcResult<H256> {
328 log::debug!(target: LOG_TARGET, "{transaction:#?}");
329
330 let Some(from) = transaction.from else {
331 log::debug!(target: LOG_TARGET, "Transaction must have a sender");
332 return Err(EthRpcError::InvalidTransaction.into());
333 };
334
335 let account = self
336 .accounts
337 .iter()
338 .find(|account| account.address() == from)
339 .ok_or(EthRpcError::AccountNotFound(from))?;
340
341 if transaction.gas.is_none() {
342 transaction.gas = Some(self.estimate_gas(transaction.clone(), None).await?);
343 }
344
345 if transaction.gas_price.is_none() {
346 transaction.gas_price = Some(self.gas_price().await?);
347 }
348
349 if transaction.nonce.is_none() {
350 transaction.nonce = Some(self.get_transaction_count(from, Default::default()).await?);
351 }
352
353 if transaction.chain_id.is_none() {
354 transaction.chain_id = Some(self.chain_id().await?);
355 }
356
357 let tx = GenericTransaction::from(transaction)
358 .try_into_unsigned()
359 .map_err(|_| EthRpcError::InvalidTransaction)?;
360 let payload = account.sign_transaction(tx).signed_payload();
361 self.send_raw_transaction(Bytes(payload)).await
362 }
363
364 async fn get_block_by_hash(
365 &self,
366 block_hash: H256,
367 hydrated_transactions: bool,
368 ) -> RpcResult<Option<BlockV1>> {
369 let Some(block) = self.client.block_by_ethereum_hash(&block_hash).await? else {
370 return Ok(None);
371 };
372 let block = self.client.evm_block(block, hydrated_transactions).await;
373 Ok(block)
374 }
375
376 async fn get_balance(&self, address: H160, block: BlockId) -> RpcResult<U256> {
377 let hash = self.client.block_hash_for_tag(block).await?;
378 let runtime_api = self.client.runtime_api(hash).await?;
379 let balance = runtime_api
380 .balance(address)
381 .ok_or(ClientError::UnsupportedRuntimeApiMethod("balance"))?
382 .await?;
383 Ok(balance)
384 }
385
386 async fn chain_id(&self) -> RpcResult<U256> {
387 Ok(self.client.chain_id().into())
388 }
389
390 async fn gas_price(&self) -> RpcResult<U256> {
391 let hash = self.client.block_hash_for_tag(Default::default()).await?;
392 let runtime_api = self.client.runtime_api(hash).await?;
393 let gas_price = runtime_api
394 .gas_price()
395 .ok_or(ClientError::UnsupportedRuntimeApiMethod("gas_price"))?
396 .await?;
397 Ok(gas_price)
398 }
399
400 async fn max_priority_fee_per_gas(&self) -> RpcResult<U256> {
401 Ok(Default::default())
404 }
405
406 async fn get_code(&self, address: H160, block: BlockId) -> RpcResult<Bytes> {
407 let hash = self.client.block_hash_for_tag(block).await?;
408 let code = self
409 .client
410 .runtime_api(hash)
411 .await?
412 .code(address)
413 .ok_or(ClientError::UnsupportedRuntimeApiMethod("code"))?
414 .await?;
415 Ok(code.into())
416 }
417
418 async fn accounts(&self) -> RpcResult<Vec<H160>> {
419 Ok(self.accounts.iter().map(|account| account.address()).collect())
420 }
421
422 async fn get_block_by_number(
423 &self,
424 block_number: BlockNumberOrTag,
425 hydrated_transactions: bool,
426 ) -> RpcResult<Option<BlockV1>> {
427 let Some(block) = self.client.block_by_number_or_tag(&block_number).await? else {
428 return Ok(None);
429 };
430 let block = self.client.evm_block(block, hydrated_transactions).await;
431 Ok(block)
432 }
433
434 async fn get_block_receipts(&self, block: BlockId) -> RpcResult<Option<Vec<ReceiptInfo>>> {
435 let receipts = self.client.block_receipts(block).await?;
436 Ok(receipts)
437 }
438
439 async fn get_block_transaction_count_by_hash(
440 &self,
441 block_hash: Option<H256>,
442 ) -> RpcResult<Option<U256>> {
443 let block_hash = if let Some(block_hash) = block_hash {
444 block_hash
445 } else {
446 self.client.latest_block().await.hash()
447 };
448
449 let Some(substrate_hash) = self.client.resolve_substrate_hash(&block_hash).await else {
450 return Ok(None);
451 };
452
453 Ok(self.client.receipts_count_per_block(&substrate_hash).await.map(U256::from))
454 }
455
456 async fn get_block_transaction_count_by_number(
457 &self,
458 block: Option<BlockNumberOrTag>,
459 ) -> RpcResult<Option<U256>> {
460 let substrate_hash = if let Some(block) = self
461 .client
462 .block_by_number_or_tag(&block.unwrap_or_else(|| BlockNumberOrTag::Latest))
463 .await?
464 {
465 block.block_hash()
466 } else {
467 return Ok(None);
468 };
469
470 Ok(self.client.receipts_count_per_block(&substrate_hash).await.map(U256::from))
471 }
472
473 async fn get_logs(&self, filter: Option<Filter>) -> RpcResult<FilterResults> {
474 let logs = self.client.logs(filter).await?;
475 Ok(FilterResults::Logs(logs))
476 }
477
478 async fn get_storage_at(
479 &self,
480 address: H160,
481 storage_slot: U256,
482 block: BlockId,
483 ) -> RpcResult<Bytes> {
484 let hash = self.client.block_hash_for_tag(block).await?;
485 let runtime_api = self.client.runtime_api(hash).await?;
486 let get_storage = runtime_api
487 .get_storage(address, storage_slot.to_big_endian())
488 .ok_or(ClientError::UnsupportedRuntimeApiMethod("get_storage"))?;
489 let bytes = match get_storage.await {
490 Ok(value) => value.unwrap_or([0u8; 32].into()),
491 Err(ClientError::ContractNotFound) => {
493 log::trace!(target: LOG_TARGET, "get_storage_at: ContractNotFound for {address:?}, returning zero");
494 [0u8; 32].into()
495 },
496 Err(err) => return Err(err.into()),
497 };
498 Ok(bytes.into())
499 }
500
501 async fn get_transaction_by_block_hash_and_index(
502 &self,
503 block_hash: H256,
504 transaction_index: U256,
505 ) -> RpcResult<Option<TransactionInfoV1>> {
506 let Some(substrate_block_hash) = self.client.resolve_substrate_hash(&block_hash).await
507 else {
508 return Ok(None);
509 };
510 self.get_transaction_by_substrate_block_hash_and_index(
511 substrate_block_hash,
512 transaction_index,
513 )
514 .await
515 }
516
517 async fn get_transaction_by_block_number_and_index(
518 &self,
519 block: BlockNumberOrTag,
520 transaction_index: U256,
521 ) -> RpcResult<Option<TransactionInfoV1>> {
522 let Some(block) = self.client.block_by_number_or_tag(&block).await? else {
523 return Ok(None);
524 };
525 self.get_transaction_by_substrate_block_hash_and_index(
526 block.block_hash(),
527 transaction_index,
528 )
529 .await
530 }
531
532 async fn get_transaction_by_hash(
533 &self,
534 transaction_hash: H256,
535 ) -> RpcResult<Option<TransactionInfoV1>> {
536 let receipt = self.client.receipt(&transaction_hash).await;
537 let signed_tx = self.client.signed_tx_by_hash(&transaction_hash).await;
538 if let (Some(receipt), Some(signed_tx)) = (receipt, signed_tx) {
539 return Ok(Some(receipt.transaction_info(signed_tx)));
540 }
541
542 Ok(None)
543 }
544
545 async fn get_transaction_count(&self, address: H160, block: BlockId) -> RpcResult<U256> {
546 let hash = self.client.block_hash_for_tag(block).await?;
547 let runtime_api = self.client.runtime_api(hash).await?;
548 let nonce = runtime_api
549 .nonce(address)
550 .ok_or(ClientError::UnsupportedRuntimeApiMethod("nonce"))?
551 .await?;
552 Ok(nonce)
553 }
554
555 async fn web3_client_version(&self) -> RpcResult<String> {
556 let git_revision = env!("GIT_REVISION");
557 let rustc_version = env!("RUSTC_VERSION");
558 let target = env!("TARGET");
559 Ok(format!("eth-rpc/{git_revision}/{target}/{rustc_version}"))
560 }
561
562 async fn fee_history(
563 &self,
564 block_count: U256,
565 newest_block: BlockNumberOrTag,
566 reward_percentiles: Option<Vec<f64>>,
567 ) -> RpcResult<FeeHistoryResult> {
568 let block_count: u32 = block_count.try_into().map_err(|_| EthRpcError::ConversionError)?;
569
570 if block_count == 0 {
573 return Ok(FeeHistoryResult {
574 oldest_block: U256::zero(),
575 base_fee_per_gas: vec![],
576 gas_used_ratio: vec![],
577 reward: vec![],
578 });
579 }
580
581 if let Some(percentiles) = reward_percentiles.as_deref() {
584 validate_reward_percentiles(percentiles)
585 .map_err(EthRpcError::InvalidRewardPercentiles)?;
586 }
587
588 let result = self.client.fee_history(block_count, newest_block, reward_percentiles).await?;
589 Ok(result)
590 }
591
592 async fn eth_subscribe(
593 &self,
594 pending: PendingSubscriptionSink,
595 kind: SubscriptionKind,
596 options: Option<SubscriptionOptions>,
597 ) {
598 let Some(subscription_parameters) = SubscriptionParameters::new(kind, options) else {
599 return pending
600 .reject(ErrorObjectOwned::owned(
601 jsonrpsee::types::error::INVALID_PARAMS_CODE,
602 "Invalid subscription parameters",
603 None::<()>,
604 ))
605 .await;
606 };
607 let Ok(sink) = pending.accept().await else {
608 return;
609 };
610
611 let stream: Pin<
612 Box<dyn Stream<Item = Result<SubscriptionItem, BroadcastStreamRecvError>> + Send>,
613 > = match subscription_parameters {
614 SubscriptionParameters::NewBlockHeaders => Box::pin(
615 BroadcastStream::new(self.client.get_block_subscription_rx())
616 .map_ok(|block| SubscriptionItem::BlockHeader(BlockHeader::from(block))),
617 ) as _,
618 SubscriptionParameters::Logs(filter) => Box::pin(
619 BroadcastStream::new(self.client.get_log_subscription_rx())
620 .try_filter(move |log| futures::future::ready(filter.matches(log)))
621 .map_ok(SubscriptionItem::Log),
622 ) as _,
623 };
624 let _ = tokio::spawn(Self::handle_subscription_forwarding(sink, stream));
625 }
626}
627
628impl EthRpcServerImpl {
629 async fn get_transaction_by_substrate_block_hash_and_index(
630 &self,
631 substrate_block_hash: H256,
632 transaction_index: U256,
633 ) -> RpcResult<Option<TransactionInfoV1>> {
634 let Some(receipt) = self
635 .client
636 .receipt_by_hash_and_index(
637 &substrate_block_hash,
638 transaction_index.try_into().map_err(|_| EthRpcError::ConversionError)?,
639 )
640 .await
641 else {
642 return Ok(None);
643 };
644 let Some(signed_tx) = self.client.signed_tx_by_hash(&receipt.transaction_hash).await else {
645 return Ok(None);
646 };
647
648 Ok(Some(receipt.transaction_info(signed_tx)))
649 }
650
651 async fn handle_subscription_forwarding(
652 sink: SubscriptionSink,
653 mut stream: Pin<
654 Box<dyn Stream<Item = Result<SubscriptionItem, BroadcastStreamRecvError>> + Send>,
655 >,
656 ) {
657 loop {
658 tokio::select! {
659 _ = sink.closed() => break,
660 item = stream.next() => {
661 match item {
662 None => break,
664 Some(Ok(sub_item)) => {
666 let msg = SubscriptionMessage::from_json(&sub_item)
667 .expect("SubscriptionItem is serializable; qed");
668 if sink.send(msg).await.is_err() {
669 break;
670 }
671 },
672 Some(Err(BroadcastStreamRecvError::Lagged(count))) => {
674 log::warn!(
675 target: LOG_TARGET,
676 "Subscription lagged, skipped {count} messages"
677 );
678 },
679 }
680 }
681 }
682 }
683 }
684}
685
686#[cfg(test)]
687mod error_codes_tests {
688 use super::*;
689 use jsonrpsee::types::error::{CALL_EXECUTION_FAILED_CODE, INVALID_PARAMS_CODE};
690
691 #[test]
692 fn eth_rpc_error_maps_to_expected_code_and_message() {
693 let cases: Vec<(EthRpcError, i32)> = vec![
694 (EthRpcError::RlpError(rlp::DecoderError::RlpIsTooShort), CALL_EXECUTION_FAILED_CODE),
695 (EthRpcError::ConversionError, INVALID_PARAMS_CODE),
696 (EthRpcError::InvalidSignature, CALL_EXECUTION_FAILED_CODE),
697 (EthRpcError::AccountNotFound(H160::repeat_byte(0xab)), CALL_EXECUTION_FAILED_CODE),
698 (EthRpcError::InvalidTransaction, CALL_EXECUTION_FAILED_CODE),
699 (
700 EthRpcError::TransactionTypeNotSupported(Byte::from(0x7eu8)),
701 CALL_EXECUTION_FAILED_CODE,
702 ),
703 ];
704
705 for (err, expected_code) in cases {
706 let expected_message = err.to_string();
707 let obj = ErrorObjectOwned::from(err);
708 assert_eq!(obj.code(), expected_code, "unexpected code for `{expected_message}`");
709 assert_eq!(obj.message(), expected_message);
710 }
711 }
712}