Skip to content

Commit 0b4117a

Browse files
prestwichclaude
andcommitted
refactor(host-rpc): collapse oracle types and add positive-result cache
Merge RpcAliasOracle and RpcAliasOracleFactory into a single RpcAliasOracle type that implements both traits. Add a shared Arc<RwLock<HashSet>> cache for positive results — once an address is confirmed as a non-delegation contract, subsequent lookups return immediately without an RPC call. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent eb9c753 commit 0b4117a

2 files changed

Lines changed: 38 additions & 28 deletions

File tree

crates/host-rpc/src/alias.rs

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,52 @@
11
use alloy::{
2-
eips::eip7702::constants::EIP7702_DELEGATION_DESIGNATOR, primitives::Address,
2+
eips::eip7702::constants::EIP7702_DELEGATION_DESIGNATOR,
3+
primitives::{Address, map::HashSet},
34
providers::Provider,
45
};
56
use signet_block_processor::{AliasOracle, AliasOracleFactory};
7+
use std::sync::{Arc, RwLock};
68

7-
/// An [`AliasOracle`] backed by an alloy RPC [`Provider`].
9+
/// An RPC-backed [`AliasOracle`] and [`AliasOracleFactory`].
810
///
911
/// Checks whether an address has non-delegation bytecode by fetching the
1012
/// code at the address via `eth_getCode`. Addresses with no code or with
1113
/// EIP-7702 delegation code are not aliased; addresses with any other
1214
/// bytecode are.
15+
///
16+
/// Positive results (address is a contract) are cached in a shared set.
17+
/// Contract status is permanent — an address cannot stop being a
18+
/// contract — so cached positives never go stale. All clones (including
19+
/// those produced by [`AliasOracleFactory::create`]) share the same
20+
/// cache.
21+
///
22+
/// Querying at `latest` is safe because alias status is stable across
23+
/// blocks: an EOA cannot become a non-delegation contract without a
24+
/// birthday attack (c.f. EIP-3607), and EIP-7702 delegations are
25+
/// excluded by the delegation designator check. Even in the
26+
/// (computationally infeasible ~2^80) birthday attack scenario, the
27+
/// result is a benign false-positive (over-aliasing), never a dangerous
28+
/// false-negative.
1329
#[derive(Debug, Clone)]
1430
pub struct RpcAliasOracle<P> {
1531
provider: P,
32+
/// Shared cache of addresses known to be non-delegation contracts.
33+
cache: Arc<RwLock<HashSet<Address>>>,
34+
}
35+
36+
impl<P> RpcAliasOracle<P> {
37+
/// Create a new [`RpcAliasOracle`] from an alloy provider.
38+
pub fn new(provider: P) -> Self {
39+
Self { provider, cache: Arc::new(RwLock::new(HashSet::default())) }
40+
}
1641
}
1742

1843
impl<P: Provider + Clone + 'static> AliasOracle for RpcAliasOracle<P> {
1944
async fn should_alias(&self, address: Address) -> eyre::Result<bool> {
45+
// Check cache first — if we've seen this address as a contract, skip RPC.
46+
if self.cache.read().expect("cache poisoned").contains(&address) {
47+
return Ok(true);
48+
}
49+
2050
let code = self.provider.get_code_at(address).await?;
2151
// No code — not a contract.
2252
if code.is_empty() {
@@ -26,36 +56,16 @@ impl<P: Provider + Clone + 'static> AliasOracle for RpcAliasOracle<P> {
2656
if code.starts_with(&EIP7702_DELEGATION_DESIGNATOR) {
2757
return Ok(false);
2858
}
29-
// Non-delegation contract — alias it.
59+
// Non-delegation contract — cache and alias it.
60+
self.cache.write().expect("cache poisoned").insert(address);
3061
Ok(true)
3162
}
3263
}
3364

34-
/// An [`AliasOracleFactory`] backed by an alloy RPC [`Provider`].
35-
///
36-
/// Creates [`RpcAliasOracle`] instances that query the host chain at
37-
/// `latest`. This is safe because alias status is stable across blocks:
38-
/// an EOA cannot become a non-delegation contract without a birthday
39-
/// attack (c.f. EIP-3607), and EIP-7702 delegations are excluded by the
40-
/// delegation designator check. Even in the (computationally infeasible
41-
/// ~2^80) birthday attack scenario, the result is a benign
42-
/// false-positive (over-aliasing), never a dangerous false-negative.
43-
#[derive(Debug, Clone)]
44-
pub struct RpcAliasOracleFactory<P> {
45-
provider: P,
46-
}
47-
48-
impl<P> RpcAliasOracleFactory<P> {
49-
/// Create a new [`RpcAliasOracleFactory`] from an alloy provider.
50-
pub const fn new(provider: P) -> Self {
51-
Self { provider }
52-
}
53-
}
54-
55-
impl<P: Provider + Clone + 'static> AliasOracleFactory for RpcAliasOracleFactory<P> {
56-
type Oracle = RpcAliasOracle<P>;
65+
impl<P: Provider + Clone + 'static> AliasOracleFactory for RpcAliasOracle<P> {
66+
type Oracle = Self;
5767

5868
fn create(&self) -> eyre::Result<Self::Oracle> {
59-
Ok(RpcAliasOracle { provider: self.provider.clone() })
69+
Ok(self.clone())
6070
}
6171
}

crates/host-rpc/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,4 @@ pub use segment::{RpcBlock, RpcChainSegment};
3333
mod metrics;
3434

3535
mod alias;
36-
pub use alias::{RpcAliasOracle, RpcAliasOracleFactory};
36+
pub use alias::RpcAliasOracle;

0 commit comments

Comments
 (0)