Skip to content
This repository was archived by the owner on Aug 3, 2026. It is now read-only.

Commit 9c136cb

Browse files
authored
chore: never evict fee token asset metadata from cache (#1494)
`wallet_getCapabilities` will try and fetch the metadata of all its fee tokens. If they're not in the cache because they got evicted, they'll be fetched onchain. This can be problematic if any of the provider is having issues This ensures that they are always in the cache without ever getting evicted.
1 parent 9849fea commit 9c136cb

2 files changed

Lines changed: 64 additions & 3 deletions

File tree

src/asset.rs

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Asset info service.
22
use crate::{
3+
chains::Chain,
34
config::RelayConfig,
45
error::{AssetError, RelayError},
56
types::{
@@ -17,8 +18,10 @@ use alloy::{
1718
sol_types::SolCall,
1819
transports::TransportErrorKind,
1920
};
21+
use futures_util::future::join_all;
2022
use schnellru::{ByLength, LruMap};
2123
use std::{
24+
collections::hash_map::Entry,
2225
pin::Pin,
2326
task::{Context, Poll, ready},
2427
};
@@ -29,7 +32,7 @@ use tokio::{
2932
},
3033
try_join,
3134
};
32-
use tracing::{error, trace};
35+
use tracing::{error, info, trace};
3336

3437
/// Messages accepted by the [`AssetInfoService`].
3538
#[derive(Debug)]
@@ -203,7 +206,34 @@ impl AssetInfoServiceHandle {
203206

204207
Ok(builder.build(metadata, tokens_uris))
205208
}
209+
210+
/// Populate fee token metadata cache for all chains.
211+
///
212+
/// These will never be evicted.
213+
pub async fn populate_fee_tokens(&self, chains: &HashMap<ChainId, Chain>) {
214+
info!("Populating fee token metadata for all chains");
215+
216+
let _ = join_all(chains.values().filter(|chain| !chain.assets().fee_tokens().is_empty()).map(
217+
async |chain| {
218+
let fee_tokens = chain.assets().fee_tokens();
219+
220+
self.get_asset_info_list(
221+
chain.provider(),
222+
fee_tokens.iter().map(|(_, desc)| Asset::from(desc.address)).collect(),
223+
)
224+
.await
225+
.inspect(|_| {
226+
info!(chain_id = chain.id(), count = fee_tokens.len(), "Pre-populated fee tokens")
227+
})
228+
.inspect_err(|e| {
229+
error!(chain_id = chain.id(), error = %e, "Failed to pre-populate fee tokens")
230+
})
231+
},
232+
))
233+
.await;
234+
}
206235
}
236+
207237
/// Service that provides [`AssetWithInfo`] about any kind of asset.
208238
///
209239
/// TODO: apart from onchain, there should be a more trusted source that can be passed when
@@ -212,6 +242,11 @@ impl AssetInfoServiceHandle {
212242
pub struct AssetInfoService {
213243
/// Cached asset metadata per chain.
214244
cache: LruMap<(ChainId, Asset), AssetWithInfo>,
245+
/// Fee token metadata per chain. These are never evicted.
246+
///
247+
/// Keys are populated during initialization based on config, but values start as `None`
248+
/// and get populated later by chains when the metadata is fetched.
249+
fee_tokens: HashMap<(ChainId, Asset), Option<AssetWithInfo>>,
215250
/// Sender half for asset info messages.
216251
command_tx: UnboundedSender<AssetInfoServiceMessage>,
217252
/// Incoming messages for the service.
@@ -225,8 +260,23 @@ impl AssetInfoService {
225260
pub fn new(capacity: u32, config: &RelayConfig) -> Self {
226261
let (command_tx, command_rx) = unbounded_channel();
227262

263+
let mut fee_tokens = HashMap::default();
264+
for (chain, chain_config) in &config.chains {
265+
for (_, asset_desc) in chain_config.assets.iter() {
266+
if asset_desc.fee_token {
267+
let asset = if asset_desc.address.is_zero() {
268+
Asset::Native
269+
} else {
270+
Asset::Token(asset_desc.address)
271+
};
272+
fee_tokens.insert((chain.id(), asset), None);
273+
}
274+
}
275+
}
276+
228277
Self {
229278
cache: LruMap::new(ByLength::new(capacity)),
279+
fee_tokens,
230280
command_tx,
231281
command_rx,
232282
native_symbols: config
@@ -272,7 +322,10 @@ impl AssetInfoService {
272322
},
273323
})
274324
} else {
275-
self.cache.get(&(chain_id, asset)).cloned()
325+
self.fee_tokens
326+
.get(&(chain_id, asset))
327+
.and_then(|opt| opt.clone())
328+
.or_else(|| self.cache.get(&(chain_id, asset)).cloned())
276329
};
277330

278331
(asset, info)
@@ -286,7 +339,13 @@ impl AssetInfoService {
286339
trace!(chain_id, ?assets, "Received update request for asset infos.");
287340

288341
for asset_with_info in assets {
289-
self.cache.get_or_insert((chain_id, asset_with_info.asset), || asset_with_info);
342+
let key = (chain_id, asset_with_info.asset);
343+
344+
if let Entry::Occupied(mut e) = self.fee_tokens.entry(key) {
345+
e.insert(Some(asset_with_info));
346+
} else {
347+
self.cache.get_or_insert(key, || asset_with_info);
348+
}
290349
}
291350
}
292351
}

src/chains.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,8 @@ impl Chains {
363363
None
364364
};
365365

366+
asset_info.populate_fee_tokens(&chains).await;
367+
366368
Ok(Self { chains, interop })
367369
}
368370

0 commit comments

Comments
 (0)