hopr_db_sql/
accounts.rs

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