hopr_chain_connector/connector/
values.rs1use std::time::Duration;
2
3use blokli_client::api::{BlokliQueryClient, RedeemedStatsSelector};
4use futures::TryFutureExt;
5use hopr_api::{
6 chain::{ChainInfo, DomainSeparators, RedemptionStats},
7 types::{internal::prelude::WinningProbability, primitive::prelude::*},
8};
9
10use crate::{
11 HoprBlockchainReader,
12 connector::HoprBlockchainConnector,
13 errors::ConnectorError,
14 utils::{ParsedChainInfo, model_to_chain_info, model_to_redeemed_stats},
15};
16
17pub(crate) const CHAIN_INFO_CACHE_KEY: u32 = 0;
18
19impl<B, C, P, R> HoprBlockchainConnector<C, R, B, P>
20where
21 C: BlokliQueryClient + Send + Sync + 'static,
22{
23 pub(crate) async fn query_cached_chain_info(&self) -> Result<ParsedChainInfo, ConnectorError> {
30 Ok(self
31 .values
32 .try_get_with(
33 CHAIN_INFO_CACHE_KEY,
34 self.client
35 .query_chain_info()
36 .map_err(ConnectorError::from)
37 .and_then(|model| futures::future::ready(model_to_chain_info(model))),
38 )
39 .await?)
40 }
41}
42
43#[async_trait::async_trait]
44impl<B, R, C, P> hopr_api::chain::ChainValues for HoprBlockchainConnector<C, B, P, R>
45where
46 B: Send + Sync,
47 C: BlokliQueryClient + Send + Sync + 'static,
48 P: Send + Sync,
49 R: Send + Sync,
50{
51 type Error = ConnectorError;
52
53 #[inline]
56 async fn balance<Cy: Currency, A: Into<Address> + Send>(&self, address: A) -> Result<Balance<Cy>, Self::Error> {
57 HoprBlockchainReader(self.client.clone()).balance(address).await
58 }
59
60 async fn domain_separators(&self) -> Result<DomainSeparators, Self::Error> {
61 Ok(self.query_cached_chain_info().await?.domain_separators)
62 }
63
64 async fn minimum_incoming_ticket_win_prob(&self) -> Result<WinningProbability, Self::Error> {
65 Ok(self.query_cached_chain_info().await?.ticket_win_prob)
66 }
67
68 async fn minimum_ticket_price(&self) -> Result<HoprBalance, Self::Error> {
69 Ok(self.query_cached_chain_info().await?.ticket_price)
70 }
71
72 async fn key_binding_fee(&self) -> Result<HoprBalance, Self::Error> {
73 Ok(self.query_cached_chain_info().await?.key_binding_fee)
74 }
75
76 async fn channel_closure_notice_period(&self) -> Result<Duration, Self::Error> {
77 Ok(self.query_cached_chain_info().await?.channel_closure_grace_period)
78 }
79
80 async fn chain_info(&self) -> Result<ChainInfo, Self::Error> {
81 Ok(self.query_cached_chain_info().await?.info)
82 }
83
84 async fn redemption_stats<A: Into<Address> + Send>(&self, safe_addr: A) -> Result<RedemptionStats, Self::Error> {
85 let safe_addr = safe_addr.into();
86 model_to_redeemed_stats(
87 self.client
88 .query_redeemed_stats(RedeemedStatsSelector::SafeAddress(safe_addr.into()))
89 .await?,
90 )
91 }
92
93 async fn typical_resolution_time(&self) -> Result<Duration, Self::Error> {
94 let v = self.query_cached_chain_info().await?;
95 Ok(v.expected_block_time * v.finality)
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use std::str::FromStr;
102
103 use hopr_api::{
104 chain::{ChainValues, DeployedSafe},
105 types::{
106 crypto::prelude::*,
107 internal::account::{AccountEntry, AccountType},
108 },
109 };
110
111 use super::*;
112 use crate::{connector::tests::create_connector, testing::BlokliTestStateBuilder};
113
114 #[tokio::test]
115 async fn connector_should_get_balance() -> anyhow::Result<()> {
116 let account = AccountEntry {
117 public_key: *OffchainKeypair::random().public(),
118 chain_addr: [1u8; Address::SIZE].into(),
119 entry_type: AccountType::NotAnnounced,
120 safe_address: Some([2u8; Address::SIZE].into()),
121 key_id: 1.into(),
122 };
123
124 let blokli_client = BlokliTestStateBuilder::default()
125 .with_accounts([(account.clone(), HoprBalance::new_base(100), XDaiBalance::new_base(1))])
126 .with_safe_allowances([(account.safe_address.unwrap(), HoprBalance::new_base(10000))])
127 .build_static_client();
128
129 let mut connector = create_connector(blokli_client)?;
130 connector.connect().await?;
131
132 assert_eq!(
133 connector.balance(account.safe_address.unwrap()).await?,
134 HoprBalance::new_base(100)
135 );
136 assert_eq!(connector.balance(account.chain_addr).await?, XDaiBalance::new_base(1));
137
138 Ok(())
139 }
140
141 #[tokio::test]
142 async fn connector_should_query_chain_info() -> anyhow::Result<()> {
143 let blokli_client = BlokliTestStateBuilder::default()
144 .with_hopr_network_chain_info("rotsee")
145 .build_static_client();
146
147 let mut connector = create_connector(blokli_client)?;
148 connector.connect().await?;
149
150 let chain_info = connector.chain_info().await?;
151
152 assert_eq!(100, chain_info.chain_id);
153 assert_eq!("rotsee", &chain_info.hopr_network_name);
154
155 assert_eq!(Duration::from_mins(5), connector.channel_closure_notice_period().await?);
156 assert_eq!(Hash::default(), connector.domain_separators().await?.channel);
157 assert_eq!(
158 HoprBalance::from_str("0.01 wxHOPR")?,
159 connector.key_binding_fee().await?
160 );
161
162 Ok(())
163 }
164
165 #[tokio::test]
166 async fn connector_should_query_chain_info_without_calling_connect_first() -> anyhow::Result<()> {
167 let blokli_client = BlokliTestStateBuilder::default()
168 .with_hopr_network_chain_info("rotsee")
169 .build_static_client();
170
171 let connector = create_connector(blokli_client)?;
172
173 let chain_info = connector.chain_info().await?;
174
175 assert_eq!(100, chain_info.chain_id);
176 assert_eq!("rotsee", &chain_info.hopr_network_name);
177
178 assert_eq!(Duration::from_mins(5), connector.channel_closure_notice_period().await?);
179 assert_eq!(Hash::default(), connector.domain_separators().await?.channel);
180 assert_eq!(
181 HoprBalance::from_str("0.01 wxHOPR")?,
182 connector.key_binding_fee().await?
183 );
184
185 Ok(())
186 }
187
188 #[tokio::test]
189 async fn connector_should_retrieve_redeemed_stats() -> anyhow::Result<()> {
190 let blokli_client = BlokliTestStateBuilder::default()
191 .with_deployed_safes([DeployedSafe {
192 address: [1u8; Address::SIZE].into(),
193 owners: vec![[2u8; Address::SIZE].into()],
194 module: [3u8; Address::SIZE].into(),
195 registered_nodes: vec![],
196 deployer: [2u8; Address::SIZE].into(),
197 }])
198 .with_hopr_network_chain_info("rotsee")
199 .build_static_client();
200
201 let connector = create_connector(blokli_client)?;
202
203 let stats = connector.redemption_stats([1u8; Address::SIZE]).await?;
204
205 assert_eq!(0, stats.redeemed_count);
206 assert_eq!(HoprBalance::zero(), stats.redeemed_value);
207
208 Ok(())
209 }
210}