hopr_chain_connector/backend/
mod.rs

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