hopr_db_sql/
accounts.rs

1use async_trait::async_trait;
2use futures::TryFutureExt;
3use multiaddr::Multiaddr;
4use sea_orm::sea_query::Expr;
5use sea_orm::{
6    ActiveModelTrait, ColumnTrait, DbErr, EntityTrait, IntoActiveModel, ModelTrait, QueryFilter, QueryOrder, Related,
7    Set,
8};
9use sea_query::{Condition, IntoCondition, OnConflict};
10
11use hopr_crypto_types::prelude::OffchainPublicKey;
12use hopr_db_entity::prelude::{Account, Announcement};
13use hopr_db_entity::{account, announcement};
14use hopr_internal_types::account::AccountType;
15use hopr_internal_types::prelude::AccountEntry;
16use hopr_primitive_types::errors::GeneralError;
17use hopr_primitive_types::prelude::{Address, ToHex};
18
19use crate::db::HoprDb;
20use crate::errors::DbSqlError::MissingAccount;
21use crate::errors::{DbSqlError, Result};
22use crate::{HoprDbGeneralModelOperations, OptTx};
23
24/// A type that can represent both [chain public key](Address) and [packet public key](OffchainPublicKey).
25#[derive(Copy, Clone, Debug, PartialEq, Eq)]
26pub enum ChainOrPacketKey {
27    /// Represents [chain public key](Address).
28    ChainKey(Address),
29    /// Represents [packet public key](OffchainPublicKey).
30    PacketKey(OffchainPublicKey),
31}
32
33impl From<Address> for ChainOrPacketKey {
34    fn from(value: Address) -> Self {
35        Self::ChainKey(value)
36    }
37}
38
39impl From<OffchainPublicKey> for ChainOrPacketKey {
40    fn from(value: OffchainPublicKey) -> Self {
41        Self::PacketKey(value)
42    }
43}
44
45impl TryFrom<ChainOrPacketKey> for OffchainPublicKey {
46    type Error = GeneralError;
47
48    fn try_from(value: ChainOrPacketKey) -> std::result::Result<Self, Self::Error> {
49        match value {
50            ChainOrPacketKey::ChainKey(_) => Err(GeneralError::InvalidInput),
51            ChainOrPacketKey::PacketKey(k) => Ok(k),
52        }
53    }
54}
55
56impl TryFrom<ChainOrPacketKey> for Address {
57    type Error = GeneralError;
58
59    fn try_from(value: ChainOrPacketKey) -> std::result::Result<Self, Self::Error> {
60        match value {
61            ChainOrPacketKey::ChainKey(k) => Ok(k),
62            ChainOrPacketKey::PacketKey(_) => Err(GeneralError::InvalidInput),
63        }
64    }
65}
66
67impl IntoCondition for ChainOrPacketKey {
68    fn into_condition(self) -> Condition {
69        match self {
70            ChainOrPacketKey::ChainKey(chain_key) => {
71                account::Column::ChainKey.eq(chain_key.to_string()).into_condition()
72            }
73            ChainOrPacketKey::PacketKey(packet_key) => {
74                account::Column::PacketKey.eq(packet_key.to_string()).into_condition()
75            }
76        }
77    }
78}
79
80/// Defines DB API for accessing HOPR accounts and corresponding on-chain announcements.
81///
82/// Accounts store the Chain and Packet key information, so as the
83/// routable network information, if the account has been announced as well.
84#[async_trait]
85pub trait HoprDbAccountOperations {
86    /// Retrieves the account entry using a Packet key or Chain key.
87    async fn get_account<'a, T>(&'a self, tx: OptTx<'a>, key: T) -> Result<Option<AccountEntry>>
88    where
89        T: Into<ChainOrPacketKey> + Send + Sync;
90
91    /// Retrieves account entry about this node's account.
92    /// This a unique account in the database that must always be present.
93    async fn get_self_account<'a>(&'a self, tx: OptTx<'a>) -> Result<AccountEntry>;
94
95    /// Retrieves entries of accounts with routable address announcements (if `public_only` is `true`)
96    /// or about all accounts without routeable address announcements (if `public_only` is `false`).
97    async fn get_accounts<'a>(&'a self, tx: OptTx<'a>, public_only: bool) -> Result<Vec<AccountEntry>>;
98
99    /// Inserts new account entry to the database.
100    /// Fails if such entry already exists.
101    async fn insert_account<'a>(&'a self, tx: OptTx<'a>, account: AccountEntry) -> Result<()>;
102
103    /// Inserts routable address announcement linked to a specific entry.
104    ///
105    /// If an account matching the given `key` (chain or off-chain key) does not exist, an
106    /// error is returned.
107    /// If such `multiaddr` has been already announced for the given account `key`, only
108    /// the `at_block` will be updated on that announcement.
109    async fn insert_announcement<'a, T>(
110        &'a self,
111        tx: OptTx<'a>,
112        key: T,
113        multiaddr: Multiaddr,
114        at_block: u32,
115    ) -> Result<AccountEntry>
116    where
117        T: Into<ChainOrPacketKey> + Send + Sync;
118
119    /// Deletes all address announcements for the given account.
120    async fn delete_all_announcements<'a, T>(&'a self, tx: OptTx<'a>, key: T) -> Result<()>
121    where
122        T: Into<ChainOrPacketKey> + Send + Sync;
123
124    /// Deletes account with the given `key` (chain or off-chain).
125    async fn delete_account<'a, T>(&'a self, tx: OptTx<'a>, key: T) -> Result<()>
126    where
127        T: Into<ChainOrPacketKey> + Send + Sync;
128
129    /// Translates the given Chain or Packet key to its counterpart.
130    ///
131    /// If `Address` is given as `key`, the result will contain `OffchainPublicKey` if present.
132    /// If `OffchainPublicKey` is given as `key`, the result will contain `Address` if present.
133    async fn translate_key<'a, T>(&'a self, tx: OptTx<'a>, key: T) -> Result<Option<ChainOrPacketKey>>
134    where
135        T: Into<ChainOrPacketKey> + Send + Sync;
136}
137
138// NOTE: this function currently assumes `announcements` are sorted from latest to earliest
139fn model_to_account_entry(account: account::Model, announcements: Vec<announcement::Model>) -> Result<AccountEntry> {
140    // Currently, we always take only the most recent announcement
141    let announcement = announcements.first();
142
143    Ok(AccountEntry::new(
144        OffchainPublicKey::from_hex(&account.packet_key)?,
145        account.chain_key.parse()?,
146        match announcement {
147            None => AccountType::NotAnnounced,
148            Some(a) => AccountType::Announced {
149                multiaddr: a.multiaddress.parse().map_err(|_| DbSqlError::DecodingError)?,
150                updated_block: a.at_block as u32,
151            },
152        },
153    ))
154}
155
156#[async_trait]
157impl HoprDbAccountOperations for HoprDb {
158    async fn get_account<'a, T: Into<ChainOrPacketKey> + Send + Sync>(
159        &'a self,
160        tx: OptTx<'a>,
161        key: T,
162    ) -> Result<Option<AccountEntry>> {
163        let cpk = key.into();
164        self.nest_transaction(tx)
165            .await?
166            .perform(|tx| {
167                Box::pin(async move {
168                    let maybe_model = Account::find()
169                        .find_with_related(Announcement)
170                        .filter(cpk)
171                        .order_by_desc(announcement::Column::AtBlock)
172                        .all(tx.as_ref())
173                        .await?
174                        .pop();
175
176                    Ok::<_, DbSqlError>(if let Some((account, announcements)) = maybe_model {
177                        Some(model_to_account_entry(account, announcements)?)
178                    } else {
179                        None
180                    })
181                })
182            })
183            .await
184    }
185
186    async fn get_self_account<'a>(&'a self, tx: OptTx<'a>) -> Result<AccountEntry> {
187        self.get_account(tx, self.me_onchain)
188            .await?
189            .ok_or(DbSqlError::MissingAccount)
190    }
191
192    async fn get_accounts<'a>(&'a self, tx: OptTx<'a>, public_only: bool) -> Result<Vec<AccountEntry>> {
193        self.nest_transaction(tx)
194            .await?
195            .perform(|tx| {
196                Box::pin(async move {
197                    Account::find()
198                        .find_with_related(Announcement)
199                        .filter(if public_only {
200                            announcement::Column::Multiaddress.ne("")
201                        } else {
202                            Expr::value(1)
203                        })
204                        .order_by_desc(announcement::Column::AtBlock)
205                        .all(tx.as_ref())
206                        .await?
207                        .into_iter()
208                        .map(|(a, b)| model_to_account_entry(a, b))
209                        .collect()
210                })
211            })
212            .await
213    }
214
215    async fn insert_account<'a>(&'a self, tx: OptTx<'a>, account: AccountEntry) -> Result<()> {
216        let myself = self.clone();
217        self.nest_transaction(tx)
218            .await?
219            .perform(|tx| {
220                Box::pin(async move {
221                    match account::Entity::insert(account::ActiveModel {
222                        chain_key: Set(account.chain_addr.to_hex()),
223                        packet_key: Set(account.public_key.to_hex()),
224                        ..Default::default()
225                    })
226                    .on_conflict(
227                        OnConflict::columns([account::Column::ChainKey, account::Column::PacketKey])
228                            .do_nothing()
229                            .to_owned(),
230                    )
231                    .exec(tx.as_ref())
232                    .await
233                    {
234                        // Proceed if succeeded or already exists
235                        Ok(_) | Err(DbErr::RecordNotInserted) => {
236                            myself
237                                .caches
238                                .chain_to_offchain
239                                .insert(account.chain_addr, Some(account.public_key))
240                                .await;
241                            myself
242                                .caches
243                                .offchain_to_chain
244                                .insert(account.public_key, Some(account.chain_addr))
245                                .await;
246
247                            if let AccountType::Announced {
248                                multiaddr,
249                                updated_block,
250                            } = account.entry_type
251                            {
252                                myself
253                                    .insert_announcement(Some(tx), account.chain_addr, multiaddr, updated_block)
254                                    .await?;
255                            }
256                            Ok::<(), DbSqlError>(())
257                        }
258                        Err(e) => Err(e.into()),
259                    }
260                })
261            })
262            .await
263    }
264
265    async fn insert_announcement<'a, T>(
266        &'a self,
267        tx: OptTx<'a>,
268        key: T,
269        multiaddr: Multiaddr,
270        at_block: u32,
271    ) -> Result<AccountEntry>
272    where
273        T: Into<ChainOrPacketKey> + Send + Sync,
274    {
275        let cpk = key.into();
276        self.nest_transaction(tx)
277            .await?
278            .perform(|tx| {
279                Box::pin(async move {
280                    let (existing_account, mut existing_announcements) = account::Entity::find()
281                        .find_with_related(announcement::Entity)
282                        .filter(cpk)
283                        .order_by_desc(announcement::Column::AtBlock)
284                        .all(tx.as_ref())
285                        .await?
286                        .pop()
287                        .ok_or(MissingAccount)?;
288
289                    if let Some((index, _)) = existing_announcements
290                        .iter()
291                        .enumerate()
292                        .find(|(_, announcement)| announcement.multiaddress == multiaddr.to_string())
293                    {
294                        let mut existing_announcement = existing_announcements.remove(index).into_active_model();
295                        existing_announcement.at_block = Set(at_block as i32);
296                        let updated_announcement = existing_announcement.update(tx.as_ref()).await?;
297
298                        // To maintain the sort order, insert at the original location
299                        existing_announcements.insert(index, updated_announcement);
300                    } else {
301                        let new_announcement = announcement::ActiveModel {
302                            account_id: Set(existing_account.id),
303                            multiaddress: Set(multiaddr.to_string()),
304                            at_block: Set(at_block as i32),
305                            ..Default::default()
306                        }
307                        .insert(tx.as_ref())
308                        .await?;
309
310                        // Assuming this is the latest announcement, so prepend it
311                        existing_announcements.insert(0, new_announcement);
312                    }
313
314                    model_to_account_entry(existing_account, existing_announcements)
315                })
316            })
317            .await
318    }
319
320    async fn delete_all_announcements<'a, T>(&'a self, tx: OptTx<'a>, key: T) -> Result<()>
321    where
322        T: Into<ChainOrPacketKey> + Send + Sync,
323    {
324        let cpk = key.into();
325        self.nest_transaction(tx)
326            .await?
327            .perform(|tx| {
328                Box::pin(async move {
329                    let to_delete = account::Entity::find_related()
330                        .filter(cpk)
331                        .all(tx.as_ref())
332                        .await?
333                        .into_iter()
334                        .map(|x| x.id)
335                        .collect::<Vec<_>>();
336
337                    if !to_delete.is_empty() {
338                        announcement::Entity::delete_many()
339                            .filter(announcement::Column::Id.is_in(to_delete))
340                            .exec(tx.as_ref())
341                            .await?;
342
343                        Ok::<_, DbSqlError>(())
344                    } else {
345                        Err(MissingAccount)
346                    }
347                })
348            })
349            .await
350    }
351
352    async fn delete_account<'a, T>(&'a self, tx: OptTx<'a>, key: T) -> Result<()>
353    where
354        T: Into<ChainOrPacketKey> + Send + Sync,
355    {
356        let myself = self.clone();
357        let cpk = key.into();
358        self.nest_transaction(tx)
359            .await?
360            .perform(|tx| {
361                Box::pin(async move {
362                    if let Some(entry) = account::Entity::find().filter(cpk).one(tx.as_ref()).await? {
363                        let account_entry = model_to_account_entry(entry.clone(), vec![])?;
364                        entry.delete(tx.as_ref()).await?;
365
366                        myself
367                            .caches
368                            .chain_to_offchain
369                            .invalidate(&account_entry.chain_addr)
370                            .await;
371                        myself
372                            .caches
373                            .offchain_to_chain
374                            .invalidate(&account_entry.public_key)
375                            .await;
376                        Ok::<_, DbSqlError>(())
377                    } else {
378                        Err(MissingAccount)
379                    }
380                })
381            })
382            .await
383    }
384
385    async fn translate_key<'a, T: Into<ChainOrPacketKey> + Send + Sync>(
386        &'a self,
387        tx: OptTx<'a>,
388        key: T,
389    ) -> Result<Option<ChainOrPacketKey>> {
390        Ok(match key.into() {
391            ChainOrPacketKey::ChainKey(chain_key) => self
392                .caches
393                .chain_to_offchain
394                .try_get_with_by_ref(
395                    &chain_key,
396                    self.nest_transaction(tx).and_then(|op| {
397                        op.perform(|tx| {
398                            Box::pin(async move {
399                                let maybe_model = Account::find()
400                                    .filter(account::Column::ChainKey.eq(chain_key.to_string()))
401                                    .one(tx.as_ref())
402                                    .await?;
403                                if let Some(m) = maybe_model {
404                                    Ok(Some(OffchainPublicKey::from_hex(&m.packet_key)?))
405                                } else {
406                                    Ok(None)
407                                }
408                            })
409                        })
410                    }),
411                )
412                .await?
413                .map(ChainOrPacketKey::PacketKey),
414            ChainOrPacketKey::PacketKey(packey_key) => self
415                .caches
416                .offchain_to_chain
417                .try_get_with_by_ref(
418                    &packey_key,
419                    self.nest_transaction(tx).and_then(|op| {
420                        op.perform(|tx| {
421                            Box::pin(async move {
422                                let maybe_model = Account::find()
423                                    .filter(account::Column::PacketKey.eq(packey_key.to_string()))
424                                    .one(tx.as_ref())
425                                    .await?;
426                                if let Some(m) = maybe_model {
427                                    Ok(Some(Address::from_hex(&m.chain_key)?))
428                                } else {
429                                    Ok(None)
430                                }
431                            })
432                        })
433                    }),
434                )
435                .await?
436                .map(ChainOrPacketKey::ChainKey),
437        })
438    }
439}
440
441#[cfg(test)]
442mod tests {
443    use super::*;
444    use crate::errors::DbSqlError;
445    use crate::errors::DbSqlError::DecodingError;
446    use crate::HoprDbGeneralModelOperations;
447    use anyhow::Context;
448    use hopr_crypto_types::prelude::{ChainKeypair, Keypair, OffchainKeypair};
449    use hopr_internal_types::prelude::AccountType::NotAnnounced;
450
451    #[async_std::test]
452    async fn test_insert_account_announcement() -> anyhow::Result<()> {
453        let db = HoprDb::new_in_memory(ChainKeypair::random()).await?;
454
455        let chain_1 = ChainKeypair::random().public().to_address();
456        let packet_1 = *OffchainKeypair::random().public();
457
458        db.insert_account(None, AccountEntry::new(packet_1, chain_1, AccountType::NotAnnounced))
459            .await?;
460
461        let acc = db.get_account(None, chain_1).await?.expect("should contain account");
462        assert_eq!(packet_1, acc.public_key, "pub keys must match");
463        assert_eq!(AccountType::NotAnnounced, acc.entry_type.clone());
464
465        let maddr: Multiaddr = "/ip4/1.2.3.4/tcp/8000".parse()?;
466        let block = 100;
467
468        let db_acc = db.insert_announcement(None, chain_1, maddr.clone(), block).await?;
469
470        let acc = db.get_account(None, chain_1).await?.context("should contain account")?;
471        assert_eq!(Some(maddr.clone()), acc.get_multiaddr(), "multiaddress must match");
472        assert_eq!(Some(block), acc.updated_at());
473        assert_eq!(acc, db_acc);
474
475        let block = 200;
476        let db_acc = db.insert_announcement(None, chain_1, maddr.clone(), block).await?;
477
478        let acc = db.get_account(None, chain_1).await?.expect("should contain account");
479        assert_eq!(Some(maddr), acc.get_multiaddr(), "multiaddress must match");
480        assert_eq!(Some(block), acc.updated_at());
481        assert_eq!(acc, db_acc);
482
483        let maddr: Multiaddr = "/dns4/useful.domain/tcp/56".parse()?;
484        let block = 300;
485        let db_acc = db.insert_announcement(None, chain_1, maddr.clone(), block).await?;
486
487        let acc = db.get_account(None, chain_1).await?.expect("should contain account");
488        assert_eq!(Some(maddr), acc.get_multiaddr(), "multiaddress must match");
489        assert_eq!(Some(block), acc.updated_at());
490        assert_eq!(acc, db_acc);
491
492        Ok(())
493    }
494
495    #[async_std::test]
496    async fn test_should_allow_reannouncement() -> anyhow::Result<()> {
497        let db = HoprDb::new_in_memory(ChainKeypair::random()).await?;
498
499        let chain_1 = ChainKeypair::random().public().to_address();
500        let packet_1 = *OffchainKeypair::random().public();
501
502        db.insert_account(None, AccountEntry::new(packet_1, chain_1, AccountType::NotAnnounced))
503            .await?;
504
505        db.insert_announcement(None, chain_1, "/ip4/1.2.3.4/tcp/8000".parse()?, 100)
506            .await?;
507
508        let ae = db.get_account(None, chain_1).await?.ok_or(MissingAccount)?;
509
510        assert_eq!(
511            "/ip4/1.2.3.4/tcp/8000",
512            ae.get_multiaddr().expect("has multiaddress").to_string()
513        );
514
515        db.insert_announcement(None, chain_1, "/ip4/1.2.3.4/tcp/8001".parse()?, 110)
516            .await?;
517
518        let ae = db.get_account(None, chain_1).await?.ok_or(MissingAccount)?;
519
520        assert_eq!(
521            "/ip4/1.2.3.4/tcp/8001",
522            ae.get_multiaddr().expect("has multiaddress").to_string()
523        );
524
525        Ok(())
526    }
527
528    #[async_std::test]
529    async fn test_should_not_insert_account_announcement_to_nonexisting_account() -> anyhow::Result<()> {
530        let db = HoprDb::new_in_memory(ChainKeypair::random()).await?;
531
532        let chain_1 = ChainKeypair::random().public().to_address();
533
534        let maddr: Multiaddr = "/ip4/1.2.3.4/tcp/8000".parse()?;
535        let block = 100;
536
537        let r = db.insert_announcement(None, chain_1, maddr.clone(), block).await;
538        assert!(
539            matches!(r, Err(MissingAccount)),
540            "should not insert announcement to non-existing account"
541        );
542
543        Ok(())
544    }
545
546    #[async_std::test]
547    async fn test_should_allow_duplicate_announcement_per_different_accounts() -> anyhow::Result<()> {
548        let db = HoprDb::new_in_memory(ChainKeypair::random()).await?;
549
550        let chain_1 = ChainKeypair::random().public().to_address();
551        let packet_1 = *OffchainKeypair::random().public();
552
553        db.insert_account(None, AccountEntry::new(packet_1, chain_1, AccountType::NotAnnounced))
554            .await?;
555
556        let chain_2 = ChainKeypair::random().public().to_address();
557        let packet_2 = *OffchainKeypair::random().public();
558
559        db.insert_account(None, AccountEntry::new(packet_2, chain_2, AccountType::NotAnnounced))
560            .await?;
561
562        let maddr: Multiaddr = "/ip4/1.2.3.4/tcp/8000".parse()?;
563        let block = 100;
564
565        let db_acc_1 = db.insert_announcement(None, chain_1, maddr.clone(), block).await?;
566        let db_acc_2 = db.insert_announcement(None, chain_2, maddr.clone(), block).await?;
567
568        let acc = db.get_account(None, chain_1).await?.expect("should contain account");
569        assert_eq!(Some(maddr.clone()), acc.get_multiaddr(), "multiaddress must match");
570        assert_eq!(Some(block), acc.updated_at());
571        assert_eq!(acc, db_acc_1);
572
573        let acc = db.get_account(None, chain_2).await?.expect("should contain account");
574        assert_eq!(Some(maddr.clone()), acc.get_multiaddr(), "multiaddress must match");
575        assert_eq!(Some(block), acc.updated_at());
576        assert_eq!(acc, db_acc_2);
577
578        Ok(())
579    }
580
581    #[async_std::test]
582    async fn test_delete_account() -> anyhow::Result<()> {
583        let db = HoprDb::new_in_memory(ChainKeypair::random()).await?;
584
585        let chain_1 = ChainKeypair::random().public().to_address();
586        db.insert_account(
587            None,
588            AccountEntry::new(
589                *OffchainKeypair::random().public(),
590                chain_1,
591                AccountType::Announced {
592                    multiaddr: "/ip4/1.2.3.4/tcp/1234".parse()?,
593                    updated_block: 10,
594                },
595            ),
596        )
597        .await?;
598
599        assert!(db.get_account(None, chain_1).await?.is_some());
600
601        db.delete_account(None, chain_1).await?;
602
603        assert!(db.get_account(None, chain_1).await?.is_none());
604
605        Ok(())
606    }
607
608    #[async_std::test]
609    async fn test_should_fail_to_delete_nonexistent_account() -> anyhow::Result<()> {
610        let db = HoprDb::new_in_memory(ChainKeypair::random()).await?;
611
612        let chain_1 = ChainKeypair::random().public().to_address();
613
614        let r = db.delete_account(None, chain_1).await;
615        assert!(
616            matches!(r, Err(MissingAccount)),
617            "should not delete non-existing account"
618        );
619
620        Ok(())
621    }
622
623    #[async_std::test]
624    async fn test_should_not_fail_on_duplicate_account_insert() -> anyhow::Result<()> {
625        let db = HoprDb::new_in_memory(ChainKeypair::random()).await?;
626
627        let chain_1 = ChainKeypair::random().public().to_address();
628        let packet_1 = *OffchainKeypair::random().public();
629
630        db.insert_account(None, AccountEntry::new(packet_1, chain_1, AccountType::NotAnnounced))
631            .await?;
632
633        db.insert_account(None, AccountEntry::new(packet_1, chain_1, AccountType::NotAnnounced))
634            .await?;
635
636        Ok(())
637    }
638
639    #[async_std::test]
640    async fn test_delete_announcements() -> anyhow::Result<()> {
641        let db = HoprDb::new_in_memory(ChainKeypair::random()).await?;
642
643        let chain_1 = ChainKeypair::random().public().to_address();
644        let mut entry = AccountEntry::new(
645            *OffchainKeypair::random().public(),
646            chain_1,
647            AccountType::Announced {
648                multiaddr: "/ip4/1.2.3.4/tcp/1234".parse()?,
649                updated_block: 10,
650            },
651        );
652
653        db.insert_account(None, entry.clone()).await?;
654
655        assert_eq!(Some(entry.clone()), db.get_account(None, chain_1).await?);
656
657        db.delete_all_announcements(None, chain_1).await?;
658
659        entry.entry_type = NotAnnounced;
660
661        assert_eq!(Some(entry), db.get_account(None, chain_1).await?);
662
663        Ok(())
664    }
665
666    #[async_std::test]
667    async fn test_should_fail_to_delete_nonexistent_account_announcements() -> anyhow::Result<()> {
668        let db = HoprDb::new_in_memory(ChainKeypair::random()).await?;
669
670        let chain_1 = ChainKeypair::random().public().to_address();
671
672        let r = db.delete_all_announcements(None, chain_1).await;
673        assert!(
674            matches!(r, Err(MissingAccount)),
675            "should not delete non-existing account"
676        );
677
678        Ok(())
679    }
680
681    #[async_std::test]
682    async fn test_translate_key() -> anyhow::Result<()> {
683        let db = HoprDb::new_in_memory(ChainKeypair::random()).await?;
684
685        let chain_1 = ChainKeypair::random().public().to_address();
686        let packet_1 = *OffchainKeypair::random().public();
687
688        let chain_2 = ChainKeypair::random().public().to_address();
689        let packet_2 = *OffchainKeypair::random().public();
690
691        let db_clone = db.clone();
692        db.begin_transaction()
693            .await?
694            .perform(|tx| {
695                Box::pin(async move {
696                    db_clone
697                        .insert_account(
698                            Some(tx),
699                            AccountEntry::new(packet_1, chain_1, AccountType::NotAnnounced),
700                        )
701                        .await?;
702                    db_clone
703                        .insert_account(
704                            Some(tx),
705                            AccountEntry::new(packet_2, chain_2, AccountType::NotAnnounced),
706                        )
707                        .await?;
708                    Ok::<(), DbSqlError>(())
709                })
710            })
711            .await?;
712
713        let a: Address = db
714            .translate_key(None, packet_1)
715            .await?
716            .context("must contain key")?
717            .try_into()?;
718
719        let b: OffchainPublicKey = db
720            .translate_key(None, chain_2)
721            .await?
722            .context("must contain key")?
723            .try_into()?;
724
725        assert_eq!(chain_1, a, "chain keys must match");
726        assert_eq!(packet_2, b, "chain keys must match");
727
728        Ok(())
729    }
730
731    #[async_std::test]
732    async fn test_translate_key_no_cache() -> anyhow::Result<()> {
733        let db = HoprDb::new_in_memory(ChainKeypair::random()).await?;
734
735        let chain_1 = ChainKeypair::random().public().to_address();
736        let packet_1 = *OffchainKeypair::random().public();
737
738        let chain_2 = ChainKeypair::random().public().to_address();
739        let packet_2 = *OffchainKeypair::random().public();
740
741        let db_clone = db.clone();
742        db.begin_transaction()
743            .await?
744            .perform(|tx| {
745                Box::pin(async move {
746                    db_clone
747                        .insert_account(
748                            Some(tx),
749                            AccountEntry::new(packet_1, chain_1, AccountType::NotAnnounced),
750                        )
751                        .await?;
752                    db_clone
753                        .insert_account(
754                            Some(tx),
755                            AccountEntry::new(packet_2, chain_2, AccountType::NotAnnounced),
756                        )
757                        .await?;
758                    Ok::<(), DbSqlError>(())
759                })
760            })
761            .await?;
762
763        db.caches.invalidate_all();
764
765        let a: Address = db
766            .translate_key(None, packet_1)
767            .await?
768            .context("must contain key")?
769            .try_into()?;
770
771        let b: OffchainPublicKey = db
772            .translate_key(None, chain_2)
773            .await?
774            .context("must contain key")?
775            .try_into()?;
776
777        assert_eq!(chain_1, a, "chain keys must match");
778        assert_eq!(packet_2, b, "chain keys must match");
779
780        Ok(())
781    }
782
783    #[async_std::test]
784    async fn test_get_accounts() -> anyhow::Result<()> {
785        let db = HoprDb::new_in_memory(ChainKeypair::random()).await?;
786
787        let chain_1 = ChainKeypair::random().public().to_address();
788        let chain_2 = ChainKeypair::random().public().to_address();
789        let chain_3 = ChainKeypair::random().public().to_address();
790
791        let db_clone = db.clone();
792        db.begin_transaction()
793            .await?
794            .perform(|tx| {
795                Box::pin(async move {
796                    db_clone
797                        .insert_account(
798                            Some(tx),
799                            AccountEntry::new(*OffchainKeypair::random().public(), chain_1, AccountType::NotAnnounced),
800                        )
801                        .await?;
802                    db_clone
803                        .insert_account(
804                            Some(tx),
805                            AccountEntry::new(
806                                *OffchainKeypair::random().public(),
807                                chain_2,
808                                AccountType::Announced {
809                                    multiaddr: "/ip4/10.10.10.10/tcp/1234".parse().map_err(|_| DecodingError)?,
810                                    updated_block: 10,
811                                },
812                            ),
813                        )
814                        .await?;
815                    db_clone
816                        .insert_account(
817                            Some(tx),
818                            AccountEntry::new(*OffchainKeypair::random().public(), chain_3, AccountType::NotAnnounced),
819                        )
820                        .await?;
821
822                    db_clone
823                        .insert_announcement(
824                            Some(tx),
825                            chain_3,
826                            "/ip4/1.2.3.4/tcp/1234".parse().map_err(|_| DecodingError)?,
827                            12,
828                        )
829                        .await?;
830                    db_clone
831                        .insert_announcement(
832                            Some(tx),
833                            chain_3,
834                            "/ip4/8.8.1.1/tcp/1234".parse().map_err(|_| DecodingError)?,
835                            15,
836                        )
837                        .await?;
838                    db_clone
839                        .insert_announcement(
840                            Some(tx),
841                            chain_3,
842                            "/ip4/1.2.3.0/tcp/234".parse().map_err(|_| DecodingError)?,
843                            14,
844                        )
845                        .await
846                })
847            })
848            .await?;
849
850        let all_accounts = db.get_accounts(None, false).await?;
851        let public_only = db.get_accounts(None, true).await?;
852
853        assert_eq!(3, all_accounts.len());
854
855        assert_eq!(2, public_only.len());
856        let acc_1 = public_only
857            .iter()
858            .find(|a| a.chain_addr.eq(&chain_2))
859            .expect("should contain 1");
860
861        let acc_2 = public_only
862            .iter()
863            .find(|a| a.chain_addr.eq(&chain_3))
864            .expect("should contain 2");
865
866        assert_eq!(
867            "/ip4/10.10.10.10/tcp/1234",
868            acc_1.get_multiaddr().expect("should have a multiaddress").to_string()
869        );
870        assert_eq!(
871            "/ip4/8.8.1.1/tcp/1234",
872            acc_2.get_multiaddr().expect("should have a multiaddress").to_string()
873        );
874
875        Ok(())
876    }
877}