use super::{ metrics::{StaticFileProviderMetrics, StaticFileProviderOperation}, LoadedJarRef, }; use crate::{ to_range, BlockHashReader, BlockNumReader, HeaderProvider, ReceiptProvider, TransactionsProvider, }; use reth_chainspec::ChainInfo; use reth_db::static_file::{HeaderMask, ReceiptMask, StaticFileCursor, TransactionMask}; use reth_db_api::models::CompactU256; use reth_primitives::{ Address, BlockHash, BlockHashOrNumber, BlockNumber, Header, Receipt, SealedHeader, TransactionMeta, TransactionSigned, TransactionSignedNoHash, TxHash, TxNumber, B256, U256, }; use reth_storage_errors::provider::{ProviderError, ProviderResult}; use std::{ ops::{Deref, RangeBounds}, sync::Arc, }; /// Provider over a specific `NippyJar` and range. #[derive(Debug)] pub struct StaticFileJarProvider<'a> { /// Main static file segment jar: LoadedJarRef<'a>, /// Another kind of static file segment to help query data from the main one. auxiliary_jar: Option>, metrics: Option>, } impl<'a> Deref for StaticFileJarProvider<'a> { type Target = LoadedJarRef<'a>; fn deref(&self) -> &Self::Target { &self.jar } } impl<'a> From> for StaticFileJarProvider<'a> { fn from(value: LoadedJarRef<'a>) -> Self { StaticFileJarProvider { jar: value, auxiliary_jar: None, metrics: None } } } impl<'a> StaticFileJarProvider<'a> { /// Provides a cursor for more granular data access. pub fn cursor<'b>(&'b self) -> ProviderResult> where 'b: 'a, { let result = StaticFileCursor::new(self.value(), self.mmap_handle())?; if let Some(metrics) = &self.metrics { metrics.record_segment_operation( self.segment(), StaticFileProviderOperation::InitCursor, None, ); } Ok(result) } /// Adds a new auxiliary static file to help query data from the main one pub fn with_auxiliary(mut self, auxiliary_jar: Self) -> Self { self.auxiliary_jar = Some(Box::new(auxiliary_jar)); self } /// Enables metrics on the provider. pub fn with_metrics(mut self, metrics: Arc) -> Self { self.metrics = Some(metrics); self } } impl<'a> HeaderProvider for StaticFileJarProvider<'a> { fn header(&self, block_hash: &BlockHash) -> ProviderResult> { Ok(self .cursor()? .get_two::>(block_hash.into())? .filter(|(_, hash)| hash == block_hash) .map(|(header, _)| header)) } fn header_by_number(&self, num: BlockNumber) -> ProviderResult> { self.cursor()?.get_one::>(num.into()) } fn header_td(&self, block_hash: &BlockHash) -> ProviderResult> { Ok(self .cursor()? .get_two::>(block_hash.into())? .filter(|(_, hash)| hash == block_hash) .map(|(td, _)| td.into())) } fn header_td_by_number(&self, num: BlockNumber) -> ProviderResult> { Ok(self.cursor()?.get_one::>(num.into())?.map(Into::into)) } fn headers_range(&self, range: impl RangeBounds) -> ProviderResult> { let range = to_range(range); let mut cursor = self.cursor()?; let mut headers = Vec::with_capacity((range.end - range.start) as usize); for num in range { if let Some(header) = cursor.get_one::>(num.into())? { headers.push(header); } } Ok(headers) } fn sealed_header(&self, number: BlockNumber) -> ProviderResult> { Ok(self .cursor()? .get_two::>(number.into())? .map(|(header, hash)| header.seal(hash))) } fn sealed_headers_while( &self, range: impl RangeBounds, mut predicate: impl FnMut(&SealedHeader) -> bool, ) -> ProviderResult> { let range = to_range(range); let mut cursor = self.cursor()?; let mut headers = Vec::with_capacity((range.end - range.start) as usize); for number in range { if let Some((header, hash)) = cursor.get_two::>(number.into())? { let sealed = header.seal(hash); if !predicate(&sealed) { break } headers.push(sealed); } } Ok(headers) } } impl<'a> BlockHashReader for StaticFileJarProvider<'a> { fn block_hash(&self, number: u64) -> ProviderResult> { self.cursor()?.get_one::>(number.into()) } fn canonical_hashes_range( &self, start: BlockNumber, end: BlockNumber, ) -> ProviderResult> { let mut cursor = self.cursor()?; let mut hashes = Vec::with_capacity((end - start) as usize); for number in start..end { if let Some(hash) = cursor.get_one::>(number.into())? { hashes.push(hash) } } Ok(hashes) } } impl<'a> BlockNumReader for StaticFileJarProvider<'a> { fn chain_info(&self) -> ProviderResult { // Information on live database Err(ProviderError::UnsupportedProvider) } fn best_block_number(&self) -> ProviderResult { // Information on live database Err(ProviderError::UnsupportedProvider) } fn last_block_number(&self) -> ProviderResult { // Information on live database Err(ProviderError::UnsupportedProvider) } fn block_number(&self, hash: B256) -> ProviderResult> { let mut cursor = self.cursor()?; Ok(cursor .get_one::>((&hash).into())? .and_then(|res| (res == hash).then(|| cursor.number()).flatten())) } } impl<'a> TransactionsProvider for StaticFileJarProvider<'a> { fn transaction_id(&self, hash: TxHash) -> ProviderResult> { let mut cursor = self.cursor()?; Ok(cursor .get_one::>((&hash).into())? .and_then(|res| (res.hash() == hash).then(|| cursor.number()).flatten())) } fn transaction_by_id(&self, num: TxNumber) -> ProviderResult> { Ok(self .cursor()? .get_one::>(num.into())? .map(|tx| tx.with_hash())) } fn transaction_by_id_no_hash( &self, num: TxNumber, ) -> ProviderResult> { self.cursor()?.get_one::>(num.into()) } fn transaction_by_hash(&self, hash: TxHash) -> ProviderResult> { Ok(self .cursor()? .get_one::>((&hash).into())? .map(|tx| tx.with_hash())) } fn transaction_by_hash_with_meta( &self, _hash: TxHash, ) -> ProviderResult> { // Information required on indexing table [`tables::TransactionBlocks`] Err(ProviderError::UnsupportedProvider) } fn transaction_block(&self, _id: TxNumber) -> ProviderResult> { // Information on indexing table [`tables::TransactionBlocks`] Err(ProviderError::UnsupportedProvider) } fn transactions_by_block( &self, _block_id: BlockHashOrNumber, ) -> ProviderResult>> { // Related to indexing tables. Live database should get the tx_range and call static file // provider with `transactions_by_tx_range` instead. Err(ProviderError::UnsupportedProvider) } fn transactions_by_block_range( &self, _range: impl RangeBounds, ) -> ProviderResult>> { // Related to indexing tables. Live database should get the tx_range and call static file // provider with `transactions_by_tx_range` instead. Err(ProviderError::UnsupportedProvider) } fn transactions_by_tx_range( &self, range: impl RangeBounds, ) -> ProviderResult> { let range = to_range(range); let mut cursor = self.cursor()?; let mut txes = Vec::with_capacity((range.end - range.start) as usize); for num in range { if let Some(tx) = cursor.get_one::>(num.into())? { txes.push(tx) } } Ok(txes) } fn senders_by_tx_range( &self, range: impl RangeBounds, ) -> ProviderResult> { let txs = self.transactions_by_tx_range(range)?; TransactionSignedNoHash::recover_signers(&txs, txs.len()) .ok_or(ProviderError::SenderRecoveryError) } fn transaction_sender(&self, num: TxNumber) -> ProviderResult> { Ok(self .cursor()? .get_one::>(num.into())? .and_then(|tx| tx.recover_signer())) } } impl<'a> ReceiptProvider for StaticFileJarProvider<'a> { fn receipt(&self, num: TxNumber) -> ProviderResult> { self.cursor()?.get_one::>(num.into()) } fn receipt_by_hash(&self, hash: TxHash) -> ProviderResult> { if let Some(tx_static_file) = &self.auxiliary_jar { if let Some(num) = tx_static_file.transaction_id(hash)? { return self.receipt(num) } } Ok(None) } fn receipts_by_block(&self, _block: BlockHashOrNumber) -> ProviderResult>> { // Related to indexing tables. StaticFile should get the tx_range and call static file // provider with `receipt()` instead for each Err(ProviderError::UnsupportedProvider) } fn receipts_by_tx_range( &self, range: impl RangeBounds, ) -> ProviderResult> { let range = to_range(range); let mut cursor = self.cursor()?; let mut receipts = Vec::with_capacity((range.end - range.start) as usize); for num in range { if let Some(tx) = cursor.get_one::>(num.into())? { receipts.push(tx) } } Ok(receipts) } }