2020-09-22 15:06:14 -07:00
|
|
|
use crate::{nonce_utils, rpc_client::RpcClient};
|
2020-09-21 16:58:38 -07:00
|
|
|
use clap::ArgMatches;
|
|
|
|
use solana_clap_utils::{
|
|
|
|
input_parsers::{pubkey_of, value_of},
|
|
|
|
nonce::*,
|
|
|
|
offline::*,
|
|
|
|
};
|
2020-09-22 15:06:14 -07:00
|
|
|
use solana_sdk::{
|
|
|
|
commitment_config::CommitmentConfig, fee_calculator::FeeCalculator, hash::Hash, pubkey::Pubkey,
|
|
|
|
};
|
2020-03-11 11:14:15 -07:00
|
|
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
pub enum Source {
|
|
|
|
Cluster,
|
|
|
|
NonceAccount(Pubkey),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Source {
|
|
|
|
pub fn get_blockhash_and_fee_calculator(
|
|
|
|
&self,
|
|
|
|
rpc_client: &RpcClient,
|
2020-06-17 11:18:48 -07:00
|
|
|
commitment: CommitmentConfig,
|
2020-03-11 11:14:15 -07:00
|
|
|
) -> Result<(Hash, FeeCalculator), Box<dyn std::error::Error>> {
|
|
|
|
match self {
|
|
|
|
Self::Cluster => {
|
2020-06-17 11:18:48 -07:00
|
|
|
let res = rpc_client
|
|
|
|
.get_recent_blockhash_with_commitment(commitment)?
|
|
|
|
.value;
|
|
|
|
Ok((res.0, res.1))
|
2020-03-11 11:14:15 -07:00
|
|
|
}
|
|
|
|
Self::NonceAccount(ref pubkey) => {
|
2020-09-21 12:55:44 -07:00
|
|
|
let data = nonce_utils::get_account_with_commitment(rpc_client, pubkey, commitment)
|
|
|
|
.and_then(|ref a| nonce_utils::data_from_account(a))?;
|
2020-03-11 11:14:15 -07:00
|
|
|
Ok((data.blockhash, data.fee_calculator))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_fee_calculator(
|
|
|
|
&self,
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
blockhash: &Hash,
|
2020-06-17 11:18:48 -07:00
|
|
|
commitment: CommitmentConfig,
|
2020-03-11 11:14:15 -07:00
|
|
|
) -> Result<Option<FeeCalculator>, Box<dyn std::error::Error>> {
|
|
|
|
match self {
|
|
|
|
Self::Cluster => {
|
2020-06-17 11:18:48 -07:00
|
|
|
let res = rpc_client
|
|
|
|
.get_fee_calculator_for_blockhash_with_commitment(blockhash, commitment)?
|
|
|
|
.value;
|
2020-03-11 11:14:15 -07:00
|
|
|
Ok(res)
|
|
|
|
}
|
|
|
|
Self::NonceAccount(ref pubkey) => {
|
2020-09-21 12:55:44 -07:00
|
|
|
let res = nonce_utils::get_account_with_commitment(rpc_client, pubkey, commitment)?;
|
|
|
|
let res = nonce_utils::data_from_account(&res)?;
|
2020-06-08 17:38:14 -07:00
|
|
|
Ok(Some(res)
|
|
|
|
.filter(|d| d.blockhash == *blockhash)
|
|
|
|
.map(|d| d.fee_calculator))
|
2020-03-11 11:14:15 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
pub enum BlockhashQuery {
|
|
|
|
None(Hash),
|
|
|
|
FeeCalculator(Source, Hash),
|
|
|
|
All(Source),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BlockhashQuery {
|
|
|
|
pub fn new(blockhash: Option<Hash>, sign_only: bool, nonce_account: Option<Pubkey>) -> Self {
|
|
|
|
let source = nonce_account
|
|
|
|
.map(Source::NonceAccount)
|
|
|
|
.unwrap_or(Source::Cluster);
|
|
|
|
match blockhash {
|
|
|
|
Some(hash) if sign_only => Self::None(hash),
|
|
|
|
Some(hash) if !sign_only => Self::FeeCalculator(source, hash),
|
|
|
|
None if !sign_only => Self::All(source),
|
|
|
|
_ => panic!("Cannot resolve blockhash"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn new_from_matches(matches: &ArgMatches<'_>) -> Self {
|
|
|
|
let blockhash = value_of(matches, BLOCKHASH_ARG.name);
|
|
|
|
let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
|
2020-09-21 14:12:51 -07:00
|
|
|
let nonce_account = pubkey_of(matches, NONCE_ARG.name);
|
2020-03-11 11:14:15 -07:00
|
|
|
BlockhashQuery::new(blockhash, sign_only, nonce_account)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_blockhash_and_fee_calculator(
|
|
|
|
&self,
|
|
|
|
rpc_client: &RpcClient,
|
2020-06-17 11:18:48 -07:00
|
|
|
commitment: CommitmentConfig,
|
2020-03-11 11:14:15 -07:00
|
|
|
) -> Result<(Hash, FeeCalculator), Box<dyn std::error::Error>> {
|
|
|
|
match self {
|
|
|
|
BlockhashQuery::None(hash) => Ok((*hash, FeeCalculator::default())),
|
|
|
|
BlockhashQuery::FeeCalculator(source, hash) => {
|
|
|
|
let fee_calculator = source
|
2020-06-17 11:18:48 -07:00
|
|
|
.get_fee_calculator(rpc_client, hash, commitment)?
|
2020-03-11 11:14:15 -07:00
|
|
|
.ok_or(format!("Hash has expired {:?}", hash))?;
|
|
|
|
Ok((*hash, fee_calculator))
|
|
|
|
}
|
2020-06-17 11:18:48 -07:00
|
|
|
BlockhashQuery::All(source) => {
|
|
|
|
source.get_blockhash_and_fee_calculator(rpc_client, commitment)
|
|
|
|
}
|
2020-03-11 11:14:15 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for BlockhashQuery {
|
|
|
|
fn default() -> Self {
|
|
|
|
BlockhashQuery::All(Source::Cluster)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2020-09-22 15:06:14 -07:00
|
|
|
use crate::{
|
|
|
|
blockhash_query,
|
2020-03-11 11:14:15 -07:00
|
|
|
rpc_request::RpcRequest,
|
2020-06-30 21:55:11 -07:00
|
|
|
rpc_response::{Response, RpcFeeCalculator, RpcResponseContext},
|
2020-03-11 11:14:15 -07:00
|
|
|
};
|
2020-09-22 15:06:14 -07:00
|
|
|
use clap::App;
|
|
|
|
use serde_json::{self, json, Value};
|
|
|
|
use solana_account_decoder::{UiAccount, UiAccountEncoding};
|
|
|
|
use solana_sdk::{account::Account, hash::hash, nonce, system_program};
|
2020-03-11 11:14:15 -07:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_blockhash_query_new_ok() {
|
|
|
|
let blockhash = hash(&[1u8]);
|
|
|
|
let nonce_pubkey = Pubkey::new(&[1u8; 32]);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
BlockhashQuery::new(Some(blockhash), true, None),
|
|
|
|
BlockhashQuery::None(blockhash),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
BlockhashQuery::new(Some(blockhash), false, None),
|
|
|
|
BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
BlockhashQuery::new(None, false, None),
|
|
|
|
BlockhashQuery::All(blockhash_query::Source::Cluster)
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
BlockhashQuery::new(Some(blockhash), true, Some(nonce_pubkey)),
|
|
|
|
BlockhashQuery::None(blockhash),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
BlockhashQuery::new(Some(blockhash), false, Some(nonce_pubkey)),
|
|
|
|
BlockhashQuery::FeeCalculator(
|
|
|
|
blockhash_query::Source::NonceAccount(nonce_pubkey),
|
|
|
|
blockhash
|
|
|
|
),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
BlockhashQuery::new(None, false, Some(nonce_pubkey)),
|
|
|
|
BlockhashQuery::All(blockhash_query::Source::NonceAccount(nonce_pubkey)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[should_panic]
|
|
|
|
fn test_blockhash_query_new_no_nonce_fail() {
|
|
|
|
BlockhashQuery::new(None, true, None);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[should_panic]
|
|
|
|
fn test_blockhash_query_new_nonce_fail() {
|
|
|
|
let nonce_pubkey = Pubkey::new(&[1u8; 32]);
|
|
|
|
BlockhashQuery::new(None, true, Some(nonce_pubkey));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_blockhash_query_new_from_matches_ok() {
|
2020-09-28 19:59:37 -07:00
|
|
|
let test_commands = App::new("blockhash_query_test")
|
|
|
|
.nonce_args(false)
|
2020-10-07 12:18:25 -07:00
|
|
|
.offline_args();
|
2020-03-11 11:14:15 -07:00
|
|
|
let blockhash = hash(&[1u8]);
|
|
|
|
let blockhash_string = blockhash.to_string();
|
|
|
|
|
|
|
|
let matches = test_commands.clone().get_matches_from(vec![
|
|
|
|
"blockhash_query_test",
|
|
|
|
"--blockhash",
|
|
|
|
&blockhash_string,
|
|
|
|
"--sign-only",
|
|
|
|
]);
|
|
|
|
assert_eq!(
|
|
|
|
BlockhashQuery::new_from_matches(&matches),
|
|
|
|
BlockhashQuery::None(blockhash),
|
|
|
|
);
|
|
|
|
|
|
|
|
let matches = test_commands.clone().get_matches_from(vec![
|
|
|
|
"blockhash_query_test",
|
|
|
|
"--blockhash",
|
|
|
|
&blockhash_string,
|
|
|
|
]);
|
|
|
|
assert_eq!(
|
|
|
|
BlockhashQuery::new_from_matches(&matches),
|
|
|
|
BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
|
|
|
|
);
|
|
|
|
|
|
|
|
let matches = test_commands
|
|
|
|
.clone()
|
|
|
|
.get_matches_from(vec!["blockhash_query_test"]);
|
|
|
|
assert_eq!(
|
|
|
|
BlockhashQuery::new_from_matches(&matches),
|
|
|
|
BlockhashQuery::All(blockhash_query::Source::Cluster),
|
|
|
|
);
|
|
|
|
|
|
|
|
let nonce_pubkey = Pubkey::new(&[1u8; 32]);
|
|
|
|
let nonce_string = nonce_pubkey.to_string();
|
|
|
|
let matches = test_commands.clone().get_matches_from(vec![
|
|
|
|
"blockhash_query_test",
|
|
|
|
"--blockhash",
|
|
|
|
&blockhash_string,
|
|
|
|
"--sign-only",
|
|
|
|
"--nonce",
|
|
|
|
&nonce_string,
|
|
|
|
]);
|
|
|
|
assert_eq!(
|
|
|
|
BlockhashQuery::new_from_matches(&matches),
|
|
|
|
BlockhashQuery::None(blockhash),
|
|
|
|
);
|
|
|
|
|
|
|
|
let matches = test_commands.clone().get_matches_from(vec![
|
|
|
|
"blockhash_query_test",
|
|
|
|
"--blockhash",
|
|
|
|
&blockhash_string,
|
|
|
|
"--nonce",
|
|
|
|
&nonce_string,
|
|
|
|
]);
|
|
|
|
assert_eq!(
|
|
|
|
BlockhashQuery::new_from_matches(&matches),
|
|
|
|
BlockhashQuery::FeeCalculator(
|
|
|
|
blockhash_query::Source::NonceAccount(nonce_pubkey),
|
|
|
|
blockhash
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[should_panic]
|
|
|
|
fn test_blockhash_query_new_from_matches_without_nonce_fail() {
|
|
|
|
let test_commands = App::new("blockhash_query_test")
|
|
|
|
.arg(blockhash_arg())
|
|
|
|
// We can really only hit this case if the arg requirements
|
|
|
|
// are broken, so unset the requires() to recreate that condition
|
|
|
|
.arg(sign_only_arg().requires(""));
|
|
|
|
|
|
|
|
let matches = test_commands
|
|
|
|
.clone()
|
|
|
|
.get_matches_from(vec!["blockhash_query_test", "--sign-only"]);
|
|
|
|
BlockhashQuery::new_from_matches(&matches);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[should_panic]
|
|
|
|
fn test_blockhash_query_new_from_matches_with_nonce_fail() {
|
|
|
|
let test_commands = App::new("blockhash_query_test")
|
|
|
|
.arg(blockhash_arg())
|
|
|
|
// We can really only hit this case if the arg requirements
|
|
|
|
// are broken, so unset the requires() to recreate that condition
|
|
|
|
.arg(sign_only_arg().requires(""));
|
|
|
|
let nonce_pubkey = Pubkey::new(&[1u8; 32]);
|
|
|
|
let nonce_string = nonce_pubkey.to_string();
|
|
|
|
|
|
|
|
let matches = test_commands.clone().get_matches_from(vec![
|
|
|
|
"blockhash_query_test",
|
|
|
|
"--sign-only",
|
|
|
|
"--nonce",
|
|
|
|
&nonce_string,
|
|
|
|
]);
|
|
|
|
BlockhashQuery::new_from_matches(&matches);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_blockhash_query_get_blockhash_fee_calc() {
|
|
|
|
let test_blockhash = hash(&[0u8]);
|
|
|
|
let rpc_blockhash = hash(&[1u8]);
|
|
|
|
let rpc_fee_calc = FeeCalculator::new(42);
|
|
|
|
let get_recent_blockhash_response = json!(Response {
|
|
|
|
context: RpcResponseContext { slot: 1 },
|
|
|
|
value: json!((
|
|
|
|
Value::String(rpc_blockhash.to_string()),
|
|
|
|
serde_json::to_value(rpc_fee_calc.clone()).unwrap()
|
|
|
|
)),
|
|
|
|
});
|
|
|
|
let get_fee_calculator_for_blockhash_response = json!(Response {
|
|
|
|
context: RpcResponseContext { slot: 1 },
|
|
|
|
value: json!(RpcFeeCalculator {
|
|
|
|
fee_calculator: rpc_fee_calc.clone()
|
|
|
|
}),
|
|
|
|
});
|
|
|
|
let mut mocks = HashMap::new();
|
|
|
|
mocks.insert(
|
|
|
|
RpcRequest::GetRecentBlockhash,
|
|
|
|
get_recent_blockhash_response.clone(),
|
|
|
|
);
|
|
|
|
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
|
|
|
assert_eq!(
|
|
|
|
BlockhashQuery::default()
|
2020-06-17 11:18:48 -07:00
|
|
|
.get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default())
|
2020-03-11 11:14:15 -07:00
|
|
|
.unwrap(),
|
|
|
|
(rpc_blockhash, rpc_fee_calc.clone()),
|
|
|
|
);
|
|
|
|
let mut mocks = HashMap::new();
|
|
|
|
mocks.insert(
|
|
|
|
RpcRequest::GetRecentBlockhash,
|
|
|
|
get_recent_blockhash_response.clone(),
|
|
|
|
);
|
|
|
|
mocks.insert(
|
|
|
|
RpcRequest::GetFeeCalculatorForBlockhash,
|
2020-05-15 09:35:43 -07:00
|
|
|
get_fee_calculator_for_blockhash_response,
|
2020-03-11 11:14:15 -07:00
|
|
|
);
|
|
|
|
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
|
|
|
assert_eq!(
|
|
|
|
BlockhashQuery::FeeCalculator(Source::Cluster, test_blockhash)
|
2020-06-17 11:18:48 -07:00
|
|
|
.get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default())
|
2020-03-11 11:14:15 -07:00
|
|
|
.unwrap(),
|
2020-05-15 09:35:43 -07:00
|
|
|
(test_blockhash, rpc_fee_calc),
|
2020-03-11 11:14:15 -07:00
|
|
|
);
|
|
|
|
let mut mocks = HashMap::new();
|
|
|
|
mocks.insert(
|
|
|
|
RpcRequest::GetRecentBlockhash,
|
2020-05-15 09:35:43 -07:00
|
|
|
get_recent_blockhash_response,
|
2020-03-11 11:14:15 -07:00
|
|
|
);
|
|
|
|
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
|
|
|
assert_eq!(
|
|
|
|
BlockhashQuery::None(test_blockhash)
|
2020-06-17 11:18:48 -07:00
|
|
|
.get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default())
|
2020-03-11 11:14:15 -07:00
|
|
|
.unwrap(),
|
|
|
|
(test_blockhash, FeeCalculator::default()),
|
|
|
|
);
|
|
|
|
let rpc_client = RpcClient::new_mock("fails".to_string());
|
|
|
|
assert!(BlockhashQuery::default()
|
2020-06-17 11:18:48 -07:00
|
|
|
.get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default())
|
2020-03-11 11:14:15 -07:00
|
|
|
.is_err());
|
|
|
|
|
|
|
|
let nonce_blockhash = Hash::new(&[2u8; 32]);
|
|
|
|
let nonce_fee_calc = FeeCalculator::new(4242);
|
|
|
|
let data = nonce::state::Data {
|
|
|
|
authority: Pubkey::new(&[3u8; 32]),
|
|
|
|
blockhash: nonce_blockhash,
|
|
|
|
fee_calculator: nonce_fee_calc.clone(),
|
|
|
|
};
|
|
|
|
let nonce_account = Account::new_data_with_space(
|
|
|
|
42,
|
|
|
|
&nonce::state::Versions::new_current(nonce::State::Initialized(data)),
|
|
|
|
nonce::State::size(),
|
|
|
|
&system_program::id(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
let nonce_pubkey = Pubkey::new(&[4u8; 32]);
|
2020-08-09 00:50:45 -07:00
|
|
|
let rpc_nonce_account = UiAccount::encode(
|
|
|
|
&nonce_pubkey,
|
|
|
|
nonce_account,
|
2020-08-15 22:06:39 -07:00
|
|
|
UiAccountEncoding::Base64,
|
2020-08-09 00:50:45 -07:00
|
|
|
None,
|
2020-08-10 15:35:29 -07:00
|
|
|
None,
|
2020-08-09 00:50:45 -07:00
|
|
|
);
|
2020-03-11 11:14:15 -07:00
|
|
|
let get_account_response = json!(Response {
|
|
|
|
context: RpcResponseContext { slot: 1 },
|
2020-05-15 09:35:43 -07:00
|
|
|
value: json!(Some(rpc_nonce_account)),
|
2020-03-11 11:14:15 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
let mut mocks = HashMap::new();
|
|
|
|
mocks.insert(RpcRequest::GetAccountInfo, get_account_response.clone());
|
|
|
|
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
|
|
|
assert_eq!(
|
|
|
|
BlockhashQuery::All(Source::NonceAccount(nonce_pubkey))
|
2020-06-17 11:18:48 -07:00
|
|
|
.get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default())
|
2020-03-11 11:14:15 -07:00
|
|
|
.unwrap(),
|
|
|
|
(nonce_blockhash, nonce_fee_calc.clone()),
|
|
|
|
);
|
|
|
|
let mut mocks = HashMap::new();
|
|
|
|
mocks.insert(RpcRequest::GetAccountInfo, get_account_response.clone());
|
|
|
|
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
|
|
|
assert_eq!(
|
|
|
|
BlockhashQuery::FeeCalculator(Source::NonceAccount(nonce_pubkey), nonce_blockhash)
|
2020-06-17 11:18:48 -07:00
|
|
|
.get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default())
|
2020-03-11 11:14:15 -07:00
|
|
|
.unwrap(),
|
2020-05-15 09:35:43 -07:00
|
|
|
(nonce_blockhash, nonce_fee_calc),
|
2020-03-11 11:14:15 -07:00
|
|
|
);
|
|
|
|
let mut mocks = HashMap::new();
|
|
|
|
mocks.insert(RpcRequest::GetAccountInfo, get_account_response.clone());
|
|
|
|
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
|
|
|
assert!(
|
|
|
|
BlockhashQuery::FeeCalculator(Source::NonceAccount(nonce_pubkey), test_blockhash)
|
2020-06-17 11:18:48 -07:00
|
|
|
.get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default())
|
2020-03-11 11:14:15 -07:00
|
|
|
.is_err()
|
|
|
|
);
|
|
|
|
let mut mocks = HashMap::new();
|
2020-05-15 09:35:43 -07:00
|
|
|
mocks.insert(RpcRequest::GetAccountInfo, get_account_response);
|
2020-03-11 11:14:15 -07:00
|
|
|
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
|
|
|
assert_eq!(
|
|
|
|
BlockhashQuery::None(nonce_blockhash)
|
2020-06-17 11:18:48 -07:00
|
|
|
.get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default())
|
2020-03-11 11:14:15 -07:00
|
|
|
.unwrap(),
|
|
|
|
(nonce_blockhash, FeeCalculator::default()),
|
|
|
|
);
|
|
|
|
|
|
|
|
let rpc_client = RpcClient::new_mock("fails".to_string());
|
|
|
|
assert!(BlockhashQuery::All(Source::NonceAccount(nonce_pubkey))
|
2020-06-17 11:18:48 -07:00
|
|
|
.get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default())
|
2020-03-11 11:14:15 -07:00
|
|
|
.is_err());
|
|
|
|
}
|
|
|
|
}
|