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:
Tyera Eulberg 2022-01-11 11:32:25 -07:00 committed by GitHub
parent a49ef49f87
commit 637e366b18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 819 additions and 179 deletions

1
Cargo.lock generated
View File

@ -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",

View File

@ -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 (

View File

@ -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,
} }
} }
} }

View File

@ -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);
} }

View File

@ -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));
} }

View File

@ -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(

View File

@ -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);
} }

View File

@ -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);
} }

View File

@ -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!();
{ {

View File

@ -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));

View File

@ -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

View File

@ -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",

View File

@ -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) => {

View File

@ -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]

View File

@ -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"

View File

@ -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));
}
}
}
}
}

View File

@ -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)]

View File

@ -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| {
error_counters.instruction_error += 1; match err {
TransactionError::InvalidRentPayingAccount => {
error_counters.invalid_rent_paying_account += 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());
}
} }

View File

@ -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(())
}
}

View File

@ -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;

View File

@ -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()

View File

@ -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 {

View File

@ -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 {

View File

@ -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) => {