1use std::str::FromStr;
2
3use blokli_client::api::{BlokliQueryClient, BlokliSubscriptionClient, BlokliTransactionClient};
4use futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt, future::BoxFuture, pin_mut, stream::BoxStream};
5use futures_time::future::FutureExt as TimeFutureExt;
6use hopr_api::{
7 chain::{AccountSelector, AnnouncementError, ChainReceipt, Multiaddr, SafeRegistrationError},
8 types::{
9 chain::prelude::*,
10 crypto::prelude::*,
11 internal::{
12 account::AccountEntry,
13 prelude::{AnnouncementData, KeyBinding},
14 },
15 primitive::prelude::*,
16 },
17};
18
19use crate::{
20 backend::Backend, connector::HoprBlockchainConnector, errors::ConnectorError, utils::model_to_account_entry,
21};
22
23impl<B, C, P, R> HoprBlockchainConnector<C, B, P, R>
24where
25 B: Backend + Send + Sync + 'static,
26{
27 pub(crate) fn build_account_stream(
28 &self,
29 selector: AccountSelector,
30 ) -> Result<impl futures::Stream<Item = AccountEntry> + Send + 'static, ConnectorError> {
31 let mut accounts = self.graph.read().nodes().collect::<Vec<_>>();
32
33 accounts.sort_unstable();
35
36 let backend = self.backend.clone();
37 Ok(futures::stream::iter(accounts).filter_map(move |account_id| {
38 let backend = backend.clone();
39 async move {
41 match hopr_utils::runtime::prelude::spawn_blocking(move || backend.get_account_by_id(&account_id)).await
42 {
43 Ok(Ok(value)) => value.filter(|c| selector.satisfies(c)),
44 Ok(Err(error)) => {
45 tracing::error!(%error, %account_id, "backend error when looking up account");
46 None
47 }
48 Err(error) => {
49 tracing::error!(%error, %account_id, "join error when looking up account");
50 None
51 }
52 }
53 }
54 }))
55 }
56}
57
58#[async_trait::async_trait]
59impl<B, C, P, R> hopr_api::chain::ChainReadAccountOperations for HoprBlockchainConnector<C, B, P, R>
60where
61 B: Backend + Send + Sync + 'static,
62 C: BlokliQueryClient + BlokliSubscriptionClient + Send + Sync + 'static,
63 P: Send + Sync + 'static,
64 R: Send + Sync,
65{
66 type Error = ConnectorError;
67
68 fn stream_accounts(&self, selector: AccountSelector) -> Result<BoxStream<'_, AccountEntry>, Self::Error> {
69 self.check_connection_state()?;
70
71 Ok(self.build_account_stream(selector)?.boxed())
72 }
73
74 async fn count_accounts(&self, selector: AccountSelector) -> Result<usize, Self::Error> {
75 self.check_connection_state()?;
76
77 Ok(self.stream_accounts(selector)?.count().await)
78 }
79
80 async fn await_key_binding(
81 &self,
82 offchain_key: &OffchainPublicKey,
83 timeout: std::time::Duration,
84 ) -> Result<AccountEntry, Self::Error> {
85 self.check_connection_state()?;
86
87 let selector = blokli_client::api::v1::AccountSelector::PacketKey((*offchain_key).into());
88 if let Some(node) = self.client.query_accounts(selector.clone()).await?.first().cloned() {
89 return model_to_account_entry(node);
90 }
91
92 let stream = self.client.subscribe_accounts(selector)?.map_err(ConnectorError::from);
93 pin_mut!(stream);
94 if let Some(node) = stream
95 .try_next()
96 .timeout(futures_time::time::Duration::from(
97 timeout.max(std::time::Duration::from_secs(1)),
98 ))
99 .await
100 .map_err(|_| ConnectorError::other(anyhow::anyhow!("timeout while waiting for key binding")))??
101 {
102 model_to_account_entry(node)
103 } else {
104 Err(ConnectorError::AccountDoesNotExist(format!(
105 "with packet key {offchain_key}"
106 )))
107 }
108 }
109}
110
111#[async_trait::async_trait]
112impl<B, C, P> hopr_api::chain::ChainWriteAccountOperations for HoprBlockchainConnector<C, B, P, P::TxRequest>
113where
114 B: Send + Sync,
115 C: BlokliTransactionClient + BlokliQueryClient + Send + Sync + 'static,
116 P: PayloadGenerator + Send + Sync + 'static,
117 P::TxRequest: Send + Sync + 'static,
118{
119 type Error = ConnectorError;
120
121 async fn announce(
122 &self,
123 multiaddrs: &[Multiaddr],
124 key: &OffchainKeypair,
125 ) -> Result<BoxFuture<'_, Result<ChainReceipt, Self::Error>>, AnnouncementError<Self::Error>> {
126 self.check_connection_state().map_err(AnnouncementError::processing)?;
127
128 let new_announced_addrs = ahash::HashSet::from_iter(multiaddrs.iter().map(|a| a.to_string()));
129
130 let existing_account = self
131 .client
132 .query_accounts(blokli_client::api::v1::AccountSelector::Address(
133 self.chain_key.public().to_address().into(),
134 ))
135 .await
136 .map_err(AnnouncementError::processing)?
137 .into_iter()
138 .find(|account| OffchainPublicKey::from_str(&account.packet_key).is_ok_and(|k| &k == key.public()));
139
140 if let Some(account) = &existing_account {
141 let old_announced_addrs = ahash::HashSet::from_iter(account.multi_addresses.iter().cloned());
142 if old_announced_addrs == new_announced_addrs || old_announced_addrs.is_superset(&new_announced_addrs) {
143 return Err(AnnouncementError::AlreadyAnnounced);
144 }
145 }
146
147 let key_binding = KeyBinding::new(self.chain_key.public().to_address(), key);
149 let key_binding_fee = if existing_account.is_none() {
150 self.query_cached_chain_info()
151 .await
152 .map_err(AnnouncementError::processing)?
153 .key_binding_fee
154 } else {
155 HoprBalance::zero()
156 };
157
158 let tx_req = self
159 .payload_generator
160 .announce(
161 AnnouncementData::new(key_binding, multiaddrs.first().cloned())
162 .map_err(|e| AnnouncementError::ProcessingError(ConnectorError::OtherError(e.into())))?,
163 key_binding_fee,
164 )
165 .map_err(AnnouncementError::processing)?;
166
167 Ok(self
168 .send_tx(tx_req, None, None)
169 .map_err(AnnouncementError::processing)
170 .await?
171 .boxed())
172 }
173
174 async fn withdraw<Cy: Currency + Send>(
175 &self,
176 balance: Balance<Cy>,
177 recipient: &Address,
178 ) -> Result<BoxFuture<'_, Result<ChainReceipt, Self::Error>>, Self::Error> {
179 self.check_connection_state()?;
180
181 let tx_req = self.payload_generator.transfer(*recipient, balance)?;
182
183 Ok(self.send_tx(tx_req, None, None).await?.boxed())
184 }
185
186 async fn withdraw_from_signer<Cy: Currency + Send>(
187 &self,
188 signer: &ChainKeypair,
189 balance: Balance<Cy>,
190 recipient: &Address,
191 ) -> Result<BoxFuture<'_, Result<ChainReceipt, Self::Error>>, Self::Error> {
192 self.check_connection_state()?;
193
194 let tx_req = self.payload_generator.transfer(*recipient, balance)?;
195
196 Ok(self.send_tx(tx_req, None, Some(signer.clone())).await?.boxed())
197 }
198
199 async fn register_safe(
200 &self,
201 safe_address: &Address,
202 ) -> Result<BoxFuture<'_, Result<ChainReceipt, Self::Error>>, SafeRegistrationError<Self::Error>> {
203 self.check_connection_state()
204 .map_err(SafeRegistrationError::processing)?;
205
206 let my_node_addr = self.chain_key.public().to_address();
208 if let Some(safe_with_node) = self
209 .client
210 .query_safe(blokli_client::api::v1::SafeSelector::RegisteredNode(
211 my_node_addr.into(),
212 ))
213 .await
214 .map_err(SafeRegistrationError::processing)?
215 .first()
216 {
217 let registered_safe_addr =
219 Address::from_hex(&safe_with_node.address).map_err(SafeRegistrationError::processing)?;
220 return Err(SafeRegistrationError::AlreadyRegistered(registered_safe_addr));
221 }
222
223 if self
225 .client
226 .query_safe(blokli_client::api::v1::SafeSelector::SafeAddress(
227 (*safe_address).into(),
228 ))
229 .await
230 .map_err(SafeRegistrationError::processing)?
231 .is_empty()
232 {
233 return Err(SafeRegistrationError::ProcessingError(
234 ConnectorError::SafeDoesNotExist(*safe_address),
235 ));
236 }
237
238 tracing::debug!(%safe_address, %my_node_addr, "safe exists, proceeding with registration");
239
240 let tx_req = self
241 .payload_generator
242 .register_safe_by_node(*safe_address)
243 .map_err(SafeRegistrationError::processing)?;
244
245 Ok(self
246 .send_tx(tx_req, None, None)
247 .map_err(SafeRegistrationError::processing)
248 .await?
249 .boxed())
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use hex_literal::hex;
256 use hopr_api::{
257 chain::{ChainReadAccountOperations, ChainWriteAccountOperations, DeployedSafe},
258 types::internal::account::AccountType,
259 };
260
261 use super::*;
262 use crate::{
263 connector::tests::{MODULE_ADDR, PRIVATE_KEY_1, PRIVATE_KEY_2, create_connector},
264 testing::BlokliTestStateBuilder,
265 };
266
267 #[tokio::test]
268 async fn connector_should_stream_and_count_accounts() -> anyhow::Result<()> {
269 let account = AccountEntry {
270 public_key: *OffchainKeypair::random().public(),
271 chain_addr: [1u8; Address::SIZE].into(),
272 entry_type: AccountType::NotAnnounced,
273 safe_address: Some([2u8; Address::SIZE].into()),
274 key_id: 1.into(),
275 };
276
277 let blokli_client = BlokliTestStateBuilder::default()
278 .with_accounts([(account.clone(), HoprBalance::new_base(100), XDaiBalance::new_base(1))])
279 .build_static_client();
280
281 let mut connector = create_connector(blokli_client)?;
282 connector.connect().await?;
283
284 let accounts = connector
285 .stream_accounts(AccountSelector::default())?
286 .collect::<Vec<_>>()
287 .await;
288
289 let count = connector.count_accounts(AccountSelector::default()).await?;
290
291 assert_eq!(accounts.len(), 1);
292 assert_eq!(count, 1);
293 assert_eq!(&accounts[0], &account);
294
295 Ok(())
296 }
297
298 #[tokio::test]
299 async fn connector_should_stream_and_count_accounts_with_selector() -> anyhow::Result<()> {
300 let account_1 = AccountEntry {
301 public_key: *OffchainKeypair::random().public(),
302 chain_addr: [1u8; Address::SIZE].into(),
303 entry_type: AccountType::NotAnnounced,
304 safe_address: Some([2u8; Address::SIZE].into()),
305 key_id: 1.into(),
306 };
307
308 let account_2 = AccountEntry {
309 public_key: *OffchainKeypair::random().public(),
310 chain_addr: [2u8; Address::SIZE].into(),
311 entry_type: AccountType::Announced(vec!["/ip4/1.2.3.4/tcp/1234".parse()?]),
312 safe_address: Some([3u8; Address::SIZE].into()),
313 key_id: 2.into(),
314 };
315
316 let blokli_client = BlokliTestStateBuilder::default()
317 .with_accounts([
318 (account_1.clone(), HoprBalance::new_base(100), XDaiBalance::new_base(1)),
319 (account_2.clone(), HoprBalance::new_base(100), XDaiBalance::new_base(1)),
320 ])
321 .build_static_client();
322
323 let mut connector = create_connector(blokli_client)?;
324 connector.connect().await?;
325
326 let selector = AccountSelector::default().with_chain_key(account_1.chain_addr);
327 let accounts = connector.stream_accounts(selector)?.collect::<Vec<_>>().await;
328 let count = connector.count_accounts(selector).await?;
329
330 assert_eq!(accounts.len(), count);
331 assert_eq!(accounts, vec![account_1.clone()]);
332
333 let selector = AccountSelector::default().with_offchain_key(account_1.public_key);
334 let accounts = connector.stream_accounts(selector)?.collect::<Vec<_>>().await;
335 let count = connector.count_accounts(selector).await?;
336
337 assert_eq!(accounts.len(), count);
338 assert_eq!(accounts, vec![account_1.clone()]);
339
340 let selector = AccountSelector::default().with_public_only(true);
341 let accounts = connector.stream_accounts(selector)?.collect::<Vec<_>>().await;
342 let count = connector.count_accounts(selector).await?;
343
344 assert_eq!(accounts.len(), count);
345 assert_eq!(accounts, vec![account_2.clone()]);
346
347 let selector = AccountSelector::default()
348 .with_chain_key(account_1.chain_addr)
349 .with_public_only(true);
350 let accounts = connector.stream_accounts(selector)?.collect::<Vec<_>>().await;
351 let count = connector.count_accounts(selector).await?;
352
353 assert_eq!(count, 0);
354 assert!(accounts.is_empty());
355
356 Ok(())
357 }
358
359 #[test_log::test(tokio::test)]
360 async fn connector_should_announce_new_account_with_multiaddresses() -> anyhow::Result<()> {
361 let blokli_client = BlokliTestStateBuilder::default()
362 .with_balances([(
363 ChainKeypair::from_secret(&PRIVATE_KEY_1)?.public().to_address(),
364 XDaiBalance::new_base(1),
365 )])
366 .with_hopr_network_chain_info("rotsee")
367 .build_dynamic_client(MODULE_ADDR.into());
368
369 let mut connector = create_connector(blokli_client)?;
370 connector.connect().await?;
371
372 let offchain_key = OffchainKeypair::from_secret(&hex!(
373 "60741b83b99e36aa0c1331578156e16b8e21166d01834abb6c64b103f885734d"
374 ))?;
375 let multiaddress = Multiaddr::from_str("/ip4/127.0.0.1/tcp/1234")?;
376
377 connector.announce(&[multiaddress], &offchain_key).await?.await?;
378
379 insta::assert_yaml_snapshot!(*connector.client.snapshot());
380
381 let accounts = connector
382 .stream_accounts(AccountSelector::default().with_public_only(true))?
383 .collect::<Vec<_>>()
384 .await;
385
386 assert_eq!(accounts.len(), 1);
387 assert_eq!(
388 accounts[0].get_multiaddrs(),
389 &[Multiaddr::from_str("/ip4/127.0.0.1/tcp/1234")?]
390 );
391
392 Ok(())
393 }
394
395 #[test_log::test(tokio::test)]
396 async fn connector_should_announce_new_account_without_multiaddresses() -> anyhow::Result<()> {
397 let blokli_client = BlokliTestStateBuilder::default()
398 .with_hopr_network_chain_info("rotsee")
399 .with_balances([(
400 ChainKeypair::from_secret(&PRIVATE_KEY_1)?.public().to_address(),
401 XDaiBalance::new_base(1),
402 )])
403 .build_dynamic_client(MODULE_ADDR.into());
404
405 let mut connector = create_connector(blokli_client)?;
406 connector.connect().await?;
407
408 let offchain_key = OffchainKeypair::from_secret(&hex!(
409 "60741b83b99e36aa0c1331578156e16b8e21166d01834abb6c64b103f885734d"
410 ))?;
411
412 connector.announce(&[], &offchain_key).await?.await?;
413
414 insta::assert_yaml_snapshot!(*connector.client.snapshot());
415
416 let accounts = connector
417 .stream_accounts(AccountSelector::default())?
418 .collect::<Vec<_>>()
419 .await;
420
421 assert_eq!(accounts.len(), 1);
422 assert!(accounts[0].get_multiaddrs().is_empty());
423
424 Ok(())
425 }
426
427 #[test_log::test(tokio::test)]
428 async fn connector_should_not_reannounce_when_existing_account_has_same_multiaddresses() -> anyhow::Result<()> {
429 let offchain_key = OffchainKeypair::from_secret(&hex!(
430 "60741b83b99e36aa0c1331578156e16b8e21166d01834abb6c64b103f885734d"
431 ))?;
432 let multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/1234".parse()?;
433 let account = AccountEntry {
434 public_key: *offchain_key.public(),
435 chain_addr: ChainKeypair::from_secret(&PRIVATE_KEY_1)?.public().to_address(),
436 entry_type: AccountType::Announced(vec![multiaddr.clone()]),
437 safe_address: Some([2u8; Address::SIZE].into()),
438 key_id: 1.into(),
439 };
440
441 let blokli_client = BlokliTestStateBuilder::default()
442 .with_accounts([(account.clone(), HoprBalance::new_base(100), XDaiBalance::new_base(1))])
443 .with_hopr_network_chain_info("rotsee")
444 .build_dynamic_client(MODULE_ADDR.into());
445
446 let mut connector = create_connector(blokli_client)?;
447 connector.connect().await?;
448
449 assert!(matches!(
450 connector.announce(&[], &offchain_key).await,
451 Err(AnnouncementError::AlreadyAnnounced)
452 ));
453
454 assert!(matches!(
455 connector.announce(&[multiaddr], &offchain_key).await,
456 Err(AnnouncementError::AlreadyAnnounced)
457 ));
458
459 insta::assert_yaml_snapshot!(*connector.client.snapshot());
460
461 Ok(())
462 }
463
464 #[tokio::test]
465 async fn connector_should_reannounce_when_existing_account_has_no_multiaddresses() -> anyhow::Result<()> {
466 let offchain_key = OffchainKeypair::from_secret(&hex!(
467 "60741b83b99e36aa0c1331578156e16b8e21166d01834abb6c64b103f885734d"
468 ))?;
469 let multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/1234".parse()?;
470 let account = AccountEntry {
471 public_key: *offchain_key.public(),
472 chain_addr: ChainKeypair::from_secret(&PRIVATE_KEY_1)?.public().to_address(),
473 entry_type: AccountType::NotAnnounced,
474 safe_address: Some([2u8; Address::SIZE].into()),
475 key_id: 1.into(),
476 };
477
478 let blokli_client = BlokliTestStateBuilder::default()
479 .with_accounts([(account.clone(), HoprBalance::new_base(100), XDaiBalance::new_base(1))])
480 .with_hopr_network_chain_info("rotsee")
481 .build_dynamic_client(MODULE_ADDR.into());
482
483 let mut connector = create_connector(blokli_client)?;
484 connector.connect().await?;
485
486 assert!(matches!(
487 connector.announce(&[], &offchain_key).await,
488 Err(AnnouncementError::AlreadyAnnounced)
489 ));
490
491 connector
492 .announce(std::slice::from_ref(&multiaddr), &offchain_key)
493 .await?
494 .await?;
495
496 insta::assert_yaml_snapshot!(*connector.client.snapshot());
497
498 let accounts = connector
499 .stream_accounts(AccountSelector::default().with_public_only(true))?
500 .collect::<Vec<_>>()
501 .await;
502
503 assert_eq!(accounts.len(), 1);
504 assert_eq!(accounts[0].get_multiaddrs(), &[multiaddr]);
505
506 Ok(())
507 }
508
509 #[tokio::test]
510 async fn connector_should_withdraw() -> anyhow::Result<()> {
511 let blokli_client = BlokliTestStateBuilder::default()
512 .with_balances([([1u8; Address::SIZE].into(), HoprBalance::zero())])
513 .with_balances([([1u8; Address::SIZE].into(), XDaiBalance::zero())])
514 .with_balances([(
515 ChainKeypair::from_secret(&PRIVATE_KEY_1)?.public().to_address(),
516 XDaiBalance::new_base(10),
517 )])
518 .with_balances([(
519 ChainKeypair::from_secret(&PRIVATE_KEY_1)?.public().to_address(),
520 HoprBalance::new_base(1000),
521 )])
522 .with_hopr_network_chain_info("rotsee")
523 .build_dynamic_client(MODULE_ADDR.into());
524
525 let mut connector = create_connector(blokli_client)?;
526 connector.connect().await?;
527
528 connector
529 .withdraw(HoprBalance::new_base(10), &[1u8; Address::SIZE].into())
530 .await?
531 .await?;
532 connector
533 .withdraw(XDaiBalance::new_base(1), &[1u8; Address::SIZE].into())
534 .await?
535 .await?;
536
537 insta::assert_yaml_snapshot!(*connector.client.snapshot());
538
539 Ok(())
540 }
541
542 #[tokio::test]
543 async fn connector_should_withdraw_from_signer() -> anyhow::Result<()> {
544 let blokli_client = BlokliTestStateBuilder::default()
545 .with_balances([([1u8; Address::SIZE].into(), HoprBalance::zero())])
546 .with_balances([([1u8; Address::SIZE].into(), XDaiBalance::zero())])
547 .with_balances([(
548 ChainKeypair::from_secret(&PRIVATE_KEY_1)?.public().to_address(),
549 XDaiBalance::new_base(10),
550 )])
551 .with_balances([(
552 ChainKeypair::from_secret(&PRIVATE_KEY_1)?.public().to_address(),
553 HoprBalance::new_base(1000),
554 )])
555 .with_balances([(
556 ChainKeypair::from_secret(&PRIVATE_KEY_2)?.public().to_address(),
557 XDaiBalance::new_base(10),
558 )])
559 .with_balances([(
560 ChainKeypair::from_secret(&PRIVATE_KEY_2)?.public().to_address(),
561 HoprBalance::new_base(1000),
562 )])
563 .with_hopr_network_chain_info("rotsee")
564 .build_dynamic_client(MODULE_ADDR.into());
565
566 let mut connector = create_connector(blokli_client)?;
567 connector.connect().await?;
568
569 let signer = ChainKeypair::from_secret(&PRIVATE_KEY_2)?;
570
571 connector
572 .withdraw_from_signer(&signer, HoprBalance::new_base(10), &[1u8; Address::SIZE].into())
573 .await?
574 .await?;
575 connector
576 .withdraw_from_signer(&signer, XDaiBalance::new_base(1), &[1u8; Address::SIZE].into())
577 .await?
578 .await?;
579
580 insta::assert_yaml_snapshot!(*connector.client.snapshot());
581
582 Ok(())
583 }
584
585 #[tokio::test]
586 async fn connector_should_register_safe() -> anyhow::Result<()> {
587 let deployer_addr = ChainKeypair::from_secret(&PRIVATE_KEY_1)?.public().to_address();
588 let blokli_client = BlokliTestStateBuilder::default()
589 .with_balances([(
590 ChainKeypair::from_secret(&PRIVATE_KEY_1)?.public().to_address(),
591 XDaiBalance::new_base(10),
592 )])
593 .with_deployed_safes([DeployedSafe {
594 address: [1u8; Address::SIZE].into(),
595 owners: vec![deployer_addr],
596 module: MODULE_ADDR.into(),
597 registered_nodes: vec![],
598 deployer: deployer_addr,
599 }])
600 .with_hopr_network_chain_info("rotsee")
601 .build_dynamic_client(MODULE_ADDR.into());
602
603 let mut connector = create_connector(blokli_client)?;
604 connector.connect().await?;
605
606 connector.register_safe(&[1u8; Address::SIZE].into()).await?.await?;
607
608 insta::assert_yaml_snapshot!(*connector.client.snapshot());
609
610 Ok(())
611 }
612
613 #[tokio::test]
614 async fn connector_should_register_safe_that_has_nodes_registered_already() -> anyhow::Result<()> {
615 let safe_addr: Address = [2u8; Address::SIZE].into();
616 let deployer_addr = ChainKeypair::from_secret(&PRIVATE_KEY_1)?.public().to_address();
617 let other_registered_node = ChainKeypair::from_secret(&PRIVATE_KEY_2)?.public().to_address();
618
619 let blokli_client = BlokliTestStateBuilder::default()
620 .with_balances([(
621 ChainKeypair::from_secret(&PRIVATE_KEY_1)?.public().to_address(),
622 XDaiBalance::new_base(10),
623 )])
624 .with_deployed_safes([DeployedSafe {
625 address: safe_addr,
626 owners: vec![deployer_addr],
627 module: MODULE_ADDR.into(),
628 registered_nodes: vec![other_registered_node],
629 deployer: deployer_addr,
630 }])
631 .with_hopr_network_chain_info("rotsee")
632 .build_dynamic_client(MODULE_ADDR.into());
633
634 let mut connector = create_connector(blokli_client)?;
635 connector.connect().await?;
636
637 connector.register_safe(&safe_addr).await?.await?;
638
639 insta::assert_yaml_snapshot!(*connector.client.snapshot());
640
641 Ok(())
642 }
643
644 #[tokio::test]
645 async fn connector_should_not_register_safe_that_does_not_exist() -> anyhow::Result<()> {
646 let safe_addr: Address = [2u8; Address::SIZE].into();
647
648 let blokli_client = BlokliTestStateBuilder::default()
649 .with_balances([(
650 ChainKeypair::from_secret(&PRIVATE_KEY_1)?.public().to_address(),
651 XDaiBalance::new_base(10),
652 )])
653 .with_hopr_network_chain_info("rotsee")
654 .build_dynamic_client(MODULE_ADDR.into());
655
656 let mut connector = create_connector(blokli_client)?;
657 connector.connect().await?;
658
659 assert!(connector.register_safe(&safe_addr).await.is_err());
660
661 insta::assert_yaml_snapshot!(*connector.client.snapshot());
662
663 Ok(())
664 }
665
666 #[tokio::test]
667 async fn connector_should_not_register_any_safe_when_node_already_registered() -> anyhow::Result<()> {
668 let deployer_addr = ChainKeypair::from_secret(&PRIVATE_KEY_2)?.public().to_address();
669 let blokli_client = BlokliTestStateBuilder::default()
670 .with_balances([(
671 ChainKeypair::from_secret(&PRIVATE_KEY_1)?.public().to_address(),
672 XDaiBalance::new_base(10),
673 )])
674 .with_deployed_safes([
675 DeployedSafe {
676 address: [2u8; Address::SIZE].into(),
677 owners: vec![deployer_addr],
678 module: MODULE_ADDR.into(),
679 registered_nodes: vec![ChainKeypair::from_secret(&PRIVATE_KEY_1)?.public().to_address()],
680 deployer: deployer_addr,
681 },
682 DeployedSafe {
683 address: [1u8; Address::SIZE].into(),
684 owners: vec![deployer_addr],
685 module: MODULE_ADDR.into(),
686 registered_nodes: vec![],
687 deployer: deployer_addr,
688 },
689 ])
690 .with_hopr_network_chain_info("rotsee")
691 .build_dynamic_client(MODULE_ADDR.into());
692
693 let mut connector = create_connector(blokli_client)?;
694 connector.connect().await?;
695
696 assert!(
697 matches!(connector.register_safe(&[1u8; Address::SIZE].into()).await, Err(SafeRegistrationError::AlreadyRegistered(a)) if a == [2u8; Address::SIZE].into())
698 );
699
700 insta::assert_yaml_snapshot!(*connector.client.snapshot());
701
702 Ok(())
703 }
704}