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 =
214 self.client.runtime_api(hash).await?.estimate_gas(transaction, block).await?;
215
216 log::trace!(
217 target: LOG_TARGET,
218 "estimate_gas result={gas_estimate:?}",
219 );
220 Ok(gas_estimate)
221 }
222
223 async fn call(
224 &self,
225 transaction: GenericTransactionV1,
226 block: Option<BlockId>,
227 state_overrides: Option<StateOverrideSetV1>,
228 ) -> RpcResult<Bytes> {
229 let block = block.unwrap_or_default();
230 let hash = self.client.block_hash_for_tag(block).await?;
231 let runtime_api = self.client.runtime_api(hash).await?;
232 let dry_run = runtime_api.dry_run(transaction, block, state_overrides).await?;
233 Ok(dry_run.data.into())
234 }
235
236 async fn send_raw_transaction(&self, transaction: Bytes) -> RpcResult<H256> {
237 let hash = H256(keccak_256(&transaction.0));
238 log::trace!(target: LOG_TARGET, "send_raw_transaction transaction: {transaction:?} ethereum_hash: {hash:?}");
239
240 if !self.allow_unprotected_txs {
241 let signed_transaction = TransactionSigned::decode(transaction.0.as_slice())
242 .map_err(|err| {
243 log::trace!(target: LOG_TARGET, "Transaction decoding failed. ethereum_hash: {hash:?}, error: {err:?}");
244 EthRpcError::InvalidTransaction
245 })?;
246
247 let is_chain_id_provided = match signed_transaction {
248 TransactionSigned::Transaction7702Signed(tx) => {
249 tx.transaction_7702_unsigned.chain_id != U256::zero()
250 },
251 TransactionSigned::Transaction4844Signed(tx) => {
252 tx.transaction_4844_unsigned.chain_id != U256::zero()
253 },
254 TransactionSigned::Transaction1559Signed(tx) => {
255 tx.transaction_1559_unsigned.chain_id != U256::zero()
256 },
257 TransactionSigned::Transaction2930Signed(tx) => {
258 tx.transaction_2930_unsigned.chain_id != U256::zero()
259 },
260 TransactionSigned::TransactionLegacySigned(tx) => {
261 tx.transaction_legacy_unsigned.chain_id.is_some()
262 },
263 };
264
265 if !is_chain_id_provided {
266 log::trace!(target: LOG_TARGET, "Invalid Transaction: transaction doesn't include a chain-id. ethereum_hash: {hash:?}");
267 Err(EthRpcError::InvalidTransaction)?;
268 }
269 }
270
271 let call = subxt_client::tx().revive().eth_transact(transaction.0);
272
273 let receiver = self.client.block_notifier().map(|sender| sender.subscribe());
275
276 let tx_status = self.client.submit(call).await.map_err(|err| {
278 log::trace!(target: LOG_TARGET, "send_raw_transaction ethereum_hash: {hash:?} failed: {err:?}");
279 err
280 })?;
281
282 if matches!(tx_status, TransactionStatus::Future) {
283 return Ok(hash);
284 }
285
286 if let Some(mut receiver) = receiver {
288 loop {
289 if let Ok(block_hash) = receiver.recv().await {
290 let Ok(Some(block)) = self.client.block_by_hash(&block_hash).await else {
291 log::debug!(target: LOG_TARGET, "Could not find the block with the received hash: {hash:?}.");
292 continue;
293 };
294 let Some(evm_block) = self.client.evm_block(block, false).await else {
295 log::debug!(target: LOG_TARGET, "Failed to get the EVM block for substrate block with hash: {hash:?}");
296 continue;
297 };
298 if evm_block.transactions.contains_tx(hash) {
299 log::debug!(target: LOG_TARGET, "{hash:} was included in a block");
300 break;
301 }
302 }
303 }
304 }
305
306 log::debug!(target: LOG_TARGET, "send_raw_transaction hash: {hash:?}");
307 Ok(hash)
308 }
309
310 async fn send_transaction(&self, mut transaction: GenericTransactionV1) -> RpcResult<H256> {
311 log::debug!(target: LOG_TARGET, "{transaction:#?}");
312
313 let Some(from) = transaction.from else {
314 log::debug!(target: LOG_TARGET, "Transaction must have a sender");
315 return Err(EthRpcError::InvalidTransaction.into());
316 };
317
318 let account = self
319 .accounts
320 .iter()
321 .find(|account| account.address() == from)
322 .ok_or(EthRpcError::AccountNotFound(from))?;
323
324 if transaction.gas.is_none() {
325 transaction.gas = Some(self.estimate_gas(transaction.clone(), None).await?);
326 }
327
328 if transaction.gas_price.is_none() {
329 transaction.gas_price = Some(self.gas_price().await?);
330 }
331
332 if transaction.nonce.is_none() {
333 transaction.nonce = Some(self.get_transaction_count(from, Default::default()).await?);
334 }
335
336 if transaction.chain_id.is_none() {
337 transaction.chain_id = Some(self.chain_id().await?);
338 }
339
340 let tx = GenericTransaction::from(transaction)
341 .try_into_unsigned()
342 .map_err(|_| EthRpcError::InvalidTransaction)?;
343 let payload = account.sign_transaction(tx).signed_payload();
344 self.send_raw_transaction(Bytes(payload)).await
345 }
346
347 async fn get_block_by_hash(
348 &self,
349 block_hash: H256,
350 hydrated_transactions: bool,
351 ) -> RpcResult<Option<BlockV1>> {
352 let Some(block) = self.client.block_by_ethereum_hash(&block_hash).await? else {
353 return Ok(None);
354 };
355 let block = self.client.evm_block(block, hydrated_transactions).await;
356 Ok(block)
357 }
358
359 async fn get_balance(&self, address: H160, block: BlockId) -> RpcResult<U256> {
360 let hash = self.client.block_hash_for_tag(block).await?;
361 let runtime_api = self.client.runtime_api(hash).await?;
362 let balance = runtime_api.balance(address).await?;
363 Ok(balance)
364 }
365
366 async fn chain_id(&self) -> RpcResult<U256> {
367 Ok(self.client.chain_id().into())
368 }
369
370 async fn gas_price(&self) -> RpcResult<U256> {
371 let hash = self.client.block_hash_for_tag(Default::default()).await?;
372 let runtime_api = self.client.runtime_api(hash).await?;
373 Ok(runtime_api.gas_price().await?)
374 }
375
376 async fn max_priority_fee_per_gas(&self) -> RpcResult<U256> {
377 Ok(Default::default())
380 }
381
382 async fn get_code(&self, address: H160, block: BlockId) -> RpcResult<Bytes> {
383 let hash = self.client.block_hash_for_tag(block).await?;
384 let code = self.client.runtime_api(hash).await?.code(address).await?;
385 Ok(code.into())
386 }
387
388 async fn accounts(&self) -> RpcResult<Vec<H160>> {
389 Ok(self.accounts.iter().map(|account| account.address()).collect())
390 }
391
392 async fn get_block_by_number(
393 &self,
394 block_number: BlockNumberOrTag,
395 hydrated_transactions: bool,
396 ) -> RpcResult<Option<BlockV1>> {
397 let Some(block) = self.client.block_by_number_or_tag(&block_number).await? else {
398 return Ok(None);
399 };
400 let block = self.client.evm_block(block, hydrated_transactions).await;
401 Ok(block)
402 }
403
404 async fn get_block_receipts(&self, block: BlockId) -> RpcResult<Option<Vec<ReceiptInfo>>> {
405 let receipts = self.client.block_receipts(block).await?;
406 Ok(receipts)
407 }
408
409 async fn get_block_transaction_count_by_hash(
410 &self,
411 block_hash: Option<H256>,
412 ) -> RpcResult<Option<U256>> {
413 let block_hash = if let Some(block_hash) = block_hash {
414 block_hash
415 } else {
416 self.client.latest_block().await.hash()
417 };
418
419 let Some(substrate_hash) = self.client.resolve_substrate_hash(&block_hash).await else {
420 return Ok(None);
421 };
422
423 Ok(self.client.receipts_count_per_block(&substrate_hash).await.map(U256::from))
424 }
425
426 async fn get_block_transaction_count_by_number(
427 &self,
428 block: Option<BlockNumberOrTag>,
429 ) -> RpcResult<Option<U256>> {
430 let substrate_hash = if let Some(block) = self
431 .client
432 .block_by_number_or_tag(&block.unwrap_or_else(|| BlockNumberOrTag::Latest))
433 .await?
434 {
435 block.block_hash()
436 } else {
437 return Ok(None);
438 };
439
440 Ok(self.client.receipts_count_per_block(&substrate_hash).await.map(U256::from))
441 }
442
443 async fn get_logs(&self, filter: Option<Filter>) -> RpcResult<FilterResults> {
444 let logs = self.client.logs(filter).await?;
445 Ok(FilterResults::Logs(logs))
446 }
447
448 async fn get_storage_at(
449 &self,
450 address: H160,
451 storage_slot: U256,
452 block: BlockId,
453 ) -> RpcResult<Bytes> {
454 let hash = self.client.block_hash_for_tag(block).await?;
455 let runtime_api = self.client.runtime_api(hash).await?;
456 let bytes = match runtime_api.get_storage(address, storage_slot.to_big_endian()).await {
457 Ok(value) => value.unwrap_or([0u8; 32].into()),
458 Err(ClientError::ContractNotFound) => {
460 log::trace!(target: LOG_TARGET, "get_storage_at: ContractNotFound for {address:?}, returning zero");
461 [0u8; 32].into()
462 },
463 Err(err) => return Err(err.into()),
464 };
465 Ok(bytes.into())
466 }
467
468 async fn get_transaction_by_block_hash_and_index(
469 &self,
470 block_hash: H256,
471 transaction_index: U256,
472 ) -> RpcResult<Option<TransactionInfoV1>> {
473 let Some(substrate_block_hash) = self.client.resolve_substrate_hash(&block_hash).await
474 else {
475 return Ok(None);
476 };
477 self.get_transaction_by_substrate_block_hash_and_index(
478 substrate_block_hash,
479 transaction_index,
480 )
481 .await
482 }
483
484 async fn get_transaction_by_block_number_and_index(
485 &self,
486 block: BlockNumberOrTag,
487 transaction_index: U256,
488 ) -> RpcResult<Option<TransactionInfoV1>> {
489 let Some(block) = self.client.block_by_number_or_tag(&block).await? else {
490 return Ok(None);
491 };
492 self.get_transaction_by_substrate_block_hash_and_index(
493 block.block_hash(),
494 transaction_index,
495 )
496 .await
497 }
498
499 async fn get_transaction_by_hash(
500 &self,
501 transaction_hash: H256,
502 ) -> RpcResult<Option<TransactionInfoV1>> {
503 let receipt = self.client.receipt(&transaction_hash).await;
504 let signed_tx = self.client.signed_tx_by_hash(&transaction_hash).await;
505 if let (Some(receipt), Some(signed_tx)) = (receipt, signed_tx) {
506 return Ok(Some(receipt.transaction_info(signed_tx)));
507 }
508
509 Ok(None)
510 }
511
512 async fn get_transaction_count(&self, address: H160, block: BlockId) -> RpcResult<U256> {
513 let hash = self.client.block_hash_for_tag(block).await?;
514 let runtime_api = self.client.runtime_api(hash).await?;
515 let nonce = runtime_api.nonce(address).await?;
516 Ok(nonce)
517 }
518
519 async fn web3_client_version(&self) -> RpcResult<String> {
520 let git_revision = env!("GIT_REVISION");
521 let rustc_version = env!("RUSTC_VERSION");
522 let target = env!("TARGET");
523 Ok(format!("eth-rpc/{git_revision}/{target}/{rustc_version}"))
524 }
525
526 async fn fee_history(
527 &self,
528 block_count: U256,
529 newest_block: BlockNumberOrTag,
530 reward_percentiles: Option<Vec<f64>>,
531 ) -> RpcResult<FeeHistoryResult> {
532 let block_count: u32 = block_count.try_into().map_err(|_| EthRpcError::ConversionError)?;
533
534 if block_count == 0 {
537 return Ok(FeeHistoryResult {
538 oldest_block: U256::zero(),
539 base_fee_per_gas: vec![],
540 gas_used_ratio: vec![],
541 reward: vec![],
542 });
543 }
544
545 if let Some(percentiles) = reward_percentiles.as_deref() {
548 validate_reward_percentiles(percentiles)
549 .map_err(EthRpcError::InvalidRewardPercentiles)?;
550 }
551
552 let result = self.client.fee_history(block_count, newest_block, reward_percentiles).await?;
553 Ok(result)
554 }
555
556 async fn eth_subscribe(
557 &self,
558 pending: PendingSubscriptionSink,
559 kind: SubscriptionKind,
560 options: Option<SubscriptionOptions>,
561 ) {
562 let Some(subscription_parameters) = SubscriptionParameters::new(kind, options) else {
563 return pending
564 .reject(ErrorObjectOwned::owned(
565 jsonrpsee::types::error::INVALID_PARAMS_CODE,
566 "Invalid subscription parameters",
567 None::<()>,
568 ))
569 .await;
570 };
571 let Ok(sink) = pending.accept().await else {
572 return;
573 };
574
575 let stream: Pin<
576 Box<dyn Stream<Item = Result<SubscriptionItem, BroadcastStreamRecvError>> + Send>,
577 > = match subscription_parameters {
578 SubscriptionParameters::NewBlockHeaders => Box::pin(
579 BroadcastStream::new(self.client.get_block_subscription_rx())
580 .map_ok(|block| SubscriptionItem::BlockHeader(BlockHeader::from(block))),
581 ) as _,
582 SubscriptionParameters::Logs(filter) => Box::pin(
583 BroadcastStream::new(self.client.get_log_subscription_rx())
584 .try_filter(move |log| futures::future::ready(filter.matches(log)))
585 .map_ok(SubscriptionItem::Log),
586 ) as _,
587 };
588 let _ = tokio::spawn(Self::handle_subscription_forwarding(sink, stream));
589 }
590}
591
592impl EthRpcServerImpl {
593 async fn get_transaction_by_substrate_block_hash_and_index(
594 &self,
595 substrate_block_hash: H256,
596 transaction_index: U256,
597 ) -> RpcResult<Option<TransactionInfoV1>> {
598 let Some(receipt) = self
599 .client
600 .receipt_by_hash_and_index(
601 &substrate_block_hash,
602 transaction_index.try_into().map_err(|_| EthRpcError::ConversionError)?,
603 )
604 .await
605 else {
606 return Ok(None);
607 };
608 let Some(signed_tx) = self.client.signed_tx_by_hash(&receipt.transaction_hash).await else {
609 return Ok(None);
610 };
611
612 Ok(Some(receipt.transaction_info(signed_tx)))
613 }
614
615 async fn handle_subscription_forwarding(
616 sink: SubscriptionSink,
617 mut stream: Pin<
618 Box<dyn Stream<Item = Result<SubscriptionItem, BroadcastStreamRecvError>> + Send>,
619 >,
620 ) {
621 loop {
622 tokio::select! {
623 _ = sink.closed() => break,
624 item = stream.next() => {
625 match item {
626 None => break,
628 Some(Ok(sub_item)) => {
630 let msg = SubscriptionMessage::from_json(&sub_item)
631 .expect("SubscriptionItem is serializable; qed");
632 if sink.send(msg).await.is_err() {
633 break;
634 }
635 },
636 Some(Err(BroadcastStreamRecvError::Lagged(count))) => {
638 log::warn!(
639 target: LOG_TARGET,
640 "Subscription lagged, skipped {count} messages"
641 );
642 },
643 }
644 }
645 }
646 }
647 }
648}
649
650#[cfg(test)]
651mod error_codes_tests {
652 use super::*;
653 use jsonrpsee::types::error::{CALL_EXECUTION_FAILED_CODE, INVALID_PARAMS_CODE};
654
655 #[test]
656 fn eth_rpc_error_maps_to_expected_code_and_message() {
657 let cases: Vec<(EthRpcError, i32)> = vec![
658 (EthRpcError::RlpError(rlp::DecoderError::RlpIsTooShort), CALL_EXECUTION_FAILED_CODE),
659 (EthRpcError::ConversionError, INVALID_PARAMS_CODE),
660 (EthRpcError::InvalidSignature, CALL_EXECUTION_FAILED_CODE),
661 (EthRpcError::AccountNotFound(H160::repeat_byte(0xab)), CALL_EXECUTION_FAILED_CODE),
662 (EthRpcError::InvalidTransaction, CALL_EXECUTION_FAILED_CODE),
663 (
664 EthRpcError::TransactionTypeNotSupported(Byte::from(0x7eu8)),
665 CALL_EXECUTION_FAILED_CODE,
666 ),
667 ];
668
669 for (err, expected_code) in cases {
670 let expected_message = err.to_string();
671 let obj = ErrorObjectOwned::from(err);
672 assert_eq!(obj.code(), expected_code, "unexpected code for `{expected_message}`");
673 assert_eq!(obj.message(), expected_message);
674 }
675 }
676}