1use std::time::Duration;
2
3use dashmap::{DashMap, Entry};
4use hopr_crypto_packet::{HoprSphinxHeaderSpec, HoprSphinxSuite};
5use hopr_crypto_types::prelude::OffchainPublicKey;
6use hopr_internal_types::prelude::{AccountEntry, ChannelEntry};
7use hopr_primitive_types::prelude::{Address, KeyIdent};
8use moka::future::Cache;
9
10use crate::{errors::DbSqlError, info::IndexerData};
11
12#[derive(Debug, Clone, strum::EnumDiscriminants)]
15#[strum_discriminants(derive(Hash))]
16pub enum CachedValue {
17 IndexerDataCache(IndexerData),
19}
20
21impl TryFrom<CachedValue> for IndexerData {
22 type Error = DbSqlError;
23
24 fn try_from(value: CachedValue) -> Result<Self, Self::Error> {
25 match value {
26 CachedValue::IndexerDataCache(data) => Ok(data),
27 }
28 }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32pub(crate) struct ChannelParties(pub(crate) Address, pub(crate) Address);
33
34#[derive(Debug, Clone)]
35pub struct CacheKeyMapper(
36 std::sync::Arc<DashMap<KeyIdent<4>, OffchainPublicKey>>,
37 std::sync::Arc<DashMap<OffchainPublicKey, KeyIdent<4>>>,
38);
39
40impl CacheKeyMapper {
41 pub fn with_capacity(capacity: usize) -> Self {
42 Self(
43 std::sync::Arc::new(DashMap::with_capacity(capacity)),
44 std::sync::Arc::new(DashMap::with_capacity(capacity)),
45 )
46 }
47
48 pub fn update_key_id_binding(&self, account: &AccountEntry) -> Result<(), DbSqlError> {
53 let id = account.key_id();
54 let key = account.public_key;
55
56 let id_entry = self.0.entry(id);
58 let key_entry = self.1.entry(key);
59
60 match (id_entry, key_entry) {
61 (Entry::Vacant(v_id), Entry::Vacant(v_key)) => {
62 v_id.insert_entry(key);
63 v_key.insert_entry(id);
64 tracing::debug!(%id, %key, "inserted key-id binding");
65 Ok(())
66 }
67 (Entry::Occupied(v_id), Entry::Occupied(v_key)) => {
68 if v_id.get() != v_key.key() {
70 Err(DbSqlError::LogicalError(format!(
71 "attempt to insert key {key} with key-id {id}, but key-id already maps to key {} while {} is \
72 expected",
73 v_id.get(),
74 v_key.key(),
75 )))
76 } else {
77 Ok(())
78 }
79 }
80 (Entry::Vacant(_), Entry::Occupied(v_key)) => {
84 tracing::debug!(
85 "attempt to insert key {key} with key-id {id} failed because key is already set as {}",
86 v_key.get()
87 );
88 Err(DbSqlError::LogicalError("inconsistent key-id binding".into()))
89 }
90 (Entry::Occupied(v_id), Entry::Vacant(_)) => {
92 tracing::debug!(
93 "attempt to insert key {key} with key-id {id} failed because key-id is already set as {}",
94 v_id.get()
95 );
96 Err(DbSqlError::LogicalError("inconsistent key-id binding".into()))
97 }
98 }
99 }
100}
101
102impl hopr_crypto_packet::KeyIdMapper<HoprSphinxSuite, HoprSphinxHeaderSpec> for CacheKeyMapper {
103 fn map_key_to_id(&self, key: &OffchainPublicKey) -> Option<KeyIdent> {
104 self.1.get(key).map(|k| *k.value())
105 }
106
107 fn map_id_to_public(&self, id: &KeyIdent) -> Option<OffchainPublicKey> {
108 self.0.get(id).map(|k| *k.value())
109 }
110}
111
112#[derive(Debug, Clone)]
114pub struct HoprIndexerDbCaches {
115 pub(crate) single_values: Cache<CachedValueDiscriminants, CachedValue>,
116 pub(crate) chain_to_offchain: Cache<Address, Option<OffchainPublicKey>>,
117 pub(crate) offchain_to_chain: Cache<OffchainPublicKey, Option<Address>>,
118 pub(crate) src_dst_to_channel: Cache<ChannelParties, Option<ChannelEntry>>,
119 pub(crate) key_id_mapper: CacheKeyMapper,
121}
122
123impl Default for HoprIndexerDbCaches {
124 fn default() -> Self {
125 Self {
126 single_values: Cache::builder().time_to_idle(Duration::from_secs(1800)).build(),
127 chain_to_offchain: Cache::builder()
128 .time_to_idle(Duration::from_secs(600))
129 .max_capacity(100_000)
130 .build(),
131 offchain_to_chain: Cache::builder()
132 .time_to_idle(Duration::from_secs(600))
133 .max_capacity(100_000)
134 .build(),
135 src_dst_to_channel: Cache::builder()
136 .time_to_live(Duration::from_secs(600))
137 .max_capacity(10_000)
138 .build(),
139 key_id_mapper: CacheKeyMapper::with_capacity(10_000),
140 }
141 }
142}
143
144#[cfg(test)]
145impl HoprIndexerDbCaches {
146 pub fn invalidate_all(&self) {
147 self.src_dst_to_channel.invalidate_all();
148 self.chain_to_offchain.invalidate_all();
149 self.offchain_to_chain.invalidate_all();
150 self.single_values.invalidate_all();
151 }
152}