referrerpolicy=no-referrer-when-downgrade

pallet_revive_eth_rpc/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17//! The [`EthRpcServer`] RPC server implementation
18#![cfg_attr(docsrs, feature(doc_cfg))]
19
20use client::ClientError;
21use jsonrpsee::{
22	core::{async_trait, RpcResult},
23	types::{ErrorCode, ErrorObjectOwned},
24};
25use pallet_revive::evm::*;
26use sp_core::{keccak_256, H160, H256, U256};
27use thiserror::Error;
28use tokio::time::Duration;
29
30pub mod cli;
31pub mod client;
32pub mod example;
33pub mod subxt_client;
34
35#[cfg(test)]
36mod tests;
37
38mod block_info_provider;
39pub use block_info_provider::*;
40
41mod receipt_provider;
42pub use receipt_provider::*;
43
44mod fee_history_provider;
45pub use fee_history_provider::*;
46
47mod receipt_extractor;
48pub use receipt_extractor::*;
49
50mod apis;
51pub use apis::*;
52
53pub const LOG_TARGET: &str = "eth-rpc";
54
55/// An EVM RPC server implementation.
56pub struct EthRpcServerImpl {
57	/// The client used to interact with the substrate node.
58	client: client::Client,
59
60	/// The accounts managed by the server.
61	accounts: Vec<Account>,
62}
63
64impl EthRpcServerImpl {
65	/// Creates a new [`EthRpcServerImpl`].
66	pub fn new(client: client::Client) -> Self {
67		Self { client, accounts: vec![] }
68	}
69
70	/// Sets the accounts managed by the server.
71	pub fn with_accounts(mut self, accounts: Vec<Account>) -> Self {
72		self.accounts = accounts;
73		self
74	}
75}
76
77/// The error type for the EVM RPC server.
78#[derive(Error, Debug)]
79pub enum EthRpcError {
80	/// A [`ClientError`] wrapper error.
81	#[error("Client error: {0}")]
82	ClientError(#[from] ClientError),
83	/// A [`rlp::DecoderError`] wrapper error.
84	#[error("Decoding error: {0}")]
85	RlpError(#[from] rlp::DecoderError),
86	/// A Decimals conversion error.
87	#[error("Conversion error")]
88	ConversionError,
89	/// An invalid signature error.
90	#[error("Invalid signature")]
91	InvalidSignature,
92	/// The account was not found at the given address
93	#[error("Account not found for address {0:?}")]
94	AccountNotFound(H160),
95	/// Received an invalid transaction
96	#[error("Invalid transaction")]
97	InvalidTransaction,
98	/// Received an invalid transaction
99	#[error("Invalid transaction {0:?}")]
100	TransactionTypeNotSupported(Byte),
101}
102
103// TODO use https://eips.ethereum.org/EIPS/eip-1474#error-codes
104impl From<EthRpcError> for ErrorObjectOwned {
105	fn from(value: EthRpcError) -> Self {
106		match value {
107			EthRpcError::ClientError(err) => Self::from(err),
108			_ => Self::owned::<String>(ErrorCode::InvalidRequest.code(), value.to_string(), None),
109		}
110	}
111}
112
113#[async_trait]
114impl EthRpcServer for EthRpcServerImpl {
115	async fn net_version(&self) -> RpcResult<String> {
116		Ok(self.client.chain_id().to_string())
117	}
118
119	async fn net_listening(&self) -> RpcResult<bool> {
120		let syncing = self.client.syncing().await?;
121		let listening = matches!(syncing, SyncingStatus::Bool(false));
122		Ok(listening)
123	}
124
125	async fn syncing(&self) -> RpcResult<SyncingStatus> {
126		Ok(self.client.syncing().await?)
127	}
128
129	async fn block_number(&self) -> RpcResult<U256> {
130		let number = self.client.block_number().await?;
131		Ok(number.into())
132	}
133
134	async fn get_transaction_receipt(
135		&self,
136		transaction_hash: H256,
137	) -> RpcResult<Option<ReceiptInfo>> {
138		let receipt = self.client.receipt(&transaction_hash).await;
139		Ok(receipt)
140	}
141
142	async fn estimate_gas(
143		&self,
144		transaction: GenericTransaction,
145		block: Option<BlockNumberOrTag>,
146	) -> RpcResult<U256> {
147		log::trace!(target: LOG_TARGET, "estimate_gas transaction={transaction:?} block={block:?}");
148		let block = block.unwrap_or_default();
149		let hash = self.client.block_hash_for_tag(block.clone().into()).await?;
150		let runtime_api = self.client.runtime_api(hash);
151		let dry_run = runtime_api.dry_run(transaction, block.into()).await?;
152		log::trace!(target: LOG_TARGET, "estimate_gas result={dry_run:?}");
153		Ok(dry_run.eth_gas)
154	}
155
156	async fn call(
157		&self,
158		transaction: GenericTransaction,
159		block: Option<BlockNumberOrTagOrHash>,
160	) -> RpcResult<Bytes> {
161		let block = block.unwrap_or_default();
162		let hash = self.client.block_hash_for_tag(block.clone()).await?;
163		let runtime_api = self.client.runtime_api(hash);
164		let dry_run = runtime_api.dry_run(transaction, block).await?;
165		Ok(dry_run.data.into())
166	}
167
168	async fn send_raw_transaction(&self, transaction: Bytes) -> RpcResult<H256> {
169		let hash = H256(keccak_256(&transaction.0));
170		log::trace!(target: LOG_TARGET, "send_raw_transaction transaction: {transaction:?} ethereum_hash: {hash:?}");
171		let call = subxt_client::tx().revive().eth_transact(transaction.0);
172
173		// Subscribe to new block only when automine is enabled.
174		let receiver = self.client.block_notifier().map(|sender| sender.subscribe());
175
176		// Submit the transaction
177		self.client.submit(call).await.map_err(|err| {
178			log::trace!(target: LOG_TARGET, "send_raw_transaction ethereum_hash: {hash:?} failed: {err:?}");
179			err
180		})?;
181
182		log::trace!(target: LOG_TARGET, "send_raw_transaction with hash: {hash:?}");
183
184		// Wait for the transaction to be included in a block if automine is enabled
185		if let Some(mut receiver) = receiver {
186			if let Err(err) = tokio::time::timeout(Duration::from_millis(500), async {
187				loop {
188					if let Ok(block_hash) = receiver.recv().await {
189						let Ok(Some(block)) = self.client.block_by_hash(&block_hash).await else {
190							log::debug!(target: LOG_TARGET, "Could not find the block with the received hash: {hash:?}.");
191							continue
192						};
193						let Some(evm_block) = self.client.evm_block(block, false).await else {
194							log::debug!(target: LOG_TARGET, "Failed to get the EVM block for substrate block with hash: {hash:?}");
195							continue
196						};
197						if evm_block.transactions.contains_tx(hash) {
198							log::debug!(target: LOG_TARGET, "{hash:} was included in a block");
199							break;
200						}
201					}
202				}
203			})
204			.await
205			{
206				log::debug!(target: LOG_TARGET, "timeout waiting for new block: {err:?}");
207			}
208		}
209
210		log::debug!(target: LOG_TARGET, "send_raw_transaction hash: {hash:?}");
211		Ok(hash)
212	}
213
214	async fn send_transaction(&self, mut transaction: GenericTransaction) -> RpcResult<H256> {
215		log::debug!(target: LOG_TARGET, "{transaction:#?}");
216
217		let Some(from) = transaction.from else {
218			log::debug!(target: LOG_TARGET, "Transaction must have a sender");
219			return Err(EthRpcError::InvalidTransaction.into());
220		};
221
222		let account = self
223			.accounts
224			.iter()
225			.find(|account| account.address() == from)
226			.ok_or(EthRpcError::AccountNotFound(from))?;
227
228		if transaction.gas.is_none() {
229			transaction.gas = Some(self.estimate_gas(transaction.clone(), None).await?);
230		}
231
232		if transaction.gas_price.is_none() {
233			transaction.gas_price = Some(self.gas_price().await?);
234		}
235
236		if transaction.nonce.is_none() {
237			transaction.nonce =
238				Some(self.get_transaction_count(from, BlockTag::Latest.into()).await?);
239		}
240
241		if transaction.chain_id.is_none() {
242			transaction.chain_id = Some(self.chain_id().await?);
243		}
244
245		let tx = transaction.try_into_unsigned().map_err(|_| EthRpcError::InvalidTransaction)?;
246		let payload = account.sign_transaction(tx).signed_payload();
247		self.send_raw_transaction(Bytes(payload)).await
248	}
249
250	async fn get_block_by_hash(
251		&self,
252		block_hash: H256,
253		hydrated_transactions: bool,
254	) -> RpcResult<Option<Block>> {
255		let Some(block) = self.client.block_by_ethereum_hash(&block_hash).await? else {
256			return Ok(None);
257		};
258		let block = self.client.evm_block(block, hydrated_transactions).await;
259		Ok(block)
260	}
261
262	async fn get_balance(&self, address: H160, block: BlockNumberOrTagOrHash) -> RpcResult<U256> {
263		let hash = self.client.block_hash_for_tag(block).await?;
264		let runtime_api = self.client.runtime_api(hash);
265		let balance = runtime_api.balance(address).await?;
266		Ok(balance)
267	}
268
269	async fn chain_id(&self) -> RpcResult<U256> {
270		Ok(self.client.chain_id().into())
271	}
272
273	async fn gas_price(&self) -> RpcResult<U256> {
274		let hash = self.client.block_hash_for_tag(BlockTag::Latest.into()).await?;
275		let runtime_api = self.client.runtime_api(hash);
276		Ok(runtime_api.gas_price().await?)
277	}
278
279	async fn max_priority_fee_per_gas(&self) -> RpcResult<U256> {
280		// We do not support tips. Hence the recommended priority fee is
281		// always zero. The effective gas price will always be the base price.
282		Ok(Default::default())
283	}
284
285	async fn get_code(&self, address: H160, block: BlockNumberOrTagOrHash) -> RpcResult<Bytes> {
286		let hash = self.client.block_hash_for_tag(block).await?;
287		let code = self.client.runtime_api(hash).code(address).await?;
288		Ok(code.into())
289	}
290
291	async fn accounts(&self) -> RpcResult<Vec<H160>> {
292		Ok(self.accounts.iter().map(|account| account.address()).collect())
293	}
294
295	async fn get_block_by_number(
296		&self,
297		block_number: BlockNumberOrTag,
298		hydrated_transactions: bool,
299	) -> RpcResult<Option<Block>> {
300		let Some(block) = self.client.block_by_number_or_tag(&block_number).await? else {
301			return Ok(None);
302		};
303		let block = self.client.evm_block(block, hydrated_transactions).await;
304		Ok(block)
305	}
306
307	async fn get_block_transaction_count_by_hash(
308		&self,
309		block_hash: Option<H256>,
310	) -> RpcResult<Option<U256>> {
311		let block_hash = if let Some(block_hash) = block_hash {
312			block_hash
313		} else {
314			self.client.latest_block().await.hash()
315		};
316
317		let Some(substrate_hash) = self.client.resolve_substrate_hash(&block_hash).await else {
318			return Ok(None);
319		};
320
321		Ok(self.client.receipts_count_per_block(&substrate_hash).await.map(U256::from))
322	}
323
324	async fn get_block_transaction_count_by_number(
325		&self,
326		block: Option<BlockNumberOrTag>,
327	) -> RpcResult<Option<U256>> {
328		let substrate_hash = if let Some(block) = self
329			.client
330			.block_by_number_or_tag(&block.unwrap_or_else(|| BlockTag::Latest.into()))
331			.await?
332		{
333			block.hash()
334		} else {
335			return Ok(None);
336		};
337
338		Ok(self.client.receipts_count_per_block(&substrate_hash).await.map(U256::from))
339	}
340
341	async fn get_logs(&self, filter: Option<Filter>) -> RpcResult<FilterResults> {
342		let logs = self.client.logs(filter).await?;
343		Ok(FilterResults::Logs(logs))
344	}
345
346	async fn get_storage_at(
347		&self,
348		address: H160,
349		storage_slot: U256,
350		block: BlockNumberOrTagOrHash,
351	) -> RpcResult<Bytes> {
352		let hash = self.client.block_hash_for_tag(block).await?;
353		let runtime_api = self.client.runtime_api(hash);
354		let bytes = runtime_api.get_storage(address, storage_slot.to_big_endian()).await?;
355		Ok(bytes.unwrap_or([0u8; 32].into()).into())
356	}
357
358	async fn get_transaction_by_block_hash_and_index(
359		&self,
360		block_hash: H256,
361		transaction_index: U256,
362	) -> RpcResult<Option<TransactionInfo>> {
363		let Some(substrate_block_hash) = self.client.resolve_substrate_hash(&block_hash).await
364		else {
365			return Ok(None);
366		};
367		self.get_transaction_by_substrate_block_hash_and_index(
368			substrate_block_hash,
369			transaction_index,
370		)
371		.await
372	}
373
374	async fn get_transaction_by_block_number_and_index(
375		&self,
376		block: BlockNumberOrTag,
377		transaction_index: U256,
378	) -> RpcResult<Option<TransactionInfo>> {
379		let Some(block) = self.client.block_by_number_or_tag(&block).await? else {
380			return Ok(None);
381		};
382		self.get_transaction_by_substrate_block_hash_and_index(block.hash(), transaction_index)
383			.await
384	}
385
386	async fn get_transaction_by_hash(
387		&self,
388		transaction_hash: H256,
389	) -> RpcResult<Option<TransactionInfo>> {
390		let receipt = self.client.receipt(&transaction_hash).await;
391		let signed_tx = self.client.signed_tx_by_hash(&transaction_hash).await;
392		if let (Some(receipt), Some(signed_tx)) = (receipt, signed_tx) {
393			return Ok(Some(TransactionInfo::new(&receipt, signed_tx)));
394		}
395
396		Ok(None)
397	}
398
399	async fn get_transaction_count(
400		&self,
401		address: H160,
402		block: BlockNumberOrTagOrHash,
403	) -> RpcResult<U256> {
404		let hash = self.client.block_hash_for_tag(block).await?;
405		let runtime_api = self.client.runtime_api(hash);
406		let nonce = runtime_api.nonce(address).await?;
407		Ok(nonce)
408	}
409
410	async fn web3_client_version(&self) -> RpcResult<String> {
411		let git_revision = env!("GIT_REVISION");
412		let rustc_version = env!("RUSTC_VERSION");
413		let target = env!("TARGET");
414		Ok(format!("eth-rpc/{git_revision}/{target}/{rustc_version}"))
415	}
416
417	async fn fee_history(
418		&self,
419		block_count: U256,
420		newest_block: BlockNumberOrTag,
421		reward_percentiles: Option<Vec<f64>>,
422	) -> RpcResult<FeeHistoryResult> {
423		let block_count: u32 = block_count.try_into().map_err(|_| EthRpcError::ConversionError)?;
424		let result = self.client.fee_history(block_count, newest_block, reward_percentiles).await?;
425		Ok(result)
426	}
427}
428
429impl EthRpcServerImpl {
430	async fn get_transaction_by_substrate_block_hash_and_index(
431		&self,
432		substrate_block_hash: H256,
433		transaction_index: U256,
434	) -> RpcResult<Option<TransactionInfo>> {
435		let Some(receipt) = self
436			.client
437			.receipt_by_hash_and_index(
438				&substrate_block_hash,
439				transaction_index.try_into().map_err(|_| EthRpcError::ConversionError)?,
440			)
441			.await
442		else {
443			return Ok(None);
444		};
445		let Some(signed_tx) = self.client.signed_tx_by_hash(&receipt.transaction_hash).await else {
446			return Ok(None);
447		};
448
449		Ok(Some(TransactionInfo::new(&receipt, signed_tx)))
450	}
451}