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