hopr_db_sql/
cache.rs

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/// Lists all singular data that can be cached and
13/// cannot be represented by a key. These values can be cached for the long term.
14#[derive(Debug, Clone, strum::EnumDiscriminants)]
15#[strum_discriminants(derive(Hash))]
16pub enum CachedValue {
17    /// Cached [IndexerData].
18    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    /// Creates key id mapping for a public key of an [account](AccountEntry).
49    ///
50    /// Does nothing if the binding already exists. Returns error if an existing binding
51    /// is not consistent.
52    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        // Lock entries in the maps to avoid concurrent modifications
57        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                // Check if the existing binding is consistent with the new one.
69                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            // This only happens on re-announcements:
81            // The re-announcement uses the same packet key and chain-key, but the block number (published at)
82            // is different, and therefore the id_entry will be vacant.
83            (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            // This should never happen.
91            (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/// Contains all caches used by the [crate::db::HoprDb].
113#[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    // KeyIdMapper must be synchronous because it is used from a sync context.
120    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}