1use {
2 crate::{nonce_utils, rpc_client::RpcClient},
3 clap::ArgMatches,
4 gemachain_clap_utils::{
5 input_parsers::{pubkey_of, value_of},
6 nonce::*,
7 offline::*,
8 },
9 gemachain_sdk::{
10 commitment_config::CommitmentConfig, fee_calculator::FeeCalculator, hash::Hash,
11 pubkey::Pubkey,
12 },
13};
14
15#[derive(Debug, PartialEq)]
16pub enum Source {
17 Cluster,
18 NonceAccount(Pubkey),
19}
20
21impl Source {
22 #[deprecated(since = "1.8.0", note = "Please use `get_blockhash` instead")]
23 pub fn get_blockhash_and_fee_calculator(
24 &self,
25 rpc_client: &RpcClient,
26 commitment: CommitmentConfig,
27 ) -> Result<(Hash, FeeCalculator), Box<dyn std::error::Error>> {
28 match self {
29 Self::Cluster => {
30 #[allow(deprecated)]
31 let res = rpc_client
32 .get_recent_blockhash_with_commitment(commitment)?
33 .value;
34 Ok((res.0, res.1))
35 }
36 Self::NonceAccount(ref pubkey) => {
37 let data = nonce_utils::get_account_with_commitment(rpc_client, pubkey, commitment)
38 .and_then(|ref a| nonce_utils::data_from_account(a))?;
39 Ok((data.blockhash, data.fee_calculator))
40 }
41 }
42 }
43
44 #[deprecated(
45 since = "1.8.0",
46 note = "Please do not use, will no longer be available in the future"
47 )]
48 pub fn get_fee_calculator(
49 &self,
50 rpc_client: &RpcClient,
51 blockhash: &Hash,
52 commitment: CommitmentConfig,
53 ) -> Result<Option<FeeCalculator>, Box<dyn std::error::Error>> {
54 match self {
55 Self::Cluster => {
56 #[allow(deprecated)]
57 let res = rpc_client
58 .get_fee_calculator_for_blockhash_with_commitment(blockhash, commitment)?
59 .value;
60 Ok(res)
61 }
62 Self::NonceAccount(ref pubkey) => {
63 let res = nonce_utils::get_account_with_commitment(rpc_client, pubkey, commitment)?;
64 let res = nonce_utils::data_from_account(&res)?;
65 Ok(Some(res)
66 .filter(|d| d.blockhash == *blockhash)
67 .map(|d| d.fee_calculator))
68 }
69 }
70 }
71
72 pub fn get_blockhash(
73 &self,
74 rpc_client: &RpcClient,
75 commitment: CommitmentConfig,
76 ) -> Result<Hash, Box<dyn std::error::Error>> {
77 match self {
78 Self::Cluster => {
79 let (blockhash, _) = rpc_client.get_latest_blockhash_with_commitment(commitment)?;
80 Ok(blockhash)
81 }
82 Self::NonceAccount(ref pubkey) => {
83 let data = nonce_utils::get_account_with_commitment(rpc_client, pubkey, commitment)
84 .and_then(|ref a| nonce_utils::data_from_account(a))?;
85 Ok(data.blockhash)
86 }
87 }
88 }
89
90 pub fn is_blockhash_valid(
91 &self,
92 rpc_client: &RpcClient,
93 blockhash: &Hash,
94 commitment: CommitmentConfig,
95 ) -> Result<bool, Box<dyn std::error::Error>> {
96 Ok(match self {
97 Self::Cluster => rpc_client.is_blockhash_valid(blockhash, commitment)?,
98 Self::NonceAccount(ref pubkey) => {
99 let _ = nonce_utils::get_account_with_commitment(rpc_client, pubkey, commitment)
100 .and_then(|ref a| nonce_utils::data_from_account(a))?;
101 true
102 }
103 })
104 }
105}
106
107#[derive(Debug, PartialEq)]
108pub enum BlockhashQuery {
109 None(Hash),
110 FeeCalculator(Source, Hash),
111 All(Source),
112}
113
114impl BlockhashQuery {
115 pub fn new(blockhash: Option<Hash>, sign_only: bool, nonce_account: Option<Pubkey>) -> Self {
116 let source = nonce_account
117 .map(Source::NonceAccount)
118 .unwrap_or(Source::Cluster);
119 match blockhash {
120 Some(hash) if sign_only => Self::None(hash),
121 Some(hash) if !sign_only => Self::FeeCalculator(source, hash),
122 None if !sign_only => Self::All(source),
123 _ => panic!("Cannot resolve blockhash"),
124 }
125 }
126
127 pub fn new_from_matches(matches: &ArgMatches<'_>) -> Self {
128 let blockhash = value_of(matches, BLOCKHASH_ARG.name);
129 let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
130 let nonce_account = pubkey_of(matches, NONCE_ARG.name);
131 BlockhashQuery::new(blockhash, sign_only, nonce_account)
132 }
133
134 #[deprecated(since = "1.8.0", note = "Please use `get_blockhash` instead")]
135 pub fn get_blockhash_and_fee_calculator(
136 &self,
137 rpc_client: &RpcClient,
138 commitment: CommitmentConfig,
139 ) -> Result<(Hash, FeeCalculator), Box<dyn std::error::Error>> {
140 match self {
141 BlockhashQuery::None(hash) => Ok((*hash, FeeCalculator::default())),
142 BlockhashQuery::FeeCalculator(source, hash) => {
143 #[allow(deprecated)]
144 let fee_calculator = source
145 .get_fee_calculator(rpc_client, hash, commitment)?
146 .ok_or(format!("Hash has expired {:?}", hash))?;
147 Ok((*hash, fee_calculator))
148 }
149 BlockhashQuery::All(source) =>
150 {
151 #[allow(deprecated)]
152 source.get_blockhash_and_fee_calculator(rpc_client, commitment)
153 }
154 }
155 }
156
157 pub fn get_blockhash(
158 &self,
159 rpc_client: &RpcClient,
160 commitment: CommitmentConfig,
161 ) -> Result<Hash, Box<dyn std::error::Error>> {
162 match self {
163 BlockhashQuery::None(hash) => Ok(*hash),
164 BlockhashQuery::FeeCalculator(source, hash) => {
165 if !source.is_blockhash_valid(rpc_client, hash, commitment)? {
166 return Err(format!("Hash has expired {:?}", hash).into());
167 }
168 Ok(*hash)
169 }
170 BlockhashQuery::All(source) => source.get_blockhash(rpc_client, commitment),
171 }
172 }
173}
174
175impl Default for BlockhashQuery {
176 fn default() -> Self {
177 BlockhashQuery::All(Source::Cluster)
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use crate::{
185 blockhash_query,
186 rpc_request::RpcRequest,
187 rpc_response::{Response, RpcFeeCalculator, RpcFees, RpcResponseContext},
188 };
189 use clap::App;
190 use serde_json::{self, json};
191 use gemachain_account_decoder::{UiAccount, UiAccountEncoding};
192 use gemachain_sdk::{account::Account, hash::hash, nonce, system_program};
193 use std::collections::HashMap;
194
195 #[test]
196 fn test_blockhash_query_new_ok() {
197 let blockhash = hash(&[1u8]);
198 let nonce_pubkey = Pubkey::new(&[1u8; 32]);
199
200 assert_eq!(
201 BlockhashQuery::new(Some(blockhash), true, None),
202 BlockhashQuery::None(blockhash),
203 );
204 assert_eq!(
205 BlockhashQuery::new(Some(blockhash), false, None),
206 BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
207 );
208 assert_eq!(
209 BlockhashQuery::new(None, false, None),
210 BlockhashQuery::All(blockhash_query::Source::Cluster)
211 );
212
213 assert_eq!(
214 BlockhashQuery::new(Some(blockhash), true, Some(nonce_pubkey)),
215 BlockhashQuery::None(blockhash),
216 );
217 assert_eq!(
218 BlockhashQuery::new(Some(blockhash), false, Some(nonce_pubkey)),
219 BlockhashQuery::FeeCalculator(
220 blockhash_query::Source::NonceAccount(nonce_pubkey),
221 blockhash
222 ),
223 );
224 assert_eq!(
225 BlockhashQuery::new(None, false, Some(nonce_pubkey)),
226 BlockhashQuery::All(blockhash_query::Source::NonceAccount(nonce_pubkey)),
227 );
228 }
229
230 #[test]
231 #[should_panic]
232 fn test_blockhash_query_new_no_nonce_fail() {
233 BlockhashQuery::new(None, true, None);
234 }
235
236 #[test]
237 #[should_panic]
238 fn test_blockhash_query_new_nonce_fail() {
239 let nonce_pubkey = Pubkey::new(&[1u8; 32]);
240 BlockhashQuery::new(None, true, Some(nonce_pubkey));
241 }
242
243 #[test]
244 fn test_blockhash_query_new_from_matches_ok() {
245 let test_commands = App::new("blockhash_query_test")
246 .nonce_args(false)
247 .offline_args();
248 let blockhash = hash(&[1u8]);
249 let blockhash_string = blockhash.to_string();
250
251 let matches = test_commands.clone().get_matches_from(vec![
252 "blockhash_query_test",
253 "--blockhash",
254 &blockhash_string,
255 "--sign-only",
256 ]);
257 assert_eq!(
258 BlockhashQuery::new_from_matches(&matches),
259 BlockhashQuery::None(blockhash),
260 );
261
262 let matches = test_commands.clone().get_matches_from(vec![
263 "blockhash_query_test",
264 "--blockhash",
265 &blockhash_string,
266 ]);
267 assert_eq!(
268 BlockhashQuery::new_from_matches(&matches),
269 BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
270 );
271
272 let matches = test_commands
273 .clone()
274 .get_matches_from(vec!["blockhash_query_test"]);
275 assert_eq!(
276 BlockhashQuery::new_from_matches(&matches),
277 BlockhashQuery::All(blockhash_query::Source::Cluster),
278 );
279
280 let nonce_pubkey = Pubkey::new(&[1u8; 32]);
281 let nonce_string = nonce_pubkey.to_string();
282 let matches = test_commands.clone().get_matches_from(vec![
283 "blockhash_query_test",
284 "--blockhash",
285 &blockhash_string,
286 "--sign-only",
287 "--nonce",
288 &nonce_string,
289 ]);
290 assert_eq!(
291 BlockhashQuery::new_from_matches(&matches),
292 BlockhashQuery::None(blockhash),
293 );
294
295 let matches = test_commands.clone().get_matches_from(vec![
296 "blockhash_query_test",
297 "--blockhash",
298 &blockhash_string,
299 "--nonce",
300 &nonce_string,
301 ]);
302 assert_eq!(
303 BlockhashQuery::new_from_matches(&matches),
304 BlockhashQuery::FeeCalculator(
305 blockhash_query::Source::NonceAccount(nonce_pubkey),
306 blockhash
307 ),
308 );
309 }
310
311 #[test]
312 #[should_panic]
313 fn test_blockhash_query_new_from_matches_without_nonce_fail() {
314 let test_commands = App::new("blockhash_query_test")
315 .arg(blockhash_arg())
316 .arg(sign_only_arg().requires(""));
319
320 let matches = test_commands
321 .clone()
322 .get_matches_from(vec!["blockhash_query_test", "--sign-only"]);
323 BlockhashQuery::new_from_matches(&matches);
324 }
325
326 #[test]
327 #[should_panic]
328 fn test_blockhash_query_new_from_matches_with_nonce_fail() {
329 let test_commands = App::new("blockhash_query_test")
330 .arg(blockhash_arg())
331 .arg(sign_only_arg().requires(""));
334 let nonce_pubkey = Pubkey::new(&[1u8; 32]);
335 let nonce_string = nonce_pubkey.to_string();
336
337 let matches = test_commands.clone().get_matches_from(vec![
338 "blockhash_query_test",
339 "--sign-only",
340 "--nonce",
341 &nonce_string,
342 ]);
343 BlockhashQuery::new_from_matches(&matches);
344 }
345
346 #[test]
347 #[allow(deprecated)]
348 fn test_blockhash_query_get_blockhash_fee_calc() {
349 let test_blockhash = hash(&[0u8]);
350 let rpc_blockhash = hash(&[1u8]);
351 let rpc_fee_calc = FeeCalculator::new(42);
352 let get_recent_blockhash_response = json!(Response {
353 context: RpcResponseContext { slot: 1 },
354 value: json!(RpcFees {
355 blockhash: rpc_blockhash.to_string(),
356 fee_calculator: rpc_fee_calc.clone(),
357 last_valid_slot: 42,
358 last_valid_block_height: 42,
359 }),
360 });
361 let get_fee_calculator_for_blockhash_response = json!(Response {
362 context: RpcResponseContext { slot: 1 },
363 value: json!(RpcFeeCalculator {
364 fee_calculator: rpc_fee_calc.clone()
365 }),
366 });
367 let mut mocks = HashMap::new();
368 mocks.insert(RpcRequest::GetFees, get_recent_blockhash_response.clone());
369 let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
370 assert_eq!(
371 BlockhashQuery::default()
372 .get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default())
373 .unwrap(),
374 (rpc_blockhash, rpc_fee_calc.clone()),
375 );
376 let mut mocks = HashMap::new();
377 mocks.insert(RpcRequest::GetFees, get_recent_blockhash_response.clone());
378 mocks.insert(
379 RpcRequest::GetFeeCalculatorForBlockhash,
380 get_fee_calculator_for_blockhash_response,
381 );
382 let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
383 assert_eq!(
384 BlockhashQuery::FeeCalculator(Source::Cluster, test_blockhash)
385 .get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default())
386 .unwrap(),
387 (test_blockhash, rpc_fee_calc),
388 );
389 let mut mocks = HashMap::new();
390 mocks.insert(RpcRequest::GetFees, get_recent_blockhash_response);
391 let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
392 assert_eq!(
393 BlockhashQuery::None(test_blockhash)
394 .get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default())
395 .unwrap(),
396 (test_blockhash, FeeCalculator::default()),
397 );
398 let rpc_client = RpcClient::new_mock("fails".to_string());
399 assert!(BlockhashQuery::default()
400 .get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default())
401 .is_err());
402
403 let nonce_blockhash = Hash::new(&[2u8; 32]);
404 let nonce_fee_calc = FeeCalculator::new(4242);
405 let data = nonce::state::Data {
406 authority: Pubkey::new(&[3u8; 32]),
407 blockhash: nonce_blockhash,
408 fee_calculator: nonce_fee_calc.clone(),
409 };
410 let nonce_account = Account::new_data_with_space(
411 42,
412 &nonce::state::Versions::new_current(nonce::State::Initialized(data)),
413 nonce::State::size(),
414 &system_program::id(),
415 )
416 .unwrap();
417 let nonce_pubkey = Pubkey::new(&[4u8; 32]);
418 let rpc_nonce_account = UiAccount::encode(
419 &nonce_pubkey,
420 &nonce_account,
421 UiAccountEncoding::Base64,
422 None,
423 None,
424 );
425 let get_account_response = json!(Response {
426 context: RpcResponseContext { slot: 1 },
427 value: json!(Some(rpc_nonce_account)),
428 });
429
430 let mut mocks = HashMap::new();
431 mocks.insert(RpcRequest::GetAccountInfo, get_account_response.clone());
432 let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
433 assert_eq!(
434 BlockhashQuery::All(Source::NonceAccount(nonce_pubkey))
435 .get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default())
436 .unwrap(),
437 (nonce_blockhash, nonce_fee_calc.clone()),
438 );
439 let mut mocks = HashMap::new();
440 mocks.insert(RpcRequest::GetAccountInfo, get_account_response.clone());
441 let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
442 assert_eq!(
443 BlockhashQuery::FeeCalculator(Source::NonceAccount(nonce_pubkey), nonce_blockhash)
444 .get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default())
445 .unwrap(),
446 (nonce_blockhash, nonce_fee_calc),
447 );
448 let mut mocks = HashMap::new();
449 mocks.insert(RpcRequest::GetAccountInfo, get_account_response.clone());
450 let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
451 assert!(
452 BlockhashQuery::FeeCalculator(Source::NonceAccount(nonce_pubkey), test_blockhash)
453 .get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default())
454 .is_err()
455 );
456 let mut mocks = HashMap::new();
457 mocks.insert(RpcRequest::GetAccountInfo, get_account_response);
458 let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
459 assert_eq!(
460 BlockhashQuery::None(nonce_blockhash)
461 .get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default())
462 .unwrap(),
463 (nonce_blockhash, FeeCalculator::default()),
464 );
465
466 let rpc_client = RpcClient::new_mock("fails".to_string());
467 assert!(BlockhashQuery::All(Source::NonceAccount(nonce_pubkey))
468 .get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default())
469 .is_err());
470 }
471}