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