1use crate::{
2 client::Client,
3 types::{BlockInfo, NetworkInfo},
4};
5use pretend::{
6 interceptor::NoopRequestInterceptor, pretend, resolver::UrlResolver, JsonResult, Pretend, Url,
7};
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11#[derive(Serialize, Deserialize, Debug)]
12struct HeightInfo {
13 height: u64,
14}
15
16#[derive(Debug, Error, Deserialize)]
17
18pub enum ResponseError {
19 #[error("Internal error")]
20 InternalError(String),
21
22 #[error("Unknown error")]
23 UnknownError(String),
24}
25
26#[pretend]
27trait NetworkInfoFetch {
28 #[request(method = "GET", path = "/info")]
29 async fn network_info(&self) -> pretend::Result<JsonResult<NetworkInfo, ResponseError>>;
30
31 #[request(method = "GET", path = "/peers")]
32 async fn peer_info(&self) -> pretend::Result<JsonResult<Vec<String>, ResponseError>>;
33
34 #[request(method = "GET", path = "/block/hash/{id}")]
35 async fn block_by_hash(
36 &self,
37 id: &str,
38 ) -> pretend::Result<JsonResult<BlockInfo, ResponseError>>;
39
40 #[request(method = "GET", path = "/block/height/{height}")]
41 async fn block_by_height(
42 &self,
43 height: u64,
44 ) -> pretend::Result<JsonResult<BlockInfo, ResponseError>>;
45}
46
47pub struct NetworkInfoClient(Pretend<Client, UrlResolver, NoopRequestInterceptor>);
48
49impl NetworkInfoClient {
50 pub fn new(url: Url) -> Self {
51 let client = Client::default();
52 let pretend = Pretend::for_client(client).with_url(url);
53 Self(pretend)
54 }
55
56 pub async fn network_info(&self) -> Result<NetworkInfo, ResponseError> {
57 let response = self
58 .0
59 .network_info()
60 .await
61 .map_err(|err| ResponseError::InternalError(err.to_string()))?;
62 match response {
63 JsonResult::Ok(n) => Ok(n),
64 JsonResult::Err(err) => Err(err),
65 }
66 }
67
68 pub async fn peer_info(&self) -> Result<Vec<String>, ResponseError> {
69 let response = self
70 .0
71 .peer_info()
72 .await
73 .map_err(|err| ResponseError::InternalError(err.to_string()))?;
74 match response {
75 JsonResult::Ok(n) => Ok(n),
76 JsonResult::Err(err) => Err(err),
77 }
78 }
79
80 pub async fn block_by_hash(&self, id: &str) -> Result<BlockInfo, ResponseError> {
81 let response = self
82 .0
83 .block_by_hash(id)
84 .await
85 .map_err(|err| ResponseError::InternalError(err.to_string()))?;
86 match response {
87 JsonResult::Ok(n) => Ok(n),
88 JsonResult::Err(err) => Err(err),
89 }
90 }
91
92 pub async fn block_by_height(&self, id: &str) -> Result<BlockInfo, ResponseError> {
93 let response = self
94 .0
95 .block_by_hash(id)
96 .await
97 .map_err(|err| ResponseError::InternalError(err.to_string()))?;
98 match response {
99 JsonResult::Ok(n) => Ok(n),
100 JsonResult::Err(err) => Err(err),
101 }
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use std::str::FromStr;
108
109 use crate::{consts::ARWEAVE_BASE_URL, crypto::base64::Base64, network::NetworkInfoClient};
110 use pretend::Url;
111 use tokio_test::block_on;
112
113 #[test]
114 fn test_network_info() {
115 let url = Url::parse(ARWEAVE_BASE_URL).unwrap();
116 let client = NetworkInfoClient::new(url);
117 let network_info = block_on(client.network_info()).unwrap();
118
119 assert_eq!(network_info.network, "arweave.N.1".to_string());
120 }
121
122 #[test]
123 fn test_peer_info() {
124 let url = Url::parse(ARWEAVE_BASE_URL).unwrap();
125 let client = NetworkInfoClient::new(url);
126 let peer_info = block_on(client.peer_info()).unwrap();
127
128 assert!(!peer_info.is_empty());
129 }
130
131 #[test]
132 fn test_block_info() {
133 let url = Url::parse(ARWEAVE_BASE_URL).unwrap();
134 let client = NetworkInfoClient::new(url);
135
136 let block_hash_v1 = "ngFDAB2KRhJgJRysuhpp1u65FjBf5WZk99_NyoMx8w6uP0IVjzb93EVkYxmcErdZ";
137 let block_info_v1 = block_on(client.block_by_hash(block_hash_v1)).unwrap();
138 assert_eq!(block_info_v1.nonce, Base64::from_str("AAEBAAABAQAAAQAAAQEBAAEAAAABAQABAQABAAEAAAEBAAAAAQAAAAAAAQAAAQEBAAEBAAEBAQEBAQEAAQEBAAABAQEAAQAAAQABAAABAAAAAAEBAQEBAAABAQEAAAAAAAABAQAAAQAAAQEAAQABAQABAQEAAAABAAABAQABAQEAAAEBAQABAQEBAQEBAAABAQEAAAABAQABAAABAAEAAQEBAQAAAAABAQABAQAAAAAAAAABAQABAAEBAAEAAQABAQABAAEBAQEBAAEAAQABAAABAQEBAQAAAQABAQEBAAEBAQAAAQEBAQABAAEBAQEBAAAAAAABAAEAAAEAAAEAAAEBAAAAAAEAAQABAAAAAAABAQABAQAAAAEBAQAAAAABAAABAAEBAQEAAAAAAQAAAQABAQABAAEAAQABAQAAAAEBAQAAAQAAAAEBAAEBAAEBAQEAAAEBAQAAAQAAAAABAAEAAQEAAQ").unwrap());
139 assert_eq!(
140 block_info_v1.previous_block,
141 Base64::from_str("V6YjG8G3he0JIIwRtzTccX39rS0jH-jOqUJy6rxrVAHY0RT0AVhG8K22wCDxy1A0")
142 .unwrap()
143 );
144 assert_eq!(block_info_v1.timestamp, 1528500720);
145 assert_eq!(block_info_v1.last_retarget, 1528500720);
146 assert_eq!(block_info_v1.diff, "31");
147 assert_eq!(block_info_v1.height, 100);
148 assert_eq!(
149 block_info_v1.hash,
150 Base64::from_str("AAAAANsEvzGbICpfAj3NN41_ox--2cNxkEhAo0aggpDPkY7zru29g24uMWUP9hTa")
151 .unwrap()
152 );
153 assert_eq!(
154 block_info_v1.indep_hash,
155 Base64::from_str(block_hash_v1).unwrap()
156 );
157 assert_eq!(block_info_v1.txs.len(), 1);
158 assert_eq!(block_info_v1.tx_root, Base64::default());
159 assert_eq!(block_info_v1.tx_tree.len(), 0);
160 assert_eq!(
161 block_info_v1.wallet_list,
162 Base64::from_str("ph2FDDuQjNbca34tz7vP9X5Xve2EGJi2ZgFqhMITAdw").unwrap()
163 );
164 assert_eq!(
165 block_info_v1.reward_addr,
166 Base64::from_str("em8MfGRInwWEAQnE6b50ENaFOf-0to4Pbygng1ilWGQ").unwrap()
167 );
168 assert_eq!(block_info_v1.tags, []);
169 assert_eq!(block_info_v1.reward_pool, 60770606104);
170 assert_eq!(block_info_v1.weave_size, 599058);
171 assert_eq!(block_info_v1.block_size, 0);
172 assert_eq!(block_info_v1.poa.option, "1");
173 assert_eq!(block_info_v1.poa.tx_path, Base64::default());
174 assert_eq!(block_info_v1.poa.data_path, Base64::default());
175 assert_eq!(block_info_v1.poa.chunk, Base64::default());
176
177 let block_hash_v2 = "5H-hJycMS_PnPOpobXu2CNobRlgqmw4yEMQSc5LeBfS7We63l8HjS-Ek3QaxK8ug";
178 let block_info_v2 = block_on(client.block_by_hash(block_hash_v2)).unwrap();
179 assert_eq!(
180 block_info_v2.nonce,
181 Base64::from_str("O3IQWXYmxLN_b0w7QyT2GTruaVIGsl-Ybhc6Pl2V20U").unwrap()
182 );
183 assert_eq!(
184 block_info_v2.previous_block,
185 Base64::from_str("VRVYubqppWUVAeCWlzHR-38dQoWcFAKbGculkVZThfj-hNMX4QVZjqkC6-PkiNGE")
186 .unwrap()
187 );
188 assert_eq!(block_info_v2.timestamp, 1567052949);
189 assert_eq!(block_info_v2.last_retarget, 1567052114);
190 assert_eq!(
191 block_info_v2.diff,
192 "115792088374597902074750511579343425068641803109251942518159264612597601665024"
193 );
194 assert_eq!(block_info_v2.height, 269512);
195 assert_eq!(
196 block_info_v2.hash,
197 Base64::from_str("____47liyh_OZdYUP4EzBoLl7JOPge9VsWPQ3b5kiU8").unwrap()
198 );
199 assert_eq!(
200 block_info_v2.indep_hash,
201 Base64::from_str(block_hash_v2).unwrap()
202 );
203 assert_eq!(block_info_v2.txs.len(), 2);
204 assert_eq!(block_info_v2.tx_root, Base64::default());
205 assert_eq!(block_info_v2.tx_tree.len(), 0);
206 assert_eq!(
207 block_info_v2.wallet_list,
208 Base64::from_str("6haahtRP5WVchxPbqtLCqDsFWidhebYJpU5PVB4zQhE").unwrap()
209 );
210 assert_eq!(
211 block_info_v2.reward_addr,
212 Base64::from_str("aE1AjkBoXBfF-PRP2dzRrbYY8cY2OYzeH551nSPRU5M").unwrap()
213 );
214 assert_eq!(block_info_v2.tags, []);
215 assert_eq!(block_info_v2.reward_pool, 0);
216 assert_eq!(block_info_v2.weave_size, 21080508475);
217 assert_eq!(block_info_v2.block_size, 991723);
218 assert!(block_info_v2.cumulative_diff.is_some());
219 assert_eq!(block_info_v2.cumulative_diff.unwrap(), "616416144");
220 assert!(block_info_v2.hash_list_merkle.is_some());
221 assert_eq!(
222 block_info_v2.hash_list_merkle.unwrap(),
223 Base64::from_str("1QVbbLwZHpNMJd8ZghRb13HZfrRu-aIIfzY29r64_yBJAcYv-Kfblv_c2pfKbQBP")
224 .unwrap()
225 );
226 assert_eq!(block_info_v2.poa.option, "1");
227 assert_eq!(block_info_v2.poa.tx_path, Base64::default());
228 assert_eq!(block_info_v2.poa.data_path, Base64::default());
229 assert_eq!(block_info_v2.poa.chunk, Base64::default());
230
231 let block_hash_v3 = "5VTARz7bwDO4GqviCSI9JXm8_JOtoQwF-QCZm0Gt2gVgwdzSY3brOtOD46bjMz09";
232 let block_info_v3 = block_on(client.block_by_hash(block_hash_v3)).unwrap();
233 assert_eq!(
234 block_info_v3.nonce,
235 Base64::from_str("W3Jy4wp2LVbDFhGX_hUjRQZCkTdEbKxz45E5OVe52Lo").unwrap()
236 );
237 assert_eq!(
238 block_info_v3.previous_block,
239 Base64::from_str("YuTyalVBTNB9t5KhuRezcIgxVz9PbQsbrcY4Tpkiu8XBPgglGM_Yql5qZd0c9PVG")
240 .unwrap()
241 );
242 assert_eq!(block_info_v3.timestamp, 1586440919);
243 assert_eq!(block_info_v3.last_retarget, 1586440919);
244 assert_eq!(
245 block_info_v3.diff,
246 "115792089039110416381168389782714091630053560834545856346499935466490404274176"
247 );
248 assert_eq!(block_info_v3.height, 422250);
249 assert_eq!(
250 block_info_v3.hash,
251 Base64::from_str("_____8422fLZnBsEsxtwEdpi8GZDHVT-aFlqroQDG44").unwrap()
252 );
253 assert_eq!(
254 block_info_v3.indep_hash,
255 Base64::from_str(block_hash_v3).unwrap()
256 );
257 assert_eq!(block_info_v3.txs.len(), 1);
258 assert_eq!(
259 block_info_v3.tx_root,
260 Base64::from_str("lsoo-p3Tj7oblZ-54WVPHoVguqgw5rA9Jf3lLH6H8zY").unwrap()
261 );
262 assert_eq!(block_info_v3.tx_tree.len(), 0);
263 assert_eq!(
264 block_info_v3.wallet_list,
265 Base64::from_str("N5NJtXhgH9bPmXoSopehcr_zqwyPjjg3igel0V8G1DdLk_BYdoRVIBsqjVA9JmFc")
266 .unwrap()
267 );
268 assert_eq!(
269 block_info_v3.reward_addr,
270 Base64::from_str("Oox7m4HIcVhUtMd6AUuGtlaOoSCmREUNPyyKQCbz4d4").unwrap()
271 );
272 assert_eq!(block_info_v3.tags, []);
273 assert_eq!(block_info_v3.reward_pool, 3026104059201252);
274 assert_eq!(block_info_v3.weave_size, 407672420044);
275 assert_eq!(block_info_v3.block_size, 937455);
276 assert!(block_info_v3.cumulative_diff.is_some());
277 assert_eq!(block_info_v3.cumulative_diff.unwrap(), "99416580392277");
278 assert!(block_info_v3.hash_list_merkle.is_some());
279 assert_eq!(
280 block_info_v3.hash_list_merkle.unwrap(),
281 Base64::from_str("akSjDrBKPuepJMOhO_S9C-iFp5zn9Glv57HGdN_WPqEToWC0Ukb37Gzs4PDA7oLU")
282 .unwrap()
283 );
284 assert_eq!(block_info_v3.poa.option, "1");
285 assert_eq!(block_info_v3.poa.tx_path.0.len(), 640);
286 assert_eq!(block_info_v3.poa.data_path.0.len(), 352);
287 assert_eq!(block_info_v3.poa.chunk.0.len(), 262144);
288 }
289}