gemachain_client/
blockhash_query.rs

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            // We can really only hit this case if the arg requirements
317            // are broken, so unset the requires() to recreate that condition
318            .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            // We can really only hit this case if the arg requirements
332            // are broken, so unset the requires() to recreate that condition
333            .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}