hopr_chain_connector/connector/
keys.rs1use hopr_api::{
2 chain::{HoprKeyIdent, KeyIdMapping},
3 types::{crypto::prelude::OffchainPublicKey, primitive::prelude::Address},
4};
5
6use crate::{backend::Backend, connector::HoprBlockchainConnector, errors::ConnectorError};
7
8pub struct HoprKeyMapper<B> {
9 pub(crate) id_to_key: moka::sync::Cache<HoprKeyIdent, Option<OffchainPublicKey>, ahash::RandomState>,
10 pub(crate) key_to_id: moka::sync::Cache<OffchainPublicKey, Option<HoprKeyIdent>, ahash::RandomState>,
11 pub(crate) backend: std::sync::Arc<B>,
12}
13
14impl<B> Clone for HoprKeyMapper<B> {
15 fn clone(&self) -> Self {
16 Self {
17 id_to_key: self.id_to_key.clone(),
18 key_to_id: self.key_to_id.clone(),
19 backend: self.backend.clone(),
20 }
21 }
22}
23
24impl<B> KeyIdMapping<HoprKeyIdent, OffchainPublicKey> for HoprKeyMapper<B>
28where
29 B: Backend + Send + Sync + 'static,
30{
31 fn map_key_to_id(&self, key: &OffchainPublicKey) -> Option<HoprKeyIdent> {
32 self.key_to_id.get_with_by_ref(key, || {
33 let start = std::time::Instant::now();
34 tracing::warn!(%key, "cache miss on map_key_to_id");
35 let result = match self.backend.get_account_by_key(key) {
36 Ok(Some(account)) => Some(account.key_id),
37 Ok(None) => None,
38 Err(error) => {
39 tracing::error!(%error, %key, "failed to get account by key");
40 None
41 }
42 };
43 tracing::trace!(
44 %key,
45 elapsed_ms = start.elapsed().as_millis() as u64,
46 found = result.is_some(),
47 "map_key_to_id backend lookup"
48 );
49 result
50 })
51 }
52
53 fn map_id_to_public(&self, id: &HoprKeyIdent) -> Option<OffchainPublicKey> {
54 self.id_to_key.get_with_by_ref(id, || {
55 let start = std::time::Instant::now();
56 tracing::warn!(%id, "cache miss on map_id_to_public");
57 let result = match self.backend.get_account_by_id(id) {
58 Ok(Some(account)) => Some(account.public_key),
59 Ok(None) => None,
60 Err(error) => {
61 tracing::error!(%error, %id, "failed to get account by id");
62 None
63 }
64 };
65 tracing::trace!(
66 %id,
67 elapsed_ms = start.elapsed().as_millis() as u64,
68 found = result.is_some(),
69 "map_id_to_public backend lookup"
70 );
71 result
72 })
73 }
74}
75
76#[async_trait::async_trait]
77impl<B, C, P, R> hopr_api::chain::ChainKeyOperations for HoprBlockchainConnector<C, B, P, R>
78where
79 B: Backend + Send + Sync + 'static,
80 C: Send + Sync,
81 P: Send + Sync,
82 R: Send + Sync,
83{
84 type Error = ConnectorError;
85 type Mapper = HoprKeyMapper<B>;
86
87 async fn chain_key_to_packet_key(&self, chain: &Address) -> Result<Option<OffchainPublicKey>, Self::Error> {
88 self.check_connection_state()?;
89
90 let backend = self.backend.clone();
91 let chain_key = *chain;
92 Ok(self
93 .chain_to_packet
94 .try_get_with_by_ref(&chain_key, async move {
95 tracing::warn!(%chain_key, "cache miss on chain_key_to_packet_key");
96 match hopr_async_runtime::prelude::spawn_blocking(move || backend.get_account_by_address(&chain_key))
97 .await
98 {
99 Ok(Ok(value)) => Ok(value.map(|account| account.public_key)),
100 Ok(Err(e)) => Err(ConnectorError::backend(e)),
101 Err(e) => Err(ConnectorError::backend(e)),
102 }
103 })
104 .await?)
105 }
106
107 async fn packet_key_to_chain_key(&self, packet: &OffchainPublicKey) -> Result<Option<Address>, Self::Error> {
108 self.check_connection_state()?;
109
110 let backend = self.backend.clone();
111 let packet_key = *packet;
112 Ok(self
113 .packet_to_chain
114 .try_get_with_by_ref(&packet_key, async move {
115 tracing::warn!(
116 peer_id = packet.to_peerid_str(),
117 "cache miss on packet_key_to_chain_key"
118 );
119 match hopr_async_runtime::prelude::spawn_blocking(move || backend.get_account_by_key(&packet_key)).await
120 {
121 Ok(Ok(value)) => Ok(value.map(|account| account.chain_addr)),
122 Ok(Err(e)) => Err(ConnectorError::backend(e)),
123 Err(e) => Err(ConnectorError::backend(e)),
124 }
125 })
126 .await?)
127 }
128
129 fn key_id_mapper_ref(&self) -> &Self::Mapper {
130 &self.mapper
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use hex_literal::hex;
137 use hopr_api::{
138 chain::ChainKeyOperations,
139 types::{crypto::prelude::*, internal::prelude::*, primitive::prelude::*},
140 };
141
142 use crate::{connector::tests::create_connector, testing::BlokliTestStateBuilder};
143
144 #[tokio::test]
145 async fn connector_should_map_keys_to_ids_and_back() -> anyhow::Result<()> {
146 let offchain_key = OffchainKeypair::from_secret(&hex!(
147 "60741b83b99e36aa0c1331578156e16b8e21166d01834abb6c64b103f885734d"
148 ))?;
149 let chain_addr: Address = [1u8; Address::SIZE].into();
150 let account = AccountEntry {
151 public_key: *offchain_key.public(),
152 chain_addr,
153 entry_type: AccountType::NotAnnounced,
154 safe_address: Some([2u8; Address::SIZE].into()),
155 key_id: 1.into(),
156 };
157
158 let blokli_client = BlokliTestStateBuilder::default()
159 .with_accounts([(account.clone(), HoprBalance::new_base(100), XDaiBalance::new_base(1))])
160 .build_static_client();
161
162 let mut connector = create_connector(blokli_client)?;
163 connector.connect().await?;
164
165 assert_eq!(
166 Some(chain_addr),
167 connector.packet_key_to_chain_key(offchain_key.public()).await?
168 );
169 assert_eq!(
170 Some(*offchain_key.public()),
171 connector.chain_key_to_packet_key(&chain_addr).await?
172 );
173
174 let mapper = connector.key_id_mapper_ref();
175
176 assert_eq!(Some(account.key_id), mapper.map_key_to_id(offchain_key.public()));
177 assert_eq!(Some(*offchain_key.public()), mapper.map_id_to_public(&account.key_id));
178
179 Ok(())
180 }
181}