Deprecate FeeCalculator returning APIs (#19120)
This commit is contained in:
@ -4,30 +4,30 @@ use solana_client::{
|
||||
rpc_client::RpcClient,
|
||||
};
|
||||
use solana_sdk::{
|
||||
commitment_config::CommitmentConfig, fee_calculator::FeeCalculator, message::Message,
|
||||
commitment_config::CommitmentConfig, hash::Hash, message::Message,
|
||||
native_token::lamports_to_sol, pubkey::Pubkey,
|
||||
};
|
||||
|
||||
pub fn check_account_for_fee(
|
||||
rpc_client: &RpcClient,
|
||||
account_pubkey: &Pubkey,
|
||||
fee_calculator: &FeeCalculator,
|
||||
blockhash: &Hash,
|
||||
message: &Message,
|
||||
) -> Result<(), CliError> {
|
||||
check_account_for_multiple_fees(rpc_client, account_pubkey, fee_calculator, &[message])
|
||||
check_account_for_multiple_fees(rpc_client, account_pubkey, blockhash, &[message])
|
||||
}
|
||||
|
||||
pub fn check_account_for_fee_with_commitment(
|
||||
rpc_client: &RpcClient,
|
||||
account_pubkey: &Pubkey,
|
||||
fee_calculator: &FeeCalculator,
|
||||
blockhash: &Hash,
|
||||
message: &Message,
|
||||
commitment: CommitmentConfig,
|
||||
) -> Result<(), CliError> {
|
||||
check_account_for_multiple_fees_with_commitment(
|
||||
rpc_client,
|
||||
account_pubkey,
|
||||
fee_calculator,
|
||||
blockhash,
|
||||
&[message],
|
||||
commitment,
|
||||
)
|
||||
@ -36,13 +36,13 @@ pub fn check_account_for_fee_with_commitment(
|
||||
pub fn check_account_for_multiple_fees(
|
||||
rpc_client: &RpcClient,
|
||||
account_pubkey: &Pubkey,
|
||||
fee_calculator: &FeeCalculator,
|
||||
blockhash: &Hash,
|
||||
messages: &[&Message],
|
||||
) -> Result<(), CliError> {
|
||||
check_account_for_multiple_fees_with_commitment(
|
||||
rpc_client,
|
||||
account_pubkey,
|
||||
fee_calculator,
|
||||
blockhash,
|
||||
messages,
|
||||
CommitmentConfig::default(),
|
||||
)
|
||||
@ -51,7 +51,7 @@ pub fn check_account_for_multiple_fees(
|
||||
pub fn check_account_for_multiple_fees_with_commitment(
|
||||
rpc_client: &RpcClient,
|
||||
account_pubkey: &Pubkey,
|
||||
fee_calculator: &FeeCalculator,
|
||||
blockhash: &Hash,
|
||||
messages: &[&Message],
|
||||
commitment: CommitmentConfig,
|
||||
) -> Result<(), CliError> {
|
||||
@ -59,7 +59,7 @@ pub fn check_account_for_multiple_fees_with_commitment(
|
||||
rpc_client,
|
||||
account_pubkey,
|
||||
0,
|
||||
fee_calculator,
|
||||
blockhash,
|
||||
messages,
|
||||
commitment,
|
||||
)
|
||||
@ -69,11 +69,11 @@ pub fn check_account_for_spend_multiple_fees_with_commitment(
|
||||
rpc_client: &RpcClient,
|
||||
account_pubkey: &Pubkey,
|
||||
balance: u64,
|
||||
fee_calculator: &FeeCalculator,
|
||||
blockhash: &Hash,
|
||||
messages: &[&Message],
|
||||
commitment: CommitmentConfig,
|
||||
) -> Result<(), CliError> {
|
||||
let fee = calculate_fee(fee_calculator, messages);
|
||||
let fee = get_fee_for_message(rpc_client, blockhash, messages)?;
|
||||
if !check_account_for_balance_with_commitment(
|
||||
rpc_client,
|
||||
account_pubkey,
|
||||
@ -98,11 +98,17 @@ pub fn check_account_for_spend_multiple_fees_with_commitment(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn calculate_fee(fee_calculator: &FeeCalculator, messages: &[&Message]) -> u64 {
|
||||
messages
|
||||
pub fn get_fee_for_message(
|
||||
rpc_client: &RpcClient,
|
||||
blockhash: &Hash,
|
||||
messages: &[&Message],
|
||||
) -> Result<u64, CliError> {
|
||||
Ok(messages
|
||||
.iter()
|
||||
.map(|message| fee_calculator.calculate_fee(message))
|
||||
.sum()
|
||||
.map(|message| rpc_client.get_fee_for_message(blockhash, message))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.iter()
|
||||
.sum())
|
||||
}
|
||||
|
||||
pub fn check_account_for_balance(
|
||||
@ -166,7 +172,6 @@ mod tests {
|
||||
value: json!(account_balance),
|
||||
});
|
||||
let pubkey = solana_sdk::pubkey::new_rand();
|
||||
let fee_calculator = FeeCalculator::new(1);
|
||||
|
||||
let pubkey0 = Pubkey::new(&[0; 32]);
|
||||
let pubkey1 = Pubkey::new(&[1; 32]);
|
||||
@ -180,21 +185,32 @@ mod tests {
|
||||
let mut mocks = HashMap::new();
|
||||
mocks.insert(RpcRequest::GetBalance, account_balance_response.clone());
|
||||
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
||||
check_account_for_fee(&rpc_client, &pubkey, &fee_calculator, &message0)
|
||||
let blockhash = rpc_client.get_latest_blockhash().unwrap();
|
||||
check_account_for_fee(&rpc_client, &pubkey, &blockhash, &message0)
|
||||
.expect("unexpected result");
|
||||
|
||||
let check_fee_response = json!(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: json!(2),
|
||||
});
|
||||
let mut mocks = HashMap::new();
|
||||
mocks.insert(RpcRequest::GetFeeForMessage, check_fee_response);
|
||||
mocks.insert(RpcRequest::GetBalance, account_balance_response.clone());
|
||||
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
||||
assert!(check_account_for_fee(&rpc_client, &pubkey, &fee_calculator, &message1).is_err());
|
||||
assert!(check_account_for_fee(&rpc_client, &pubkey, &blockhash, &message1).is_err());
|
||||
|
||||
let check_fee_response = json!(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: json!(2),
|
||||
});
|
||||
let mut mocks = HashMap::new();
|
||||
mocks.insert(RpcRequest::GetFeeForMessage, check_fee_response);
|
||||
mocks.insert(RpcRequest::GetBalance, account_balance_response);
|
||||
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
||||
assert!(check_account_for_multiple_fees(
|
||||
&rpc_client,
|
||||
&pubkey,
|
||||
&fee_calculator,
|
||||
&blockhash,
|
||||
&[&message0, &message0]
|
||||
)
|
||||
.is_err());
|
||||
@ -204,18 +220,18 @@ mod tests {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: json!(account_balance),
|
||||
});
|
||||
let check_fee_response = json!(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: json!(1),
|
||||
});
|
||||
|
||||
let mut mocks = HashMap::new();
|
||||
mocks.insert(RpcRequest::GetFeeForMessage, check_fee_response);
|
||||
mocks.insert(RpcRequest::GetBalance, account_balance_response);
|
||||
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
||||
|
||||
check_account_for_multiple_fees(
|
||||
&rpc_client,
|
||||
&pubkey,
|
||||
&fee_calculator,
|
||||
&[&message0, &message0],
|
||||
)
|
||||
.expect("unexpected result");
|
||||
check_account_for_multiple_fees(&rpc_client, &pubkey, &blockhash, &[&message0, &message0])
|
||||
.expect("unexpected result");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -237,27 +253,45 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_fee() {
|
||||
let fee_calculator = FeeCalculator::new(1);
|
||||
// No messages, no fee.
|
||||
assert_eq!(calculate_fee(&fee_calculator, &[]), 0);
|
||||
fn test_get_fee_for_message() {
|
||||
let check_fee_response = json!(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: json!(1),
|
||||
});
|
||||
let mut mocks = HashMap::new();
|
||||
mocks.insert(RpcRequest::GetFeeForMessage, check_fee_response);
|
||||
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
||||
let blockhash = rpc_client.get_latest_blockhash().unwrap();
|
||||
|
||||
// No signatures, no fee.
|
||||
let message = Message::default();
|
||||
assert_eq!(calculate_fee(&fee_calculator, &[&message, &message]), 0);
|
||||
// No messages, no fee.
|
||||
assert_eq!(
|
||||
get_fee_for_message(&rpc_client, &blockhash, &[]).unwrap(),
|
||||
0
|
||||
);
|
||||
|
||||
// One message w/ one signature, a fee.
|
||||
let pubkey0 = Pubkey::new(&[0; 32]);
|
||||
let pubkey1 = Pubkey::new(&[1; 32]);
|
||||
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
|
||||
let message0 = Message::new(&[ix0], Some(&pubkey0));
|
||||
assert_eq!(calculate_fee(&fee_calculator, &[&message0]), 1);
|
||||
assert_eq!(
|
||||
get_fee_for_message(&rpc_client, &blockhash, &[&message0]).unwrap(),
|
||||
1
|
||||
);
|
||||
|
||||
// Two messages, additive fees.
|
||||
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
|
||||
let ix1 = system_instruction::transfer(&pubkey1, &pubkey0, 1);
|
||||
let message1 = Message::new(&[ix0, ix1], Some(&pubkey0));
|
||||
assert_eq!(calculate_fee(&fee_calculator, &[&message0, &message1]), 3);
|
||||
// No signatures, no fee.
|
||||
let check_fee_response = json!(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: json!(0),
|
||||
});
|
||||
let mut mocks = HashMap::new();
|
||||
mocks.insert(RpcRequest::GetFeeForMessage, check_fee_response);
|
||||
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
||||
let message = Message::default();
|
||||
assert_eq!(
|
||||
get_fee_for_message(&rpc_client, &blockhash, &[&message, &message]).unwrap(),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1523,7 +1523,7 @@ pub fn request_and_confirm_airdrop(
|
||||
to_pubkey: &Pubkey,
|
||||
lamports: u64,
|
||||
) -> ClientResult<Signature> {
|
||||
let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let recent_blockhash = rpc_client.get_latest_blockhash()?;
|
||||
let signature =
|
||||
rpc_client.request_airdrop_with_blockhash(to_pubkey, lamports, &recent_blockhash)?;
|
||||
rpc_client.confirm_transaction_with_spinner(
|
||||
|
@ -142,9 +142,10 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
SubCommand::with_name("cluster-version")
|
||||
.about("Get the version of the cluster entrypoint"),
|
||||
)
|
||||
// Deprecated in v1.8.0
|
||||
.subcommand(
|
||||
SubCommand::with_name("fees")
|
||||
.about("Display current cluster fees")
|
||||
.about("Display current cluster fees (Deprecated in v1.8.0)")
|
||||
.arg(
|
||||
Arg::with_name("blockhash")
|
||||
.long("blockhash")
|
||||
@ -950,6 +951,7 @@ pub fn process_fees(
|
||||
blockhash: Option<&Hash>,
|
||||
) -> ProcessResult {
|
||||
let fees = if let Some(recent_blockhash) = blockhash {
|
||||
#[allow(deprecated)]
|
||||
let result = rpc_client.get_fee_calculator_for_blockhash_with_commitment(
|
||||
recent_blockhash,
|
||||
config.commitment,
|
||||
@ -966,6 +968,7 @@ pub fn process_fees(
|
||||
CliFees::none()
|
||||
}
|
||||
} else {
|
||||
#[allow(deprecated)]
|
||||
let result = rpc_client.get_fees_with_commitment(config.commitment)?;
|
||||
CliFees::some(
|
||||
result.context.slot,
|
||||
@ -1374,7 +1377,7 @@ pub fn process_ping(
|
||||
let mut confirmed_count = 0;
|
||||
let mut confirmation_time: VecDeque<u64> = VecDeque::with_capacity(1024);
|
||||
|
||||
let (mut blockhash, mut fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let mut blockhash = rpc_client.get_latest_blockhash()?;
|
||||
let mut blockhash_transaction_count = 0;
|
||||
let mut blockhash_acquired = Instant::now();
|
||||
if let Some(fixed_blockhash) = fixed_blockhash {
|
||||
@ -1393,9 +1396,8 @@ pub fn process_ping(
|
||||
let now = Instant::now();
|
||||
if fixed_blockhash.is_none() && now.duration_since(blockhash_acquired).as_secs() > 60 {
|
||||
// Fetch a new blockhash every minute
|
||||
let (new_blockhash, new_fee_calculator) = rpc_client.get_new_blockhash(&blockhash)?;
|
||||
let new_blockhash = rpc_client.get_new_latest_blockhash(&blockhash)?;
|
||||
blockhash = new_blockhash;
|
||||
fee_calculator = new_fee_calculator;
|
||||
blockhash_transaction_count = 0;
|
||||
blockhash_acquired = Instant::now();
|
||||
}
|
||||
@ -1414,7 +1416,7 @@ pub fn process_ping(
|
||||
rpc_client,
|
||||
false,
|
||||
SpendAmount::Some(lamports),
|
||||
&fee_calculator,
|
||||
&blockhash,
|
||||
&config.signers[0].pubkey(),
|
||||
build_message,
|
||||
config.commitment,
|
||||
|
@ -409,12 +409,12 @@ fn process_activate(
|
||||
|
||||
let rent = rpc_client.get_minimum_balance_for_rent_exemption(Feature::size_of())?;
|
||||
|
||||
let (blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let blockhash = rpc_client.get_latest_blockhash()?;
|
||||
let (message, _) = resolve_spend_tx_and_check_account_balance(
|
||||
rpc_client,
|
||||
false,
|
||||
SpendAmount::Some(rent),
|
||||
&fee_calculator,
|
||||
&blockhash,
|
||||
&config.signers[0].pubkey(),
|
||||
|lamports| {
|
||||
Message::new(
|
||||
|
@ -351,7 +351,7 @@ pub fn process_authorize_nonce_account(
|
||||
memo: Option<&String>,
|
||||
new_authority: &Pubkey,
|
||||
) -> ProcessResult {
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let latest_blockhash = rpc_client.get_latest_blockhash()?;
|
||||
|
||||
let nonce_authority = config.signers[nonce_authority];
|
||||
let ixs = vec![authorize_nonce_account(
|
||||
@ -362,12 +362,12 @@ pub fn process_authorize_nonce_account(
|
||||
.with_memo(memo);
|
||||
let message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
tx.try_sign(&config.signers, latest_blockhash)?;
|
||||
|
||||
check_account_for_fee_with_commitment(
|
||||
rpc_client,
|
||||
&config.signers[0].pubkey(),
|
||||
&fee_calculator,
|
||||
&latest_blockhash,
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
@ -434,13 +434,13 @@ pub fn process_create_nonce_account(
|
||||
Message::new(&ixs, Some(&config.signers[0].pubkey()))
|
||||
};
|
||||
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let latest_blockhash = rpc_client.get_latest_blockhash()?;
|
||||
|
||||
let (message, lamports) = resolve_spend_tx_and_check_account_balance(
|
||||
rpc_client,
|
||||
false,
|
||||
amount,
|
||||
&fee_calculator,
|
||||
&latest_blockhash,
|
||||
&config.signers[0].pubkey(),
|
||||
build_message,
|
||||
config.commitment,
|
||||
@ -468,7 +468,7 @@ pub fn process_create_nonce_account(
|
||||
}
|
||||
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
tx.try_sign(&config.signers, latest_blockhash)?;
|
||||
let merge_errors =
|
||||
get_feature_is_active(rpc_client, &merge_nonce_error_into_system_error::id())?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
@ -544,14 +544,14 @@ pub fn process_new_nonce(
|
||||
&nonce_authority.pubkey(),
|
||||
)]
|
||||
.with_memo(memo);
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let latest_blockhash = rpc_client.get_latest_blockhash()?;
|
||||
let message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
tx.try_sign(&config.signers, latest_blockhash)?;
|
||||
check_account_for_fee_with_commitment(
|
||||
rpc_client,
|
||||
&config.signers[0].pubkey(),
|
||||
&fee_calculator,
|
||||
&latest_blockhash,
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
@ -611,7 +611,7 @@ pub fn process_withdraw_from_nonce_account(
|
||||
destination_account_pubkey: &Pubkey,
|
||||
lamports: u64,
|
||||
) -> ProcessResult {
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let latest_blockhash = rpc_client.get_latest_blockhash()?;
|
||||
|
||||
let nonce_authority = config.signers[nonce_authority];
|
||||
let ixs = vec![withdraw_nonce_account(
|
||||
@ -623,11 +623,11 @@ pub fn process_withdraw_from_nonce_account(
|
||||
.with_memo(memo);
|
||||
let message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
tx.try_sign(&config.signers, latest_blockhash)?;
|
||||
check_account_for_fee_with_commitment(
|
||||
rpc_client,
|
||||
&config.signers[0].pubkey(),
|
||||
&fee_calculator,
|
||||
&latest_blockhash,
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
|
@ -23,7 +23,6 @@ use solana_client::{
|
||||
rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
|
||||
rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType},
|
||||
rpc_request::MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS,
|
||||
rpc_response::Fees,
|
||||
tpu_client::{TpuClient, TpuClientConfig},
|
||||
};
|
||||
use solana_rbpf::{
|
||||
@ -1067,7 +1066,7 @@ fn process_set_authority(
|
||||
};
|
||||
|
||||
trace!("Set a new authority");
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash()?;
|
||||
let blockhash = rpc_client.get_latest_blockhash()?;
|
||||
|
||||
let mut tx = if let Some(ref pubkey) = program_pubkey {
|
||||
Transaction::new_unsigned(Message::new(
|
||||
@ -1343,7 +1342,7 @@ fn close(
|
||||
recipient_pubkey: &Pubkey,
|
||||
authority_signer: &dyn Signer,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash()?;
|
||||
let blockhash = rpc_client.get_latest_blockhash()?;
|
||||
|
||||
let mut tx = Transaction::new_unsigned(Message::new(
|
||||
&[bpf_loader_upgradeable::close(
|
||||
@ -1891,14 +1890,14 @@ fn check_payer(
|
||||
balance_needed: u64,
|
||||
messages: &[&Message],
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (_, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let blockhash = rpc_client.get_latest_blockhash()?;
|
||||
|
||||
// Does the payer have enough?
|
||||
check_account_for_spend_multiple_fees_with_commitment(
|
||||
rpc_client,
|
||||
&config.signers[0].pubkey(),
|
||||
balance_needed,
|
||||
&fee_calculator,
|
||||
&blockhash,
|
||||
messages,
|
||||
config.commitment,
|
||||
)?;
|
||||
@ -1920,7 +1919,7 @@ fn send_deploy_messages(
|
||||
if let Some(message) = initial_message {
|
||||
if let Some(initial_signer) = initial_signer {
|
||||
trace!("Preparing the required accounts");
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash()?;
|
||||
let blockhash = rpc_client.get_latest_blockhash()?;
|
||||
|
||||
let mut initial_transaction = Transaction::new_unsigned(message.clone());
|
||||
// Most of the initial_transaction combinations require both the fee-payer and new program
|
||||
@ -1943,13 +1942,8 @@ fn send_deploy_messages(
|
||||
if let Some(write_messages) = write_messages {
|
||||
if let Some(write_signer) = write_signer {
|
||||
trace!("Writing program data");
|
||||
let Fees {
|
||||
blockhash,
|
||||
last_valid_block_height,
|
||||
..
|
||||
} = rpc_client
|
||||
.get_fees_with_commitment(config.commitment)?
|
||||
.value;
|
||||
let (blockhash, last_valid_block_height) =
|
||||
rpc_client.get_latest_blockhash_with_commitment(config.commitment)?;
|
||||
let mut write_transactions = vec![];
|
||||
for message in write_messages.iter() {
|
||||
let mut tx = Transaction::new_unsigned(message.clone());
|
||||
@ -1972,7 +1966,7 @@ fn send_deploy_messages(
|
||||
if let Some(message) = final_message {
|
||||
if let Some(final_signers) = final_signers {
|
||||
trace!("Deploying program");
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash()?;
|
||||
let blockhash = rpc_client.get_latest_blockhash()?;
|
||||
|
||||
let mut final_tx = Transaction::new_unsigned(message.clone());
|
||||
let mut signers = final_signers.to_vec();
|
||||
@ -2141,11 +2135,8 @@ fn send_and_confirm_transactions_with_spinner<T: Signers>(
|
||||
send_retries -= 1;
|
||||
|
||||
// Re-sign any failed transactions with a new blockhash and retry
|
||||
let Fees {
|
||||
blockhash,
|
||||
last_valid_block_height: new_last_valid_block_height,
|
||||
..
|
||||
} = rpc_client.get_fees_with_commitment(commitment)?.value;
|
||||
let (blockhash, new_last_valid_block_height) =
|
||||
rpc_client.get_latest_blockhash_with_commitment(commitment)?;
|
||||
last_valid_block_height = new_last_valid_block_height;
|
||||
transactions = vec![];
|
||||
for (_, mut transaction) in pending_transactions.into_iter() {
|
||||
|
@ -1,12 +1,12 @@
|
||||
use crate::{
|
||||
checks::{calculate_fee, check_account_for_balance_with_commitment},
|
||||
checks::{check_account_for_balance_with_commitment, get_fee_for_message},
|
||||
cli::CliError,
|
||||
};
|
||||
use clap::ArgMatches;
|
||||
use solana_clap_utils::{input_parsers::lamports_of_sol, offline::SIGN_ONLY_ARG};
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_sdk::{
|
||||
commitment_config::CommitmentConfig, fee_calculator::FeeCalculator, message::Message,
|
||||
commitment_config::CommitmentConfig, hash::Hash, message::Message,
|
||||
native_token::lamports_to_sol, pubkey::Pubkey,
|
||||
};
|
||||
|
||||
@ -47,7 +47,7 @@ pub fn resolve_spend_tx_and_check_account_balance<F>(
|
||||
rpc_client: &RpcClient,
|
||||
sign_only: bool,
|
||||
amount: SpendAmount,
|
||||
fee_calculator: &FeeCalculator,
|
||||
blockhash: &Hash,
|
||||
from_pubkey: &Pubkey,
|
||||
build_message: F,
|
||||
commitment: CommitmentConfig,
|
||||
@ -59,7 +59,7 @@ where
|
||||
rpc_client,
|
||||
sign_only,
|
||||
amount,
|
||||
fee_calculator,
|
||||
blockhash,
|
||||
from_pubkey,
|
||||
from_pubkey,
|
||||
build_message,
|
||||
@ -71,7 +71,7 @@ pub fn resolve_spend_tx_and_check_account_balances<F>(
|
||||
rpc_client: &RpcClient,
|
||||
sign_only: bool,
|
||||
amount: SpendAmount,
|
||||
fee_calculator: &FeeCalculator,
|
||||
blockhash: &Hash,
|
||||
from_pubkey: &Pubkey,
|
||||
fee_pubkey: &Pubkey,
|
||||
build_message: F,
|
||||
@ -82,26 +82,28 @@ where
|
||||
{
|
||||
if sign_only {
|
||||
let (message, SpendAndFee { spend, fee: _ }) = resolve_spend_message(
|
||||
rpc_client,
|
||||
amount,
|
||||
fee_calculator,
|
||||
None,
|
||||
0,
|
||||
from_pubkey,
|
||||
fee_pubkey,
|
||||
build_message,
|
||||
);
|
||||
)?;
|
||||
Ok((message, spend))
|
||||
} else {
|
||||
let from_balance = rpc_client
|
||||
.get_balance_with_commitment(from_pubkey, commitment)?
|
||||
.value;
|
||||
let (message, SpendAndFee { spend, fee }) = resolve_spend_message(
|
||||
rpc_client,
|
||||
amount,
|
||||
fee_calculator,
|
||||
Some(blockhash),
|
||||
from_balance,
|
||||
from_pubkey,
|
||||
fee_pubkey,
|
||||
build_message,
|
||||
);
|
||||
)?;
|
||||
if from_pubkey == fee_pubkey {
|
||||
if from_balance == 0 || from_balance < spend + fee {
|
||||
return Err(CliError::InsufficientFundsForSpendAndFee(
|
||||
@ -130,43 +132,46 @@ where
|
||||
}
|
||||
|
||||
fn resolve_spend_message<F>(
|
||||
rpc_client: &RpcClient,
|
||||
amount: SpendAmount,
|
||||
fee_calculator: &FeeCalculator,
|
||||
blockhash: Option<&Hash>,
|
||||
from_balance: u64,
|
||||
from_pubkey: &Pubkey,
|
||||
fee_pubkey: &Pubkey,
|
||||
build_message: F,
|
||||
) -> (Message, SpendAndFee)
|
||||
) -> Result<(Message, SpendAndFee), CliError>
|
||||
where
|
||||
F: Fn(u64) -> Message,
|
||||
{
|
||||
match amount {
|
||||
SpendAmount::Some(lamports) => {
|
||||
let message = build_message(lamports);
|
||||
let fee = calculate_fee(fee_calculator, &[&message]);
|
||||
(
|
||||
message,
|
||||
SpendAndFee {
|
||||
spend: lamports,
|
||||
fee,
|
||||
},
|
||||
)
|
||||
}
|
||||
SpendAmount::All => {
|
||||
let fee = match blockhash {
|
||||
Some(blockhash) => {
|
||||
let dummy_message = build_message(0);
|
||||
let fee = calculate_fee(fee_calculator, &[&dummy_message]);
|
||||
get_fee_for_message(rpc_client, blockhash, &[&dummy_message])?
|
||||
}
|
||||
None => 0, // Offline, cannot calulate fee
|
||||
};
|
||||
|
||||
match amount {
|
||||
SpendAmount::Some(lamports) => Ok((
|
||||
build_message(lamports),
|
||||
SpendAndFee {
|
||||
spend: lamports,
|
||||
fee,
|
||||
},
|
||||
)),
|
||||
SpendAmount::All => {
|
||||
let lamports = if from_pubkey == fee_pubkey {
|
||||
from_balance.saturating_sub(fee)
|
||||
} else {
|
||||
from_balance
|
||||
};
|
||||
(
|
||||
Ok((
|
||||
build_message(lamports),
|
||||
SpendAndFee {
|
||||
spend: lamports,
|
||||
fee,
|
||||
},
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1272,14 +1272,13 @@ pub fn process_create_stake_account(
|
||||
}
|
||||
};
|
||||
|
||||
let (recent_blockhash, fee_calculator) =
|
||||
blockhash_query.get_blockhash_and_fee_calculator(rpc_client, config.commitment)?;
|
||||
let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
|
||||
|
||||
let (message, lamports) = resolve_spend_tx_and_check_account_balances(
|
||||
rpc_client,
|
||||
sign_only,
|
||||
amount,
|
||||
&fee_calculator,
|
||||
&recent_blockhash,
|
||||
&from.pubkey(),
|
||||
&fee_payer.pubkey(),
|
||||
build_message,
|
||||
@ -1387,8 +1386,7 @@ pub fn process_stake_authorize(
|
||||
}
|
||||
ixs = ixs.with_memo(memo);
|
||||
|
||||
let (recent_blockhash, fee_calculator) =
|
||||
blockhash_query.get_blockhash_and_fee_calculator(rpc_client, config.commitment)?;
|
||||
let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
|
||||
|
||||
let nonce_authority = config.signers[nonce_authority];
|
||||
let fee_payer = config.signers[fee_payer];
|
||||
@ -1427,7 +1425,7 @@ pub fn process_stake_authorize(
|
||||
check_account_for_fee_with_commitment(
|
||||
rpc_client,
|
||||
&tx.message.account_keys[0],
|
||||
&fee_calculator,
|
||||
&recent_blockhash,
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
@ -1455,8 +1453,7 @@ pub fn process_deactivate_stake_account(
|
||||
seed: Option<&String>,
|
||||
fee_payer: SignerIndex,
|
||||
) -> ProcessResult {
|
||||
let (recent_blockhash, fee_calculator) =
|
||||
blockhash_query.get_blockhash_and_fee_calculator(rpc_client, config.commitment)?;
|
||||
let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
|
||||
let stake_authority = config.signers[stake_authority];
|
||||
|
||||
let stake_account_address = if let Some(seed) = seed {
|
||||
@ -1507,7 +1504,7 @@ pub fn process_deactivate_stake_account(
|
||||
check_account_for_fee_with_commitment(
|
||||
rpc_client,
|
||||
&tx.message.account_keys[0],
|
||||
&fee_calculator,
|
||||
&recent_blockhash,
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
@ -1543,8 +1540,7 @@ pub fn process_withdraw_stake(
|
||||
*stake_account_pubkey
|
||||
};
|
||||
|
||||
let (recent_blockhash, fee_calculator) =
|
||||
blockhash_query.get_blockhash_and_fee_calculator(rpc_client, config.commitment)?;
|
||||
let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
|
||||
|
||||
let fee_payer = config.signers[fee_payer];
|
||||
let nonce_authority = config.signers[nonce_authority];
|
||||
@ -1575,7 +1571,7 @@ pub fn process_withdraw_stake(
|
||||
rpc_client,
|
||||
sign_only,
|
||||
amount,
|
||||
&fee_calculator,
|
||||
&recent_blockhash,
|
||||
&stake_account_address,
|
||||
&fee_payer.pubkey(),
|
||||
build_message,
|
||||
@ -1606,7 +1602,7 @@ pub fn process_withdraw_stake(
|
||||
check_account_for_fee_with_commitment(
|
||||
rpc_client,
|
||||
&tx.message.account_keys[0],
|
||||
&fee_calculator,
|
||||
&recent_blockhash,
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
@ -1692,8 +1688,7 @@ pub fn process_split_stake(
|
||||
}
|
||||
}
|
||||
|
||||
let (recent_blockhash, fee_calculator) =
|
||||
blockhash_query.get_blockhash_and_fee_calculator(rpc_client, config.commitment)?;
|
||||
let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
|
||||
|
||||
let ixs = if let Some(seed) = split_stake_account_seed {
|
||||
stake_instruction::split_with_seed(
|
||||
@ -1751,7 +1746,7 @@ pub fn process_split_stake(
|
||||
check_account_for_fee_with_commitment(
|
||||
rpc_client,
|
||||
&tx.message.account_keys[0],
|
||||
&fee_calculator,
|
||||
&recent_blockhash,
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
@ -1812,8 +1807,7 @@ pub fn process_merge_stake(
|
||||
}
|
||||
}
|
||||
|
||||
let (recent_blockhash, fee_calculator) =
|
||||
blockhash_query.get_blockhash_and_fee_calculator(rpc_client, config.commitment)?;
|
||||
let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
|
||||
|
||||
let ixs = stake_instruction::merge(
|
||||
stake_account_pubkey,
|
||||
@ -1858,7 +1852,7 @@ pub fn process_merge_stake(
|
||||
check_account_for_fee_with_commitment(
|
||||
rpc_client,
|
||||
&tx.message.account_keys[0],
|
||||
&fee_calculator,
|
||||
&recent_blockhash,
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
@ -1887,8 +1881,7 @@ pub fn process_stake_set_lockup(
|
||||
memo: Option<&String>,
|
||||
fee_payer: SignerIndex,
|
||||
) -> ProcessResult {
|
||||
let (recent_blockhash, fee_calculator) =
|
||||
blockhash_query.get_blockhash_and_fee_calculator(rpc_client, config.commitment)?;
|
||||
let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
|
||||
let custodian = config.signers[custodian];
|
||||
|
||||
let ixs = vec![if new_custodian_signer.is_some() {
|
||||
@ -1934,7 +1927,7 @@ pub fn process_stake_set_lockup(
|
||||
check_account_for_fee_with_commitment(
|
||||
rpc_client,
|
||||
&tx.message.account_keys[0],
|
||||
&fee_calculator,
|
||||
&recent_blockhash,
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
@ -2291,8 +2284,7 @@ pub fn process_delegate_stake(
|
||||
}
|
||||
}
|
||||
|
||||
let (recent_blockhash, fee_calculator) =
|
||||
blockhash_query.get_blockhash_and_fee_calculator(rpc_client, config.commitment)?;
|
||||
let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
|
||||
|
||||
let ixs = vec![stake_instruction::delegate_stake(
|
||||
stake_account_pubkey,
|
||||
@ -2337,7 +2329,7 @@ pub fn process_delegate_stake(
|
||||
check_account_for_fee_with_commitment(
|
||||
rpc_client,
|
||||
&tx.message.account_keys[0],
|
||||
&fee_calculator,
|
||||
&recent_blockhash,
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
|
@ -345,18 +345,18 @@ pub fn process_set_validator_info(
|
||||
};
|
||||
|
||||
// Submit transaction
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let latest_blockhash = rpc_client.get_latest_blockhash()?;
|
||||
let (message, _) = resolve_spend_tx_and_check_account_balance(
|
||||
rpc_client,
|
||||
false,
|
||||
SpendAmount::Some(lamports),
|
||||
&fee_calculator,
|
||||
&latest_blockhash,
|
||||
&config.signers[0].pubkey(),
|
||||
build_message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&signers, recent_blockhash)?;
|
||||
tx.try_sign(&signers, latest_blockhash)?;
|
||||
let signature_str = rpc_client.send_and_confirm_transaction_with_spinner(&tx)?;
|
||||
|
||||
println!("Success! Validator info published at: {:?}", info_pubkey);
|
||||
|
@ -605,19 +605,19 @@ pub fn process_create_vote_account(
|
||||
}
|
||||
}
|
||||
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let latest_blockhash = rpc_client.get_latest_blockhash()?;
|
||||
|
||||
let (message, _) = resolve_spend_tx_and_check_account_balance(
|
||||
rpc_client,
|
||||
false,
|
||||
amount,
|
||||
&fee_calculator,
|
||||
&latest_blockhash,
|
||||
&config.signers[0].pubkey(),
|
||||
build_message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
tx.try_sign(&config.signers, latest_blockhash)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, config)
|
||||
}
|
||||
@ -639,7 +639,7 @@ pub fn process_vote_authorize(
|
||||
(&authorized.pubkey(), "authorized_account".to_string()),
|
||||
(new_authorized_pubkey, "new_authorized_pubkey".to_string()),
|
||||
)?;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let latest_blockhash = rpc_client.get_latest_blockhash()?;
|
||||
let vote_ix = if new_authorized_signer.is_some() {
|
||||
vote_instruction::authorize_checked(
|
||||
vote_account_pubkey, // vote account to update
|
||||
@ -659,11 +659,11 @@ pub fn process_vote_authorize(
|
||||
|
||||
let message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
tx.try_sign(&config.signers, latest_blockhash)?;
|
||||
check_account_for_fee_with_commitment(
|
||||
rpc_client,
|
||||
&config.signers[0].pubkey(),
|
||||
&fee_calculator,
|
||||
&latest_blockhash,
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
@ -686,7 +686,7 @@ pub fn process_vote_update_validator(
|
||||
(vote_account_pubkey, "vote_account_pubkey".to_string()),
|
||||
(&new_identity_pubkey, "new_identity_account".to_string()),
|
||||
)?;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let latest_blockhash = rpc_client.get_latest_blockhash()?;
|
||||
let ixs = vec![vote_instruction::update_validator_identity(
|
||||
vote_account_pubkey,
|
||||
&authorized_withdrawer.pubkey(),
|
||||
@ -696,11 +696,11 @@ pub fn process_vote_update_validator(
|
||||
|
||||
let message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
tx.try_sign(&config.signers, latest_blockhash)?;
|
||||
check_account_for_fee_with_commitment(
|
||||
rpc_client,
|
||||
&config.signers[0].pubkey(),
|
||||
&fee_calculator,
|
||||
&latest_blockhash,
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
@ -717,7 +717,7 @@ pub fn process_vote_update_commission(
|
||||
memo: Option<&String>,
|
||||
) -> ProcessResult {
|
||||
let authorized_withdrawer = config.signers[withdraw_authority];
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let latest_blockhash = rpc_client.get_latest_blockhash()?;
|
||||
let ixs = vec![vote_instruction::update_commission(
|
||||
vote_account_pubkey,
|
||||
&authorized_withdrawer.pubkey(),
|
||||
@ -727,11 +727,11 @@ pub fn process_vote_update_commission(
|
||||
|
||||
let message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
tx.try_sign(&config.signers, latest_blockhash)?;
|
||||
check_account_for_fee_with_commitment(
|
||||
rpc_client,
|
||||
&config.signers[0].pubkey(),
|
||||
&fee_calculator,
|
||||
&latest_blockhash,
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
@ -836,7 +836,7 @@ pub fn process_withdraw_from_vote_account(
|
||||
destination_account_pubkey: &Pubkey,
|
||||
memo: Option<&String>,
|
||||
) -> ProcessResult {
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let latest_blockhash = rpc_client.get_latest_blockhash()?;
|
||||
let withdraw_authority = config.signers[withdraw_authority];
|
||||
|
||||
let current_balance = rpc_client.get_balance(vote_account_pubkey)?;
|
||||
@ -865,11 +865,11 @@ pub fn process_withdraw_from_vote_account(
|
||||
|
||||
let message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
|
||||
let mut transaction = Transaction::new_unsigned(message);
|
||||
transaction.try_sign(&config.signers, recent_blockhash)?;
|
||||
transaction.try_sign(&config.signers, latest_blockhash)?;
|
||||
check_account_for_fee_with_commitment(
|
||||
rpc_client,
|
||||
&config.signers[0].pubkey(),
|
||||
&fee_calculator,
|
||||
&latest_blockhash,
|
||||
&transaction.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
|
@ -645,8 +645,7 @@ pub fn process_transfer(
|
||||
let from = config.signers[from];
|
||||
let mut from_pubkey = from.pubkey();
|
||||
|
||||
let (recent_blockhash, fee_calculator) =
|
||||
blockhash_query.get_blockhash_and_fee_calculator(rpc_client, config.commitment)?;
|
||||
let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
|
||||
|
||||
if !sign_only && !allow_unfunded_recipient {
|
||||
let recipient_balance = rpc_client
|
||||
@ -706,7 +705,7 @@ pub fn process_transfer(
|
||||
rpc_client,
|
||||
sign_only,
|
||||
amount,
|
||||
&fee_calculator,
|
||||
&recent_blockhash,
|
||||
&from_pubkey,
|
||||
&fee_payer.pubkey(),
|
||||
build_message,
|
||||
|
@ -356,7 +356,7 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
process_command(&config_validator).unwrap();
|
||||
|
||||
// Delegate stake offline
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
|
||||
let blockhash = rpc_client.get_latest_blockhash().unwrap();
|
||||
config_offline.command = CliCommand::DelegateStake {
|
||||
stake_account_pubkey: stake_keypair.pubkey(),
|
||||
vote_account_pubkey: test_validator.vote_account_address(),
|
||||
@ -394,7 +394,7 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
process_command(&config_payer).unwrap();
|
||||
|
||||
// Deactivate stake offline
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
|
||||
let blockhash = rpc_client.get_latest_blockhash().unwrap();
|
||||
config_offline.command = CliCommand::DeactivateStake {
|
||||
stake_account_pubkey: stake_keypair.pubkey(),
|
||||
stake_authority: 0,
|
||||
@ -714,7 +714,7 @@ fn test_stake_authorize() {
|
||||
// Offline assignment of new nonced stake authority
|
||||
let nonced_authority = Keypair::new();
|
||||
let nonced_authority_pubkey = nonced_authority.pubkey();
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
|
||||
let blockhash = rpc_client.get_latest_blockhash().unwrap();
|
||||
config_offline.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![StakeAuthorizationIndexed {
|
||||
@ -964,7 +964,7 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
check_recent_balance(100_000 - SIG_FEE - SIG_FEE, &rpc_client, &payer_pubkey);
|
||||
|
||||
// Assign authority with offline fee payer
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
|
||||
let blockhash = rpc_client.get_latest_blockhash().unwrap();
|
||||
config_offline.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![StakeAuthorizationIndexed {
|
||||
|
@ -106,7 +106,7 @@ fn test_transfer() {
|
||||
check_recent_balance(50, &rpc_client, &offline_pubkey);
|
||||
|
||||
// Offline transfer
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
|
||||
let blockhash = rpc_client.get_latest_blockhash().unwrap();
|
||||
offline.command = CliCommand::Transfer {
|
||||
amount: SpendAmount::Some(10),
|
||||
to: recipient_pubkey,
|
||||
@ -318,7 +318,7 @@ fn test_transfer_multisession_signing() {
|
||||
|
||||
check_ready(&rpc_client);
|
||||
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
|
||||
let blockhash = rpc_client.get_latest_blockhash().unwrap();
|
||||
|
||||
// Offline fee-payer signs first
|
||||
let mut fee_payer_config = CliConfig::recent_for_tests();
|
||||
|
Reference in New Issue
Block a user