Skip to main content

hopr_chain_connector/backend/
mod.rs

1mod tempdb;
2
3pub use hopr_api::{
4    chain::HoprKeyIdent,
5    types::{
6        crypto::prelude::OffchainPublicKey,
7        internal::{
8            account::AccountEntry,
9            channels::{ChannelEntry, ChannelId},
10        },
11        primitive::prelude::Address,
12    },
13};
14
15/// Represents a cache backend for the connector.
16pub trait Backend {
17    type Error: std::error::Error + Send + Sync + 'static;
18    /// Inserts an [`AccountEntry`] into the backend.
19    ///
20    /// Returns the old value if one was present in the backend before.
21    fn insert_account(&self, entry: AccountEntry) -> Result<Option<AccountEntry>, Self::Error>;
22    /// Inserts a [`ChannelEntry`] into the backend.
23    ///
24    /// Returns the old value if one was present in the backend before.
25    fn insert_channel(&self, channel: ChannelEntry) -> Result<Option<ChannelEntry>, Self::Error>;
26    /// Retrieves an [`AccountEntry`] by [`HoprKeyIdent`].
27    fn get_account_by_id(&self, id: &HoprKeyIdent) -> Result<Option<AccountEntry>, Self::Error>;
28    /// Retrieves an [`AccountEntry`] by [`OffchainPublicKey`].
29    fn get_account_by_key(&self, key: &OffchainPublicKey) -> Result<Option<AccountEntry>, Self::Error>;
30    /// Retrieves an [`AccountEntry`] by an on-chain [`Address`].
31    fn get_account_by_address(&self, chain_key: &Address) -> Result<Option<AccountEntry>, Self::Error>;
32    /// Retrieves a [`ChannelEntry`] by its [`ChannelId`].
33    fn get_channel_by_id(&self, id: &ChannelId) -> Result<Option<ChannelEntry>, Self::Error>;
34}
35
36pub use tempdb::{TempDbBackend, TempDbError};
37
38/// Represents a backend that stores all data in-memory.
39///
40/// This is useful mainly for testing.
41#[cfg(any(test, feature = "testing"))]
42#[derive(Clone)]
43pub struct InMemoryBackend {
44    accounts: std::sync::Arc<dashmap::DashMap<HoprKeyIdent, AccountEntry, ahash::RandomState>>,
45    channels: std::sync::Arc<dashmap::DashMap<ChannelId, ChannelEntry, ahash::RandomState>>,
46}
47
48#[cfg(any(test, feature = "testing"))]
49const DEFAULT_INMEMORY_BACKEND_CAPACITY: usize = 1024;
50
51#[cfg(any(test, feature = "testing"))]
52impl Default for InMemoryBackend {
53    fn default() -> Self {
54        Self {
55            accounts: dashmap::DashMap::with_capacity_and_hasher(
56                DEFAULT_INMEMORY_BACKEND_CAPACITY,
57                ahash::RandomState::default(),
58            )
59            .into(),
60            channels: dashmap::DashMap::with_capacity_and_hasher(
61                DEFAULT_INMEMORY_BACKEND_CAPACITY,
62                ahash::RandomState::default(),
63            )
64            .into(),
65        }
66    }
67}
68
69#[cfg(any(test, feature = "testing"))]
70impl Backend for InMemoryBackend {
71    type Error = std::convert::Infallible;
72
73    fn insert_account(&self, entry: AccountEntry) -> Result<Option<AccountEntry>, Self::Error> {
74        Ok(self.accounts.insert(entry.key_id, entry))
75    }
76
77    fn insert_channel(&self, channel: ChannelEntry) -> Result<Option<ChannelEntry>, Self::Error> {
78        Ok(self.channels.insert(*channel.get_id(), channel))
79    }
80
81    fn get_account_by_id(&self, id: &HoprKeyIdent) -> Result<Option<AccountEntry>, Self::Error> {
82        Ok(self.accounts.get(id).map(|e| e.value().clone()))
83    }
84
85    fn get_account_by_key(&self, key: &OffchainPublicKey) -> Result<Option<AccountEntry>, Self::Error> {
86        Ok(self
87            .accounts
88            .iter()
89            .find(|account| &account.public_key == key)
90            .map(|account| account.value().clone()))
91    }
92
93    fn get_account_by_address(&self, chain_key: &Address) -> Result<Option<AccountEntry>, Self::Error> {
94        Ok(self
95            .accounts
96            .iter()
97            .find(|account| &account.chain_addr == chain_key)
98            .map(|account| account.value().clone()))
99    }
100
101    fn get_channel_by_id(&self, id: &ChannelId) -> Result<Option<ChannelEntry>, Self::Error> {
102        Ok(self.channels.get(id).map(|e| *e.value()))
103    }
104}
105
106#[cfg(test)]
107pub(crate) mod tests {
108    use hopr_api::types::{
109        crypto::keypairs::{ChainKeypair, Keypair, OffchainKeypair},
110        internal::{
111            account::{AccountEntry, AccountType},
112            channels::{ChannelEntry, ChannelStatus, generate_channel_id},
113        },
114        primitive::{balance::HoprBalance, prelude::Address},
115    };
116
117    use crate::{Backend, InMemoryBackend};
118
119    pub(crate) fn test_backend<B: Backend>(backend: B) -> anyhow::Result<()> {
120        let kp = OffchainKeypair::random();
121        let cp = ChainKeypair::random();
122
123        let account = AccountEntry {
124            public_key: (*kp.public()),
125            chain_addr: cp.public().to_address(),
126            entry_type: AccountType::Announced(vec!["/ip4/1.2.3.4/tcp/1234".parse()?]),
127            safe_address: Some(Address::new(&[3u8; 20])),
128            key_id: 3.into(),
129        };
130
131        let src = Address::new(&[1u8; 20]);
132        let dst = Address::new(&[2u8; 20]);
133
134        let channel = ChannelEntry::builder()
135            .between(src, dst)
136            .balance(HoprBalance::new_base(1000))
137            .ticket_index(10u32.into())
138            .status(ChannelStatus::PendingToClose(std::time::SystemTime::now()))
139            .epoch(10u32)
140            .build()?;
141
142        backend.insert_account(account.clone())?;
143        backend.insert_channel(channel)?;
144
145        let a1 = backend
146            .get_account_by_id(&account.key_id)?
147            .ok_or(anyhow::anyhow!("account not found"))?;
148        let a2 = backend
149            .get_account_by_key(kp.public())?
150            .ok_or(anyhow::anyhow!("account not found"))?;
151        let a3 = backend
152            .get_account_by_address(cp.public().as_ref())?
153            .ok_or(anyhow::anyhow!("account not found"))?;
154
155        assert_eq!(a1, account);
156        assert_eq!(a2, account);
157        assert_eq!(a3, account);
158
159        let id = generate_channel_id(&src, &dst);
160        let c1 = backend
161            .get_channel_by_id(&id)?
162            .ok_or(anyhow::anyhow!("channel not found"))?;
163
164        assert_eq!(c1, channel);
165
166        Ok(())
167    }
168
169    #[test]
170    fn test_inmemory() -> anyhow::Result<()> {
171        let backend = InMemoryBackend::default();
172        test_backend(backend)
173    }
174}