Prevent rent-paying account creation (#22292)
* Fixup typo * Add new feature * Add new TransactionError * Add framework for checking account state before and after transaction processing * Fail transactions that leave new rent-paying accounts * Only check rent-state of writable tx accounts * Review comments: combine process_result success behavior; log and metrics before feature activation * Fix tests that assume rent-exempt accounts are okay * Remove test no longer relevant * Remove native/sysvar special case * Move metrics submission to report legacy->legacy rent paying transitions as well
This commit is contained in:
parent
a49ef49f87
commit
637e366b18
|
@ -5706,6 +5706,7 @@ dependencies = [
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"dir-diff",
|
"dir-diff",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
|
"enum-iterator",
|
||||||
"flate2",
|
"flate2",
|
||||||
"fnv",
|
"fnv",
|
||||||
"index_list",
|
"index_list",
|
||||||
|
|
|
@ -53,7 +53,8 @@ Create TYPE "TransactionErrorCode" AS ENUM (
|
||||||
'AddressLookupTableNotFound',
|
'AddressLookupTableNotFound',
|
||||||
'InvalidAddressLookupTableOwner',
|
'InvalidAddressLookupTableOwner',
|
||||||
'InvalidAddressLookupTableData',
|
'InvalidAddressLookupTableData',
|
||||||
'InvalidAddressLookupTableIndex'
|
'InvalidAddressLookupTableIndex',
|
||||||
|
'InvalidRentPayingAccount'
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TYPE "TransactionError" AS (
|
CREATE TYPE "TransactionError" AS (
|
||||||
|
|
|
@ -336,6 +336,7 @@ pub enum DbTransactionErrorCode {
|
||||||
InvalidAddressLookupTableOwner,
|
InvalidAddressLookupTableOwner,
|
||||||
InvalidAddressLookupTableData,
|
InvalidAddressLookupTableData,
|
||||||
InvalidAddressLookupTableIndex,
|
InvalidAddressLookupTableIndex,
|
||||||
|
InvalidRentPayingAccount,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&TransactionError> for DbTransactionErrorCode {
|
impl From<&TransactionError> for DbTransactionErrorCode {
|
||||||
|
@ -376,6 +377,7 @@ impl From<&TransactionError> for DbTransactionErrorCode {
|
||||||
TransactionError::InvalidAddressLookupTableIndex => {
|
TransactionError::InvalidAddressLookupTableIndex => {
|
||||||
Self::InvalidAddressLookupTableIndex
|
Self::InvalidAddressLookupTableIndex
|
||||||
}
|
}
|
||||||
|
TransactionError::InvalidRentPayingAccount => Self::InvalidRentPayingAccount,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ use {
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
commitment_config::CommitmentConfig,
|
commitment_config::CommitmentConfig,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
|
native_token::sol_to_lamports,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
signature::{keypair_from_seed, Keypair, Signer},
|
signature::{keypair_from_seed, Keypair, Signer},
|
||||||
system_program,
|
system_program,
|
||||||
|
@ -73,10 +74,14 @@ fn full_battery_tests(
|
||||||
&rpc_client,
|
&rpc_client,
|
||||||
&config_payer,
|
&config_payer,
|
||||||
&config_payer.signers[0].pubkey(),
|
&config_payer.signers[0].pubkey(),
|
||||||
2000,
|
sol_to_lamports(2000.0),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
check_recent_balance(2000, &rpc_client, &config_payer.signers[0].pubkey());
|
check_recent_balance(
|
||||||
|
sol_to_lamports(2000.0),
|
||||||
|
&rpc_client,
|
||||||
|
&config_payer.signers[0].pubkey(),
|
||||||
|
);
|
||||||
|
|
||||||
let mut config_nonce = CliConfig::recent_for_tests();
|
let mut config_nonce = CliConfig::recent_for_tests();
|
||||||
config_nonce.json_rpc_url = json_rpc_url;
|
config_nonce.json_rpc_url = json_rpc_url;
|
||||||
|
@ -108,12 +113,16 @@ fn full_battery_tests(
|
||||||
seed,
|
seed,
|
||||||
nonce_authority: optional_authority,
|
nonce_authority: optional_authority,
|
||||||
memo: None,
|
memo: None,
|
||||||
amount: SpendAmount::Some(1000),
|
amount: SpendAmount::Some(sol_to_lamports(1000.0)),
|
||||||
};
|
};
|
||||||
|
|
||||||
process_command(&config_payer).unwrap();
|
process_command(&config_payer).unwrap();
|
||||||
check_recent_balance(1000, &rpc_client, &config_payer.signers[0].pubkey());
|
check_recent_balance(
|
||||||
check_recent_balance(1000, &rpc_client, &nonce_account);
|
sol_to_lamports(1000.0),
|
||||||
|
&rpc_client,
|
||||||
|
&config_payer.signers[0].pubkey(),
|
||||||
|
);
|
||||||
|
check_recent_balance(sol_to_lamports(1000.0), &rpc_client, &nonce_account);
|
||||||
|
|
||||||
// Get nonce
|
// Get nonce
|
||||||
config_payer.signers.pop();
|
config_payer.signers.pop();
|
||||||
|
@ -161,12 +170,16 @@ fn full_battery_tests(
|
||||||
nonce_authority: index,
|
nonce_authority: index,
|
||||||
memo: None,
|
memo: None,
|
||||||
destination_account_pubkey: payee_pubkey,
|
destination_account_pubkey: payee_pubkey,
|
||||||
lamports: 100,
|
lamports: sol_to_lamports(100.0),
|
||||||
};
|
};
|
||||||
process_command(&config_payer).unwrap();
|
process_command(&config_payer).unwrap();
|
||||||
check_recent_balance(1000, &rpc_client, &config_payer.signers[0].pubkey());
|
check_recent_balance(
|
||||||
check_recent_balance(900, &rpc_client, &nonce_account);
|
sol_to_lamports(1000.0),
|
||||||
check_recent_balance(100, &rpc_client, &payee_pubkey);
|
&rpc_client,
|
||||||
|
&config_payer.signers[0].pubkey(),
|
||||||
|
);
|
||||||
|
check_recent_balance(sol_to_lamports(900.0), &rpc_client, &nonce_account);
|
||||||
|
check_recent_balance(sol_to_lamports(100.0), &rpc_client, &payee_pubkey);
|
||||||
|
|
||||||
// Show nonce account
|
// Show nonce account
|
||||||
config_payer.command = CliCommand::ShowNonceAccount {
|
config_payer.command = CliCommand::ShowNonceAccount {
|
||||||
|
@ -208,12 +221,16 @@ fn full_battery_tests(
|
||||||
nonce_authority: 1,
|
nonce_authority: 1,
|
||||||
memo: None,
|
memo: None,
|
||||||
destination_account_pubkey: payee_pubkey,
|
destination_account_pubkey: payee_pubkey,
|
||||||
lamports: 100,
|
lamports: sol_to_lamports(100.0),
|
||||||
};
|
};
|
||||||
process_command(&config_payer).unwrap();
|
process_command(&config_payer).unwrap();
|
||||||
check_recent_balance(1000, &rpc_client, &config_payer.signers[0].pubkey());
|
check_recent_balance(
|
||||||
check_recent_balance(800, &rpc_client, &nonce_account);
|
sol_to_lamports(1000.0),
|
||||||
check_recent_balance(200, &rpc_client, &payee_pubkey);
|
&rpc_client,
|
||||||
|
&config_payer.signers[0].pubkey(),
|
||||||
|
);
|
||||||
|
check_recent_balance(sol_to_lamports(800.0), &rpc_client, &nonce_account);
|
||||||
|
check_recent_balance(sol_to_lamports(200.0), &rpc_client, &payee_pubkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -241,18 +258,26 @@ fn test_create_account_with_seed() {
|
||||||
&rpc_client,
|
&rpc_client,
|
||||||
&CliConfig::recent_for_tests(),
|
&CliConfig::recent_for_tests(),
|
||||||
&offline_nonce_authority_signer.pubkey(),
|
&offline_nonce_authority_signer.pubkey(),
|
||||||
42,
|
sol_to_lamports(42.0),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
request_and_confirm_airdrop(
|
request_and_confirm_airdrop(
|
||||||
&rpc_client,
|
&rpc_client,
|
||||||
&CliConfig::recent_for_tests(),
|
&CliConfig::recent_for_tests(),
|
||||||
&online_nonce_creator_signer.pubkey(),
|
&online_nonce_creator_signer.pubkey(),
|
||||||
4242,
|
sol_to_lamports(4242.0),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
check_recent_balance(42, &rpc_client, &offline_nonce_authority_signer.pubkey());
|
check_recent_balance(
|
||||||
check_recent_balance(4242, &rpc_client, &online_nonce_creator_signer.pubkey());
|
sol_to_lamports(42.0),
|
||||||
|
&rpc_client,
|
||||||
|
&offline_nonce_authority_signer.pubkey(),
|
||||||
|
);
|
||||||
|
check_recent_balance(
|
||||||
|
sol_to_lamports(4242.0),
|
||||||
|
&rpc_client,
|
||||||
|
&online_nonce_creator_signer.pubkey(),
|
||||||
|
);
|
||||||
check_recent_balance(0, &rpc_client, &to_address);
|
check_recent_balance(0, &rpc_client, &to_address);
|
||||||
|
|
||||||
check_ready(&rpc_client);
|
check_ready(&rpc_client);
|
||||||
|
@ -273,12 +298,20 @@ fn test_create_account_with_seed() {
|
||||||
seed: Some(seed),
|
seed: Some(seed),
|
||||||
nonce_authority: Some(authority_pubkey),
|
nonce_authority: Some(authority_pubkey),
|
||||||
memo: None,
|
memo: None,
|
||||||
amount: SpendAmount::Some(241),
|
amount: SpendAmount::Some(sol_to_lamports(241.0)),
|
||||||
};
|
};
|
||||||
process_command(&creator_config).unwrap();
|
process_command(&creator_config).unwrap();
|
||||||
check_recent_balance(241, &rpc_client, &nonce_address);
|
check_recent_balance(sol_to_lamports(241.0), &rpc_client, &nonce_address);
|
||||||
check_recent_balance(42, &rpc_client, &offline_nonce_authority_signer.pubkey());
|
check_recent_balance(
|
||||||
check_recent_balance(4000, &rpc_client, &online_nonce_creator_signer.pubkey());
|
sol_to_lamports(42.0),
|
||||||
|
&rpc_client,
|
||||||
|
&offline_nonce_authority_signer.pubkey(),
|
||||||
|
);
|
||||||
|
check_recent_balance(
|
||||||
|
sol_to_lamports(4000.999999999),
|
||||||
|
&rpc_client,
|
||||||
|
&online_nonce_creator_signer.pubkey(),
|
||||||
|
);
|
||||||
check_recent_balance(0, &rpc_client, &to_address);
|
check_recent_balance(0, &rpc_client, &to_address);
|
||||||
|
|
||||||
// Fetch nonce hash
|
// Fetch nonce hash
|
||||||
|
@ -299,7 +332,7 @@ fn test_create_account_with_seed() {
|
||||||
authority_config.command = CliCommand::ClusterVersion;
|
authority_config.command = CliCommand::ClusterVersion;
|
||||||
process_command(&authority_config).unwrap_err();
|
process_command(&authority_config).unwrap_err();
|
||||||
authority_config.command = CliCommand::Transfer {
|
authority_config.command = CliCommand::Transfer {
|
||||||
amount: SpendAmount::Some(10),
|
amount: SpendAmount::Some(sol_to_lamports(10.0)),
|
||||||
to: to_address,
|
to: to_address,
|
||||||
from: 0,
|
from: 0,
|
||||||
sign_only: true,
|
sign_only: true,
|
||||||
|
@ -325,7 +358,7 @@ fn test_create_account_with_seed() {
|
||||||
submit_config.json_rpc_url = test_validator.rpc_url();
|
submit_config.json_rpc_url = test_validator.rpc_url();
|
||||||
submit_config.signers = vec![&authority_presigner];
|
submit_config.signers = vec![&authority_presigner];
|
||||||
submit_config.command = CliCommand::Transfer {
|
submit_config.command = CliCommand::Transfer {
|
||||||
amount: SpendAmount::Some(10),
|
amount: SpendAmount::Some(sol_to_lamports(10.0)),
|
||||||
to: to_address,
|
to: to_address,
|
||||||
from: 0,
|
from: 0,
|
||||||
sign_only: false,
|
sign_only: false,
|
||||||
|
@ -344,8 +377,16 @@ fn test_create_account_with_seed() {
|
||||||
derived_address_program_id: None,
|
derived_address_program_id: None,
|
||||||
};
|
};
|
||||||
process_command(&submit_config).unwrap();
|
process_command(&submit_config).unwrap();
|
||||||
check_recent_balance(241, &rpc_client, &nonce_address);
|
check_recent_balance(sol_to_lamports(241.0), &rpc_client, &nonce_address);
|
||||||
check_recent_balance(31, &rpc_client, &offline_nonce_authority_signer.pubkey());
|
check_recent_balance(
|
||||||
check_recent_balance(4000, &rpc_client, &online_nonce_creator_signer.pubkey());
|
sol_to_lamports(31.999999999),
|
||||||
check_recent_balance(10, &rpc_client, &to_address);
|
&rpc_client,
|
||||||
|
&offline_nonce_authority_signer.pubkey(),
|
||||||
|
);
|
||||||
|
check_recent_balance(
|
||||||
|
sol_to_lamports(4000.999999999),
|
||||||
|
&rpc_client,
|
||||||
|
&online_nonce_creator_signer.pubkey(),
|
||||||
|
);
|
||||||
|
check_recent_balance(sol_to_lamports(10.0), &rpc_client, &to_address);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ use {
|
||||||
solana_faucet::faucet::run_local_faucet,
|
solana_faucet::faucet::run_local_faucet,
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
commitment_config::CommitmentConfig,
|
commitment_config::CommitmentConfig,
|
||||||
|
native_token::sol_to_lamports,
|
||||||
signature::{Keypair, Signer},
|
signature::{Keypair, Signer},
|
||||||
},
|
},
|
||||||
solana_streamer::socket::SocketAddrSpace,
|
solana_streamer::socket::SocketAddrSpace,
|
||||||
|
@ -22,7 +23,7 @@ fn test_cli_request_airdrop() {
|
||||||
bob_config.json_rpc_url = test_validator.rpc_url();
|
bob_config.json_rpc_url = test_validator.rpc_url();
|
||||||
bob_config.command = CliCommand::Airdrop {
|
bob_config.command = CliCommand::Airdrop {
|
||||||
pubkey: None,
|
pubkey: None,
|
||||||
lamports: 50,
|
lamports: sol_to_lamports(50.0),
|
||||||
};
|
};
|
||||||
let keypair = Keypair::new();
|
let keypair = Keypair::new();
|
||||||
bob_config.signers = vec![&keypair];
|
bob_config.signers = vec![&keypair];
|
||||||
|
@ -36,5 +37,5 @@ fn test_cli_request_airdrop() {
|
||||||
let balance = rpc_client
|
let balance = rpc_client
|
||||||
.get_balance(&bob_config.signers[0].pubkey())
|
.get_balance(&bob_config.signers[0].pubkey())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(balance, 50);
|
assert_eq!(balance, sol_to_lamports(50.0));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1572,7 +1572,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||||
config_offline.command = CliCommand::WithdrawStake {
|
config_offline.command = CliCommand::WithdrawStake {
|
||||||
stake_account_pubkey: stake_pubkey,
|
stake_account_pubkey: stake_pubkey,
|
||||||
destination_account_pubkey: recipient_pubkey,
|
destination_account_pubkey: recipient_pubkey,
|
||||||
amount: SpendAmount::Some(42),
|
amount: SpendAmount::Some(50_000),
|
||||||
withdraw_authority: 0,
|
withdraw_authority: 0,
|
||||||
custodian: None,
|
custodian: None,
|
||||||
sign_only: true,
|
sign_only: true,
|
||||||
|
@ -1591,7 +1591,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||||
config.command = CliCommand::WithdrawStake {
|
config.command = CliCommand::WithdrawStake {
|
||||||
stake_account_pubkey: stake_pubkey,
|
stake_account_pubkey: stake_pubkey,
|
||||||
destination_account_pubkey: recipient_pubkey,
|
destination_account_pubkey: recipient_pubkey,
|
||||||
amount: SpendAmount::Some(42),
|
amount: SpendAmount::Some(50_000),
|
||||||
withdraw_authority: 0,
|
withdraw_authority: 0,
|
||||||
custodian: None,
|
custodian: None,
|
||||||
sign_only: false,
|
sign_only: false,
|
||||||
|
@ -1607,7 +1607,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||||
fee_payer: 0,
|
fee_payer: 0,
|
||||||
};
|
};
|
||||||
process_command(&config).unwrap();
|
process_command(&config).unwrap();
|
||||||
check_recent_balance(42, &rpc_client, &recipient_pubkey);
|
check_recent_balance(50_000, &rpc_client, &recipient_pubkey);
|
||||||
|
|
||||||
// Fetch nonce hash
|
// Fetch nonce hash
|
||||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||||
|
|
|
@ -14,6 +14,7 @@ use {
|
||||||
solana_faucet::faucet::run_local_faucet,
|
solana_faucet::faucet::run_local_faucet,
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
commitment_config::CommitmentConfig,
|
commitment_config::CommitmentConfig,
|
||||||
|
native_token::sol_to_lamports,
|
||||||
nonce::State as NonceState,
|
nonce::State as NonceState,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
signature::{keypair_from_seed, Keypair, NullSigner, Signer},
|
signature::{keypair_from_seed, Keypair, NullSigner, Signer},
|
||||||
|
@ -49,15 +50,16 @@ fn test_transfer() {
|
||||||
let sender_pubkey = config.signers[0].pubkey();
|
let sender_pubkey = config.signers[0].pubkey();
|
||||||
let recipient_pubkey = Pubkey::new(&[1u8; 32]);
|
let recipient_pubkey = Pubkey::new(&[1u8; 32]);
|
||||||
|
|
||||||
request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, 50_000).unwrap();
|
request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, sol_to_lamports(5.0))
|
||||||
check_recent_balance(50_000, &rpc_client, &sender_pubkey);
|
.unwrap();
|
||||||
|
check_recent_balance(sol_to_lamports(5.0), &rpc_client, &sender_pubkey);
|
||||||
check_recent_balance(0, &rpc_client, &recipient_pubkey);
|
check_recent_balance(0, &rpc_client, &recipient_pubkey);
|
||||||
|
|
||||||
check_ready(&rpc_client);
|
check_ready(&rpc_client);
|
||||||
|
|
||||||
// Plain ole transfer
|
// Plain ole transfer
|
||||||
config.command = CliCommand::Transfer {
|
config.command = CliCommand::Transfer {
|
||||||
amount: SpendAmount::Some(10),
|
amount: SpendAmount::Some(sol_to_lamports(1.0)),
|
||||||
to: recipient_pubkey,
|
to: recipient_pubkey,
|
||||||
from: 0,
|
from: 0,
|
||||||
sign_only: false,
|
sign_only: false,
|
||||||
|
@ -73,12 +75,12 @@ fn test_transfer() {
|
||||||
derived_address_program_id: None,
|
derived_address_program_id: None,
|
||||||
};
|
};
|
||||||
process_command(&config).unwrap();
|
process_command(&config).unwrap();
|
||||||
check_recent_balance(49_989, &rpc_client, &sender_pubkey);
|
check_recent_balance(sol_to_lamports(4.0) - 1, &rpc_client, &sender_pubkey);
|
||||||
check_recent_balance(10, &rpc_client, &recipient_pubkey);
|
check_recent_balance(sol_to_lamports(1.0), &rpc_client, &recipient_pubkey);
|
||||||
|
|
||||||
// Plain ole transfer, failure due to InsufficientFundsForSpendAndFee
|
// Plain ole transfer, failure due to InsufficientFundsForSpendAndFee
|
||||||
config.command = CliCommand::Transfer {
|
config.command = CliCommand::Transfer {
|
||||||
amount: SpendAmount::Some(49_989),
|
amount: SpendAmount::Some(sol_to_lamports(4.0)),
|
||||||
to: recipient_pubkey,
|
to: recipient_pubkey,
|
||||||
from: 0,
|
from: 0,
|
||||||
sign_only: false,
|
sign_only: false,
|
||||||
|
@ -94,8 +96,8 @@ fn test_transfer() {
|
||||||
derived_address_program_id: None,
|
derived_address_program_id: None,
|
||||||
};
|
};
|
||||||
assert!(process_command(&config).is_err());
|
assert!(process_command(&config).is_err());
|
||||||
check_recent_balance(49_989, &rpc_client, &sender_pubkey);
|
check_recent_balance(sol_to_lamports(4.0) - 1, &rpc_client, &sender_pubkey);
|
||||||
check_recent_balance(10, &rpc_client, &recipient_pubkey);
|
check_recent_balance(sol_to_lamports(1.0), &rpc_client, &recipient_pubkey);
|
||||||
|
|
||||||
let mut offline = CliConfig::recent_for_tests();
|
let mut offline = CliConfig::recent_for_tests();
|
||||||
offline.json_rpc_url = String::default();
|
offline.json_rpc_url = String::default();
|
||||||
|
@ -105,13 +107,14 @@ fn test_transfer() {
|
||||||
process_command(&offline).unwrap_err();
|
process_command(&offline).unwrap_err();
|
||||||
|
|
||||||
let offline_pubkey = offline.signers[0].pubkey();
|
let offline_pubkey = offline.signers[0].pubkey();
|
||||||
request_and_confirm_airdrop(&rpc_client, &offline, &offline_pubkey, 50).unwrap();
|
request_and_confirm_airdrop(&rpc_client, &offline, &offline_pubkey, sol_to_lamports(1.0))
|
||||||
check_recent_balance(50, &rpc_client, &offline_pubkey);
|
.unwrap();
|
||||||
|
check_recent_balance(sol_to_lamports(1.0), &rpc_client, &offline_pubkey);
|
||||||
|
|
||||||
// Offline transfer
|
// Offline transfer
|
||||||
let blockhash = rpc_client.get_latest_blockhash().unwrap();
|
let blockhash = rpc_client.get_latest_blockhash().unwrap();
|
||||||
offline.command = CliCommand::Transfer {
|
offline.command = CliCommand::Transfer {
|
||||||
amount: SpendAmount::Some(10),
|
amount: SpendAmount::Some(sol_to_lamports(0.5)),
|
||||||
to: recipient_pubkey,
|
to: recipient_pubkey,
|
||||||
from: 0,
|
from: 0,
|
||||||
sign_only: true,
|
sign_only: true,
|
||||||
|
@ -133,7 +136,7 @@ fn test_transfer() {
|
||||||
let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
|
let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
|
||||||
config.signers = vec![&offline_presigner];
|
config.signers = vec![&offline_presigner];
|
||||||
config.command = CliCommand::Transfer {
|
config.command = CliCommand::Transfer {
|
||||||
amount: SpendAmount::Some(10),
|
amount: SpendAmount::Some(sol_to_lamports(0.5)),
|
||||||
to: recipient_pubkey,
|
to: recipient_pubkey,
|
||||||
from: 0,
|
from: 0,
|
||||||
sign_only: false,
|
sign_only: false,
|
||||||
|
@ -149,8 +152,8 @@ fn test_transfer() {
|
||||||
derived_address_program_id: None,
|
derived_address_program_id: None,
|
||||||
};
|
};
|
||||||
process_command(&config).unwrap();
|
process_command(&config).unwrap();
|
||||||
check_recent_balance(39, &rpc_client, &offline_pubkey);
|
check_recent_balance(sol_to_lamports(0.5) - 1, &rpc_client, &offline_pubkey);
|
||||||
check_recent_balance(20, &rpc_client, &recipient_pubkey);
|
check_recent_balance(sol_to_lamports(1.5), &rpc_client, &recipient_pubkey);
|
||||||
|
|
||||||
// Create nonce account
|
// Create nonce account
|
||||||
let nonce_account = keypair_from_seed(&[3u8; 32]).unwrap();
|
let nonce_account = keypair_from_seed(&[3u8; 32]).unwrap();
|
||||||
|
@ -166,7 +169,11 @@ fn test_transfer() {
|
||||||
amount: SpendAmount::Some(minimum_nonce_balance),
|
amount: SpendAmount::Some(minimum_nonce_balance),
|
||||||
};
|
};
|
||||||
process_command(&config).unwrap();
|
process_command(&config).unwrap();
|
||||||
check_recent_balance(49_987 - minimum_nonce_balance, &rpc_client, &sender_pubkey);
|
check_recent_balance(
|
||||||
|
sol_to_lamports(4.0) - 3 - minimum_nonce_balance,
|
||||||
|
&rpc_client,
|
||||||
|
&sender_pubkey,
|
||||||
|
);
|
||||||
|
|
||||||
// Fetch nonce hash
|
// Fetch nonce hash
|
||||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||||
|
@ -181,7 +188,7 @@ fn test_transfer() {
|
||||||
// Nonced transfer
|
// Nonced transfer
|
||||||
config.signers = vec![&default_signer];
|
config.signers = vec![&default_signer];
|
||||||
config.command = CliCommand::Transfer {
|
config.command = CliCommand::Transfer {
|
||||||
amount: SpendAmount::Some(10),
|
amount: SpendAmount::Some(sol_to_lamports(1.0)),
|
||||||
to: recipient_pubkey,
|
to: recipient_pubkey,
|
||||||
from: 0,
|
from: 0,
|
||||||
sign_only: false,
|
sign_only: false,
|
||||||
|
@ -200,8 +207,12 @@ fn test_transfer() {
|
||||||
derived_address_program_id: None,
|
derived_address_program_id: None,
|
||||||
};
|
};
|
||||||
process_command(&config).unwrap();
|
process_command(&config).unwrap();
|
||||||
check_recent_balance(49_976 - minimum_nonce_balance, &rpc_client, &sender_pubkey);
|
check_recent_balance(
|
||||||
check_recent_balance(30, &rpc_client, &recipient_pubkey);
|
sol_to_lamports(3.0) - 4 - minimum_nonce_balance,
|
||||||
|
&rpc_client,
|
||||||
|
&sender_pubkey,
|
||||||
|
);
|
||||||
|
check_recent_balance(sol_to_lamports(2.5), &rpc_client, &recipient_pubkey);
|
||||||
let new_nonce_hash = nonce_utils::get_account_with_commitment(
|
let new_nonce_hash = nonce_utils::get_account_with_commitment(
|
||||||
&rpc_client,
|
&rpc_client,
|
||||||
&nonce_account.pubkey(),
|
&nonce_account.pubkey(),
|
||||||
|
@ -221,7 +232,11 @@ fn test_transfer() {
|
||||||
new_authority: offline_pubkey,
|
new_authority: offline_pubkey,
|
||||||
};
|
};
|
||||||
process_command(&config).unwrap();
|
process_command(&config).unwrap();
|
||||||
check_recent_balance(49_975 - minimum_nonce_balance, &rpc_client, &sender_pubkey);
|
check_recent_balance(
|
||||||
|
sol_to_lamports(3.0) - 5 - minimum_nonce_balance,
|
||||||
|
&rpc_client,
|
||||||
|
&sender_pubkey,
|
||||||
|
);
|
||||||
|
|
||||||
// Fetch nonce hash
|
// Fetch nonce hash
|
||||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||||
|
@ -236,7 +251,7 @@ fn test_transfer() {
|
||||||
// Offline, nonced transfer
|
// Offline, nonced transfer
|
||||||
offline.signers = vec![&default_offline_signer];
|
offline.signers = vec![&default_offline_signer];
|
||||||
offline.command = CliCommand::Transfer {
|
offline.command = CliCommand::Transfer {
|
||||||
amount: SpendAmount::Some(10),
|
amount: SpendAmount::Some(sol_to_lamports(0.4)),
|
||||||
to: recipient_pubkey,
|
to: recipient_pubkey,
|
||||||
from: 0,
|
from: 0,
|
||||||
sign_only: true,
|
sign_only: true,
|
||||||
|
@ -257,7 +272,7 @@ fn test_transfer() {
|
||||||
let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
|
let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
|
||||||
config.signers = vec![&offline_presigner];
|
config.signers = vec![&offline_presigner];
|
||||||
config.command = CliCommand::Transfer {
|
config.command = CliCommand::Transfer {
|
||||||
amount: SpendAmount::Some(10),
|
amount: SpendAmount::Some(sol_to_lamports(0.4)),
|
||||||
to: recipient_pubkey,
|
to: recipient_pubkey,
|
||||||
from: 0,
|
from: 0,
|
||||||
sign_only: false,
|
sign_only: false,
|
||||||
|
@ -276,8 +291,8 @@ fn test_transfer() {
|
||||||
derived_address_program_id: None,
|
derived_address_program_id: None,
|
||||||
};
|
};
|
||||||
process_command(&config).unwrap();
|
process_command(&config).unwrap();
|
||||||
check_recent_balance(28, &rpc_client, &offline_pubkey);
|
check_recent_balance(sol_to_lamports(0.1) - 2, &rpc_client, &offline_pubkey);
|
||||||
check_recent_balance(40, &rpc_client, &recipient_pubkey);
|
check_recent_balance(sol_to_lamports(2.9), &rpc_client, &recipient_pubkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -305,18 +320,26 @@ fn test_transfer_multisession_signing() {
|
||||||
&rpc_client,
|
&rpc_client,
|
||||||
&CliConfig::recent_for_tests(),
|
&CliConfig::recent_for_tests(),
|
||||||
&offline_from_signer.pubkey(),
|
&offline_from_signer.pubkey(),
|
||||||
43,
|
sol_to_lamports(43.0),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
request_and_confirm_airdrop(
|
request_and_confirm_airdrop(
|
||||||
&rpc_client,
|
&rpc_client,
|
||||||
&CliConfig::recent_for_tests(),
|
&CliConfig::recent_for_tests(),
|
||||||
&offline_fee_payer_signer.pubkey(),
|
&offline_fee_payer_signer.pubkey(),
|
||||||
3,
|
sol_to_lamports(1.0) + 3,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
check_recent_balance(43, &rpc_client, &offline_from_signer.pubkey());
|
check_recent_balance(
|
||||||
check_recent_balance(3, &rpc_client, &offline_fee_payer_signer.pubkey());
|
sol_to_lamports(43.0),
|
||||||
|
&rpc_client,
|
||||||
|
&offline_from_signer.pubkey(),
|
||||||
|
);
|
||||||
|
check_recent_balance(
|
||||||
|
sol_to_lamports(1.0) + 3,
|
||||||
|
&rpc_client,
|
||||||
|
&offline_fee_payer_signer.pubkey(),
|
||||||
|
);
|
||||||
check_recent_balance(0, &rpc_client, &to_pubkey);
|
check_recent_balance(0, &rpc_client, &to_pubkey);
|
||||||
|
|
||||||
check_ready(&rpc_client);
|
check_ready(&rpc_client);
|
||||||
|
@ -331,7 +354,7 @@ fn test_transfer_multisession_signing() {
|
||||||
fee_payer_config.command = CliCommand::ClusterVersion;
|
fee_payer_config.command = CliCommand::ClusterVersion;
|
||||||
process_command(&fee_payer_config).unwrap_err();
|
process_command(&fee_payer_config).unwrap_err();
|
||||||
fee_payer_config.command = CliCommand::Transfer {
|
fee_payer_config.command = CliCommand::Transfer {
|
||||||
amount: SpendAmount::Some(42),
|
amount: SpendAmount::Some(sol_to_lamports(42.0)),
|
||||||
to: to_pubkey,
|
to: to_pubkey,
|
||||||
from: 1,
|
from: 1,
|
||||||
sign_only: true,
|
sign_only: true,
|
||||||
|
@ -362,7 +385,7 @@ fn test_transfer_multisession_signing() {
|
||||||
from_config.command = CliCommand::ClusterVersion;
|
from_config.command = CliCommand::ClusterVersion;
|
||||||
process_command(&from_config).unwrap_err();
|
process_command(&from_config).unwrap_err();
|
||||||
from_config.command = CliCommand::Transfer {
|
from_config.command = CliCommand::Transfer {
|
||||||
amount: SpendAmount::Some(42),
|
amount: SpendAmount::Some(sol_to_lamports(42.0)),
|
||||||
to: to_pubkey,
|
to: to_pubkey,
|
||||||
from: 1,
|
from: 1,
|
||||||
sign_only: true,
|
sign_only: true,
|
||||||
|
@ -390,7 +413,7 @@ fn test_transfer_multisession_signing() {
|
||||||
config.json_rpc_url = test_validator.rpc_url();
|
config.json_rpc_url = test_validator.rpc_url();
|
||||||
config.signers = vec![&fee_payer_presigner, &from_presigner];
|
config.signers = vec![&fee_payer_presigner, &from_presigner];
|
||||||
config.command = CliCommand::Transfer {
|
config.command = CliCommand::Transfer {
|
||||||
amount: SpendAmount::Some(42),
|
amount: SpendAmount::Some(sol_to_lamports(42.0)),
|
||||||
to: to_pubkey,
|
to: to_pubkey,
|
||||||
from: 1,
|
from: 1,
|
||||||
sign_only: false,
|
sign_only: false,
|
||||||
|
@ -407,9 +430,17 @@ fn test_transfer_multisession_signing() {
|
||||||
};
|
};
|
||||||
process_command(&config).unwrap();
|
process_command(&config).unwrap();
|
||||||
|
|
||||||
check_recent_balance(1, &rpc_client, &offline_from_signer.pubkey());
|
check_recent_balance(
|
||||||
check_recent_balance(1, &rpc_client, &offline_fee_payer_signer.pubkey());
|
sol_to_lamports(1.0),
|
||||||
check_recent_balance(42, &rpc_client, &to_pubkey);
|
&rpc_client,
|
||||||
|
&offline_from_signer.pubkey(),
|
||||||
|
);
|
||||||
|
check_recent_balance(
|
||||||
|
sol_to_lamports(1.0) + 1,
|
||||||
|
&rpc_client,
|
||||||
|
&offline_fee_payer_signer.pubkey(),
|
||||||
|
);
|
||||||
|
check_recent_balance(sol_to_lamports(42.0), &rpc_client, &to_pubkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -551,17 +582,19 @@ fn test_transfer_with_seed() {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, 1).unwrap();
|
request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, sol_to_lamports(1.0))
|
||||||
request_and_confirm_airdrop(&rpc_client, &config, &derived_address, 50_000).unwrap();
|
.unwrap();
|
||||||
check_recent_balance(1, &rpc_client, &sender_pubkey);
|
request_and_confirm_airdrop(&rpc_client, &config, &derived_address, sol_to_lamports(5.0))
|
||||||
check_recent_balance(50_000, &rpc_client, &derived_address);
|
.unwrap();
|
||||||
|
check_recent_balance(sol_to_lamports(1.0), &rpc_client, &sender_pubkey);
|
||||||
|
check_recent_balance(sol_to_lamports(5.0), &rpc_client, &derived_address);
|
||||||
check_recent_balance(0, &rpc_client, &recipient_pubkey);
|
check_recent_balance(0, &rpc_client, &recipient_pubkey);
|
||||||
|
|
||||||
check_ready(&rpc_client);
|
check_ready(&rpc_client);
|
||||||
|
|
||||||
// Transfer with seed
|
// Transfer with seed
|
||||||
config.command = CliCommand::Transfer {
|
config.command = CliCommand::Transfer {
|
||||||
amount: SpendAmount::Some(50_000),
|
amount: SpendAmount::Some(sol_to_lamports(5.0)),
|
||||||
to: recipient_pubkey,
|
to: recipient_pubkey,
|
||||||
from: 0,
|
from: 0,
|
||||||
sign_only: false,
|
sign_only: false,
|
||||||
|
@ -577,7 +610,7 @@ fn test_transfer_with_seed() {
|
||||||
derived_address_program_id: Some(derived_address_program_id),
|
derived_address_program_id: Some(derived_address_program_id),
|
||||||
};
|
};
|
||||||
process_command(&config).unwrap();
|
process_command(&config).unwrap();
|
||||||
check_recent_balance(0, &rpc_client, &sender_pubkey);
|
check_recent_balance(sol_to_lamports(1.0) - 1, &rpc_client, &sender_pubkey);
|
||||||
check_recent_balance(50_000, &rpc_client, &recipient_pubkey);
|
check_recent_balance(sol_to_lamports(5.0), &rpc_client, &recipient_pubkey);
|
||||||
check_recent_balance(0, &rpc_client, &derived_address);
|
check_recent_balance(0, &rpc_client, &derived_address);
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@ fn test_vote_authorize_and_withdraw() {
|
||||||
// Transfer in some more SOL
|
// Transfer in some more SOL
|
||||||
config.signers = vec![&default_signer];
|
config.signers = vec![&default_signer];
|
||||||
config.command = CliCommand::Transfer {
|
config.command = CliCommand::Transfer {
|
||||||
amount: SpendAmount::Some(1_000),
|
amount: SpendAmount::Some(10_000),
|
||||||
to: vote_account_pubkey,
|
to: vote_account_pubkey,
|
||||||
from: 0,
|
from: 0,
|
||||||
sign_only: false,
|
sign_only: false,
|
||||||
|
@ -90,7 +90,7 @@ fn test_vote_authorize_and_withdraw() {
|
||||||
derived_address_program_id: None,
|
derived_address_program_id: None,
|
||||||
};
|
};
|
||||||
process_command(&config).unwrap();
|
process_command(&config).unwrap();
|
||||||
let expected_balance = expected_balance + 1_000;
|
let expected_balance = expected_balance + 10_000;
|
||||||
check_recent_balance(expected_balance, &rpc_client, &vote_account_pubkey);
|
check_recent_balance(expected_balance, &rpc_client, &vote_account_pubkey);
|
||||||
|
|
||||||
// Authorize vote account withdrawal to another signer
|
// Authorize vote account withdrawal to another signer
|
||||||
|
@ -169,7 +169,7 @@ fn test_vote_authorize_and_withdraw() {
|
||||||
config.command = CliCommand::WithdrawFromVoteAccount {
|
config.command = CliCommand::WithdrawFromVoteAccount {
|
||||||
vote_account_pubkey,
|
vote_account_pubkey,
|
||||||
withdraw_authority: 1,
|
withdraw_authority: 1,
|
||||||
withdraw_amount: SpendAmount::Some(100),
|
withdraw_amount: SpendAmount::Some(1_000),
|
||||||
destination_account_pubkey: destination_account,
|
destination_account_pubkey: destination_account,
|
||||||
sign_only: false,
|
sign_only: false,
|
||||||
dump_transaction_message: false,
|
dump_transaction_message: false,
|
||||||
|
@ -180,9 +180,9 @@ fn test_vote_authorize_and_withdraw() {
|
||||||
fee_payer: 0,
|
fee_payer: 0,
|
||||||
};
|
};
|
||||||
process_command(&config).unwrap();
|
process_command(&config).unwrap();
|
||||||
let expected_balance = expected_balance - 100;
|
let expected_balance = expected_balance - 1_000;
|
||||||
check_recent_balance(expected_balance, &rpc_client, &vote_account_pubkey);
|
check_recent_balance(expected_balance, &rpc_client, &vote_account_pubkey);
|
||||||
check_recent_balance(100, &rpc_client, &destination_account);
|
check_recent_balance(1_000, &rpc_client, &destination_account);
|
||||||
|
|
||||||
// Re-assign validator identity
|
// Re-assign validator identity
|
||||||
let new_identity_keypair = Keypair::new();
|
let new_identity_keypair = Keypair::new();
|
||||||
|
@ -293,7 +293,7 @@ fn test_offline_vote_authorize_and_withdraw() {
|
||||||
// Transfer in some more SOL
|
// Transfer in some more SOL
|
||||||
config_payer.signers = vec![&default_signer];
|
config_payer.signers = vec![&default_signer];
|
||||||
config_payer.command = CliCommand::Transfer {
|
config_payer.command = CliCommand::Transfer {
|
||||||
amount: SpendAmount::Some(1_000),
|
amount: SpendAmount::Some(10_000),
|
||||||
to: vote_account_pubkey,
|
to: vote_account_pubkey,
|
||||||
from: 0,
|
from: 0,
|
||||||
sign_only: false,
|
sign_only: false,
|
||||||
|
@ -309,7 +309,7 @@ fn test_offline_vote_authorize_and_withdraw() {
|
||||||
derived_address_program_id: None,
|
derived_address_program_id: None,
|
||||||
};
|
};
|
||||||
process_command(&config_payer).unwrap();
|
process_command(&config_payer).unwrap();
|
||||||
let expected_balance = expected_balance + 1_000;
|
let expected_balance = expected_balance + 10_000;
|
||||||
check_recent_balance(expected_balance, &rpc_client, &vote_account_pubkey);
|
check_recent_balance(expected_balance, &rpc_client, &vote_account_pubkey);
|
||||||
|
|
||||||
// Authorize vote account withdrawal to another signer, offline
|
// Authorize vote account withdrawal to another signer, offline
|
||||||
|
@ -367,7 +367,7 @@ fn test_offline_vote_authorize_and_withdraw() {
|
||||||
config_offline.command = CliCommand::WithdrawFromVoteAccount {
|
config_offline.command = CliCommand::WithdrawFromVoteAccount {
|
||||||
vote_account_pubkey,
|
vote_account_pubkey,
|
||||||
withdraw_authority: 1,
|
withdraw_authority: 1,
|
||||||
withdraw_amount: SpendAmount::Some(100),
|
withdraw_amount: SpendAmount::Some(1_000),
|
||||||
destination_account_pubkey: destination_account,
|
destination_account_pubkey: destination_account,
|
||||||
sign_only: true,
|
sign_only: true,
|
||||||
dump_transaction_message: false,
|
dump_transaction_message: false,
|
||||||
|
@ -387,7 +387,7 @@ fn test_offline_vote_authorize_and_withdraw() {
|
||||||
config_payer.command = CliCommand::WithdrawFromVoteAccount {
|
config_payer.command = CliCommand::WithdrawFromVoteAccount {
|
||||||
vote_account_pubkey,
|
vote_account_pubkey,
|
||||||
withdraw_authority: 1,
|
withdraw_authority: 1,
|
||||||
withdraw_amount: SpendAmount::Some(100),
|
withdraw_amount: SpendAmount::Some(1_000),
|
||||||
destination_account_pubkey: destination_account,
|
destination_account_pubkey: destination_account,
|
||||||
sign_only: false,
|
sign_only: false,
|
||||||
dump_transaction_message: false,
|
dump_transaction_message: false,
|
||||||
|
@ -398,9 +398,9 @@ fn test_offline_vote_authorize_and_withdraw() {
|
||||||
fee_payer: 0,
|
fee_payer: 0,
|
||||||
};
|
};
|
||||||
process_command(&config_payer).unwrap();
|
process_command(&config_payer).unwrap();
|
||||||
let expected_balance = expected_balance - 100;
|
let expected_balance = expected_balance - 1_000;
|
||||||
check_recent_balance(expected_balance, &rpc_client, &vote_account_pubkey);
|
check_recent_balance(expected_balance, &rpc_client, &vote_account_pubkey);
|
||||||
check_recent_balance(100, &rpc_client, &destination_account);
|
check_recent_balance(1_000, &rpc_client, &destination_account);
|
||||||
|
|
||||||
// Re-assign validator identity offline
|
// Re-assign validator identity offline
|
||||||
let blockhash = rpc_client.get_latest_blockhash().unwrap();
|
let blockhash = rpc_client.get_latest_blockhash().unwrap();
|
||||||
|
@ -483,9 +483,7 @@ fn test_offline_vote_authorize_and_withdraw() {
|
||||||
memo: None,
|
memo: None,
|
||||||
fee_payer: 0,
|
fee_payer: 0,
|
||||||
};
|
};
|
||||||
let result = process_command(&config_payer).unwrap();
|
process_command(&config_payer).unwrap();
|
||||||
println!("{:?}", result);
|
|
||||||
check_recent_balance(0, &rpc_client, &vote_account_pubkey);
|
check_recent_balance(0, &rpc_client, &vote_account_pubkey);
|
||||||
println!("what");
|
|
||||||
check_recent_balance(expected_balance, &rpc_client, &destination_account);
|
check_recent_balance(expected_balance, &rpc_client, &destination_account);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2425,30 +2425,47 @@ mod tests {
|
||||||
fn test_write_persist_transaction_status() {
|
fn test_write_persist_transaction_status() {
|
||||||
solana_logger::setup();
|
solana_logger::setup();
|
||||||
let GenesisConfigInfo {
|
let GenesisConfigInfo {
|
||||||
genesis_config,
|
mut genesis_config,
|
||||||
mint_keypair,
|
mint_keypair,
|
||||||
..
|
..
|
||||||
} = create_slow_genesis_config(10_000);
|
} = create_slow_genesis_config(solana_sdk::native_token::sol_to_lamports(1000.0));
|
||||||
|
genesis_config.rent.lamports_per_byte_year = 50;
|
||||||
|
genesis_config.rent.exemption_threshold = 2.0;
|
||||||
let bank = Arc::new(Bank::new_no_wallclock_throttle_for_tests(&genesis_config));
|
let bank = Arc::new(Bank::new_no_wallclock_throttle_for_tests(&genesis_config));
|
||||||
let pubkey = solana_sdk::pubkey::new_rand();
|
let pubkey = solana_sdk::pubkey::new_rand();
|
||||||
let pubkey1 = solana_sdk::pubkey::new_rand();
|
let pubkey1 = solana_sdk::pubkey::new_rand();
|
||||||
let keypair1 = Keypair::new();
|
let keypair1 = Keypair::new();
|
||||||
|
|
||||||
let success_tx =
|
let rent_exempt_amount = bank.get_minimum_balance_for_rent_exemption(0);
|
||||||
system_transaction::transfer(&mint_keypair, &pubkey, 1, genesis_config.hash());
|
|
||||||
|
let success_tx = system_transaction::transfer(
|
||||||
|
&mint_keypair,
|
||||||
|
&pubkey,
|
||||||
|
rent_exempt_amount,
|
||||||
|
genesis_config.hash(),
|
||||||
|
);
|
||||||
let success_signature = success_tx.signatures[0];
|
let success_signature = success_tx.signatures[0];
|
||||||
let entry_1 = next_entry(&genesis_config.hash(), 1, vec![success_tx.clone()]);
|
let entry_1 = next_entry(&genesis_config.hash(), 1, vec![success_tx.clone()]);
|
||||||
let ix_error_tx =
|
let ix_error_tx = system_transaction::transfer(
|
||||||
system_transaction::transfer(&keypair1, &pubkey1, 10, genesis_config.hash());
|
&keypair1,
|
||||||
|
&pubkey1,
|
||||||
|
2 * rent_exempt_amount,
|
||||||
|
genesis_config.hash(),
|
||||||
|
);
|
||||||
let ix_error_signature = ix_error_tx.signatures[0];
|
let ix_error_signature = ix_error_tx.signatures[0];
|
||||||
let entry_2 = next_entry(&entry_1.hash, 1, vec![ix_error_tx.clone()]);
|
let entry_2 = next_entry(&entry_1.hash, 1, vec![ix_error_tx.clone()]);
|
||||||
let fail_tx =
|
let fail_tx = system_transaction::transfer(
|
||||||
system_transaction::transfer(&mint_keypair, &pubkey1, 1, genesis_config.hash());
|
&mint_keypair,
|
||||||
|
&pubkey1,
|
||||||
|
rent_exempt_amount,
|
||||||
|
genesis_config.hash(),
|
||||||
|
);
|
||||||
let entry_3 = next_entry(&entry_2.hash, 1, vec![fail_tx.clone()]);
|
let entry_3 = next_entry(&entry_2.hash, 1, vec![fail_tx.clone()]);
|
||||||
let entries = vec![entry_1, entry_2, entry_3];
|
let entries = vec![entry_1, entry_2, entry_3];
|
||||||
|
|
||||||
let transactions = sanitize_transactions(vec![success_tx, ix_error_tx, fail_tx]);
|
let transactions = sanitize_transactions(vec![success_tx, ix_error_tx, fail_tx]);
|
||||||
bank.transfer(4, &mint_keypair, &keypair1.pubkey()).unwrap();
|
bank.transfer(rent_exempt_amount, &mint_keypair, &keypair1.pubkey())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let ledger_path = get_tmp_ledger_path!();
|
let ledger_path = get_tmp_ledger_path!();
|
||||||
{
|
{
|
||||||
|
|
|
@ -3821,10 +3821,12 @@ pub mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_write_persist_transaction_status() {
|
fn test_write_persist_transaction_status() {
|
||||||
let GenesisConfigInfo {
|
let GenesisConfigInfo {
|
||||||
genesis_config,
|
mut genesis_config,
|
||||||
mint_keypair,
|
mint_keypair,
|
||||||
..
|
..
|
||||||
} = create_genesis_config(1000);
|
} = create_genesis_config(solana_sdk::native_token::sol_to_lamports(1000.0));
|
||||||
|
genesis_config.rent.lamports_per_byte_year = 50;
|
||||||
|
genesis_config.rent.exemption_threshold = 2.0;
|
||||||
let (ledger_path, _) = create_new_tmp_ledger!(&genesis_config);
|
let (ledger_path, _) = create_new_tmp_ledger!(&genesis_config);
|
||||||
{
|
{
|
||||||
let blockstore = Blockstore::open(&ledger_path)
|
let blockstore = Blockstore::open(&ledger_path)
|
||||||
|
@ -3837,7 +3839,11 @@ pub mod tests {
|
||||||
|
|
||||||
let bank0 = Arc::new(Bank::new_for_tests(&genesis_config));
|
let bank0 = Arc::new(Bank::new_for_tests(&genesis_config));
|
||||||
bank0
|
bank0
|
||||||
.transfer(4, &mint_keypair, &keypair2.pubkey())
|
.transfer(
|
||||||
|
bank0.get_minimum_balance_for_rent_exemption(0),
|
||||||
|
&mint_keypair,
|
||||||
|
&keypair2.pubkey(),
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let bank1 = Arc::new(Bank::new_from_parent(&bank0, &Pubkey::default(), 1));
|
let bank1 = Arc::new(Bank::new_from_parent(&bank0, &Pubkey::default(), 1));
|
||||||
|
|
|
@ -72,7 +72,7 @@ async fn setup_vote(context: &mut ProgramTestContext) -> Pubkey {
|
||||||
instructions.push(system_instruction::create_account(
|
instructions.push(system_instruction::create_account(
|
||||||
&context.payer.pubkey(),
|
&context.payer.pubkey(),
|
||||||
&validator_keypair.pubkey(),
|
&validator_keypair.pubkey(),
|
||||||
42,
|
Rent::default().minimum_balance(0),
|
||||||
0,
|
0,
|
||||||
&system_program::id(),
|
&system_program::id(),
|
||||||
));
|
));
|
||||||
|
@ -182,57 +182,6 @@ async fn clock_sysvar_updated_from_warp() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn rent_collected_from_warp() {
|
|
||||||
let program_id = Pubkey::new_unique();
|
|
||||||
// Initialize and start the test network
|
|
||||||
let program_test = ProgramTest::default();
|
|
||||||
|
|
||||||
let mut context = program_test.start_with_context().await;
|
|
||||||
let account_size = 100;
|
|
||||||
let keypair = Keypair::new();
|
|
||||||
let account_lamports = Rent::default().minimum_balance(account_size) - 100; // not rent exempt
|
|
||||||
let instruction = system_instruction::create_account(
|
|
||||||
&context.payer.pubkey(),
|
|
||||||
&keypair.pubkey(),
|
|
||||||
account_lamports,
|
|
||||||
account_size as u64,
|
|
||||||
&program_id,
|
|
||||||
);
|
|
||||||
let transaction = Transaction::new_signed_with_payer(
|
|
||||||
&[instruction],
|
|
||||||
Some(&context.payer.pubkey()),
|
|
||||||
&[&context.payer, &keypair],
|
|
||||||
context.last_blockhash,
|
|
||||||
);
|
|
||||||
context
|
|
||||||
.banks_client
|
|
||||||
.process_transaction(transaction)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let account = context
|
|
||||||
.banks_client
|
|
||||||
.get_account(keypair.pubkey())
|
|
||||||
.await
|
|
||||||
.expect("account exists")
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(account.lamports, account_lamports);
|
|
||||||
|
|
||||||
// Warp forward and see that rent has been collected
|
|
||||||
// This test was a bit flaky with one warp, but two warps always works
|
|
||||||
let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch;
|
|
||||||
context.warp_to_slot(slots_per_epoch).unwrap();
|
|
||||||
context.warp_to_slot(slots_per_epoch * 2).unwrap();
|
|
||||||
|
|
||||||
let account = context
|
|
||||||
.banks_client
|
|
||||||
.get_account(keypair.pubkey())
|
|
||||||
.await
|
|
||||||
.expect("account exists")
|
|
||||||
.unwrap();
|
|
||||||
assert!(account.lamports < account_lamports);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn stake_rewards_from_warp() {
|
async fn stake_rewards_from_warp() {
|
||||||
// Initialize and start the test network
|
// Initialize and start the test network
|
||||||
|
|
|
@ -869,6 +869,26 @@ dependencies = [
|
||||||
"cfg-if 0.1.10",
|
"cfg-if 0.1.10",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enum-iterator"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6"
|
||||||
|
dependencies = [
|
||||||
|
"enum-iterator-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enum-iterator-derive"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2 1.0.24",
|
||||||
|
"quote 1.0.6",
|
||||||
|
"syn 1.0.67",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "enum-ordinalize"
|
name = "enum-ordinalize"
|
||||||
version = "3.1.10"
|
version = "3.1.10"
|
||||||
|
@ -3451,6 +3471,7 @@ dependencies = [
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"dir-diff",
|
"dir-diff",
|
||||||
|
"enum-iterator",
|
||||||
"flate2",
|
"flate2",
|
||||||
"fnv",
|
"fnv",
|
||||||
"index_list",
|
"index_list",
|
||||||
|
|
|
@ -20,6 +20,7 @@ use {
|
||||||
commitment_config::CommitmentConfig,
|
commitment_config::CommitmentConfig,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
|
rent::Rent,
|
||||||
signature::{Keypair, Signer},
|
signature::{Keypair, Signer},
|
||||||
system_transaction,
|
system_transaction,
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
|
@ -80,7 +81,12 @@ fn test_rpc_send_tx() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
info!("blockhash: {:?}", blockhash);
|
info!("blockhash: {:?}", blockhash);
|
||||||
let tx = system_transaction::transfer(&alice, &bob_pubkey, 20, blockhash);
|
let tx = system_transaction::transfer(
|
||||||
|
&alice,
|
||||||
|
&bob_pubkey,
|
||||||
|
Rent::default().minimum_balance(0),
|
||||||
|
blockhash,
|
||||||
|
);
|
||||||
let serialized_encoded_tx = bs58::encode(serialize(&tx).unwrap()).into_string();
|
let serialized_encoded_tx = bs58::encode(serialize(&tx).unwrap()).into_string();
|
||||||
|
|
||||||
let req = json_req!("sendTransaction", json!([serialized_encoded_tx]));
|
let req = json_req!("sendTransaction", json!([serialized_encoded_tx]));
|
||||||
|
@ -243,7 +249,7 @@ fn test_rpc_subscriptions() {
|
||||||
system_transaction::transfer(
|
system_transaction::transfer(
|
||||||
&alice,
|
&alice,
|
||||||
&solana_sdk::pubkey::new_rand(),
|
&solana_sdk::pubkey::new_rand(),
|
||||||
1,
|
Rent::default().minimum_balance(0),
|
||||||
recent_blockhash,
|
recent_blockhash,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -381,7 +387,7 @@ fn test_rpc_subscriptions() {
|
||||||
let timeout = deadline.saturating_duration_since(Instant::now());
|
let timeout = deadline.saturating_duration_since(Instant::now());
|
||||||
match account_receiver.recv_timeout(timeout) {
|
match account_receiver.recv_timeout(timeout) {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
assert_eq!(result.value.lamports, 1);
|
assert_eq!(result.value.lamports, Rent::default().minimum_balance(0));
|
||||||
account_notifications -= 1;
|
account_notifications -= 1;
|
||||||
}
|
}
|
||||||
Err(_err) => {
|
Err(_err) => {
|
||||||
|
|
|
@ -4273,23 +4273,32 @@ pub fn create_test_transactions_and_populate_blockstore(
|
||||||
let keypair3 = keypairs[3];
|
let keypair3 = keypairs[3];
|
||||||
let slot = bank.slot();
|
let slot = bank.slot();
|
||||||
let blockhash = bank.confirmed_last_blockhash();
|
let blockhash = bank.confirmed_last_blockhash();
|
||||||
|
let rent_exempt_amount = bank.get_minimum_balance_for_rent_exemption(0);
|
||||||
|
|
||||||
// Generate transactions for processing
|
// Generate transactions for processing
|
||||||
// Successful transaction
|
// Successful transaction
|
||||||
let success_tx =
|
let success_tx = solana_sdk::system_transaction::transfer(
|
||||||
solana_sdk::system_transaction::transfer(mint_keypair, &keypair1.pubkey(), 2, blockhash);
|
mint_keypair,
|
||||||
|
&keypair1.pubkey(),
|
||||||
|
rent_exempt_amount,
|
||||||
|
blockhash,
|
||||||
|
);
|
||||||
let success_signature = success_tx.signatures[0];
|
let success_signature = success_tx.signatures[0];
|
||||||
let entry_1 = solana_entry::entry::next_entry(&blockhash, 1, vec![success_tx]);
|
let entry_1 = solana_entry::entry::next_entry(&blockhash, 1, vec![success_tx]);
|
||||||
// Failed transaction, InstructionError
|
// Failed transaction, InstructionError
|
||||||
let ix_error_tx =
|
let ix_error_tx = solana_sdk::system_transaction::transfer(
|
||||||
solana_sdk::system_transaction::transfer(keypair2, &keypair3.pubkey(), 10, blockhash);
|
keypair2,
|
||||||
|
&keypair3.pubkey(),
|
||||||
|
2 * rent_exempt_amount,
|
||||||
|
blockhash,
|
||||||
|
);
|
||||||
let ix_error_signature = ix_error_tx.signatures[0];
|
let ix_error_signature = ix_error_tx.signatures[0];
|
||||||
let entry_2 = solana_entry::entry::next_entry(&entry_1.hash, 1, vec![ix_error_tx]);
|
let entry_2 = solana_entry::entry::next_entry(&entry_1.hash, 1, vec![ix_error_tx]);
|
||||||
// Failed transaction
|
// Failed transaction
|
||||||
let fail_tx = solana_sdk::system_transaction::transfer(
|
let fail_tx = solana_sdk::system_transaction::transfer(
|
||||||
mint_keypair,
|
mint_keypair,
|
||||||
&keypair2.pubkey(),
|
&keypair2.pubkey(),
|
||||||
2,
|
rent_exempt_amount,
|
||||||
Hash::default(),
|
Hash::default(),
|
||||||
);
|
);
|
||||||
let entry_3 = solana_entry::entry::next_entry(&entry_2.hash, 1, vec![fail_tx]);
|
let entry_3 = solana_entry::entry::next_entry(&entry_2.hash, 1, vec![fail_tx]);
|
||||||
|
@ -4412,6 +4421,7 @@ pub mod tests {
|
||||||
) -> RpcHandler {
|
) -> RpcHandler {
|
||||||
let (bank_forks, alice, leader_vote_keypair) = new_bank_forks();
|
let (bank_forks, alice, leader_vote_keypair) = new_bank_forks();
|
||||||
let bank = bank_forks.read().unwrap().working_bank();
|
let bank = bank_forks.read().unwrap().working_bank();
|
||||||
|
let rent_exempt_amount = bank.get_minimum_balance_for_rent_exemption(0);
|
||||||
|
|
||||||
let vote_pubkey = leader_vote_keypair.pubkey();
|
let vote_pubkey = leader_vote_keypair.pubkey();
|
||||||
let mut vote_account = bank.get_account(&vote_pubkey).unwrap_or_default();
|
let mut vote_account = bank.get_account(&vote_pubkey).unwrap_or_default();
|
||||||
|
@ -4431,7 +4441,8 @@ pub mod tests {
|
||||||
let keypair1 = Keypair::new();
|
let keypair1 = Keypair::new();
|
||||||
let keypair2 = Keypair::new();
|
let keypair2 = Keypair::new();
|
||||||
let keypair3 = Keypair::new();
|
let keypair3 = Keypair::new();
|
||||||
bank.transfer(4, &alice, &keypair2.pubkey()).unwrap();
|
bank.transfer(rent_exempt_amount, &alice, &keypair2.pubkey())
|
||||||
|
.unwrap();
|
||||||
let max_complete_transaction_status_slot = Arc::new(AtomicU64::new(blockstore.max_root()));
|
let max_complete_transaction_status_slot = Arc::new(AtomicU64::new(blockstore.max_root()));
|
||||||
let confirmed_block_signatures = create_test_transactions_and_populate_blockstore(
|
let confirmed_block_signatures = create_test_transactions_and_populate_blockstore(
|
||||||
vec![&alice, &keypair1, &keypair2, &keypair3],
|
vec![&alice, &keypair1, &keypair2, &keypair3],
|
||||||
|
@ -4501,10 +4512,14 @@ pub mod tests {
|
||||||
let validator_exit = create_validator_exit(&exit);
|
let validator_exit = create_validator_exit(&exit);
|
||||||
|
|
||||||
let blockhash = bank.confirmed_last_blockhash();
|
let blockhash = bank.confirmed_last_blockhash();
|
||||||
let tx = system_transaction::transfer(&alice, pubkey, 20, blockhash);
|
let tx = system_transaction::transfer(&alice, pubkey, rent_exempt_amount, blockhash);
|
||||||
bank.process_transaction(&tx).expect("process transaction");
|
bank.process_transaction(&tx).expect("process transaction");
|
||||||
let tx =
|
let tx = system_transaction::transfer(
|
||||||
system_transaction::transfer(&alice, &non_circulating_accounts()[0], 20, blockhash);
|
&alice,
|
||||||
|
&non_circulating_accounts()[0],
|
||||||
|
rent_exempt_amount,
|
||||||
|
blockhash,
|
||||||
|
);
|
||||||
bank.process_transaction(&tx).expect("process transaction");
|
bank.process_transaction(&tx).expect("process transaction");
|
||||||
|
|
||||||
let tx = system_transaction::transfer(&alice, pubkey, std::u64::MAX, blockhash);
|
let tx = system_transaction::transfer(&alice, pubkey, std::u64::MAX, blockhash);
|
||||||
|
@ -4812,13 +4827,16 @@ pub mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_supply() {
|
fn test_get_supply() {
|
||||||
let bob_pubkey = solana_sdk::pubkey::new_rand();
|
let bob_pubkey = solana_sdk::pubkey::new_rand();
|
||||||
let RpcHandler { io, meta, .. } = start_rpc_handler_with_tx(&bob_pubkey);
|
let RpcHandler { io, meta, bank, .. } = start_rpc_handler_with_tx(&bob_pubkey);
|
||||||
let req = r#"{"jsonrpc":"2.0","id":1,"method":"getSupply"}"#;
|
let req = r#"{"jsonrpc":"2.0","id":1,"method":"getSupply"}"#;
|
||||||
let res = io.handle_request_sync(req, meta);
|
let res = io.handle_request_sync(req, meta);
|
||||||
let json: Value = serde_json::from_str(&res.unwrap()).unwrap();
|
let json: Value = serde_json::from_str(&res.unwrap()).unwrap();
|
||||||
let supply: RpcSupply = serde_json::from_value(json["result"]["value"].clone())
|
let supply: RpcSupply = serde_json::from_value(json["result"]["value"].clone())
|
||||||
.expect("actual response deserialization");
|
.expect("actual response deserialization");
|
||||||
assert_eq!(supply.non_circulating, 20);
|
assert_eq!(
|
||||||
|
supply.non_circulating,
|
||||||
|
bank.get_minimum_balance_for_rent_exemption(0)
|
||||||
|
);
|
||||||
assert!(supply.circulating >= TEST_MINT_LAMPORTS);
|
assert!(supply.circulating >= TEST_MINT_LAMPORTS);
|
||||||
assert!(supply.total >= TEST_MINT_LAMPORTS + 20);
|
assert!(supply.total >= TEST_MINT_LAMPORTS + 20);
|
||||||
let expected_accounts: Vec<String> = non_circulating_accounts()
|
let expected_accounts: Vec<String> = non_circulating_accounts()
|
||||||
|
@ -4837,13 +4855,16 @@ pub mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_supply_exclude_account_list() {
|
fn test_get_supply_exclude_account_list() {
|
||||||
let bob_pubkey = solana_sdk::pubkey::new_rand();
|
let bob_pubkey = solana_sdk::pubkey::new_rand();
|
||||||
let RpcHandler { io, meta, .. } = start_rpc_handler_with_tx(&bob_pubkey);
|
let RpcHandler { io, meta, bank, .. } = start_rpc_handler_with_tx(&bob_pubkey);
|
||||||
let req = r#"{"jsonrpc":"2.0","id":1,"method":"getSupply","params":[{"excludeNonCirculatingAccountsList":true}]}"#;
|
let req = r#"{"jsonrpc":"2.0","id":1,"method":"getSupply","params":[{"excludeNonCirculatingAccountsList":true}]}"#;
|
||||||
let res = io.handle_request_sync(req, meta);
|
let res = io.handle_request_sync(req, meta);
|
||||||
let json: Value = serde_json::from_str(&res.unwrap()).unwrap();
|
let json: Value = serde_json::from_str(&res.unwrap()).unwrap();
|
||||||
let supply: RpcSupply = serde_json::from_value(json["result"]["value"].clone())
|
let supply: RpcSupply = serde_json::from_value(json["result"]["value"].clone())
|
||||||
.expect("actual response deserialization");
|
.expect("actual response deserialization");
|
||||||
assert_eq!(supply.non_circulating, 20);
|
assert_eq!(
|
||||||
|
supply.non_circulating,
|
||||||
|
bank.get_minimum_balance_for_rent_exemption(0)
|
||||||
|
);
|
||||||
assert!(supply.circulating >= TEST_MINT_LAMPORTS);
|
assert!(supply.circulating >= TEST_MINT_LAMPORTS);
|
||||||
assert!(supply.total >= TEST_MINT_LAMPORTS + 20);
|
assert!(supply.total >= TEST_MINT_LAMPORTS + 20);
|
||||||
assert!(supply.non_circulating_accounts.is_empty());
|
assert!(supply.non_circulating_accounts.is_empty());
|
||||||
|
@ -5165,7 +5186,7 @@ pub mod tests {
|
||||||
"context":{"slot":0},
|
"context":{"slot":0},
|
||||||
"value":{
|
"value":{
|
||||||
"owner": "11111111111111111111111111111111",
|
"owner": "11111111111111111111111111111111",
|
||||||
"lamports": 20,
|
"lamports": bank.get_minimum_balance_for_rent_exemption(0),
|
||||||
"data": "",
|
"data": "",
|
||||||
"executable": false,
|
"executable": false,
|
||||||
"rentEpoch": 0
|
"rentEpoch": 0
|
||||||
|
@ -5236,9 +5257,11 @@ pub mod tests {
|
||||||
let bob_pubkey = solana_sdk::pubkey::new_rand();
|
let bob_pubkey = solana_sdk::pubkey::new_rand();
|
||||||
let RpcHandler { io, meta, bank, .. } = start_rpc_handler_with_tx(&bob_pubkey);
|
let RpcHandler { io, meta, bank, .. } = start_rpc_handler_with_tx(&bob_pubkey);
|
||||||
|
|
||||||
|
let rent_exempt_amount = bank.get_minimum_balance_for_rent_exemption(0);
|
||||||
|
|
||||||
let address = Pubkey::new(&[9; 32]);
|
let address = Pubkey::new(&[9; 32]);
|
||||||
let data = vec![1, 2, 3, 4, 5];
|
let data = vec![1, 2, 3, 4, 5];
|
||||||
let mut account = AccountSharedData::new(42, 5, &Pubkey::default());
|
let mut account = AccountSharedData::new(rent_exempt_amount + 1, 5, &Pubkey::default());
|
||||||
account.set_data(data.clone());
|
account.set_data(data.clone());
|
||||||
bank.store_account(&address, &account);
|
bank.store_account(&address, &account);
|
||||||
|
|
||||||
|
@ -5256,7 +5279,7 @@ pub mod tests {
|
||||||
"context":{"slot":0},
|
"context":{"slot":0},
|
||||||
"value":[{
|
"value":[{
|
||||||
"owner": "11111111111111111111111111111111",
|
"owner": "11111111111111111111111111111111",
|
||||||
"lamports": 20,
|
"lamports": rent_exempt_amount,
|
||||||
"data": ["", "base64"],
|
"data": ["", "base64"],
|
||||||
"executable": false,
|
"executable": false,
|
||||||
"rentEpoch": 0
|
"rentEpoch": 0
|
||||||
|
@ -5264,7 +5287,7 @@ pub mod tests {
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
"owner": "11111111111111111111111111111111",
|
"owner": "11111111111111111111111111111111",
|
||||||
"lamports": 42,
|
"lamports": rent_exempt_amount + 1,
|
||||||
"data": [base64::encode(&data), "base64"],
|
"data": [base64::encode(&data), "base64"],
|
||||||
"executable": false,
|
"executable": false,
|
||||||
"rentEpoch": 0
|
"rentEpoch": 0
|
||||||
|
@ -5376,7 +5399,7 @@ pub mod tests {
|
||||||
"pubkey": "{}",
|
"pubkey": "{}",
|
||||||
"account": {{
|
"account": {{
|
||||||
"owner": "{}",
|
"owner": "{}",
|
||||||
"lamports": 20,
|
"lamports": {},
|
||||||
"data": "",
|
"data": "",
|
||||||
"executable": false,
|
"executable": false,
|
||||||
"rentEpoch": 0
|
"rentEpoch": 0
|
||||||
|
@ -5386,7 +5409,8 @@ pub mod tests {
|
||||||
"id":1}}
|
"id":1}}
|
||||||
"#,
|
"#,
|
||||||
bob.pubkey(),
|
bob.pubkey(),
|
||||||
new_program_id
|
new_program_id,
|
||||||
|
bank.get_minimum_balance_for_rent_exemption(0),
|
||||||
);
|
);
|
||||||
let expected: Response =
|
let expected: Response =
|
||||||
serde_json::from_str(&expected).expect("expected response deserialization");
|
serde_json::from_str(&expected).expect("expected response deserialization");
|
||||||
|
@ -5579,8 +5603,10 @@ pub mod tests {
|
||||||
..
|
..
|
||||||
} = start_rpc_handler_with_tx(&solana_sdk::pubkey::new_rand());
|
} = start_rpc_handler_with_tx(&solana_sdk::pubkey::new_rand());
|
||||||
|
|
||||||
|
let rent_exempt_amount = bank.get_minimum_balance_for_rent_exemption(0);
|
||||||
let bob_pubkey = solana_sdk::pubkey::new_rand();
|
let bob_pubkey = solana_sdk::pubkey::new_rand();
|
||||||
let mut tx = system_transaction::transfer(&alice, &bob_pubkey, 1234, blockhash);
|
let mut tx =
|
||||||
|
system_transaction::transfer(&alice, &bob_pubkey, rent_exempt_amount, blockhash);
|
||||||
let tx_serialized_encoded = bs58::encode(serialize(&tx).unwrap()).into_string();
|
let tx_serialized_encoded = bs58::encode(serialize(&tx).unwrap()).into_string();
|
||||||
tx.signatures[0] = Signature::default();
|
tx.signatures[0] = Signature::default();
|
||||||
let tx_badsig_serialized_encoded = bs58::encode(serialize(&tx).unwrap()).into_string();
|
let tx_badsig_serialized_encoded = bs58::encode(serialize(&tx).unwrap()).into_string();
|
||||||
|
@ -5621,7 +5647,7 @@ pub mod tests {
|
||||||
"data": ["", "base64"],
|
"data": ["", "base64"],
|
||||||
"executable": false,
|
"executable": false,
|
||||||
"owner": "11111111111111111111111111111111",
|
"owner": "11111111111111111111111111111111",
|
||||||
"lamports": 1234,
|
"lamports": rent_exempt_amount,
|
||||||
"rentEpoch": 0
|
"rentEpoch": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -5871,6 +5897,7 @@ pub mod tests {
|
||||||
blockhash,
|
blockhash,
|
||||||
alice,
|
alice,
|
||||||
confirmed_block_signatures,
|
confirmed_block_signatures,
|
||||||
|
bank,
|
||||||
..
|
..
|
||||||
} = start_rpc_handler_with_tx(&bob_pubkey);
|
} = start_rpc_handler_with_tx(&bob_pubkey);
|
||||||
|
|
||||||
|
@ -5889,7 +5916,12 @@ pub mod tests {
|
||||||
assert_eq!(None, result.confirmations);
|
assert_eq!(None, result.confirmations);
|
||||||
|
|
||||||
// Test getSignatureStatus request on unprocessed tx
|
// Test getSignatureStatus request on unprocessed tx
|
||||||
let tx = system_transaction::transfer(&alice, &bob_pubkey, 10, blockhash);
|
let tx = system_transaction::transfer(
|
||||||
|
&alice,
|
||||||
|
&bob_pubkey,
|
||||||
|
bank.get_minimum_balance_for_rent_exemption(0) + 10,
|
||||||
|
blockhash,
|
||||||
|
);
|
||||||
let req = format!(
|
let req = format!(
|
||||||
r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatuses","params":[["{}"]]}}"#,
|
r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatuses","params":[["{}"]]}}"#,
|
||||||
tx.signatures[0]
|
tx.signatures[0]
|
||||||
|
|
|
@ -19,6 +19,7 @@ bzip2 = "0.4.3"
|
||||||
dashmap = { version = "5.0.0", features = ["rayon", "raw-api"] }
|
dashmap = { version = "5.0.0", features = ["rayon", "raw-api"] }
|
||||||
crossbeam-channel = "0.5"
|
crossbeam-channel = "0.5"
|
||||||
dir-diff = "0.3.2"
|
dir-diff = "0.3.2"
|
||||||
|
enum-iterator = "0.7.0"
|
||||||
flate2 = "1.0.22"
|
flate2 = "1.0.22"
|
||||||
fnv = "1.0.7"
|
fnv = "1.0.7"
|
||||||
index_list = "0.2.7"
|
index_list = "0.2.7"
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
use {
|
||||||
|
enum_iterator::IntoEnumIterator,
|
||||||
|
log::*,
|
||||||
|
solana_sdk::{
|
||||||
|
account::{AccountSharedData, ReadableAccount},
|
||||||
|
rent::Rent,
|
||||||
|
transaction::{Result, TransactionError},
|
||||||
|
transaction_context::TransactionContext,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, IntoEnumIterator)]
|
||||||
|
pub(crate) enum RentState {
|
||||||
|
Uninitialized, // account.lamports == 0
|
||||||
|
RentPaying, // 0 < account.lamports < rent-exempt-minimum
|
||||||
|
RentExempt, // account.lamports >= rent-exempt-minimum
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RentState {
|
||||||
|
pub(crate) fn from_account(account: &AccountSharedData, rent: &Rent) -> Self {
|
||||||
|
if account.lamports() == 0 {
|
||||||
|
Self::Uninitialized
|
||||||
|
} else if !rent.is_exempt(account.lamports(), account.data().len()) {
|
||||||
|
Self::RentPaying
|
||||||
|
} else {
|
||||||
|
Self::RentExempt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn transition_allowed_from(&self, pre_rent_state: &RentState) -> bool {
|
||||||
|
// Only a legacy RentPaying account may end in the RentPaying state after message processing
|
||||||
|
!(self == &Self::RentPaying && pre_rent_state != &Self::RentPaying)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn submit_rent_state_metrics(pre_rent_state: &RentState, post_rent_state: &RentState) {
|
||||||
|
match (pre_rent_state, post_rent_state) {
|
||||||
|
(&RentState::Uninitialized, &RentState::RentPaying) => {
|
||||||
|
inc_new_counter_info!("rent_paying_err-new_account", 1);
|
||||||
|
}
|
||||||
|
(&RentState::RentPaying, &RentState::RentPaying) => {
|
||||||
|
inc_new_counter_info!("rent_paying_ok-legacy", 1);
|
||||||
|
}
|
||||||
|
(_, &RentState::RentPaying) => {
|
||||||
|
inc_new_counter_info!("rent_paying_err-other", 1);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn check_rent_state(
|
||||||
|
pre_rent_state: Option<&RentState>,
|
||||||
|
post_rent_state: Option<&RentState>,
|
||||||
|
transaction_context: &TransactionContext,
|
||||||
|
index: usize,
|
||||||
|
) -> Result<()> {
|
||||||
|
if let Some((pre_rent_state, post_rent_state)) = pre_rent_state.zip(post_rent_state) {
|
||||||
|
submit_rent_state_metrics(pre_rent_state, post_rent_state);
|
||||||
|
if !post_rent_state.transition_allowed_from(pre_rent_state) {
|
||||||
|
debug!(
|
||||||
|
"Account {:?} not rent exempt, state {:?}",
|
||||||
|
transaction_context.get_key_of_account_at_index(index),
|
||||||
|
transaction_context.get_account_at_index(index).borrow(),
|
||||||
|
);
|
||||||
|
return Err(TransactionError::InvalidRentPayingAccount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use {super::*, solana_sdk::pubkey::Pubkey};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_account() {
|
||||||
|
let program_id = Pubkey::new_unique();
|
||||||
|
let uninitialized_account = AccountSharedData::new(0, 0, &Pubkey::default());
|
||||||
|
|
||||||
|
let account_data_size = 100;
|
||||||
|
|
||||||
|
let rent = Rent::free();
|
||||||
|
let rent_exempt_account = AccountSharedData::new(1, account_data_size, &program_id); // if rent is free, all accounts with non-zero lamports and non-empty data are rent-exempt
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
RentState::from_account(&uninitialized_account, &rent),
|
||||||
|
RentState::Uninitialized
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
RentState::from_account(&rent_exempt_account, &rent),
|
||||||
|
RentState::RentExempt
|
||||||
|
);
|
||||||
|
|
||||||
|
let rent = Rent::default();
|
||||||
|
let rent_minimum_balance = rent.minimum_balance(account_data_size);
|
||||||
|
let rent_paying_account = AccountSharedData::new(
|
||||||
|
rent_minimum_balance.saturating_sub(1),
|
||||||
|
account_data_size,
|
||||||
|
&program_id,
|
||||||
|
);
|
||||||
|
let rent_exempt_account = AccountSharedData::new(
|
||||||
|
rent.minimum_balance(account_data_size),
|
||||||
|
account_data_size,
|
||||||
|
&program_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
RentState::from_account(&uninitialized_account, &rent),
|
||||||
|
RentState::Uninitialized
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
RentState::from_account(&rent_paying_account, &rent),
|
||||||
|
RentState::RentPaying
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
RentState::from_account(&rent_exempt_account, &rent),
|
||||||
|
RentState::RentExempt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transition_allowed_from() {
|
||||||
|
for post_rent_state in RentState::into_enum_iter() {
|
||||||
|
for pre_rent_state in RentState::into_enum_iter() {
|
||||||
|
if post_rent_state == RentState::RentPaying
|
||||||
|
&& pre_rent_state != RentState::RentPaying
|
||||||
|
{
|
||||||
|
assert!(!post_rent_state.transition_allowed_from(&pre_rent_state));
|
||||||
|
} else {
|
||||||
|
assert!(post_rent_state.transition_allowed_from(&pre_rent_state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -213,6 +213,7 @@ pub struct ErrorCounters {
|
||||||
pub invalid_program_for_execution: usize,
|
pub invalid_program_for_execution: usize,
|
||||||
pub not_allowed_during_cluster_maintenance: usize,
|
pub not_allowed_during_cluster_maintenance: usize,
|
||||||
pub invalid_writable_account: usize,
|
pub invalid_writable_account: usize,
|
||||||
|
pub invalid_rent_paying_account: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy)]
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
|
|
|
@ -160,6 +160,8 @@ use {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod transaction_account_state_info;
|
||||||
|
|
||||||
pub const SECONDS_PER_YEAR: f64 = 365.25 * 24.0 * 60.0 * 60.0;
|
pub const SECONDS_PER_YEAR: f64 = 365.25 * 24.0 * 60.0 * 60.0;
|
||||||
|
|
||||||
pub const MAX_LEADER_SCHEDULE_STAKES: Epoch = 5;
|
pub const MAX_LEADER_SCHEDULE_STAKES: Epoch = 5;
|
||||||
|
@ -214,7 +216,7 @@ impl RentDebits {
|
||||||
}
|
}
|
||||||
|
|
||||||
type BankStatusCache = StatusCache<Result<()>>;
|
type BankStatusCache = StatusCache<Result<()>>;
|
||||||
#[frozen_abi(digest = "2r36f5cfgP7ABq7D3kRkRfQZWdggGFUnnhwTrVEWhoTC")]
|
#[frozen_abi(digest = "Gr2MTwWyUdkbF6FxM6TSwGaC3c5buUirHmh64oAPgg7Z")]
|
||||||
pub type BankSlotDelta = SlotDelta<Result<()>>;
|
pub type BankSlotDelta = SlotDelta<Result<()>>;
|
||||||
|
|
||||||
// Eager rent collection repeats in cyclic manner.
|
// Eager rent collection repeats in cyclic manner.
|
||||||
|
@ -3697,6 +3699,9 @@ impl Bank {
|
||||||
compute_budget.max_invoke_depth.saturating_add(1),
|
compute_budget.max_invoke_depth.saturating_add(1),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let pre_account_state_info =
|
||||||
|
self.get_transaction_account_state_info(&transaction_context, tx.message());
|
||||||
|
|
||||||
let instruction_recorder = if enable_cpi_recording {
|
let instruction_recorder = if enable_cpi_recording {
|
||||||
Some(InstructionRecorder::new_ref(
|
Some(InstructionRecorder::new_ref(
|
||||||
tx.message().instructions().len(),
|
tx.message().instructions().len(),
|
||||||
|
@ -3746,11 +3751,28 @@ impl Bank {
|
||||||
);
|
);
|
||||||
|
|
||||||
let status = process_result
|
let status = process_result
|
||||||
|
.and_then(|info| {
|
||||||
|
let post_account_state_info =
|
||||||
|
self.get_transaction_account_state_info(&transaction_context, tx.message());
|
||||||
|
self.verify_transaction_account_state_changes(
|
||||||
|
&pre_account_state_info,
|
||||||
|
&post_account_state_info,
|
||||||
|
&transaction_context,
|
||||||
|
)
|
||||||
|
.map(|_| info)
|
||||||
|
})
|
||||||
.map(|info| {
|
.map(|info| {
|
||||||
self.store_accounts_data_len(info.accounts_data_len);
|
self.store_accounts_data_len(info.accounts_data_len);
|
||||||
})
|
})
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
|
match err {
|
||||||
|
TransactionError::InvalidRentPayingAccount => {
|
||||||
|
error_counters.invalid_rent_paying_account += 1;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
error_counters.instruction_error += 1;
|
error_counters.instruction_error += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
err
|
err
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -6254,7 +6276,7 @@ impl Bank {
|
||||||
// Adjust capitalization.... it has been wrapping, reducing the real capitalization by 1-lamport
|
// Adjust capitalization.... it has been wrapping, reducing the real capitalization by 1-lamport
|
||||||
self.capitalization.fetch_add(1, Relaxed);
|
self.capitalization.fetch_add(1, Relaxed);
|
||||||
info!(
|
info!(
|
||||||
"purged rewards pool accont: {}, new capitalization: {}",
|
"purged rewards pool account: {}, new capitalization: {}",
|
||||||
reward_pubkey,
|
reward_pubkey,
|
||||||
self.capitalization()
|
self.capitalization()
|
||||||
);
|
);
|
||||||
|
@ -7085,6 +7107,12 @@ pub(crate) mod tests {
|
||||||
bootstrap_validator_stake_lamports,
|
bootstrap_validator_stake_lamports,
|
||||||
)
|
)
|
||||||
.genesis_config;
|
.genesis_config;
|
||||||
|
// While we are preventing new accounts left in a rent-paying state, not quite ready to rip
|
||||||
|
// out all the rent assessment tests. Just deactivate the feature for now.
|
||||||
|
genesis_config
|
||||||
|
.accounts
|
||||||
|
.remove(&feature_set::require_rent_exempt_accounts::id())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
genesis_config.epoch_schedule = EpochSchedule::custom(
|
genesis_config.epoch_schedule = EpochSchedule::custom(
|
||||||
MINIMUM_SLOTS_PER_EPOCH,
|
MINIMUM_SLOTS_PER_EPOCH,
|
||||||
|
@ -15526,4 +15554,282 @@ pub(crate) mod tests {
|
||||||
7
|
7
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
enum MockTransferInstruction {
|
||||||
|
Transfer(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mock_transfer_process_instruction(
|
||||||
|
_first_instruction_account: usize,
|
||||||
|
data: &[u8],
|
||||||
|
invoke_context: &mut InvokeContext,
|
||||||
|
) -> result::Result<(), InstructionError> {
|
||||||
|
let transaction_context = &invoke_context.transaction_context;
|
||||||
|
let instruction_context = transaction_context.get_current_instruction_context()?;
|
||||||
|
if let Ok(instruction) = bincode::deserialize(data) {
|
||||||
|
match instruction {
|
||||||
|
MockTransferInstruction::Transfer(amount) => {
|
||||||
|
instruction_context
|
||||||
|
.try_borrow_instruction_account(transaction_context, 1)?
|
||||||
|
.checked_sub_lamports(amount)?;
|
||||||
|
instruction_context
|
||||||
|
.try_borrow_instruction_account(transaction_context, 2)?
|
||||||
|
.checked_add_lamports(amount)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(InstructionError::InvalidInstructionData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_mock_transfer(
|
||||||
|
payer: &Keypair,
|
||||||
|
from: &Keypair,
|
||||||
|
to: &Keypair,
|
||||||
|
amount: u64,
|
||||||
|
mock_program_id: Pubkey,
|
||||||
|
recent_blockhash: Hash,
|
||||||
|
) -> Transaction {
|
||||||
|
let account_metas = vec![
|
||||||
|
AccountMeta::new(payer.pubkey(), true),
|
||||||
|
AccountMeta::new(from.pubkey(), true),
|
||||||
|
AccountMeta::new(to.pubkey(), true),
|
||||||
|
];
|
||||||
|
let transfer_instruction = Instruction::new_with_bincode(
|
||||||
|
mock_program_id,
|
||||||
|
&MockTransferInstruction::Transfer(amount),
|
||||||
|
account_metas,
|
||||||
|
);
|
||||||
|
Transaction::new_signed_with_payer(
|
||||||
|
&[transfer_instruction],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
&[payer, from, to],
|
||||||
|
recent_blockhash,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_rent_state_changes_existing_accounts() {
|
||||||
|
let GenesisConfigInfo {
|
||||||
|
mut genesis_config,
|
||||||
|
mint_keypair,
|
||||||
|
..
|
||||||
|
} = create_genesis_config_with_leader(sol_to_lamports(100.), &Pubkey::new_unique(), 42);
|
||||||
|
genesis_config.rent = Rent::default();
|
||||||
|
|
||||||
|
let mock_program_id = Pubkey::new_unique();
|
||||||
|
let account_data_size = 100;
|
||||||
|
let rent_exempt_minimum = genesis_config.rent.minimum_balance(account_data_size);
|
||||||
|
|
||||||
|
// Create legacy accounts of various kinds
|
||||||
|
let rent_paying_account = Keypair::new();
|
||||||
|
genesis_config.accounts.insert(
|
||||||
|
rent_paying_account.pubkey(),
|
||||||
|
Account::new(rent_exempt_minimum - 1, account_data_size, &mock_program_id),
|
||||||
|
);
|
||||||
|
let rent_exempt_account = Keypair::new();
|
||||||
|
genesis_config.accounts.insert(
|
||||||
|
rent_exempt_account.pubkey(),
|
||||||
|
Account::new(rent_exempt_minimum, account_data_size, &mock_program_id),
|
||||||
|
);
|
||||||
|
// Activate features, including require_rent_exempt_accounts
|
||||||
|
activate_all_features(&mut genesis_config);
|
||||||
|
|
||||||
|
let mut bank = Bank::new_for_tests(&genesis_config);
|
||||||
|
bank.add_builtin(
|
||||||
|
"mock_program",
|
||||||
|
&mock_program_id,
|
||||||
|
mock_transfer_process_instruction,
|
||||||
|
);
|
||||||
|
let recent_blockhash = bank.last_blockhash();
|
||||||
|
|
||||||
|
let check_account_is_rent_exempt = |pubkey: &Pubkey| -> bool {
|
||||||
|
let account = bank.get_account(pubkey).unwrap();
|
||||||
|
Rent::default().is_exempt(account.lamports(), account.data().len())
|
||||||
|
};
|
||||||
|
|
||||||
|
// RentPaying account can be left as Uninitialized, in other RentPaying states, or RentExempt
|
||||||
|
let tx = create_mock_transfer(
|
||||||
|
&mint_keypair, // payer
|
||||||
|
&rent_paying_account, // from
|
||||||
|
&mint_keypair, // to
|
||||||
|
1,
|
||||||
|
mock_program_id,
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
|
let result = bank.process_transaction(&tx);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert!(!check_account_is_rent_exempt(&rent_paying_account.pubkey()));
|
||||||
|
let tx = create_mock_transfer(
|
||||||
|
&mint_keypair, // payer
|
||||||
|
&rent_paying_account, // from
|
||||||
|
&mint_keypair, // to
|
||||||
|
rent_exempt_minimum - 2,
|
||||||
|
mock_program_id,
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
|
let result = bank.process_transaction(&tx);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert!(bank.get_account(&rent_paying_account.pubkey()).is_none());
|
||||||
|
|
||||||
|
bank.store_account(
|
||||||
|
// restore program-owned account
|
||||||
|
&rent_paying_account.pubkey(),
|
||||||
|
&AccountSharedData::new(rent_exempt_minimum - 1, account_data_size, &mock_program_id),
|
||||||
|
);
|
||||||
|
let result = bank.transfer(1, &mint_keypair, &rent_paying_account.pubkey());
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert!(check_account_is_rent_exempt(&rent_paying_account.pubkey()));
|
||||||
|
|
||||||
|
// RentExempt account can only remain RentExempt or be Uninitialized
|
||||||
|
let tx = create_mock_transfer(
|
||||||
|
&mint_keypair, // payer
|
||||||
|
&rent_exempt_account, // from
|
||||||
|
&mint_keypair, // to
|
||||||
|
1,
|
||||||
|
mock_program_id,
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
|
let result = bank.process_transaction(&tx);
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(check_account_is_rent_exempt(&rent_exempt_account.pubkey()));
|
||||||
|
let result = bank.transfer(1, &mint_keypair, &rent_exempt_account.pubkey());
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert!(check_account_is_rent_exempt(&rent_exempt_account.pubkey()));
|
||||||
|
let tx = create_mock_transfer(
|
||||||
|
&mint_keypair, // payer
|
||||||
|
&rent_exempt_account, // from
|
||||||
|
&mint_keypair, // to
|
||||||
|
rent_exempt_minimum + 1,
|
||||||
|
mock_program_id,
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
|
let result = bank.process_transaction(&tx);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert!(bank.get_account(&rent_exempt_account.pubkey()).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_rent_state_changes_new_accounts() {
|
||||||
|
let GenesisConfigInfo {
|
||||||
|
mut genesis_config,
|
||||||
|
mint_keypair,
|
||||||
|
..
|
||||||
|
} = create_genesis_config_with_leader(sol_to_lamports(100.), &Pubkey::new_unique(), 42);
|
||||||
|
genesis_config.rent = Rent::default();
|
||||||
|
|
||||||
|
let mock_program_id = Pubkey::new_unique();
|
||||||
|
let account_data_size = 100;
|
||||||
|
let rent_exempt_minimum = genesis_config.rent.minimum_balance(account_data_size);
|
||||||
|
|
||||||
|
// Activate features, including require_rent_exempt_accounts
|
||||||
|
activate_all_features(&mut genesis_config);
|
||||||
|
|
||||||
|
let mut bank = Bank::new_for_tests(&genesis_config);
|
||||||
|
bank.add_builtin(
|
||||||
|
"mock_program",
|
||||||
|
&mock_program_id,
|
||||||
|
mock_transfer_process_instruction,
|
||||||
|
);
|
||||||
|
let recent_blockhash = bank.last_blockhash();
|
||||||
|
|
||||||
|
let check_account_is_rent_exempt = |pubkey: &Pubkey| -> bool {
|
||||||
|
let account = bank.get_account(pubkey).unwrap();
|
||||||
|
Rent::default().is_exempt(account.lamports(), account.data().len())
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try to create RentPaying account
|
||||||
|
let rent_paying_account = Keypair::new();
|
||||||
|
let tx = system_transaction::create_account(
|
||||||
|
&mint_keypair,
|
||||||
|
&rent_paying_account,
|
||||||
|
recent_blockhash,
|
||||||
|
rent_exempt_minimum - 1,
|
||||||
|
account_data_size as u64,
|
||||||
|
&mock_program_id,
|
||||||
|
);
|
||||||
|
let result = bank.process_transaction(&tx);
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(bank.get_account(&rent_paying_account.pubkey()).is_none());
|
||||||
|
|
||||||
|
// Try to create RentExempt account
|
||||||
|
let rent_exempt_account = Keypair::new();
|
||||||
|
let tx = system_transaction::create_account(
|
||||||
|
&mint_keypair,
|
||||||
|
&rent_exempt_account,
|
||||||
|
recent_blockhash,
|
||||||
|
rent_exempt_minimum,
|
||||||
|
account_data_size as u64,
|
||||||
|
&mock_program_id,
|
||||||
|
);
|
||||||
|
let result = bank.process_transaction(&tx);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert!(check_account_is_rent_exempt(&rent_exempt_account.pubkey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rent_state_changes_sysvars() {
|
||||||
|
let GenesisConfigInfo {
|
||||||
|
mut genesis_config,
|
||||||
|
mint_keypair,
|
||||||
|
..
|
||||||
|
} = create_genesis_config_with_leader(sol_to_lamports(100.), &Pubkey::new_unique(), 42);
|
||||||
|
genesis_config.rent = Rent::default();
|
||||||
|
// Activate features, including require_rent_exempt_accounts
|
||||||
|
activate_all_features(&mut genesis_config);
|
||||||
|
|
||||||
|
let validator_pubkey = solana_sdk::pubkey::new_rand();
|
||||||
|
let validator_stake_lamports = sol_to_lamports(1.);
|
||||||
|
let validator_staking_keypair = Keypair::new();
|
||||||
|
let validator_voting_keypair = Keypair::new();
|
||||||
|
|
||||||
|
let validator_vote_account = vote_state::create_account(
|
||||||
|
&validator_voting_keypair.pubkey(),
|
||||||
|
&validator_pubkey,
|
||||||
|
0,
|
||||||
|
validator_stake_lamports,
|
||||||
|
);
|
||||||
|
|
||||||
|
let validator_stake_account = stake_state::create_account(
|
||||||
|
&validator_staking_keypair.pubkey(),
|
||||||
|
&validator_voting_keypair.pubkey(),
|
||||||
|
&validator_vote_account,
|
||||||
|
&genesis_config.rent,
|
||||||
|
validator_stake_lamports,
|
||||||
|
);
|
||||||
|
|
||||||
|
genesis_config.accounts.insert(
|
||||||
|
validator_pubkey,
|
||||||
|
Account::new(
|
||||||
|
genesis_config.rent.minimum_balance(0),
|
||||||
|
0,
|
||||||
|
&system_program::id(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
genesis_config.accounts.insert(
|
||||||
|
validator_staking_keypair.pubkey(),
|
||||||
|
Account::from(validator_stake_account),
|
||||||
|
);
|
||||||
|
genesis_config.accounts.insert(
|
||||||
|
validator_voting_keypair.pubkey(),
|
||||||
|
Account::from(validator_vote_account),
|
||||||
|
);
|
||||||
|
|
||||||
|
let bank = Bank::new_for_tests(&genesis_config);
|
||||||
|
|
||||||
|
// Ensure transactions with sysvars succeed, even though sysvars appear RentPaying by balance
|
||||||
|
let tx = Transaction::new_signed_with_payer(
|
||||||
|
&[stake_instruction::deactivate_stake(
|
||||||
|
&validator_staking_keypair.pubkey(),
|
||||||
|
&validator_staking_keypair.pubkey(),
|
||||||
|
)],
|
||||||
|
Some(&mint_keypair.pubkey()),
|
||||||
|
&[&mint_keypair, &validator_staking_keypair],
|
||||||
|
bank.last_blockhash(),
|
||||||
|
);
|
||||||
|
let result = bank.process_transaction(&tx);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
account_rent_state::{check_rent_state, RentState},
|
||||||
|
bank::Bank,
|
||||||
|
},
|
||||||
|
solana_sdk::{
|
||||||
|
account::ReadableAccount, feature_set, message::SanitizedMessage, native_loader,
|
||||||
|
transaction::Result, transaction_context::TransactionContext,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) struct TransactionAccountStateInfo {
|
||||||
|
rent_state: Option<RentState>, // None: readonly account
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bank {
|
||||||
|
pub(crate) fn get_transaction_account_state_info(
|
||||||
|
&self,
|
||||||
|
transaction_context: &TransactionContext,
|
||||||
|
message: &SanitizedMessage,
|
||||||
|
) -> Vec<TransactionAccountStateInfo> {
|
||||||
|
(0..transaction_context.get_number_of_accounts())
|
||||||
|
.map(|i| {
|
||||||
|
let rent_state = if message.is_writable(i) {
|
||||||
|
let account = transaction_context.get_account_at_index(i).borrow();
|
||||||
|
|
||||||
|
// Native programs appear to be RentPaying because they carry low lamport
|
||||||
|
// balances; however they will never be loaded as writable
|
||||||
|
debug_assert!(!native_loader::check_id(account.owner()));
|
||||||
|
|
||||||
|
Some(RentState::from_account(
|
||||||
|
&account,
|
||||||
|
&self.rent_collector().rent,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
TransactionAccountStateInfo { rent_state }
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn verify_transaction_account_state_changes(
|
||||||
|
&self,
|
||||||
|
pre_state_infos: &[TransactionAccountStateInfo],
|
||||||
|
post_state_infos: &[TransactionAccountStateInfo],
|
||||||
|
transaction_context: &TransactionContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
let require_rent_exempt_accounts = self
|
||||||
|
.feature_set
|
||||||
|
.is_active(&feature_set::require_rent_exempt_accounts::id());
|
||||||
|
for (i, (pre_state_info, post_state_info)) in
|
||||||
|
pre_state_infos.iter().zip(post_state_infos).enumerate()
|
||||||
|
{
|
||||||
|
if let Err(err) = check_rent_state(
|
||||||
|
pre_state_info.rent_state.as_ref(),
|
||||||
|
post_state_info.rent_state.as_ref(),
|
||||||
|
transaction_context,
|
||||||
|
i,
|
||||||
|
) {
|
||||||
|
// Feature gate only wraps the actual error return so that the metrics and debug
|
||||||
|
// logging generated by `check_rent_state()` can be examined before feature
|
||||||
|
// activation
|
||||||
|
if require_rent_exempt_accounts {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
#![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(min_specialization))]
|
#![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(min_specialization))]
|
||||||
#![allow(clippy::integer_arithmetic)]
|
#![allow(clippy::integer_arithmetic)]
|
||||||
pub mod account_info;
|
pub mod account_info;
|
||||||
|
pub mod account_rent_state;
|
||||||
pub mod accounts;
|
pub mod accounts;
|
||||||
pub mod accounts_background_service;
|
pub mod accounts_background_service;
|
||||||
pub mod accounts_cache;
|
pub mod accounts_cache;
|
||||||
|
|
|
@ -295,6 +295,10 @@ pub mod max_tx_account_locks {
|
||||||
solana_sdk::declare_id!("CBkDroRDqm8HwHe6ak9cguPjUomrASEkfmxEaZ5CNNxz");
|
solana_sdk::declare_id!("CBkDroRDqm8HwHe6ak9cguPjUomrASEkfmxEaZ5CNNxz");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod require_rent_exempt_accounts {
|
||||||
|
solana_sdk::declare_id!("BkFDxiJQWZXGTZaJQxH7wVEHkAmwCgSEVkrvswFfRJPD");
|
||||||
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
/// Map of feature identifiers to user-visible description
|
/// Map of feature identifiers to user-visible description
|
||||||
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
||||||
|
@ -363,6 +367,7 @@ lazy_static! {
|
||||||
(reject_all_elf_rw::id(), "reject all read-write data in program elfs"),
|
(reject_all_elf_rw::id(), "reject all read-write data in program elfs"),
|
||||||
(cap_accounts_data_len::id(), "cap the accounts data len"),
|
(cap_accounts_data_len::id(), "cap the accounts data len"),
|
||||||
(max_tx_account_locks::id(), "enforce max number of locked accounts per transaction"),
|
(max_tx_account_locks::id(), "enforce max number of locked accounts per transaction"),
|
||||||
|
(require_rent_exempt_accounts::id(), "require all new transaction accounts with data to be rent-exempt"),
|
||||||
/*************** ADD NEW FEATURES HERE ***************/
|
/*************** ADD NEW FEATURES HERE ***************/
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -125,6 +125,12 @@ pub enum TransactionError {
|
||||||
/// Address table lookup uses an invalid index
|
/// Address table lookup uses an invalid index
|
||||||
#[error("Transaction address table lookup uses an invalid index")]
|
#[error("Transaction address table lookup uses an invalid index")]
|
||||||
InvalidAddressLookupTableIndex,
|
InvalidAddressLookupTableIndex,
|
||||||
|
|
||||||
|
/// Transaction leaves an account with a lower balance than rent-exempt minimum
|
||||||
|
#[error(
|
||||||
|
"Transaction leaves an account with data with a lower balance than rent-exempt minimum"
|
||||||
|
)]
|
||||||
|
InvalidRentPayingAccount,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SanitizeError> for TransactionError {
|
impl From<SanitizeError> for TransactionError {
|
||||||
|
|
|
@ -51,6 +51,7 @@ enum TransactionErrorType {
|
||||||
INVALID_ADDRESS_LOOKUP_TABLE_OWNER = 24;
|
INVALID_ADDRESS_LOOKUP_TABLE_OWNER = 24;
|
||||||
INVALID_ADDRESS_LOOKUP_TABLE_DATA = 25;
|
INVALID_ADDRESS_LOOKUP_TABLE_DATA = 25;
|
||||||
INVALID_ADDRESS_LOOKUP_TABLE_INDEX = 26;
|
INVALID_ADDRESS_LOOKUP_TABLE_INDEX = 26;
|
||||||
|
INVALID_RENT_PAYING_ACCOUNT = 27;
|
||||||
}
|
}
|
||||||
|
|
||||||
message InstructionError {
|
message InstructionError {
|
||||||
|
|
|
@ -574,6 +574,7 @@ impl TryFrom<tx_by_addr::TransactionError> for TransactionError {
|
||||||
24 => TransactionError::InvalidAddressLookupTableOwner,
|
24 => TransactionError::InvalidAddressLookupTableOwner,
|
||||||
25 => TransactionError::InvalidAddressLookupTableData,
|
25 => TransactionError::InvalidAddressLookupTableData,
|
||||||
26 => TransactionError::InvalidAddressLookupTableIndex,
|
26 => TransactionError::InvalidAddressLookupTableIndex,
|
||||||
|
27 => TransactionError::InvalidRentPayingAccount,
|
||||||
_ => return Err("Invalid TransactionError"),
|
_ => return Err("Invalid TransactionError"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -662,6 +663,10 @@ impl From<TransactionError> for tx_by_addr::TransactionError {
|
||||||
TransactionError::InvalidAddressLookupTableIndex => {
|
TransactionError::InvalidAddressLookupTableIndex => {
|
||||||
tx_by_addr::TransactionErrorType::InvalidAddressLookupTableIndex
|
tx_by_addr::TransactionErrorType::InvalidAddressLookupTableIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TransactionError::InvalidRentPayingAccount => {
|
||||||
|
tx_by_addr::TransactionErrorType::InvalidRentPayingAccount
|
||||||
|
}
|
||||||
} as i32,
|
} as i32,
|
||||||
instruction_error: match transaction_error {
|
instruction_error: match transaction_error {
|
||||||
TransactionError::InstructionError(index, ref instruction_error) => {
|
TransactionError::InstructionError(index, ref instruction_error) => {
|
||||||
|
|
Loading…
Reference in New Issue