feat(provider): add TransactionsProvider and WithdrawalsProvider (#1431)

This commit is contained in:
Roman Krasiuk
2023-02-17 17:26:47 +02:00
committed by GitHub
parent bacb3c59b2
commit 54b6a4ae94
11 changed files with 285 additions and 117 deletions

View File

@ -1,7 +1,7 @@
use crate::{BlockNumber, H256};
/// Current status of the blockchain's head.
#[derive(Debug, Eq, PartialEq)]
#[derive(Default, Debug, Eq, PartialEq)]
pub struct ChainInfo {
/// The block hash of the highest fully synced block.
pub best_hash: H256,

View File

@ -191,8 +191,8 @@ where
self.inner.signers.iter().flat_map(|s| s.accounts()).collect()
}
async fn transaction_by_hash(&self, _hash: H256) -> Result<Option<TransactionSigned>> {
todo!()
async fn transaction_by_hash(&self, hash: H256) -> Result<Option<TransactionSigned>> {
self.client().transaction_by_hash(hash)
}
}

View File

@ -11,8 +11,8 @@
/// Various provider traits.
mod traits;
pub use traits::{
AccountProvider, BlockHashProvider, BlockProvider, HeaderProvider, StateProvider,
StateProviderFactory,
AccountProvider, BlockHashProvider, BlockIdProvider, BlockProvider, HeaderProvider,
StateProvider, StateProviderFactory, TransactionsProvider, WithdrawalsProvider,
};
/// Provider trait implementations.

View File

@ -1,17 +1,21 @@
use crate::{BlockHashProvider, BlockProvider, Error, HeaderProvider, StateProviderFactory};
use crate::{
BlockHashProvider, BlockIdProvider, BlockProvider, Error, HeaderProvider, StateProviderFactory,
TransactionsProvider, WithdrawalsProvider,
};
use reth_db::{
cursor::DbCursorRO,
database::{Database, DatabaseGAT},
tables,
transaction::DbTx,
};
use reth_interfaces::Result;
use reth_primitives::{
rpc::BlockId, Block, BlockHash, BlockNumber, ChainInfo, ChainSpec, Hardfork, Header, H256, U256,
rpc::BlockId, Block, BlockHash, BlockNumber, ChainInfo, ChainSpec, Hardfork, Header,
TransactionSigned, TxHash, TxNumber, Withdrawal, H256, U256,
};
use std::{ops::RangeBounds, sync::Arc};
mod state;
use reth_db::cursor::DbCursorRO;
pub use state::{
chain::ChainState,
historical::{HistoricalStateProvider, HistoricalStateProviderRef},
@ -92,7 +96,7 @@ impl<DB: Database> BlockHashProvider for ShareableDatabase<DB> {
}
}
impl<DB: Database> BlockProvider for ShareableDatabase<DB> {
impl<DB: Database> BlockIdProvider for ShareableDatabase<DB> {
fn chain_info(&self) -> Result<ChainInfo> {
let best_number = self
.db
@ -103,47 +107,116 @@ impl<DB: Database> BlockProvider for ShareableDatabase<DB> {
Ok(ChainInfo { best_hash, best_number, last_finalized: None, safe_finalized: None })
}
fn block_number(&self, hash: H256) -> Result<Option<BlockNumber>> {
self.db.view(|tx| tx.get::<tables::HeaderNumbers>(hash))?.map_err(Into::into)
}
}
impl<DB: Database> BlockProvider for ShareableDatabase<DB> {
fn block(&self, id: BlockId) -> Result<Option<Block>> {
if let Some(number) = self.block_number_for_id(id)? {
if let Some(header) = self.header_by_number(number)? {
let id = BlockId::Number(number.into());
let tx = self.db.tx()?;
let body = tx
.get::<tables::BlockBodies>(header.number)?
.ok_or(Error::BlockBody { number })?;
let mut tx_cursor = tx.cursor_read::<tables::Transactions>()?;
let mut transactions = Vec::with_capacity(body.tx_count as usize);
for id in body.tx_id_range() {
let (_, transaction) =
tx_cursor.seek_exact(id)?.ok_or(Error::Transaction { id })?;
transactions.push(transaction);
}
let transactions =
self.transactions_by_block(id)?.ok_or(Error::BlockBody { number })?;
let ommers = tx.get::<tables::BlockOmmers>(header.number)?.map(|o| o.ommers);
let withdrawals = self.withdrawals_by_block(id, header.timestamp)?;
let header_timestamp = header.timestamp;
let mut block = Block {
return Ok(Some(Block {
header,
body: transactions,
ommers: ommers.unwrap_or_default(),
withdrawals: None,
};
if self.chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(header_timestamp) {
let withdrawals =
tx.get::<tables::BlockWithdrawals>(number)?.map(|w| w.withdrawals);
block.withdrawals = Some(withdrawals.unwrap_or_default());
}
return Ok(Some(block))
withdrawals,
}))
}
}
Ok(None)
}
}
fn block_number(&self, hash: H256) -> Result<Option<BlockNumber>> {
self.db.view(|tx| tx.get::<tables::HeaderNumbers>(hash))?.map_err(Into::into)
impl<DB: Database> TransactionsProvider for ShareableDatabase<DB> {
fn transaction_by_id(&self, id: TxNumber) -> Result<Option<TransactionSigned>> {
self.db.view(|tx| tx.get::<tables::Transactions>(id))?.map_err(Into::into)
}
fn transaction_by_hash(&self, hash: TxHash) -> Result<Option<TransactionSigned>> {
self.db
.view(|tx| {
if let Some(id) = tx.get::<tables::TxHashNumber>(hash)? {
tx.get::<tables::Transactions>(id)
} else {
Ok(None)
}
})?
.map_err(Into::into)
}
fn transactions_by_block(&self, id: BlockId) -> Result<Option<Vec<TransactionSigned>>> {
if let Some(number) = self.block_number_for_id(id)? {
let tx = self.db.tx()?;
if let Some(body) = tx.get::<tables::BlockBodies>(number)? {
let tx_range = body.tx_id_range();
if tx_range.is_empty() {
Ok(Some(Vec::default()))
} else {
let mut tx_cursor = tx.cursor_read::<tables::Transactions>()?;
let transactions = tx_cursor
.walk_range(tx_range)?
.map(|result| result.map(|(_, tx)| tx))
.collect::<std::result::Result<Vec<_>, _>>()?;
Ok(Some(transactions))
}
} else {
Ok(None)
}
} else {
Ok(None)
}
}
fn transactions_by_block_range(
&self,
range: impl RangeBounds<BlockNumber>,
) -> Result<Vec<Vec<TransactionSigned>>> {
let tx = self.db.tx()?;
let mut results = Vec::default();
let mut body_cursor = tx.cursor_read::<tables::BlockBodies>()?;
let mut tx_cursor = tx.cursor_read::<tables::Transactions>()?;
for entry in body_cursor.walk_range(range)? {
let (_, body) = entry?;
let tx_range = body.tx_id_range();
if body.tx_id_range().is_empty() {
results.push(Vec::default());
} else {
results.push(
tx_cursor
.walk_range(tx_range)?
.map(|result| result.map(|(_, tx)| tx))
.collect::<std::result::Result<Vec<_>, _>>()?,
);
}
}
Ok(results)
}
}
impl<DB: Database> WithdrawalsProvider for ShareableDatabase<DB> {
fn withdrawals_by_block(&self, id: BlockId, timestamp: u64) -> Result<Option<Vec<Withdrawal>>> {
if self.chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(timestamp) {
if let Some(number) = self.block_number_for_id(id)? {
Ok(self
.db
.view(|tx| tx.get::<tables::BlockWithdrawals>(number))??
.map(|w| w.withdrawals))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
}
@ -183,9 +256,8 @@ impl<DB: Database> StateProviderFactory for ShareableDatabase<DB> {
#[cfg(test)]
mod tests {
use crate::{BlockProvider, StateProviderFactory};
use super::ShareableDatabase;
use crate::{BlockIdProvider, StateProviderFactory};
use reth_db::mdbx::{test_utils::create_test_db, EnvKind, WriteMap};
use reth_primitives::{ChainSpecBuilder, H256};

View File

@ -1,14 +1,14 @@
use crate::{
AccountProvider, BlockHashProvider, BlockProvider, HeaderProvider, StateProvider,
StateProviderFactory,
AccountProvider, BlockHashProvider, BlockIdProvider, BlockProvider, HeaderProvider,
StateProvider, StateProviderFactory, TransactionsProvider,
};
use parking_lot::Mutex;
use reth_interfaces::Result;
use reth_primitives::{
keccak256,
rpc::{BlockId, BlockNumber},
Account, Address, Block, BlockHash, Bytes, ChainInfo, Header, StorageKey, StorageValue, H256,
U256,
Account, Address, Block, BlockHash, Bytes, ChainInfo, Header, StorageKey, StorageValue,
TransactionSigned, TxHash, H256, U256,
};
use std::{collections::HashMap, ops::RangeBounds, sync::Arc};
@ -119,6 +119,34 @@ impl HeaderProvider for MockEthProvider {
}
}
impl TransactionsProvider for MockEthProvider {
fn transaction_by_id(
&self,
_id: reth_primitives::TxNumber,
) -> Result<Option<TransactionSigned>> {
unimplemented!()
}
fn transaction_by_hash(&self, hash: TxHash) -> Result<Option<TransactionSigned>> {
Ok(self
.blocks
.lock()
.iter()
.find_map(|(_, block)| block.body.iter().find(|tx| tx.hash == hash).cloned()))
}
fn transactions_by_block(&self, id: BlockId) -> Result<Option<Vec<TransactionSigned>>> {
Ok(self.block(id)?.map(|b| b.body))
}
fn transactions_by_block_range(
&self,
_range: impl RangeBounds<reth_primitives::BlockNumber>,
) -> Result<Vec<Vec<TransactionSigned>>> {
unimplemented!()
}
}
impl BlockHashProvider for MockEthProvider {
fn block_hash(&self, number: U256) -> Result<Option<H256>> {
let lock = self.blocks.lock();
@ -137,7 +165,7 @@ impl BlockHashProvider for MockEthProvider {
}
}
impl BlockProvider for MockEthProvider {
impl BlockIdProvider for MockEthProvider {
fn chain_info(&self) -> Result<ChainInfo> {
let lock = self.headers.lock();
Ok(lock
@ -152,6 +180,14 @@ impl BlockProvider for MockEthProvider {
.expect("provider is empty"))
}
fn block_number(&self, hash: H256) -> Result<Option<reth_primitives::BlockNumber>> {
let lock = self.blocks.lock();
let num = lock.iter().find_map(|(h, b)| if *h == hash { Some(b.number) } else { None });
Ok(num)
}
}
impl BlockProvider for MockEthProvider {
fn block(&self, id: BlockId) -> Result<Option<Block>> {
let lock = self.blocks.lock();
match id {
@ -164,12 +200,6 @@ impl BlockProvider for MockEthProvider {
}
}
}
fn block_number(&self, hash: H256) -> Result<Option<reth_primitives::BlockNumber>> {
let lock = self.blocks.lock();
let num = lock.iter().find_map(|(h, b)| if *h == hash { Some(b.number) } else { None });
Ok(num)
}
}
impl AccountProvider for MockEthProvider {

View File

@ -1,11 +1,11 @@
use crate::{
AccountProvider, BlockHashProvider, BlockProvider, HeaderProvider, StateProvider,
StateProviderFactory,
AccountProvider, BlockHashProvider, BlockIdProvider, BlockProvider, HeaderProvider,
StateProvider, StateProviderFactory, TransactionsProvider,
};
use reth_interfaces::Result;
use reth_primitives::{
rpc::BlockId, Account, Address, Block, BlockHash, BlockNumber, Bytes, ChainInfo, Header,
StorageKey, StorageValue, H256, U256,
StorageKey, StorageValue, TransactionSigned, TxHash, TxNumber, H256, U256,
};
use std::ops::RangeBounds;
@ -21,18 +21,9 @@ impl BlockHashProvider for NoopProvider {
}
}
impl BlockProvider for NoopProvider {
impl BlockIdProvider for NoopProvider {
fn chain_info(&self) -> Result<ChainInfo> {
Ok(ChainInfo {
best_hash: Default::default(),
best_number: 0,
last_finalized: None,
safe_finalized: None,
})
}
fn block(&self, _id: BlockId) -> Result<Option<Block>> {
Ok(None)
Ok(ChainInfo::default())
}
fn block_number(&self, _hash: H256) -> Result<Option<BlockNumber>> {
@ -40,6 +31,33 @@ impl BlockProvider for NoopProvider {
}
}
impl BlockProvider for NoopProvider {
fn block(&self, _id: BlockId) -> Result<Option<Block>> {
Ok(None)
}
}
impl TransactionsProvider for NoopProvider {
fn transaction_by_hash(&self, _hash: TxHash) -> Result<Option<TransactionSigned>> {
Ok(None)
}
fn transaction_by_id(&self, _id: TxNumber) -> Result<Option<TransactionSigned>> {
Ok(None)
}
fn transactions_by_block(&self, _block_id: BlockId) -> Result<Option<Vec<TransactionSigned>>> {
Ok(None)
}
fn transactions_by_block_range(
&self,
_range: impl RangeBounds<BlockNumber>,
) -> Result<Vec<Vec<TransactionSigned>>> {
Ok(Vec::default())
}
}
impl HeaderProvider for NoopProvider {
fn header(&self, _block_hash: &BlockHash) -> Result<Option<Header>> {
Ok(None)

View File

@ -1,63 +1,12 @@
use super::BlockHashProvider;
use crate::HeaderProvider;
use crate::{BlockIdProvider, HeaderProvider, TransactionsProvider};
use reth_interfaces::Result;
use reth_primitives::{
rpc::{BlockId, BlockNumber},
Block, ChainInfo, H256, U256,
};
use reth_primitives::{rpc::BlockId, Block};
/// Api trait for fetching `Block` related data.
#[auto_impl::auto_impl(&, Arc)]
pub trait BlockProvider: BlockHashProvider + HeaderProvider + Send + Sync {
/// Returns the current info for the chain.
fn chain_info(&self) -> Result<ChainInfo>;
pub trait BlockProvider:
BlockIdProvider + HeaderProvider + TransactionsProvider + Send + Sync
{
/// Returns the block. Returns `None` if block is not found.
fn block(&self, id: BlockId) -> Result<Option<Block>>;
/// Converts the `BlockNumber` variants.
fn convert_block_number(
&self,
num: BlockNumber,
) -> Result<Option<reth_primitives::BlockNumber>> {
let num = match num {
BlockNumber::Latest => self.chain_info()?.best_number,
BlockNumber::Earliest => 0,
BlockNumber::Pending => return Ok(None),
BlockNumber::Number(num) => num.as_u64(),
BlockNumber::Finalized => return Ok(self.chain_info()?.last_finalized),
BlockNumber::Safe => return Ok(self.chain_info()?.safe_finalized),
};
Ok(Some(num))
}
/// Get the hash of the block by matching the given id.
fn block_hash_for_id(&self, block_id: BlockId) -> Result<Option<H256>> {
match block_id {
BlockId::Hash(hash) => Ok(Some(H256(hash.0))),
BlockId::Number(num) => {
if matches!(num, BlockNumber::Latest) {
return Ok(Some(self.chain_info()?.best_hash))
}
self.convert_block_number(num)?
.map(|num| self.block_hash(U256::from(num)))
.transpose()
.map(|maybe_hash| maybe_hash.flatten())
}
}
}
/// Get the number of the block by matching the given id.
fn block_number_for_id(
&self,
block_id: BlockId,
) -> Result<Option<reth_primitives::BlockNumber>> {
match block_id {
BlockId::Hash(hash) => self.block_number(H256(hash.0)),
BlockId::Number(num) => self.convert_block_number(num),
}
}
/// Gets the `Block` for the given hash. Returns `None` if no block with this hash exists.
fn block_number(&self, hash: H256) -> Result<Option<reth_primitives::BlockNumber>>;
}

View File

@ -0,0 +1,59 @@
use super::BlockHashProvider;
use reth_interfaces::Result;
use reth_primitives::{
rpc::{BlockId, BlockNumber},
ChainInfo, H256, U256,
};
/// Client trait for transforming [BlockId].
#[auto_impl::auto_impl(&, Arc)]
pub trait BlockIdProvider: BlockHashProvider + Send + Sync {
/// Returns the current info for the chain.
fn chain_info(&self) -> Result<ChainInfo>;
/// Converts the `BlockNumber` variants.
fn convert_block_number(
&self,
num: BlockNumber,
) -> Result<Option<reth_primitives::BlockNumber>> {
let num = match num {
BlockNumber::Latest => self.chain_info()?.best_number,
BlockNumber::Earliest => 0,
BlockNumber::Pending => return Ok(None),
BlockNumber::Number(num) => num.as_u64(),
BlockNumber::Finalized => return Ok(self.chain_info()?.last_finalized),
BlockNumber::Safe => return Ok(self.chain_info()?.safe_finalized),
};
Ok(Some(num))
}
/// Get the hash of the block by matching the given id.
fn block_hash_for_id(&self, block_id: BlockId) -> Result<Option<H256>> {
match block_id {
BlockId::Hash(hash) => Ok(Some(H256(hash.0))),
BlockId::Number(num) => {
if matches!(num, BlockNumber::Latest) {
return Ok(Some(self.chain_info()?.best_hash))
}
self.convert_block_number(num)?
.map(|num| self.block_hash(U256::from(num)))
.transpose()
.map(|maybe_hash| maybe_hash.flatten())
}
}
}
/// Get the number of the block by matching the given id.
fn block_number_for_id(
&self,
block_id: BlockId,
) -> Result<Option<reth_primitives::BlockNumber>> {
match block_id {
BlockId::Hash(hash) => self.block_number(H256(hash.0)),
BlockId::Number(num) => self.convert_block_number(num),
}
}
/// Gets the `Block` for the given hash. Returns `None` if no block with this hash exists.
fn block_number(&self, hash: H256) -> Result<Option<reth_primitives::BlockNumber>>;
}

View File

@ -9,8 +9,17 @@ pub use block::BlockProvider;
mod block_hash;
pub use block_hash::BlockHashProvider;
mod block_id;
pub use block_id::BlockIdProvider;
mod header;
pub use header::HeaderProvider;
mod state;
pub use state::{StateProvider, StateProviderFactory};
mod transactions;
pub use transactions::TransactionsProvider;
mod withdrawals;
pub use withdrawals::WithdrawalsProvider;

View File

@ -0,0 +1,23 @@
use crate::BlockIdProvider;
use reth_interfaces::Result;
use reth_primitives::{rpc::BlockId, BlockNumber, TransactionSigned, TxHash, TxNumber};
use std::ops::RangeBounds;
/// Client trait for fetching [TransactionSigned] related data.
#[auto_impl::auto_impl(&, Arc)]
pub trait TransactionsProvider: BlockIdProvider + Send + Sync {
/// Get transaction by id.
fn transaction_by_id(&self, id: TxNumber) -> Result<Option<TransactionSigned>>;
/// Get transaction by transaction hash.
fn transaction_by_hash(&self, hash: TxHash) -> Result<Option<TransactionSigned>>;
/// Get transactions by block id.
fn transactions_by_block(&self, block: BlockId) -> Result<Option<Vec<TransactionSigned>>>;
/// Get transactions by block range.
fn transactions_by_block_range(
&self,
range: impl RangeBounds<BlockNumber>,
) -> Result<Vec<Vec<TransactionSigned>>>;
}

View File

@ -0,0 +1,8 @@
use reth_interfaces::Result;
use reth_primitives::{rpc::BlockId, Withdrawal};
/// Client trait for fetching [Withdrawal] related data.
pub trait WithdrawalsProvider: Send + Sync {
/// Get withdrawals by block id.
fn withdrawals_by_block(&self, id: BlockId, timestamp: u64) -> Result<Option<Vec<Withdrawal>>>;
}