// This file is part of Bizinikiwi. // Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! The client connects to the source bizinikiwi chain //! and is used by the rpc server to query and send transactions to the bizinikiwi chain. pub(crate) mod runtime_api; pub(crate) mod storage_api; use crate::{ subxt_client::{self, revive::calls::types::EthTransact, SrcChainConfig}, BlockInfoProvider, BlockTag, FeeHistoryProvider, ReceiptProvider, SubxtBlockInfoProvider, TracerType, TransactionInfo, }; use jsonrpsee::types::{error::CALL_EXECUTION_FAILED_CODE, ErrorObjectOwned}; use pezkuwi_subxt::{ backend::{ legacy::{rpc_methods::SystemHealth, LegacyRpcMethods}, rpc::{ reconnecting_rpc_client::{ExponentialBackoff, RpcClient as ReconnectingRpcClient}, RpcClient, }, }, config::{HashFor, Header}, ext::pezkuwi_subxt_rpcs::rpc_params, Config, OnlineClient, }; use pezpallet_revive::{ evm::{ decode_revert_reason, Block, BlockNumberOrTag, BlockNumberOrTagOrHash, FeeHistoryResult, Filter, GenericTransaction, HashesOrTransactionInfos, Log, ReceiptInfo, SyncingProgress, SyncingStatus, Trace, TransactionSigned, TransactionTrace, H256, }, EthTransactError, }; use pezsp_runtime::traits::Block as BlockT; use pezsp_weights::Weight; use runtime_api::RuntimeApi; use std::{ops::Range, sync::Arc, time::Duration}; use storage_api::StorageApi; use thiserror::Error; use tokio::sync::Mutex; /// The bizinikiwi block type. pub type BizinikiwiBlock = pezkuwi_subxt::blocks::Block>; /// The bizinikiwi block header. pub type BizinikiwiBlockHeader = ::Header; /// The bizinikiwi block number type. pub type BizinikiwiBlockNumber = ::Number; /// The bizinikiwi block hash type. pub type BizinikiwiBlockHash = HashFor; /// The runtime balance type. pub type Balance = u128; /// The subscription type used to listen to new blocks. #[derive(Debug, Clone, Copy, PartialEq)] pub enum SubscriptionType { /// Subscribe to best blocks. BestBlocks, /// Subscribe to finalized blocks. FinalizedBlocks, } /// The error type for the client. #[derive(Error, Debug)] pub enum ClientError { /// A [`jsonrpsee::core::ClientError`] wrapper error. #[error(transparent)] Jsonrpsee(#[from] jsonrpsee::core::ClientError), /// A [`pezkuwi_subxt::Error`] wrapper error. #[error(transparent)] SubxtError(#[from] pezkuwi_subxt::Error), #[error(transparent)] RpcError(#[from] pezkuwi_subxt::ext::pezkuwi_subxt_rpcs::Error), /// A [`sqlx::Error`] wrapper error. #[error(transparent)] SqlxError(#[from] sqlx::Error), /// A [`codec::Error`] wrapper error. #[error(transparent)] CodecError(#[from] codec::Error), /// Transcact call failed. #[error("contract reverted: {0:?}")] TransactError(EthTransactError), /// A decimal conversion failed. #[error("conversion failed")] ConversionFailed, /// The block hash was not found. #[error("hash not found")] BlockNotFound, /// The contract was not found. #[error("Contract not found")] ContractNotFound, #[error("No Ethereum extrinsic found")] EthExtrinsicNotFound, /// The transaction fee could not be found #[error("transactionFeePaid event not found")] TxFeeNotFound, /// Failed to decode a raw payload into a signed transaction. #[error("Failed to decode a raw payload into a signed transaction")] TxDecodingFailed, /// Failed to recover eth address. #[error("failed to recover eth address")] RecoverEthAddressFailed, /// Failed to filter logs. #[error("Failed to filter logs")] LogFilterFailed(#[from] anyhow::Error), /// Receipt storage was not found. #[error("Receipt storage not found")] ReceiptDataNotFound, /// Ethereum block was not found. #[error("Ethereum block not found")] EthereumBlockNotFound, /// Receipt data length mismatch. #[error("Receipt data length mismatch")] ReceiptDataLengthMismatch, } impl From for ClientError { fn from(err: pezkuwi_subxt::error::EventsError) -> Self { ClientError::SubxtError(pezkuwi_subxt::Error::from(err)) } } impl From for ClientError { fn from(err: pezkuwi_subxt::error::ExtrinsicError) -> Self { ClientError::SubxtError(pezkuwi_subxt::Error::from(err)) } } impl From for ClientError { fn from(err: pezkuwi_subxt::error::BlockError) -> Self { ClientError::SubxtError(pezkuwi_subxt::Error::from(err)) } } impl From for ClientError { fn from(err: pezkuwi_subxt::error::BackendError) -> Self { ClientError::SubxtError(pezkuwi_subxt::Error::from(err)) } } impl From for ClientError { fn from(err: pezkuwi_subxt::error::RuntimeApiError) -> Self { ClientError::SubxtError(pezkuwi_subxt::Error::from(err)) } } impl From for ClientError { fn from(err: pezkuwi_subxt::error::ConstantError) -> Self { ClientError::SubxtError(pezkuwi_subxt::Error::from(err)) } } impl From for ClientError { fn from(err: pezkuwi_subxt::error::OnlineClientError) -> Self { ClientError::SubxtError(pezkuwi_subxt::Error::from(err)) } } const LOG_TARGET: &str = "eth-rpc::client"; const REVERT_CODE: i32 = 3; const NOTIFIER_CAPACITY: usize = 16; impl From for ErrorObjectOwned { fn from(err: ClientError) -> Self { match err { ClientError::SubxtError(pezkuwi_subxt::Error::OtherRpcClientError( pezkuwi_subxt::ext::pezkuwi_subxt_rpcs::Error::User(err), )) | ClientError::RpcError(pezkuwi_subxt::ext::pezkuwi_subxt_rpcs::Error::User(err)) => { ErrorObjectOwned::owned::>(err.code, err.message, None) }, ClientError::TransactError(EthTransactError::Data(data)) => { let msg = match decode_revert_reason(&data) { Some(reason) => format!("execution reverted: {reason}"), None => "execution reverted".to_string(), }; let data = format!("0x{}", hex::encode(data)); ErrorObjectOwned::owned::(REVERT_CODE, msg, Some(data)) }, ClientError::TransactError(EthTransactError::Message(msg)) => { ErrorObjectOwned::owned::(CALL_EXECUTION_FAILED_CODE, msg, None) }, _ => { ErrorObjectOwned::owned::(CALL_EXECUTION_FAILED_CODE, err.to_string(), None) }, } } } /// A client connect to a node and maintains a cache of the last `CACHE_SIZE` blocks. #[derive(Clone)] pub struct Client { api: OnlineClient, rpc_client: RpcClient, rpc: LegacyRpcMethods, receipt_provider: ReceiptProvider, block_provider: SubxtBlockInfoProvider, fee_history_provider: FeeHistoryProvider, chain_id: u64, max_block_weight: Weight, /// Whether the node has automine enabled. automine: bool, /// A notifier, that informs subscribers of new best blocks. block_notifier: Option>, /// A lock to ensure only one subscription can perform write operations at a time. subscription_lock: Arc>, } /// Fetch the chain ID from the bizinikiwi chain. async fn chain_id(api: &OnlineClient) -> Result { let query = subxt_client::constants().revive().chain_id(); api.constants().at(&query).map_err(|err| err.into()) } /// Fetch the max block weight from the bizinikiwi chain. async fn max_block_weight(api: &OnlineClient) -> Result { let query = subxt_client::constants().system().block_weights(); let weights = api.constants().at(&query)?; let max_block = weights.per_class.normal.max_extrinsic.unwrap_or(weights.max_block); Ok(max_block.0) } /// Get the automine status from the node. async fn get_automine(rpc_client: &RpcClient) -> bool { match rpc_client.request::("getAutomine", rpc_params![]).await { Ok(val) => val, Err(err) => { log::info!(target: LOG_TARGET, "Node does not have getAutomine RPC. Defaulting to automine=false. error: {err:?}"); false }, } } /// Connect to a node at the given URL, and return the underlying API, RPC client, and legacy RPC /// clients. pub async fn connect( pez_node_rpc_url: &str, ) -> Result<(OnlineClient, RpcClient, LegacyRpcMethods), ClientError> { log::info!(target: LOG_TARGET, "🌐 Connecting to node at: {pez_node_rpc_url} ..."); let rpc_client = ReconnectingRpcClient::builder() .retry_policy(ExponentialBackoff::from_millis(100).max_delay(Duration::from_secs(10))) .build(pez_node_rpc_url.to_string()) .await?; let rpc_client = RpcClient::new(rpc_client); log::info!(target: LOG_TARGET, "🌟 Connected to node at: {pez_node_rpc_url}"); let api = OnlineClient::::from_rpc_client(rpc_client.clone()).await?; let rpc = LegacyRpcMethods::::new(rpc_client.clone()); Ok((api, rpc_client, rpc)) } impl Client { /// Create a new client instance. pub async fn new( api: OnlineClient, rpc_client: RpcClient, rpc: LegacyRpcMethods, block_provider: SubxtBlockInfoProvider, receipt_provider: ReceiptProvider, ) -> Result { let (chain_id, max_block_weight, automine) = tokio::try_join!(chain_id(&api), max_block_weight(&api), async { Ok(get_automine(&rpc_client).await) },)?; let client = Self { api, rpc_client, rpc, receipt_provider, block_provider, fee_history_provider: FeeHistoryProvider::default(), chain_id, max_block_weight, automine, block_notifier: automine .then(|| tokio::sync::broadcast::channel::(NOTIFIER_CAPACITY).0), subscription_lock: Arc::new(Mutex::new(())), }; Ok(client) } /// Creates a block notifier instance. pub fn create_block_notifier(&mut self) { self.block_notifier = Some(tokio::sync::broadcast::channel::(NOTIFIER_CAPACITY).0); } /// Subscribe to past blocks executing the callback for each block in `range`. async fn subscribe_past_blocks( &self, range: Range, callback: F, ) -> Result<(), ClientError> where F: Fn(Arc) -> Fut + Send + Sync, Fut: std::future::Future> + Send, { let mut block = self .block_provider .block_by_number(range.end) .await? .ok_or(ClientError::BlockNotFound)?; loop { let block_number = block.number(); log::trace!(target: "eth-rpc::subscription", "Processing past block #{block_number}"); let parent_hash = block.header().parent_hash; callback(block.clone()).await.inspect_err(|err| { log::error!(target: "eth-rpc::subscription", "Failed to process past block #{block_number}: {err:?}"); })?; if range.start < block_number { block = self .block_provider .block_by_hash(&parent_hash) .await? .ok_or(ClientError::BlockNotFound)?; } else { return Ok(()); } } } /// Subscribe to new blocks, and execute the async closure for each block. async fn subscribe_new_blocks( &self, subscription_type: SubscriptionType, callback: F, ) -> Result<(), ClientError> where F: Fn(BizinikiwiBlock) -> Fut + Send + Sync, Fut: std::future::Future> + Send, { let mut block_stream = match subscription_type { SubscriptionType::BestBlocks => self.api.blocks().subscribe_best().await, SubscriptionType::FinalizedBlocks => self.api.blocks().subscribe_finalized().await, } .inspect_err(|err| { log::error!(target: LOG_TARGET, "Failed to subscribe to blocks: {err:?}"); })?; while let Some(block) = block_stream.next().await { let block = match block { Ok(block) => block, Err(err) => { if err.is_disconnected_will_reconnect() { log::warn!( target: LOG_TARGET, "The RPC connection was lost and we may have missed a few blocks ({subscription_type:?}): {err:?}" ); continue; } log::error!(target: LOG_TARGET, "Failed to fetch block ({subscription_type:?}): {err:?}"); return Err(err.into()); }, }; // Acquire lock to ensure only one subscription can perform write operations at a time let _guard = self.subscription_lock.lock().await; let block_number = block.number(); log::trace!(target: "eth-rpc::subscription", "⏳ Processing {subscription_type:?} block: {block_number}"); if let Err(err) = callback(block).await { log::error!(target: LOG_TARGET, "Failed to process block {block_number}: {err:?}"); } else { log::trace!(target: "eth-rpc::subscription", "✅ Processed {subscription_type:?} block: {block_number}"); } } log::info!(target: LOG_TARGET, "Block subscription ended"); Ok(()) } /// Start the block subscription, and populate the block cache. pub async fn subscribe_and_cache_new_blocks( &self, subscription_type: SubscriptionType, ) -> Result<(), ClientError> { log::info!(target: LOG_TARGET, "🔌 Subscribing to new blocks ({subscription_type:?})"); self.subscribe_new_blocks(subscription_type, |block| async { let hash = block.hash(); let evm_block = self.runtime_api(hash).eth_block().await?; let (_, receipts): (Vec<_>, Vec<_>) = self .receipt_provider .insert_block_receipts(&block, &evm_block.hash) .await? .into_iter() .unzip(); self.block_provider.update_latest(Arc::new(block), subscription_type).await; self.fee_history_provider.update_fee_history(&evm_block, &receipts).await; // Only broadcast for best blocks to avoid duplicate notifications. match (subscription_type, &self.block_notifier) { (SubscriptionType::BestBlocks, Some(sender)) if sender.receiver_count() > 0 => { let _ = sender.send(hash); }, _ => {}, } Ok(()) }) .await } /// Cache old blocks up to the given block number. pub async fn subscribe_and_cache_blocks( &self, index_last_n_blocks: BizinikiwiBlockNumber, ) -> Result<(), ClientError> { let last = self.latest_block().await.number().saturating_sub(1); let range = last.saturating_sub(index_last_n_blocks)..last; log::info!(target: LOG_TARGET, "🗄️ Indexing past blocks in range {range:?}"); self.subscribe_past_blocks(range, |block| async move { let ethereum_hash = self .runtime_api(block.hash()) .eth_block_hash(pezpallet_revive::evm::U256::from(block.number())) .await? .ok_or(ClientError::EthereumBlockNotFound)?; self.receipt_provider.insert_block_receipts(&block, ðereum_hash).await?; Ok(()) }) .await?; log::info!(target: LOG_TARGET, "🗄️ Finished indexing past blocks"); Ok(()) } /// Get the block hash for the given block number or tag. pub async fn block_hash_for_tag( &self, at: BlockNumberOrTagOrHash, ) -> Result { match at { BlockNumberOrTagOrHash::BlockHash(hash) => self .resolve_bizinikiwi_hash(&hash) .await .ok_or(ClientError::EthereumBlockNotFound), BlockNumberOrTagOrHash::BlockNumber(block_number) => { let n: BizinikiwiBlockNumber = (block_number).try_into().map_err(|_| ClientError::ConversionFailed)?; let hash = self.get_block_hash(n).await?.ok_or(ClientError::BlockNotFound)?; Ok(hash) }, BlockNumberOrTagOrHash::BlockTag(BlockTag::Finalized | BlockTag::Safe) => { let block = self.latest_finalized_block().await; Ok(block.hash()) }, BlockNumberOrTagOrHash::BlockTag(_) => { let block = self.latest_block().await; Ok(block.hash()) }, } } /// Get the storage API for the given block. pub fn storage_api(&self, block_hash: H256) -> StorageApi { StorageApi::new(self.api.storage().at(block_hash), block_hash) } /// Get the runtime API for the given block. pub fn runtime_api(&self, block_hash: H256) -> RuntimeApi { RuntimeApi::new(self.api.runtime_api().at(block_hash)) } /// Get the latest finalized block. pub async fn latest_finalized_block(&self) -> Arc { self.block_provider.latest_finalized_block().await } /// Get the latest best block. pub async fn latest_block(&self) -> Arc { self.block_provider.latest_block().await } /// Expose the transaction API. pub async fn submit( &self, call: pezkuwi_subxt::tx::DefaultPayload, ) -> Result { let ext = self.api.tx().create_unsigned(&call).map_err(ClientError::from)?; let hash: H256 = self .rpc_client .request("author_submitExtrinsic", rpc_params![to_hex(ext.encoded())]) .await?; log::debug!(target: LOG_TARGET, "Submitted transaction with bizinikiwi hash: {hash:?}"); Ok(hash) } /// Get an EVM transaction receipt by hash. pub async fn receipt(&self, tx_hash: &H256) -> Option { self.receipt_provider.receipt_by_hash(tx_hash).await } pub async fn sync_state( &self, ) -> Result, ClientError> { let client = self.rpc_client.clone(); let sync_state: pezsc_rpc::system::SyncState = client.request("system_syncState", Default::default()).await?; Ok(sync_state) } /// Get the syncing status of the chain. pub async fn syncing(&self) -> Result { let health = self.rpc.system_health().await?; let status = if health.is_syncing { let sync_state = self.sync_state().await?; SyncingProgress { current_block: Some(sync_state.current_block.into()), highest_block: Some(sync_state.highest_block.into()), starting_block: Some(sync_state.starting_block.into()), } .into() } else { SyncingStatus::Bool(false) }; Ok(status) } /// Get an EVM transaction receipt by hash. pub async fn receipt_by_hash_and_index( &self, block_hash: &H256, transaction_index: usize, ) -> Option { self.receipt_provider .receipt_by_block_hash_and_index(block_hash, transaction_index) .await } pub async fn signed_tx_by_hash(&self, tx_hash: &H256) -> Option { self.receipt_provider.signed_tx_by_hash(tx_hash).await } /// Get receipts count per block. pub async fn receipts_count_per_block( &self, block_hash: &BizinikiwiBlockHash, ) -> Option { self.receipt_provider.receipts_count_per_block(block_hash).await } /// Get an EVM transaction receipt by specified Ethereum block hash. pub async fn receipt_by_ethereum_hash_and_index( &self, ethereum_hash: &H256, transaction_index: usize, ) -> Option { // Fallback: use hash as Bizinikiwi hash if Ethereum hash cannot be resolved let bizinikiwi_hash = self.resolve_bizinikiwi_hash(ethereum_hash).await.unwrap_or(*ethereum_hash); self.receipt_by_hash_and_index(&bizinikiwi_hash, transaction_index).await } /// Get the system health. pub async fn system_health(&self) -> Result { let health = self.rpc.system_health().await?; Ok(health) } /// Get the block number of the latest block. pub async fn block_number(&self) -> Result { let latest_block = self.block_provider.latest_block().await; Ok(latest_block.number()) } /// Get a block hash for the given block number. pub async fn get_block_hash( &self, block_number: BizinikiwiBlockNumber, ) -> Result, ClientError> { let maybe_block = self.block_provider.block_by_number(block_number).await?; Ok(maybe_block.map(|block| block.hash())) } /// Get a block for the specified hash or number. pub async fn block_by_number_or_tag( &self, block: &BlockNumberOrTag, ) -> Result>, ClientError> { match block { BlockNumberOrTag::U256(n) => { let n = (*n).try_into().map_err(|_| ClientError::ConversionFailed)?; self.block_by_number(n).await }, BlockNumberOrTag::BlockTag(BlockTag::Finalized | BlockTag::Safe) => { let block = self.block_provider.latest_finalized_block().await; Ok(Some(block)) }, BlockNumberOrTag::BlockTag(_) => { let block = self.block_provider.latest_block().await; Ok(Some(block)) }, } } /// Get a block by hash pub async fn block_by_hash( &self, hash: &BizinikiwiBlockHash, ) -> Result>, ClientError> { self.block_provider.block_by_hash(hash).await } /// Resolve Ethereum block hash to Bizinikiwi block hash, then get the block. /// This method provides the abstraction layer needed by the RPC APIs. pub async fn resolve_bizinikiwi_hash(&self, ethereum_hash: &H256) -> Option { self.receipt_provider.get_bizinikiwi_hash(ethereum_hash).await } /// Resolve Bizinikiwi block hash to Ethereum block hash, then get the block. /// This method provides the abstraction layer needed by the RPC APIs. pub async fn resolve_ethereum_hash(&self, bizinikiwi_hash: &H256) -> Option { self.receipt_provider.get_ethereum_hash(bizinikiwi_hash).await } /// Get a block by Ethereum hash with automatic resolution to Bizinikiwi hash. /// Falls back to treating the hash as a Bizinikiwi hash if no mapping exists. pub async fn block_by_ethereum_hash( &self, ethereum_hash: &H256, ) -> Result>, ClientError> { // First try to resolve the Ethereum hash to a Bizinikiwi hash if let Some(bizinikiwi_hash) = self.resolve_bizinikiwi_hash(ethereum_hash).await { return self.block_by_hash(&bizinikiwi_hash).await; } // Fallback: treat the provided hash as a Bizinikiwi hash (backward compatibility) self.block_by_hash(ethereum_hash).await } /// Get a block by number pub async fn block_by_number( &self, block_number: BizinikiwiBlockNumber, ) -> Result>, ClientError> { self.block_provider.block_by_number(block_number).await } async fn tracing_block( &self, block_hash: H256, ) -> Result< pezsp_runtime::generic::Block< pezsp_runtime::generic::Header, pezsp_runtime::OpaqueExtrinsic, >, ClientError, > { let signed_block: pezsp_runtime::generic::SignedBlock< pezsp_runtime::generic::Block< pezsp_runtime::generic::Header, pezsp_runtime::OpaqueExtrinsic, >, > = self .rpc_client .request("chain_getBlock", rpc_params![block_hash]) .await .unwrap(); Ok(signed_block.block) } /// Get the transaction traces for the given block. pub async fn trace_block_by_number( &self, at: BlockNumberOrTag, config: TracerType, ) -> Result, ClientError> { if self.receipt_provider.is_before_earliest_block(&at) { return Ok(vec![]); } let block_hash = self.block_hash_for_tag(at.into()).await?; let block = self.tracing_block(block_hash).await?; let parent_hash = block.header().parent_hash; let runtime_api = RuntimeApi::new(self.api.runtime_api().at(parent_hash)); let traces = runtime_api.trace_block(block, config.clone()).await?; let mut hashes = self .receipt_provider .block_transaction_hashes(&block_hash) .await .ok_or(ClientError::EthExtrinsicNotFound)?; let traces = traces.into_iter().filter_map(|(index, trace)| { Some(TransactionTrace { tx_hash: hashes.remove(&(index as usize))?, trace }) }); Ok(traces.collect()) } /// Get the transaction traces for the given transaction. pub async fn trace_transaction( &self, transaction_hash: H256, config: TracerType, ) -> Result { let (block_hash, transaction_index) = self .receipt_provider .find_transaction(&transaction_hash) .await .ok_or(ClientError::EthExtrinsicNotFound)?; let block = self.tracing_block(block_hash).await?; let parent_hash = block.header.parent_hash; let runtime_api = self.runtime_api(parent_hash); runtime_api.trace_tx(block, transaction_index as u32, config).await } /// Get the transaction traces for the given block. pub async fn trace_call( &self, transaction: GenericTransaction, block: BlockNumberOrTagOrHash, config: TracerType, ) -> Result { let block_hash = self.block_hash_for_tag(block).await?; let runtime_api = self.runtime_api(block_hash); runtime_api.trace_call(transaction, config).await } /// Get the EVM block for the given Bizinikiwi block. pub async fn evm_block( &self, block: Arc, hydrated_transactions: bool, ) -> Option { log::trace!(target: LOG_TARGET, "Get Ethereum block for hash {:?}", block.hash()); // This could potentially fail under below circumstances: // - state has been pruned // - the block author cannot be obtained from the digest logs (highly unlikely) // - the node we are targeting has an outdated revive pezpallet (or ETH block functionality // is disabled) match self.runtime_api(block.hash()).eth_block().await { Ok(mut eth_block) => { log::trace!(target: LOG_TARGET, "Ethereum block from runtime API hash {:?}", eth_block.hash); if hydrated_transactions { // Hydrate the block. let tx_infos = self .receipt_provider .receipts_from_block(&block) .await .unwrap_or_default() .into_iter() .map(|(signed_tx, receipt)| TransactionInfo::new(&receipt, signed_tx)) .collect::>(); eth_block.transactions = HashesOrTransactionInfos::TransactionInfos(tx_infos); } Some(eth_block) }, Err(err) => { log::error!(target: LOG_TARGET, "Failed to get Ethereum block for hash {:?}: {err:?}", block.hash()); None }, } } /// Get the chain ID. pub fn chain_id(&self) -> u64 { self.chain_id } /// Get the Max Block Weight. pub fn max_block_weight(&self) -> Weight { self.max_block_weight } /// Get the block notifier, if automine is enabled or Self::create_block_notifier was called. pub fn block_notifier(&self) -> Option> { self.block_notifier.clone() } /// Get the logs matching the given filter. pub async fn logs(&self, filter: Option) -> Result, ClientError> { let logs = self.receipt_provider.logs(filter).await.map_err(ClientError::LogFilterFailed)?; Ok(logs) } pub async fn fee_history( &self, block_count: u32, latest_block: BlockNumberOrTag, reward_percentiles: Option>, ) -> Result { let Some(latest_block) = self.block_by_number_or_tag(&latest_block).await? else { return Err(ClientError::BlockNotFound); }; self.fee_history_provider .fee_history(block_count, latest_block.number(), reward_percentiles) .await } /// Check if automine is enabled. pub fn is_automine(&self) -> bool { self.automine } /// Get the automine status from the node. pub async fn get_automine(&self) -> bool { get_automine(&self.rpc_client).await } } fn to_hex(bytes: impl AsRef<[u8]>) -> String { format!("0x{}", hex::encode(bytes.as_ref())) }