Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into alexey/prune-mode-tar…
Browse files Browse the repository at this point in the history
…get-block
  • Loading branch information
shekhirin committed Aug 1, 2023
2 parents 599b329 + 1249601 commit 3df29f0
Show file tree
Hide file tree
Showing 14 changed files with 393 additions and 107 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions crates/primitives/src/prune/target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ use serde::{Deserialize, Serialize};
#[serde(default)]
pub struct PruneModes {
/// Sender Recovery pruning configuration.
#[serde(skip_serializing_if = "Option::is_none")]
// TODO(alexey): removing min blocks restriction is possible if we start calculating the senders
// dynamically on blockchain tree unwind.
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<64, _>"
)]
pub sender_recovery: Option<PruneMode>,
/// Transaction Lookup pruning configuration.
#[serde(skip_serializing_if = "Option::is_none")]
Expand Down Expand Up @@ -110,7 +115,7 @@ impl PruneModes {
}

impl_prune_parts!(
(sender_recovery, SenderRecovery, None),
(sender_recovery, SenderRecovery, Some(64)),
(transaction_lookup, TransactionLookup, None),
(receipts, Receipts, Some(64)),
(account_history, AccountHistory, Some(64)),
Expand Down
139 changes: 130 additions & 9 deletions crates/prune/src/pruner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ pub type PrunerWithResult<DB> = (Pruner<DB>, PrunerResult);
pub struct BatchSizes {
receipts: usize,
transaction_lookup: usize,
transaction_senders: usize,
}

impl Default for BatchSizes {
fn default() -> Self {
Self { receipts: 10000, transaction_lookup: 10000 }
Self { receipts: 10000, transaction_lookup: 10000, transaction_senders: 10000 }
}
}

Expand Down Expand Up @@ -83,6 +84,12 @@ impl<DB: Database> Pruner<DB> {
self.prune_transaction_lookup(&provider, to_block, prune_mode)?;
}

if let Some((to_block, prune_mode)) =
self.modes.prune_target_block_sender_recovery(tip_block_number)?
{
self.prune_transaction_senders(&provider, to_block, prune_mode)?;
}

provider.commit()?;

self.last_pruned_block_number = Some(tip_block_number);
Expand Down Expand Up @@ -124,13 +131,16 @@ impl<DB: Database> Pruner<DB> {
prune_part: PrunePart,
to_block: BlockNumber,
) -> reth_interfaces::Result<Option<RangeInclusive<TxNumber>>> {
let from_tx_num = provider
.get_prune_checkpoint(prune_part)?
.map(|checkpoint| provider.block_body_indices(checkpoint.block_number + 1))
.transpose()?
.flatten()
.map(|body| body.first_tx_num)
.unwrap_or_default();
let checkpoint = provider.get_prune_checkpoint(prune_part)?.unwrap_or(PruneCheckpoint {
block_number: 0, // No checkpoint, fresh pruning
prune_mode: PruneMode::Full, // Doesn't matter in this case, can be anything
});
// Get first transaction of the next block after the highest pruned one
let from_tx_num =
provider.block_body_indices(checkpoint.block_number + 1)?.map(|body| body.first_tx_num);
// If no block body index is found, the DB is either corrupted or we've already pruned up to
// the latest block, so there's no thing to prune now.
let Some(from_tx_num) = from_tx_num else { return Ok(None) };

let to_tx_num = match provider.block_body_indices(to_block)? {
Some(body) => body,
Expand Down Expand Up @@ -200,7 +210,7 @@ impl<DB: Database> Pruner<DB> {
)? {
Some(range) => range,
None => {
trace!(target: "pruner", "No receipts to prune");
trace!(target: "pruner", "No transaction lookup entries to prune");
return Ok(())
}
};
Expand Down Expand Up @@ -248,6 +258,50 @@ impl<DB: Database> Pruner<DB> {

Ok(())
}

/// Prune transaction senders up to the provided block, inclusive.
#[instrument(level = "trace", skip(self, provider), target = "pruner")]
fn prune_transaction_senders(
&self,
provider: &DatabaseProviderRW<'_, DB>,
to_block: BlockNumber,
prune_mode: PruneMode,
) -> PrunerResult {
let range = match self.get_next_tx_num_range_from_checkpoint(
provider,
PrunePart::SenderRecovery,
to_block,
)? {
Some(range) => range,
None => {
trace!(target: "pruner", "No transaction senders to prune");
return Ok(())
}
};
let total = range.clone().count();

let mut processed = 0;
provider.prune_table_in_batches::<tables::TxSenders, _>(
range,
self.batch_sizes.transaction_senders,
|entries| {
processed += entries;
trace!(
target: "pruner",
%entries,
progress = format!("{:.1}%", 100.0 * processed as f64 / total as f64),
"Pruned transaction senders"
);
},
)?;

provider.save_prune_checkpoint(
PrunePart::SenderRecovery,
PruneCheckpoint { block_number: to_block, prune_mode },
)?;

Ok(())
}
}

#[cfg(test)]
Expand Down Expand Up @@ -409,4 +463,71 @@ mod tests {
// ended last time
test_prune(20);
}

#[test]
fn prune_transaction_senders() {
let tx = TestTransaction::default();
let mut rng = generators::rng();

let blocks = random_block_range(&mut rng, 0..=100, H256::zero(), 0..10);
tx.insert_blocks(blocks.iter(), None).expect("insert blocks");

let mut transaction_senders = Vec::new();
for block in &blocks {
for transaction in &block.body {
transaction_senders.push((
transaction_senders.len() as u64,
transaction.recover_signer().expect("recover signer"),
));
}
}
tx.insert_transaction_senders(transaction_senders).expect("insert transaction senders");

assert_eq!(
tx.table::<tables::Transactions>().unwrap().len(),
blocks.iter().map(|block| block.body.len()).sum::<usize>()
);
assert_eq!(
tx.table::<tables::Transactions>().unwrap().len(),
tx.table::<tables::TxSenders>().unwrap().len()
);

let test_prune = |to_block: BlockNumber| {
let prune_mode = PruneMode::Before(to_block);
let pruner = Pruner::new(
tx.inner_raw(),
MAINNET.clone(),
5,
0,
PruneModes { sender_recovery: Some(prune_mode), ..Default::default() },
BatchSizes {
// Less than total amount of blocks to prune to test the batching logic
transaction_senders: 10,
..Default::default()
},
);

let provider = tx.inner_rw();
assert_matches!(
pruner.prune_transaction_senders(&provider, to_block, prune_mode),
Ok(())
);
provider.commit().expect("commit");

assert_eq!(
tx.table::<tables::TxSenders>().unwrap().len(),
blocks[to_block as usize + 1..].iter().map(|block| block.body.len()).sum::<usize>()
);
assert_eq!(
tx.inner().get_prune_checkpoint(PrunePart::SenderRecovery).unwrap(),
Some(PruneCheckpoint { block_number: to_block, prune_mode })
);
};

// Pruning first time ever, no previous checkpoint is present
test_prune(10);
// Prune second time, previous checkpoint is present, should continue pruning from where
// ended last time
test_prune(20);
}
}
66 changes: 61 additions & 5 deletions crates/revm/revm-inspectors/src/tracing/builder/parity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,25 @@ impl ParityTraceBuilder {
if with_traces {
let trace = node.parity_transaction_trace(trace_address);
traces.push(trace);

// check if the trace node is a selfdestruct
if node.is_selfdestruct() {
// selfdestructs are not recorded as individual call traces but are derived from
// the call trace and are added as additional `TransactionTrace` objects in the
// trace array
let addr = {
let last = traces.last_mut().expect("exists");
let mut addr = last.trace_address.clone();
addr.push(last.subtraces);
// need to account for the additional selfdestruct trace
last.subtraces += 1;
addr
};

if let Some(trace) = node.parity_selfdestruct_trace(addr) {
traces.push(trace);
}
}
}
if with_diff {
node.parity_update_state_diff(&mut diff);
Expand All @@ -232,11 +251,15 @@ impl ParityTraceBuilder {
/// Returns an iterator over all recorded traces for `trace_transaction`
pub fn into_transaction_traces_iter(self) -> impl Iterator<Item = TransactionTrace> {
let trace_addresses = self.trace_addresses();
self.nodes
.into_iter()
.zip(trace_addresses)
.filter(|(node, _)| !node.is_precompile())
.map(|(node, trace_address)| node.parity_transaction_trace(trace_address))
TransactionTraceIter {
next_selfdestruct: None,
iter: self
.nodes
.into_iter()
.zip(trace_addresses)
.filter(|(node, _)| !node.is_precompile())
.map(|(node, trace_address)| (node.parity_transaction_trace(trace_address), node)),
}
}

/// Returns the raw traces of the transaction
Expand Down Expand Up @@ -344,6 +367,39 @@ impl ParityTraceBuilder {
}
}

/// An iterator for [TransactionTrace]s
///
/// This iterator handles additional selfdestruct actions based on the last emitted
/// [TransactionTrace], since selfdestructs are not recorded as individual call traces but are
/// derived from recorded call
struct TransactionTraceIter<Iter> {
iter: Iter,
next_selfdestruct: Option<TransactionTrace>,
}

impl<Iter> Iterator for TransactionTraceIter<Iter>
where
Iter: Iterator<Item = (TransactionTrace, CallTraceNode)>,
{
type Item = TransactionTrace;

fn next(&mut self) -> Option<Self::Item> {
if let Some(selfdestruct) = self.next_selfdestruct.take() {
return Some(selfdestruct)
}
let (mut trace, node) = self.iter.next()?;
if node.is_selfdestruct() {
// since selfdestructs are emitted as additional trace, increase the trace count
let mut addr = trace.trace_address.clone();
addr.push(trace.subtraces);
// need to account for the additional selfdestruct trace
trace.subtraces += 1;
self.next_selfdestruct = node.parity_selfdestruct_trace(addr);
}
Some(trace)
}
}

/// addresses are presorted via breadth first walk thru [CallTraceNode]s, this can be done by a
/// walker in [crate::tracing::builder::walker]
///
Expand Down
44 changes: 36 additions & 8 deletions crates/revm/revm-inspectors/src/tracing/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,12 @@ impl CallTraceNode {
self.trace.status
}

/// Returns true if the call was a selfdestruct
#[inline]
pub(crate) fn is_selfdestruct(&self) -> bool {
self.status() == InstructionResult::SelfDestruct
}

/// Updates the values of the state diff
pub(crate) fn parity_update_state_diff(&self, diff: &mut StateDiff) {
let addr = self.trace.address;
Expand Down Expand Up @@ -348,9 +354,7 @@ impl CallTraceNode {
/// Converts this node into a parity `TransactionTrace`
pub(crate) fn parity_transaction_trace(&self, trace_address: Vec<usize>) -> TransactionTrace {
let action = self.parity_action();
let result = if action.is_selfdestruct() ||
(self.trace.is_error() && !self.trace.is_revert())
{
let result = if self.trace.is_error() && !self.trace.is_revert() {
// if the trace is a selfdestruct or an error that is not a revert, the result is None
None
} else {
Expand All @@ -377,15 +381,39 @@ impl CallTraceNode {
}
}

/// Returns the `Action` for a parity trace
pub(crate) fn parity_action(&self) -> Action {
if self.status() == InstructionResult::SelfDestruct {
return Action::Selfdestruct(SelfdestructAction {
/// If the trace is a selfdestruct, returns the `Action` for a parity trace.
pub(crate) fn parity_selfdestruct_action(&self) -> Option<Action> {
if self.is_selfdestruct() {
Some(Action::Selfdestruct(SelfdestructAction {
address: self.trace.address,
refund_address: self.trace.selfdestruct_refund_target.unwrap_or_default(),
balance: self.trace.value,
})
}))
} else {
None
}
}

/// If the trace is a selfdestruct, returns the `TransactionTrace` for a parity trace.
pub(crate) fn parity_selfdestruct_trace(
&self,
trace_address: Vec<usize>,
) -> Option<TransactionTrace> {
let trace = self.parity_selfdestruct_action()?;
Some(TransactionTrace {
action: trace,
error: None,
result: None,
trace_address,
subtraces: 0,
})
}

/// Returns the `Action` for a parity trace.
///
/// Caution: This does not include the selfdestruct action, if the trace is a selfdestruct,
/// since those are handled in addition to the call action.
pub(crate) fn parity_action(&self) -> Action {
match self.kind() {
CallKind::Call | CallKind::StaticCall | CallKind::CallCode | CallKind::DelegateCall => {
Action::Call(CallAction {
Expand Down
Loading

0 comments on commit 3df29f0

Please sign in to comment.