mod receipts;
mod set;
mod static_file;
mod user;
use crate::{PruneLimiter, PrunerError};
use alloy_primitives::{BlockNumber, TxNumber};
use reth_provider::{errors::provider::ProviderResult, BlockReader, PruneCheckpointWriter};
use reth_prune_types::{PruneCheckpoint, PruneMode, PrunePurpose, PruneSegment, SegmentOutput};
pub use set::SegmentSet;
pub use static_file::{
Headers as StaticFileHeaders, Receipts as StaticFileReceipts,
Transactions as StaticFileTransactions,
};
use std::{fmt::Debug, ops::RangeInclusive};
use tracing::error;
pub use user::{
AccountHistory, Receipts as UserReceipts, ReceiptsByLogs, SenderRecovery, StorageHistory,
TransactionLookup,
};
pub trait Segment<Provider>: Debug + Send + Sync {
fn segment(&self) -> PruneSegment;
fn mode(&self) -> Option<PruneMode>;
fn purpose(&self) -> PrunePurpose;
fn prune(&self, provider: &Provider, input: PruneInput) -> Result<SegmentOutput, PrunerError>;
fn save_checkpoint(
&self,
provider: &Provider,
checkpoint: PruneCheckpoint,
) -> ProviderResult<()>
where
Provider: PruneCheckpointWriter,
{
provider.save_prune_checkpoint(self.segment(), checkpoint)
}
}
#[derive(Debug)]
#[cfg_attr(test, derive(Clone))]
pub struct PruneInput {
pub(crate) previous_checkpoint: Option<PruneCheckpoint>,
pub(crate) to_block: BlockNumber,
pub(crate) limiter: PruneLimiter,
}
impl PruneInput {
pub(crate) fn get_next_tx_num_range<Provider: BlockReader>(
&self,
provider: &Provider,
) -> ProviderResult<Option<RangeInclusive<TxNumber>>> {
let from_tx_number = self.previous_checkpoint
.and_then(|checkpoint| match checkpoint.tx_number {
Some(tx_number) => Some(tx_number + 1),
_ => {
error!(target: "pruner", ?checkpoint, "Expected transaction number in prune checkpoint, found None");
None
},
})
.unwrap_or_default();
let to_tx_number = match provider.block_body_indices(self.to_block)? {
Some(body) => {
let last_tx = body.last_tx_num();
if last_tx + body.tx_count() == 0 {
return Ok(None)
}
last_tx
}
None => return Ok(None),
};
let range = from_tx_number..=to_tx_number;
if range.is_empty() {
return Ok(None)
}
Ok(Some(range))
}
pub(crate) fn get_next_block_range(&self) -> Option<RangeInclusive<BlockNumber>> {
let from_block = self.get_start_next_block_range();
let range = from_block..=self.to_block;
if range.is_empty() {
return None
}
Some(range)
}
pub(crate) fn get_start_next_block_range(&self) -> u64 {
self.previous_checkpoint
.and_then(|checkpoint| checkpoint.block_number)
.map(|block_number| block_number + 1)
.unwrap_or(0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::B256;
use reth_primitives_traits::BlockBody;
use reth_provider::{
providers::BlockchainProvider2,
test_utils::{create_test_provider_factory, MockEthProvider},
};
use reth_testing_utils::generators::{self, random_block_range, BlockRangeParams};
#[test]
fn test_prune_input_get_next_tx_num_range_no_to_block() {
let input = PruneInput {
previous_checkpoint: None,
to_block: 10,
limiter: PruneLimiter::default(),
};
let provider = MockEthProvider::default();
let range = input.get_next_tx_num_range(&provider).expect("Expected range");
assert!(range.is_none());
}
#[test]
fn test_prune_input_get_next_tx_num_range_no_tx() {
let input = PruneInput {
previous_checkpoint: None,
to_block: 10,
limiter: PruneLimiter::default(),
};
let mut rng = generators::rng();
let factory = create_test_provider_factory();
let blocks = random_block_range(
&mut rng,
0..=10,
BlockRangeParams { parent: Some(B256::ZERO), tx_count: 0..1, ..Default::default() },
);
let provider_rw = factory.provider_rw().expect("failed to get provider_rw");
for block in &blocks {
provider_rw
.insert_historical_block(
block.clone().seal_with_senders().expect("failed to seal block with senders"),
)
.expect("failed to insert block");
}
provider_rw.commit().expect("failed to commit");
let provider = BlockchainProvider2::new(factory).unwrap();
let range = input.get_next_tx_num_range(&provider).expect("Expected range");
assert!(range.is_none());
}
#[test]
fn test_prune_input_get_next_tx_num_range_valid() {
let input = PruneInput {
previous_checkpoint: None,
to_block: 10,
limiter: PruneLimiter::default(),
};
let mut rng = generators::rng();
let factory = create_test_provider_factory();
let blocks = random_block_range(
&mut rng,
0..=10,
BlockRangeParams { parent: Some(B256::ZERO), tx_count: 0..5, ..Default::default() },
);
let provider_rw = factory.provider_rw().expect("failed to get provider_rw");
for block in &blocks {
provider_rw
.insert_historical_block(
block.clone().seal_with_senders().expect("failed to seal block with senders"),
)
.expect("failed to insert block");
}
provider_rw.commit().expect("failed to commit");
let provider = BlockchainProvider2::new(factory).unwrap();
let range = input.get_next_tx_num_range(&provider).expect("Expected range").unwrap();
let num_txs =
blocks.iter().map(|block| block.body.transactions().len() as u64).sum::<u64>();
assert_eq!(range, 0..=num_txs - 1);
}
#[test]
fn test_prune_input_get_next_tx_checkpoint_without_tx_number() {
let input = PruneInput {
previous_checkpoint: Some(PruneCheckpoint {
block_number: Some(5),
tx_number: None,
prune_mode: PruneMode::Full,
}),
to_block: 10,
limiter: PruneLimiter::default(),
};
let mut rng = generators::rng();
let factory = create_test_provider_factory();
let blocks = random_block_range(
&mut rng,
0..=10,
BlockRangeParams { parent: Some(B256::ZERO), tx_count: 0..5, ..Default::default() },
);
let provider_rw = factory.provider_rw().expect("failed to get provider_rw");
for block in &blocks {
provider_rw
.insert_historical_block(
block.clone().seal_with_senders().expect("failed to seal block with senders"),
)
.expect("failed to insert block");
}
provider_rw.commit().expect("failed to commit");
let provider = BlockchainProvider2::new(factory).unwrap();
let range = input.get_next_tx_num_range(&provider).expect("Expected range").unwrap();
let num_txs =
blocks.iter().map(|block| block.body.transactions().len() as u64).sum::<u64>();
assert_eq!(range, 0..=num_txs - 1,);
}
#[test]
fn test_prune_input_get_next_tx_empty_range() {
let mut rng = generators::rng();
let factory = create_test_provider_factory();
let blocks = random_block_range(
&mut rng,
0..=10,
BlockRangeParams { parent: Some(B256::ZERO), tx_count: 0..5, ..Default::default() },
);
let provider_rw = factory.provider_rw().expect("failed to get provider_rw");
for block in &blocks {
provider_rw
.insert_historical_block(
block.clone().seal_with_senders().expect("failed to seal block with senders"),
)
.expect("failed to insert block");
}
provider_rw.commit().expect("failed to commit");
let provider = BlockchainProvider2::new(factory).unwrap();
let num_txs =
blocks.iter().map(|block| block.body.transactions().len() as u64).sum::<u64>();
let max_range = num_txs - 1;
let input = PruneInput {
previous_checkpoint: Some(PruneCheckpoint {
block_number: Some(5),
tx_number: Some(max_range),
prune_mode: PruneMode::Full,
}),
to_block: 10,
limiter: PruneLimiter::default(),
};
let range = input.get_next_tx_num_range(&provider).expect("Expected range");
assert!(range.is_none());
}
}