add credit-debit rent handling (#6947)
* add credit-debit rent handling * add tests * charge rent for validator account for fee credit * rent is stored per tx instead of account
This commit is contained in:
parent
b150da837a
commit
9f6a2e51b2
|
@ -431,7 +431,7 @@ mod tests {
|
||||||
"lamports": 51,
|
"lamports": 51,
|
||||||
"data": expected_data,
|
"data": expected_data,
|
||||||
"executable": false,
|
"executable": false,
|
||||||
"rent_epoch": 0,
|
"rent_epoch": 1,
|
||||||
},
|
},
|
||||||
"subscription": 0,
|
"subscription": 0,
|
||||||
}
|
}
|
||||||
|
@ -576,7 +576,7 @@ mod tests {
|
||||||
"lamports": 100,
|
"lamports": 100,
|
||||||
"data": [],
|
"data": [],
|
||||||
"executable": false,
|
"executable": false,
|
||||||
"rent_epoch": 0,
|
"rent_epoch": 1,
|
||||||
},
|
},
|
||||||
"subscription": 0,
|
"subscription": 0,
|
||||||
}
|
}
|
||||||
|
|
|
@ -346,7 +346,7 @@ mod tests {
|
||||||
subscriptions.check_account(&alice.pubkey(), 0, &bank_forks);
|
subscriptions.check_account(&alice.pubkey(), 0, &bank_forks);
|
||||||
let string = transport_receiver.poll();
|
let string = transport_receiver.poll();
|
||||||
if let Async::Ready(Some(response)) = string.unwrap() {
|
if let Async::Ready(Some(response)) = string.unwrap() {
|
||||||
let expected = format!(r#"{{"jsonrpc":"2.0","method":"accountNotification","params":{{"result":{{"data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"executable":false,"lamports":1,"owner":[2,203,81,223,225,24,34,35,203,214,138,130,144,208,35,77,63,16,87,51,47,198,115,123,98,188,19,160,0,0,0,0],"rent_epoch":0}},"subscription":0}}}}"#);
|
let expected = format!(r#"{{"jsonrpc":"2.0","method":"accountNotification","params":{{"result":{{"data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"executable":false,"lamports":1,"owner":[2,203,81,223,225,24,34,35,203,214,138,130,144,208,35,77,63,16,87,51,47,198,115,123,98,188,19,160,0,0,0,0],"rent_epoch":1}},"subscription":0}}}}"#);
|
||||||
assert_eq!(expected, response);
|
assert_eq!(expected, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,7 +401,7 @@ mod tests {
|
||||||
subscriptions.check_program(&solana_budget_api::id(), 0, &bank_forks);
|
subscriptions.check_program(&solana_budget_api::id(), 0, &bank_forks);
|
||||||
let string = transport_receiver.poll();
|
let string = transport_receiver.poll();
|
||||||
if let Async::Ready(Some(response)) = string.unwrap() {
|
if let Async::Ready(Some(response)) = string.unwrap() {
|
||||||
let expected = format!(r#"{{"jsonrpc":"2.0","method":"programNotification","params":{{"result":["{:?}",{{"data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"executable":false,"lamports":1,"owner":[2,203,81,223,225,24,34,35,203,214,138,130,144,208,35,77,63,16,87,51,47,198,115,123,98,188,19,160,0,0,0,0],"rent_epoch":0}}],"subscription":0}}}}"#, alice.pubkey());
|
let expected = format!(r#"{{"jsonrpc":"2.0","method":"programNotification","params":{{"result":["{:?}",{{"data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"executable":false,"lamports":1,"owner":[2,203,81,223,225,24,34,35,203,214,138,130,144,208,35,77,63,16,87,51,47,198,115,123,98,188,19,160,0,0,0,0],"rent_epoch":1}}],"subscription":0}}}}"#, alice.pubkey());
|
||||||
assert_eq!(expected, response);
|
assert_eq!(expected, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,10 +46,10 @@ pub struct Accounts {
|
||||||
|
|
||||||
// for the load instructions
|
// for the load instructions
|
||||||
pub type TransactionAccounts = Vec<Account>;
|
pub type TransactionAccounts = Vec<Account>;
|
||||||
pub type TransactionRents = Vec<u64>;
|
pub type TransactionRent = u64;
|
||||||
pub type TransactionLoaders = Vec<Vec<(Pubkey, Account)>>;
|
pub type TransactionLoaders = Vec<Vec<(Pubkey, Account)>>;
|
||||||
|
|
||||||
pub type TransactionLoadResult = (TransactionAccounts, TransactionLoaders, TransactionRents);
|
pub type TransactionLoadResult = (TransactionAccounts, TransactionLoaders, TransactionRent);
|
||||||
|
|
||||||
impl Accounts {
|
impl Accounts {
|
||||||
pub fn new(paths: Option<String>) -> Self {
|
pub fn new(paths: Option<String>) -> Self {
|
||||||
|
@ -92,7 +92,7 @@ impl Accounts {
|
||||||
fee: u64,
|
fee: u64,
|
||||||
error_counters: &mut ErrorCounters,
|
error_counters: &mut ErrorCounters,
|
||||||
rent_collector: &RentCollector,
|
rent_collector: &RentCollector,
|
||||||
) -> Result<(TransactionAccounts, TransactionRents)> {
|
) -> Result<(TransactionAccounts, TransactionRent)> {
|
||||||
// Copy all the accounts
|
// Copy all the accounts
|
||||||
let message = tx.message();
|
let message = tx.message();
|
||||||
if tx.signatures.is_empty() && fee != 0 {
|
if tx.signatures.is_empty() && fee != 0 {
|
||||||
|
@ -107,21 +107,27 @@ impl Accounts {
|
||||||
// There is no way to predict what program will execute without an error
|
// There is no way to predict what program will execute without an error
|
||||||
// If a fee can pay for execution then the program will be scheduled
|
// If a fee can pay for execution then the program will be scheduled
|
||||||
let mut accounts: TransactionAccounts = Vec::with_capacity(message.account_keys.len());
|
let mut accounts: TransactionAccounts = Vec::with_capacity(message.account_keys.len());
|
||||||
let mut rents: TransactionRents = Vec::with_capacity(message.account_keys.len());
|
let mut tx_rent: TransactionRent = 0;
|
||||||
for key in message
|
for (i, key) in message
|
||||||
.account_keys
|
.account_keys
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|key| !message.program_ids().contains(key))
|
.enumerate()
|
||||||
|
.filter(|(_, key)| !message.program_ids().contains(key))
|
||||||
{
|
{
|
||||||
let (account, rent) = AccountsDB::load(storage, ancestors, accounts_index, key)
|
let (account, rent) = AccountsDB::load(storage, ancestors, accounts_index, key)
|
||||||
.and_then(|(mut account, _)| {
|
.and_then(|(mut account, _)| {
|
||||||
let rent_due = rent_collector.update(&mut account);
|
let rent_due: u64;
|
||||||
Some((account, rent_due))
|
if message.is_writable(i) {
|
||||||
|
rent_due = rent_collector.update(&mut account);
|
||||||
|
Some((account, rent_due))
|
||||||
|
} else {
|
||||||
|
Some((account, 0))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
accounts.push(account);
|
accounts.push(account);
|
||||||
rents.push(rent);
|
tx_rent += rent;
|
||||||
}
|
}
|
||||||
|
|
||||||
if accounts.is_empty() || accounts[0].lamports == 0 {
|
if accounts.is_empty() || accounts[0].lamports == 0 {
|
||||||
|
@ -135,7 +141,7 @@ impl Accounts {
|
||||||
Err(TransactionError::InsufficientFundsForFee)
|
Err(TransactionError::InsufficientFundsForFee)
|
||||||
} else {
|
} else {
|
||||||
accounts[0].lamports -= fee;
|
accounts[0].lamports -= fee;
|
||||||
Ok((accounts, rents))
|
Ok((accounts, tx_rent))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -514,9 +520,10 @@ impl Accounts {
|
||||||
txs_iteration_order: Option<&[usize]>,
|
txs_iteration_order: Option<&[usize]>,
|
||||||
res: &[Result<()>],
|
res: &[Result<()>],
|
||||||
loaded: &mut [Result<TransactionLoadResult>],
|
loaded: &mut [Result<TransactionLoadResult>],
|
||||||
|
rent_collector: &RentCollector,
|
||||||
) {
|
) {
|
||||||
let accounts_to_store =
|
let accounts_to_store =
|
||||||
self.collect_accounts_to_store(txs, txs_iteration_order, res, loaded);
|
self.collect_accounts_to_store(txs, txs_iteration_order, res, loaded, rent_collector);
|
||||||
self.accounts_db.store(slot, &accounts_to_store);
|
self.accounts_db.store(slot, &accounts_to_store);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -536,6 +543,7 @@ impl Accounts {
|
||||||
txs_iteration_order: Option<&'a [usize]>,
|
txs_iteration_order: Option<&'a [usize]>,
|
||||||
res: &'a [Result<()>],
|
res: &'a [Result<()>],
|
||||||
loaded: &'a mut [Result<TransactionLoadResult>],
|
loaded: &'a mut [Result<TransactionLoadResult>],
|
||||||
|
rent_collector: &RentCollector,
|
||||||
) -> Vec<(&'a Pubkey, &'a Account)> {
|
) -> Vec<(&'a Pubkey, &'a Account)> {
|
||||||
let mut accounts = Vec::with_capacity(loaded.len());
|
let mut accounts = Vec::with_capacity(loaded.len());
|
||||||
for (i, (raccs, tx)) in loaded
|
for (i, (raccs, tx)) in loaded
|
||||||
|
@ -549,9 +557,18 @@ impl Accounts {
|
||||||
|
|
||||||
let message = &tx.message();
|
let message = &tx.message();
|
||||||
let acc = raccs.as_mut().unwrap();
|
let acc = raccs.as_mut().unwrap();
|
||||||
for ((i, key), account) in message.account_keys.iter().enumerate().zip(acc.0.iter()) {
|
for ((i, key), account) in message
|
||||||
|
.account_keys
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.zip(acc.0.iter_mut())
|
||||||
|
{
|
||||||
if message.is_writable(i) {
|
if message.is_writable(i) {
|
||||||
accounts.push((key, account));
|
if account.rent_epoch == 0 {
|
||||||
|
account.rent_epoch = rent_collector.epoch;
|
||||||
|
acc.2 += rent_collector.update(account);
|
||||||
|
}
|
||||||
|
accounts.push((key, &*account));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1327,6 +1344,8 @@ mod tests {
|
||||||
let keypair1 = Keypair::new();
|
let keypair1 = Keypair::new();
|
||||||
let pubkey = Pubkey::new_rand();
|
let pubkey = Pubkey::new_rand();
|
||||||
|
|
||||||
|
let rent_collector = RentCollector::default();
|
||||||
|
|
||||||
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
|
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
|
||||||
let message = Message::new_with_compiled_instructions(
|
let message = Message::new_with_compiled_instructions(
|
||||||
1,
|
1,
|
||||||
|
@ -1358,20 +1377,20 @@ mod tests {
|
||||||
|
|
||||||
let transaction_accounts0 = vec![account0, account2.clone()];
|
let transaction_accounts0 = vec![account0, account2.clone()];
|
||||||
let transaction_loaders0 = vec![];
|
let transaction_loaders0 = vec![];
|
||||||
let transaction_rents0 = vec![0, 0];
|
let transaction_rent0 = 0;
|
||||||
let loaded0 = Ok((
|
let loaded0 = Ok((
|
||||||
transaction_accounts0,
|
transaction_accounts0,
|
||||||
transaction_loaders0,
|
transaction_loaders0,
|
||||||
transaction_rents0,
|
transaction_rent0,
|
||||||
));
|
));
|
||||||
|
|
||||||
let transaction_accounts1 = vec![account1, account2.clone()];
|
let transaction_accounts1 = vec![account1, account2.clone()];
|
||||||
let transaction_loaders1 = vec![];
|
let transaction_loaders1 = vec![];
|
||||||
let transaction_rents1 = vec![0, 0];
|
let transaction_rent1 = 0;
|
||||||
let loaded1 = Ok((
|
let loaded1 = Ok((
|
||||||
transaction_accounts1,
|
transaction_accounts1,
|
||||||
transaction_loaders1,
|
transaction_loaders1,
|
||||||
transaction_rents1,
|
transaction_rent1,
|
||||||
));
|
));
|
||||||
|
|
||||||
let mut loaded = vec![loaded0, loaded1];
|
let mut loaded = vec![loaded0, loaded1];
|
||||||
|
@ -1388,7 +1407,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let collected_accounts =
|
let collected_accounts =
|
||||||
accounts.collect_accounts_to_store(&txs, None, &loaders, &mut loaded);
|
accounts.collect_accounts_to_store(&txs, None, &loaders, &mut loaded, &rent_collector);
|
||||||
assert_eq!(collected_accounts.len(), 2);
|
assert_eq!(collected_accounts.len(), 2);
|
||||||
assert!(collected_accounts
|
assert!(collected_accounts
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -230,6 +230,11 @@ pub struct Bank {
|
||||||
/// Latest transaction fees for transactions processed by this bank
|
/// Latest transaction fees for transactions processed by this bank
|
||||||
fee_calculator: FeeCalculator,
|
fee_calculator: FeeCalculator,
|
||||||
|
|
||||||
|
/// Rent that have been collected
|
||||||
|
#[serde(serialize_with = "serialize_atomicu64")]
|
||||||
|
#[serde(deserialize_with = "deserialize_atomicu64")]
|
||||||
|
collected_rent: AtomicU64,
|
||||||
|
|
||||||
/// latest rent collector, knows the epoch
|
/// latest rent collector, knows the epoch
|
||||||
rent_collector: RentCollector,
|
rent_collector: RentCollector,
|
||||||
|
|
||||||
|
@ -333,6 +338,7 @@ impl Bank {
|
||||||
slots_per_segment: parent.slots_per_segment,
|
slots_per_segment: parent.slots_per_segment,
|
||||||
slots_per_year: parent.slots_per_year,
|
slots_per_year: parent.slots_per_year,
|
||||||
epoch_schedule,
|
epoch_schedule,
|
||||||
|
collected_rent: AtomicU64::new(0),
|
||||||
rent_collector: parent.rent_collector.clone_with_epoch(epoch),
|
rent_collector: parent.rent_collector.clone_with_epoch(epoch),
|
||||||
max_tick_height: (slot + 1) * parent.ticks_per_slot,
|
max_tick_height: (slot + 1) * parent.ticks_per_slot,
|
||||||
block_height: parent.block_height + 1,
|
block_height: parent.block_height + 1,
|
||||||
|
@ -596,6 +602,7 @@ impl Bank {
|
||||||
if *hash == Hash::default() {
|
if *hash == Hash::default() {
|
||||||
// finish up any deferred changes to account state
|
// finish up any deferred changes to account state
|
||||||
self.collect_fees();
|
self.collect_fees();
|
||||||
|
self.distribute_rent();
|
||||||
|
|
||||||
// freeze is a one-way trip, idempotent
|
// freeze is a one-way trip, idempotent
|
||||||
*hash = self.hash_internal_state();
|
*hash = self.hash_internal_state();
|
||||||
|
@ -1169,7 +1176,9 @@ impl Bank {
|
||||||
iteration_order,
|
iteration_order,
|
||||||
executed,
|
executed,
|
||||||
loaded_accounts,
|
loaded_accounts,
|
||||||
|
&self.rent_collector,
|
||||||
);
|
);
|
||||||
|
self.collect_rent(executed, loaded_accounts);
|
||||||
|
|
||||||
self.update_cached_accounts(txs, iteration_order, executed, loaded_accounts);
|
self.update_cached_accounts(txs, iteration_order, executed, loaded_accounts);
|
||||||
|
|
||||||
|
@ -1180,6 +1189,34 @@ impl Bank {
|
||||||
self.filter_program_errors_and_collect_fee(txs, iteration_order, executed)
|
self.filter_program_errors_and_collect_fee(txs, iteration_order, executed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn distribute_rent(&self) {
|
||||||
|
let total_rent_collected = self.collected_rent.load(Ordering::Relaxed);
|
||||||
|
|
||||||
|
if total_rent_collected != 0 {
|
||||||
|
let burned_portion =
|
||||||
|
(total_rent_collected * u64::from(self.rent_collector.rent.burn_percent)) / 100;
|
||||||
|
let _rent_to_be_distributed = total_rent_collected - burned_portion;
|
||||||
|
// TODO: distribute remaining rent amount to validators
|
||||||
|
// self.capitalization.fetch_sub(burned_portion, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_rent(&self, res: &[Result<()>], loaded_accounts: &[Result<TransactionLoadResult>]) {
|
||||||
|
let mut collected_rent: u64 = 0;
|
||||||
|
for (i, raccs) in loaded_accounts.iter().enumerate() {
|
||||||
|
if res[i].is_err() || raccs.is_err() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let acc = raccs.as_ref().unwrap();
|
||||||
|
|
||||||
|
collected_rent += acc.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.collected_rent
|
||||||
|
.fetch_add(collected_rent, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
/// Process a batch of transactions.
|
/// Process a batch of transactions.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn load_execute_and_commit_transactions(
|
pub fn load_execute_and_commit_transactions(
|
||||||
|
@ -1268,6 +1305,8 @@ impl Bank {
|
||||||
|
|
||||||
pub fn deposit(&self, pubkey: &Pubkey, lamports: u64) {
|
pub fn deposit(&self, pubkey: &Pubkey, lamports: u64) {
|
||||||
let mut account = self.get_account(pubkey).unwrap_or_default();
|
let mut account = self.get_account(pubkey).unwrap_or_default();
|
||||||
|
self.collected_rent
|
||||||
|
.fetch_add(self.rent_collector.update(&mut account), Ordering::Relaxed);
|
||||||
account.lamports += lamports;
|
account.lamports += lamports;
|
||||||
self.store_account(pubkey, &account);
|
self.store_account(pubkey, &account);
|
||||||
}
|
}
|
||||||
|
@ -1625,12 +1664,14 @@ mod tests {
|
||||||
status_cache::MAX_CACHE_ENTRIES,
|
status_cache::MAX_CACHE_ENTRIES,
|
||||||
};
|
};
|
||||||
use bincode::{deserialize_from, serialize_into, serialized_size};
|
use bincode::{deserialize_from, serialize_into, serialized_size};
|
||||||
|
use solana_sdk::instruction::AccountMeta;
|
||||||
|
use solana_sdk::system_program::solana_system_program;
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
account::KeyedAccount,
|
account::KeyedAccount,
|
||||||
clock::DEFAULT_TICKS_PER_SLOT,
|
clock::DEFAULT_TICKS_PER_SLOT,
|
||||||
epoch_schedule::MINIMUM_SLOTS_PER_EPOCH,
|
epoch_schedule::MINIMUM_SLOTS_PER_EPOCH,
|
||||||
genesis_config::create_genesis_config,
|
genesis_config::create_genesis_config,
|
||||||
instruction::InstructionError,
|
instruction::{Instruction, InstructionError},
|
||||||
message::{Message, MessageHeader},
|
message::{Message, MessageHeader},
|
||||||
poh_config::PohConfig,
|
poh_config::PohConfig,
|
||||||
rent::Rent,
|
rent::Rent,
|
||||||
|
@ -1643,7 +1684,7 @@ mod tests {
|
||||||
vote_instruction,
|
vote_instruction,
|
||||||
vote_state::{self, Vote, VoteInit, VoteState, MAX_LOCKOUT_HISTORY},
|
vote_state::{self, Vote, VoteInit, VoteState, MAX_LOCKOUT_HISTORY},
|
||||||
};
|
};
|
||||||
use std::{io::Cursor, time::Duration};
|
use std::{io::Cursor, result, time::Duration};
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1734,6 +1775,401 @@ mod tests {
|
||||||
assert_eq!(bank.capitalization(), bank2.capitalization());
|
assert_eq!(bank.capitalization(), bank2.capitalization());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_credit_debit_rent_no_side_effect_on_hash() {
|
||||||
|
let (mut genesis_block, _mint_keypair) = create_genesis_config(10);
|
||||||
|
let keypair1: Keypair = Keypair::new();
|
||||||
|
let keypair2: Keypair = Keypair::new();
|
||||||
|
let keypair3: Keypair = Keypair::new();
|
||||||
|
let keypair4: Keypair = Keypair::new();
|
||||||
|
|
||||||
|
// Transaction between these two keypairs will fail
|
||||||
|
let keypair5: Keypair = Keypair::new();
|
||||||
|
let keypair6: Keypair = Keypair::new();
|
||||||
|
|
||||||
|
genesis_block.rent = Rent {
|
||||||
|
lamports_per_byte_year: 1,
|
||||||
|
exemption_threshold: 21.0,
|
||||||
|
burn_percent: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
let root_bank = Arc::new(Bank::new(&genesis_block));
|
||||||
|
let bank = Bank::new_from_parent(
|
||||||
|
&root_bank,
|
||||||
|
&Pubkey::default(),
|
||||||
|
2 * (SECONDS_PER_YEAR
|
||||||
|
// * (ns/s)/(ns/tick) / ticks/slot = 1/s/1/tick = ticks/s
|
||||||
|
*(1_000_000_000.0 / duration_as_ns(&genesis_block.poh_config.target_tick_duration) as f64)
|
||||||
|
// / ticks/slot
|
||||||
|
/ genesis_block.ticks_per_slot as f64) as u64,
|
||||||
|
);
|
||||||
|
|
||||||
|
let root_bank_2 = Arc::new(Bank::new(&genesis_block));
|
||||||
|
let bank_with_success_txs = Bank::new_from_parent(
|
||||||
|
&root_bank_2,
|
||||||
|
&Pubkey::default(),
|
||||||
|
2 * (SECONDS_PER_YEAR
|
||||||
|
// * (ns/s)/(ns/tick) / ticks/slot = 1/s/1/tick = ticks/s
|
||||||
|
*(1_000_000_000.0 / duration_as_ns(&genesis_block.poh_config.target_tick_duration) as f64)
|
||||||
|
// / ticks/slot
|
||||||
|
/ genesis_block.ticks_per_slot as f64) as u64,
|
||||||
|
);
|
||||||
|
assert_eq!(bank.last_blockhash(), genesis_block.hash());
|
||||||
|
|
||||||
|
// Initialize credit-debit and credit only accounts
|
||||||
|
let account1 = Account::new(264, 1, &Pubkey::default());
|
||||||
|
let account2 = Account::new(264, 1, &Pubkey::default());
|
||||||
|
let account3 = Account::new(264, 1, &Pubkey::default());
|
||||||
|
let account4 = Account::new(264, 1, &Pubkey::default());
|
||||||
|
let account5 = Account::new(10, 1, &Pubkey::default());
|
||||||
|
let account6 = Account::new(10, 1, &Pubkey::default());
|
||||||
|
|
||||||
|
bank.store_account(&keypair1.pubkey(), &account1);
|
||||||
|
bank.store_account(&keypair2.pubkey(), &account2);
|
||||||
|
bank.store_account(&keypair3.pubkey(), &account3);
|
||||||
|
bank.store_account(&keypair4.pubkey(), &account4);
|
||||||
|
bank.store_account(&keypair5.pubkey(), &account5);
|
||||||
|
bank.store_account(&keypair6.pubkey(), &account6);
|
||||||
|
|
||||||
|
bank_with_success_txs.store_account(&keypair1.pubkey(), &account1);
|
||||||
|
bank_with_success_txs.store_account(&keypair2.pubkey(), &account2);
|
||||||
|
bank_with_success_txs.store_account(&keypair3.pubkey(), &account3);
|
||||||
|
bank_with_success_txs.store_account(&keypair4.pubkey(), &account4);
|
||||||
|
bank_with_success_txs.store_account(&keypair5.pubkey(), &account5);
|
||||||
|
bank_with_success_txs.store_account(&keypair6.pubkey(), &account6);
|
||||||
|
|
||||||
|
// Make native instruction loader rent exempt
|
||||||
|
let system_program_id = solana_system_program().1;
|
||||||
|
let mut system_program_account = bank.get_account(&system_program_id).unwrap();
|
||||||
|
system_program_account.lamports =
|
||||||
|
bank.get_minimum_balance_for_rent_exemption(system_program_account.data.len());
|
||||||
|
bank.store_account(&system_program_id, &system_program_account);
|
||||||
|
bank_with_success_txs.store_account(&system_program_id, &system_program_account);
|
||||||
|
|
||||||
|
let t1 =
|
||||||
|
system_transaction::transfer(&keypair1, &keypair2.pubkey(), 1, genesis_block.hash());
|
||||||
|
let t2 =
|
||||||
|
system_transaction::transfer(&keypair3, &keypair4.pubkey(), 1, genesis_block.hash());
|
||||||
|
let t3 =
|
||||||
|
system_transaction::transfer(&keypair5, &keypair6.pubkey(), 1, genesis_block.hash());
|
||||||
|
|
||||||
|
let res = bank.process_transactions(&vec![t1.clone(), t2.clone(), t3.clone()]);
|
||||||
|
|
||||||
|
assert_eq!(res.len(), 3);
|
||||||
|
assert_eq!(res[0], Ok(()));
|
||||||
|
assert_eq!(res[1], Ok(()));
|
||||||
|
assert_eq!(res[2], Err(TransactionError::AccountNotFound));
|
||||||
|
|
||||||
|
bank.freeze();
|
||||||
|
|
||||||
|
let rwlockguard_bank_hash = bank.hash.read().unwrap();
|
||||||
|
let bank_hash = rwlockguard_bank_hash.as_ref();
|
||||||
|
|
||||||
|
let res = bank_with_success_txs.process_transactions(&vec![t2.clone(), t1.clone()]);
|
||||||
|
|
||||||
|
assert_eq!(res.len(), 2);
|
||||||
|
assert_eq!(res[0], Ok(()));
|
||||||
|
assert_eq!(res[1], Ok(()));
|
||||||
|
|
||||||
|
bank_with_success_txs.freeze();
|
||||||
|
|
||||||
|
let rwlockguard_bank_with_success_txs_hash = bank_with_success_txs.hash.read().unwrap();
|
||||||
|
let bank_with_success_txs_hash = rwlockguard_bank_with_success_txs_hash.as_ref();
|
||||||
|
|
||||||
|
assert_eq!(bank_with_success_txs_hash, bank_hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
enum MockInstruction {
|
||||||
|
Deduction,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mock_process_instruction(
|
||||||
|
_program_id: &Pubkey,
|
||||||
|
keyed_accounts: &mut [KeyedAccount],
|
||||||
|
data: &[u8],
|
||||||
|
) -> result::Result<(), InstructionError> {
|
||||||
|
if let Ok(instruction) = bincode::deserialize(data) {
|
||||||
|
match instruction {
|
||||||
|
MockInstruction::Deduction => {
|
||||||
|
keyed_accounts[1].account.lamports += 1;
|
||||||
|
keyed_accounts[2].account.lamports -= 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(InstructionError::InvalidInstructionData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_mock_transaction(
|
||||||
|
payer: &Keypair,
|
||||||
|
keypair1: &Keypair,
|
||||||
|
keypair2: &Keypair,
|
||||||
|
read_only_keypair: &Keypair,
|
||||||
|
mock_program_id: Pubkey,
|
||||||
|
recent_blockhash: Hash,
|
||||||
|
) -> Transaction {
|
||||||
|
let account_metas = vec![
|
||||||
|
AccountMeta::new(payer.pubkey(), true),
|
||||||
|
AccountMeta::new(keypair1.pubkey(), true),
|
||||||
|
AccountMeta::new(keypair2.pubkey(), true),
|
||||||
|
AccountMeta::new_readonly(read_only_keypair.pubkey(), false),
|
||||||
|
];
|
||||||
|
let deduct_instruction =
|
||||||
|
Instruction::new(mock_program_id, &MockInstruction::Deduction, account_metas);
|
||||||
|
Transaction::new_signed_with_payer(
|
||||||
|
vec![deduct_instruction],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
&[payer, keypair1, keypair2],
|
||||||
|
recent_blockhash,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store_accounts_for_rent_test(
|
||||||
|
bank: &Bank,
|
||||||
|
keypairs: &mut Vec<Keypair>,
|
||||||
|
mock_program_id: Pubkey,
|
||||||
|
) {
|
||||||
|
let mut account_pairs: Vec<(Pubkey, Account)> = Vec::with_capacity(keypairs.len() - 1);
|
||||||
|
account_pairs.push((
|
||||||
|
keypairs[0].pubkey(),
|
||||||
|
Account::new(51224, 1, &Pubkey::default()),
|
||||||
|
));
|
||||||
|
account_pairs.push((
|
||||||
|
keypairs[1].pubkey(),
|
||||||
|
Account::new(51224, 1, &Pubkey::default()),
|
||||||
|
));
|
||||||
|
account_pairs.push((
|
||||||
|
keypairs[2].pubkey(),
|
||||||
|
Account::new(51224, 1, &Pubkey::default()),
|
||||||
|
));
|
||||||
|
account_pairs.push((
|
||||||
|
keypairs[3].pubkey(),
|
||||||
|
Account::new(51224, 1, &Pubkey::default()),
|
||||||
|
));
|
||||||
|
account_pairs.push((
|
||||||
|
keypairs[4].pubkey(),
|
||||||
|
Account::new(10, 1, &Pubkey::default()),
|
||||||
|
));
|
||||||
|
account_pairs.push((
|
||||||
|
keypairs[5].pubkey(),
|
||||||
|
Account::new(10, 1, &Pubkey::default()),
|
||||||
|
));
|
||||||
|
account_pairs.push((
|
||||||
|
keypairs[6].pubkey(),
|
||||||
|
Account::new(102_468, 1, &Pubkey::default()),
|
||||||
|
));
|
||||||
|
|
||||||
|
account_pairs.push((
|
||||||
|
keypairs[8].pubkey(),
|
||||||
|
Account::new(52153, 1, &Pubkey::default()),
|
||||||
|
));
|
||||||
|
account_pairs.push((
|
||||||
|
keypairs[9].pubkey(),
|
||||||
|
Account::new(10, 1, &Pubkey::default()),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Feeding to MockProgram to test read only rent behaviour
|
||||||
|
account_pairs.push((
|
||||||
|
keypairs[10].pubkey(),
|
||||||
|
Account::new(51225, 1, &Pubkey::default()),
|
||||||
|
));
|
||||||
|
account_pairs.push((
|
||||||
|
keypairs[11].pubkey(),
|
||||||
|
Account::new(51225, 1, &mock_program_id),
|
||||||
|
));
|
||||||
|
account_pairs.push((
|
||||||
|
keypairs[12].pubkey(),
|
||||||
|
Account::new(51225, 1, &mock_program_id),
|
||||||
|
));
|
||||||
|
account_pairs.push((
|
||||||
|
keypairs[13].pubkey(),
|
||||||
|
Account::new(14, 23, &mock_program_id),
|
||||||
|
));
|
||||||
|
|
||||||
|
for account_pair in account_pairs.iter() {
|
||||||
|
bank.store_account(&account_pair.0, &account_pair.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_child_bank_for_rent_test(
|
||||||
|
root_bank: &Arc<Bank>,
|
||||||
|
genesis_block: &GenesisConfig,
|
||||||
|
mock_program_id: Pubkey,
|
||||||
|
) -> Bank {
|
||||||
|
let mut bank = Bank::new_from_parent(
|
||||||
|
root_bank,
|
||||||
|
&Pubkey::default(),
|
||||||
|
2 * (SECONDS_PER_YEAR
|
||||||
|
// * (ns/s)/(ns/tick) / ticks/slot = 1/s/1/tick = ticks/s
|
||||||
|
*(1_000_000_000.0 / duration_as_ns(&genesis_block.poh_config.target_tick_duration) as f64)
|
||||||
|
// / ticks/slot
|
||||||
|
/ genesis_block.ticks_per_slot as f64) as u64,
|
||||||
|
);
|
||||||
|
bank.rent_collector.slots_per_year = 421_812.0;
|
||||||
|
bank.add_instruction_processor(mock_program_id, mock_process_instruction);
|
||||||
|
|
||||||
|
let system_program_id = solana_system_program().1;
|
||||||
|
let mut system_program_account = bank.get_account(&system_program_id).unwrap();
|
||||||
|
system_program_account.lamports =
|
||||||
|
bank.get_minimum_balance_for_rent_exemption(system_program_account.data.len());
|
||||||
|
bank.store_account(&system_program_id, &system_program_account);
|
||||||
|
|
||||||
|
bank
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[allow(clippy::cognitive_complexity)]
|
||||||
|
fn test_rent() {
|
||||||
|
let mock_program_id = Pubkey::new(&[2u8; 32]);
|
||||||
|
|
||||||
|
let (mut genesis_block, _mint_keypair) = create_genesis_config(10);
|
||||||
|
let mut keypairs: Vec<Keypair> = Vec::with_capacity(14);
|
||||||
|
for _i in 0..14 {
|
||||||
|
keypairs.push(Keypair::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
genesis_block.rent = Rent {
|
||||||
|
lamports_per_byte_year: 1,
|
||||||
|
exemption_threshold: 1000.0,
|
||||||
|
burn_percent: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
let root_bank = Arc::new(Bank::new(&genesis_block));
|
||||||
|
let bank = create_child_bank_for_rent_test(&root_bank, &genesis_block, mock_program_id);
|
||||||
|
|
||||||
|
assert_eq!(bank.last_blockhash(), genesis_block.hash());
|
||||||
|
|
||||||
|
store_accounts_for_rent_test(&bank, &mut keypairs, mock_program_id);
|
||||||
|
|
||||||
|
let t1 = system_transaction::transfer(
|
||||||
|
&keypairs[0],
|
||||||
|
&keypairs[1].pubkey(),
|
||||||
|
1,
|
||||||
|
genesis_block.hash(),
|
||||||
|
);
|
||||||
|
let t2 = system_transaction::transfer(
|
||||||
|
&keypairs[2],
|
||||||
|
&keypairs[3].pubkey(),
|
||||||
|
1,
|
||||||
|
genesis_block.hash(),
|
||||||
|
);
|
||||||
|
let t3 = system_transaction::transfer(
|
||||||
|
&keypairs[4],
|
||||||
|
&keypairs[5].pubkey(),
|
||||||
|
1,
|
||||||
|
genesis_block.hash(),
|
||||||
|
);
|
||||||
|
let t4 = system_transaction::transfer(
|
||||||
|
&keypairs[6],
|
||||||
|
&keypairs[7].pubkey(),
|
||||||
|
51223,
|
||||||
|
genesis_block.hash(),
|
||||||
|
);
|
||||||
|
let t5 = system_transaction::transfer(
|
||||||
|
&keypairs[8],
|
||||||
|
&keypairs[9].pubkey(),
|
||||||
|
929,
|
||||||
|
genesis_block.hash(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let t6 = create_mock_transaction(
|
||||||
|
&keypairs[10],
|
||||||
|
&keypairs[11],
|
||||||
|
&keypairs[12],
|
||||||
|
&keypairs[13],
|
||||||
|
mock_program_id,
|
||||||
|
genesis_block.hash(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = bank.process_transactions(&[
|
||||||
|
t6.clone(),
|
||||||
|
t5.clone(),
|
||||||
|
t1.clone(),
|
||||||
|
t2.clone(),
|
||||||
|
t3.clone(),
|
||||||
|
t4.clone(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_eq!(res.len(), 6);
|
||||||
|
assert_eq!(res[0], Ok(()));
|
||||||
|
assert_eq!(res[1], Ok(()));
|
||||||
|
assert_eq!(res[2], Ok(()));
|
||||||
|
assert_eq!(res[3], Ok(()));
|
||||||
|
assert_eq!(res[4], Err(TransactionError::AccountNotFound));
|
||||||
|
assert_eq!(res[5], Ok(()));
|
||||||
|
|
||||||
|
bank.freeze();
|
||||||
|
|
||||||
|
let mut rent_collected = 0;
|
||||||
|
|
||||||
|
// 51224 - 51222(Rent) - 1(transfer)
|
||||||
|
assert_eq!(bank.get_balance(&keypairs[0].pubkey()), 1);
|
||||||
|
rent_collected += 51222;
|
||||||
|
|
||||||
|
// 51224 - 51222(Rent) - 1(transfer)
|
||||||
|
assert_eq!(bank.get_balance(&keypairs[1].pubkey()), 3);
|
||||||
|
rent_collected += 51222;
|
||||||
|
|
||||||
|
// 51224 - 51222(Rent) - 1(transfer)
|
||||||
|
assert_eq!(bank.get_balance(&keypairs[2].pubkey()), 1);
|
||||||
|
rent_collected += 51222;
|
||||||
|
|
||||||
|
// 51224 - 51222(Rent) - 1(transfer)
|
||||||
|
assert_eq!(bank.get_balance(&keypairs[3].pubkey()), 3);
|
||||||
|
rent_collected += 51222;
|
||||||
|
|
||||||
|
// No rent deducted
|
||||||
|
assert_eq!(bank.get_balance(&keypairs[4].pubkey()), 10);
|
||||||
|
assert_eq!(bank.get_balance(&keypairs[5].pubkey()), 10);
|
||||||
|
|
||||||
|
// 102_468 - 51222(Rent) - 51223(transfer)
|
||||||
|
assert_eq!(bank.get_balance(&keypairs[6].pubkey()), 23);
|
||||||
|
rent_collected += 51222;
|
||||||
|
|
||||||
|
// 0 + 51223(transfer) - 917(Rent)
|
||||||
|
assert_eq!(bank.get_balance(&keypairs[7].pubkey()), 50306);
|
||||||
|
// Epoch should be updated
|
||||||
|
// Rent deducted on store side
|
||||||
|
let account8 = bank.get_account(&keypairs[7].pubkey()).unwrap();
|
||||||
|
// Epoch should be set correctly.
|
||||||
|
assert_eq!(account8.rent_epoch, bank.epoch + 1);
|
||||||
|
rent_collected += 917;
|
||||||
|
|
||||||
|
// 52153 - 51222(Rent) - 929(Transfer)
|
||||||
|
assert_eq!(bank.get_balance(&keypairs[8].pubkey()), 2);
|
||||||
|
rent_collected += 51222;
|
||||||
|
|
||||||
|
let account10 = bank.get_account(&keypairs[9].pubkey()).unwrap();
|
||||||
|
// Account was overwritten at load time, since it didn't have sufficient balance to pay rent
|
||||||
|
// Then, at store time we deducted 917 rent for the current epoch, once it has balance
|
||||||
|
assert_eq!(account10.rent_epoch, bank.epoch + 1);
|
||||||
|
// account data is blank now
|
||||||
|
assert_eq!(account10.data.len(), 0);
|
||||||
|
// 10 - 10(Rent) + 929(Transfer) - 917(Rent)
|
||||||
|
assert_eq!(account10.lamports, 12);
|
||||||
|
rent_collected += 927;
|
||||||
|
|
||||||
|
// 51225 - 51222(Rent)
|
||||||
|
assert_eq!(bank.get_balance(&keypairs[10].pubkey()), 3);
|
||||||
|
rent_collected += 51222;
|
||||||
|
|
||||||
|
// 51225 - 51222(Rent) + 1(Addition by program)
|
||||||
|
assert_eq!(bank.get_balance(&keypairs[11].pubkey()), 4);
|
||||||
|
rent_collected += 51222;
|
||||||
|
|
||||||
|
// 51225 - 51222(Rent) - 1(Deduction by program)
|
||||||
|
assert_eq!(bank.get_balance(&keypairs[12].pubkey()), 2);
|
||||||
|
rent_collected += 51222;
|
||||||
|
|
||||||
|
// No rent for read-only account
|
||||||
|
assert_eq!(bank.get_balance(&keypairs[13].pubkey()), 14);
|
||||||
|
|
||||||
|
// Bank's collected rent should be sum of rent collected from all accounts
|
||||||
|
assert_eq!(bank.collected_rent.load(Ordering::Relaxed), rent_collected);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bank_update_rewards() {
|
fn test_bank_update_rewards() {
|
||||||
// create a bank that ticks really slowly...
|
// create a bank that ticks really slowly...
|
||||||
|
|
Loading…
Reference in New Issue