← work
[security]2024-07-01

block-tracer

A contract at address X might not be the contract you audited. After SELFDESTRUCT + CREATE, the bytecode changes but the address doesn't. block-tracer scans for this reinit pattern — 32-way parallel traces, reth's MDBX queried directly for current state.

RustrethtokioEthereum

Overview

After EIP-6780, SELFDESTRUCT only deletes a contract's code in the same transaction it was created. But before the merge, contracts could be destroyed and recreated at the same address — creating a subtle security surface: a contract that looks safe at address X might be a different contract than the one audited.

block-tracer detects these reinitialized contracts by scanning a range of Ethereum blocks for SELFDESTRUCT + CREATE pairs at the same address.

How it works

cargo run <start_block> <end_block>
# output: reinitialized_contracts.json

For each block in range:

  1. Call trace_block via reth's RPC to get all create/selfdestruct actions
  2. Collect self-destructed addresses and subsequently created addresses
  3. Find the intersection — addresses that appear in both sets
  4. For addresses where the reinit falls outside the scan range, query reth's PlainAccountState table directly to check current status

Performance design

Tracing blocks via RPC is the bottleneck (90% of runtime). The implementation spawns one tokio task per block, with a semaphore capping concurrency at 32 to avoid overwhelming the reth node.

DB queries are batched: self-destructed addresses are chunked and queried in parallel via separate tokio tasks. The gain here is modest (reth's MDBX is fast), but it avoids serializing on large scan ranges.

Accessing reth internals

The interesting part is the direct DB access: rather than making an RPC call for every address, we open reth's MDBX database directly and query the PlainAccountState table. This requires knowing reth's internal table layout — which is stable but not formally documented.