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