Skip to content

Commit d7be452

Browse files
prestwichclaude
andauthored
fix: create alias oracle once per block instead of per address (#102)
* fix: create alias oracle once per block instead of per address AliasOracleFactory::create() was called for every address in the transaction loop, causing each check to potentially see different host state. Move the create() call before the loop so all addresses in a block are checked against the same state snapshot. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: expand alias oracle comment with Latest-vs-pinned analysis Explain why Latest is safe (EOAs can't become contracts without a birthday attack), the only risk (computationally infeasible collision), and why over-aliasing is benign compared to under-aliasing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c602aa8 commit d7be452

2 files changed

Lines changed: 36 additions & 14 deletions

File tree

crates/block-processor/src/v1/processor.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,6 @@ where
103103
Ok(trevm)
104104
}
105105

106-
/// Check if the given address should be aliased.
107-
fn should_alias(&self, address: Address) -> eyre::Result<bool> {
108-
self.alias_oracle.create()?.should_alias(address)
109-
}
110-
111106
/// Process a single extracted block, returning an [`ExecutedBlock`].
112107
///
113108
/// The caller is responsible for driving extraction (via [`Extractor`])
@@ -185,11 +180,13 @@ where
185180
None => VecDeque::new(),
186181
};
187182

188-
// Determine which addresses need to be aliased.
183+
// Determine which addresses need to be aliased. Create the oracle
184+
// once so every address check sees the same host state snapshot.
185+
let oracle = self.alias_oracle.create()?;
189186
let mut to_alias: HashSet<Address> = Default::default();
190187
for transact in block_extracts.transacts() {
191188
let addr = transact.host_sender();
192-
if !to_alias.contains(&addr) && self.should_alias(addr)? {
189+
if !to_alias.contains(&addr) && oracle.should_alias(addr)? {
193190
to_alias.insert(addr);
194191
}
195192
}

crates/node/src/alias.rs

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,38 @@ impl AliasOracleFactory for RethAliasOracleFactory {
6464
type Oracle = RethAliasOracle;
6565

6666
fn create(&self) -> eyre::Result<Self::Oracle> {
67-
// NB: This becomes a problem if anyone ever birthday attacks a
68-
// contract/EOA pair (c.f. EIP-3607). In practice this is unlikely to
69-
// happen for the foreseeable future, and if it does we can revisit
70-
// this decision.
71-
// We considered taking the host height as an argument to this method,
72-
// but this would require all nodes to be archive nodes in order to
73-
// sync, which is less than ideal
67+
// ## Why `Latest` instead of a pinned host height
68+
//
69+
// We use `Latest` rather than pinning to a specific host block
70+
// height because pinning would require every node to be an archive
71+
// node in order to sync historical state, which is impractical.
72+
//
73+
// ## Why `Latest` is safe
74+
//
75+
// An EOA cannot become a non-delegation contract without a birthday
76+
// attack (c.f. EIP-3607). CREATE and CREATE2 addresses are
77+
// deterministic and cannot target an existing EOA. EIP-7702
78+
// delegations are explicitly excluded by the `is_eip7702()` check
79+
// in `should_alias`, so delegated EOAs are never aliased. This
80+
// means the alias status of an address is stable across blocks
81+
// under normal conditions, making `Latest` equivalent to any
82+
// pinned height.
83+
//
84+
// ## The only risk: birthday attacks
85+
//
86+
// A birthday attack could produce a CREATE/CREATE2 collision with
87+
// an existing EOA, causing `should_alias` to return a false
88+
// positive. This is computationally infeasible for the foreseeable
89+
// future (~2^80 work), and if it ever becomes practical we can
90+
// revisit this decision.
91+
//
92+
// ## Over-aliasing vs under-aliasing
93+
//
94+
// Even in the birthday attack scenario, the result is
95+
// over-aliasing (a false positive), which is benign: a transaction
96+
// sender gets an aliased address when it shouldn't. The dangerous
97+
// failure mode — under-aliasing — cannot occur here because
98+
// contract bytecode is never removed once deployed.
7499
self.0
75100
.state_by_block_number_or_tag(alloy::eips::BlockNumberOrTag::Latest)
76101
.map(RethAliasOracle)

0 commit comments

Comments
 (0)