diff --git a/crates/block-processor/src/alias.rs b/crates/block-processor/src/alias.rs index f9daaab..dd4bc07 100644 --- a/crates/block-processor/src/alias.rs +++ b/crates/block-processor/src/alias.rs @@ -71,13 +71,38 @@ impl AliasOracleFactory for Box { type Oracle = StateProviderBox; fn create(&self) -> eyre::Result { - // NB: This becomes a problem if anyone ever birthday attacks a - // contract/EOA pair (c.f. EIP-3607). In practice this is unlikely to - // happen for the foreseeable future, and if it does we can revisit - // this decision. - // We considered taking the host height as an argument to this method, - // but this would require all nodes to be archive nodes in order to - // sync, which is less than ideal + // ## Why `Latest` instead of a pinned host height + // + // We use `Latest` rather than pinning to a specific host block + // height because pinning would require every node to be an archive + // node in order to sync historical state, which is impractical. + // + // ## Why `Latest` is safe + // + // An EOA cannot become a non-delegation contract without a birthday + // attack (c.f. EIP-3607). CREATE and CREATE2 addresses are + // deterministic and cannot target an existing EOA. EIP-7702 + // delegations are explicitly excluded by the `is_eip7702()` check + // in `should_alias`, so delegated EOAs are never aliased. This + // means the alias status of an address is stable across blocks + // under normal conditions, making `Latest` equivalent to any + // pinned height. + // + // ## The only risk: birthday attacks + // + // A birthday attack could produce a CREATE/CREATE2 collision with + // an existing EOA, causing `should_alias` to return a false + // positive. This is computationally infeasible for the foreseeable + // future (~2^80 work), and if it ever becomes practical we can + // revisit this decision. + // + // ## Over-aliasing vs under-aliasing + // + // Even in the birthday attack scenario, the result is + // over-aliasing (a false positive), which is benign: a transaction + // sender gets an aliased address when it shouldn't. The dangerous + // failure mode — under-aliasing — cannot occur here because + // contract bytecode is never removed once deployed. self.state_by_block_number_or_tag(alloy::eips::BlockNumberOrTag::Latest).map_err(Into::into) } } diff --git a/crates/block-processor/src/v1/processor.rs b/crates/block-processor/src/v1/processor.rs index 154986e..baca8cb 100644 --- a/crates/block-processor/src/v1/processor.rs +++ b/crates/block-processor/src/v1/processor.rs @@ -107,11 +107,6 @@ where Ok(trevm) } - /// Check if the given address should be aliased. - fn should_alias(&self, address: Address) -> eyre::Result { - self.alias_oracle.create()?.should_alias(address) - } - /// Called when the host chain has committed a block or set of blocks. #[instrument(skip_all, fields(count = chain.len(), first = chain.first().number(), tip = chain.tip().number()))] pub async fn on_host_commit(&self, chain: &Chain) -> eyre::Result> @@ -272,11 +267,14 @@ where None => VecDeque::new(), }; - // Determine which addresses need to be aliased. + // Determine which addresses need to be aliased. The oracle is + // created once per block so all addresses share the same state + // snapshot. + let oracle = self.alias_oracle.create()?; let mut to_alias: HashSet
= Default::default(); for transact in block_extracts.transacts() { let addr = transact.host_sender(); - if !to_alias.contains(&addr) && self.should_alias(addr)? { + if !to_alias.contains(&addr) && oracle.should_alias(addr)? { to_alias.insert(addr); } }