use cost model to limit new account creation (#21369)
* use cost model to limit new account creation * handle every system instruction * remove & * simplify match * simplify match * add datapoint for account data size * add postgres error handling * handle accounts:unlock_accounts
This commit is contained in:
committed by
GitHub
parent
025a5a3b9c
commit
90f41fd9b7
@ -330,6 +330,7 @@ pub enum DbTransactionErrorCode {
|
|||||||
WouldExceedMaxBlockCostLimit,
|
WouldExceedMaxBlockCostLimit,
|
||||||
UnsupportedVersion,
|
UnsupportedVersion,
|
||||||
InvalidWritableAccount,
|
InvalidWritableAccount,
|
||||||
|
WouldExceedMaxAccountDataCostLimit,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&TransactionError> for DbTransactionErrorCode {
|
impl From<&TransactionError> for DbTransactionErrorCode {
|
||||||
@ -358,6 +359,9 @@ impl From<&TransactionError> for DbTransactionErrorCode {
|
|||||||
TransactionError::WouldExceedMaxBlockCostLimit => Self::WouldExceedMaxBlockCostLimit,
|
TransactionError::WouldExceedMaxBlockCostLimit => Self::WouldExceedMaxBlockCostLimit,
|
||||||
TransactionError::UnsupportedVersion => Self::UnsupportedVersion,
|
TransactionError::UnsupportedVersion => Self::UnsupportedVersion,
|
||||||
TransactionError::InvalidWritableAccount => Self::InvalidWritableAccount,
|
TransactionError::InvalidWritableAccount => Self::InvalidWritableAccount,
|
||||||
|
TransactionError::WouldExceedMaxAccountDataCostLimit => {
|
||||||
|
Self::WouldExceedMaxAccountDataCostLimit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -949,8 +949,8 @@ impl BankingStage {
|
|||||||
bank.prepare_sanitized_batch_with_results(txs, transactions_qos_results.into_iter());
|
bank.prepare_sanitized_batch_with_results(txs, transactions_qos_results.into_iter());
|
||||||
lock_time.stop();
|
lock_time.stop();
|
||||||
|
|
||||||
// retryable_txs includes AccountInUse, WouldExceedMaxBlockCostLimit and
|
// retryable_txs includes AccountInUse, WouldExceedMaxBlockCostLimit
|
||||||
// WouldExceedMaxAccountCostLimit
|
// WouldExceedMaxAccountCostLimit, and WouldExceedMaxAccountDataCostLimit
|
||||||
let (result, mut retryable_txs) = Self::process_and_record_transactions_locked(
|
let (result, mut retryable_txs) = Self::process_and_record_transactions_locked(
|
||||||
bank,
|
bank,
|
||||||
poh,
|
poh,
|
||||||
|
@ -133,6 +133,10 @@ impl QosService {
|
|||||||
self.metrics.retried_txs_per_account_limit_count.fetch_add(1, Ordering::Relaxed);
|
self.metrics.retried_txs_per_account_limit_count.fetch_add(1, Ordering::Relaxed);
|
||||||
Err(TransactionError::WouldExceedMaxAccountCostLimit)
|
Err(TransactionError::WouldExceedMaxAccountCostLimit)
|
||||||
}
|
}
|
||||||
|
CostTrackerError::WouldExceedAccountDataMaxLimit => {
|
||||||
|
self.metrics.retried_txs_per_account_data_limit_count.fetch_add(1, Ordering::Relaxed);
|
||||||
|
Err(TransactionError::WouldExceedMaxAccountDataCostLimit)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -165,6 +169,7 @@ struct QosServiceMetrics {
|
|||||||
selected_txs_count: AtomicU64,
|
selected_txs_count: AtomicU64,
|
||||||
retried_txs_per_block_limit_count: AtomicU64,
|
retried_txs_per_block_limit_count: AtomicU64,
|
||||||
retried_txs_per_account_limit_count: AtomicU64,
|
retried_txs_per_account_limit_count: AtomicU64,
|
||||||
|
retried_txs_per_account_data_limit_count: AtomicU64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QosServiceMetrics {
|
impl QosServiceMetrics {
|
||||||
@ -204,6 +209,12 @@ impl QosServiceMetrics {
|
|||||||
.swap(0, Ordering::Relaxed) as i64,
|
.swap(0, Ordering::Relaxed) as i64,
|
||||||
i64
|
i64
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"retried_txs_per_account_data_limit_count",
|
||||||
|
self.retried_txs_per_account_data_limit_count
|
||||||
|
.swap(0, Ordering::Relaxed) as i64,
|
||||||
|
i64
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1032,11 +1032,12 @@ impl Accounts {
|
|||||||
let keys: Vec<_> = txs
|
let keys: Vec<_> = txs
|
||||||
.zip(results)
|
.zip(results)
|
||||||
.filter_map(|(tx, res)| match res {
|
.filter_map(|(tx, res)| match res {
|
||||||
Err(TransactionError::AccountInUse) => None,
|
Err(TransactionError::AccountInUse)
|
||||||
Err(TransactionError::SanitizeFailure) => None,
|
| Err(TransactionError::SanitizeFailure)
|
||||||
Err(TransactionError::AccountLoadedTwice) => None,
|
| Err(TransactionError::AccountLoadedTwice)
|
||||||
Err(TransactionError::WouldExceedMaxBlockCostLimit) => None,
|
| Err(TransactionError::WouldExceedMaxBlockCostLimit)
|
||||||
Err(TransactionError::WouldExceedMaxAccountCostLimit) => None,
|
| Err(TransactionError::WouldExceedMaxAccountCostLimit)
|
||||||
|
| Err(TransactionError::WouldExceedMaxAccountDataCostLimit) => None,
|
||||||
_ => Some(tx.get_account_locks(demote_program_write_locks)),
|
_ => Some(tx.get_account_locks(demote_program_write_locks)),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -240,7 +240,7 @@ impl ExecuteTimings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BankStatusCache = StatusCache<Result<()>>;
|
type BankStatusCache = StatusCache<Result<()>>;
|
||||||
#[frozen_abi(digest = "32EjVUc6shHHVPpsnBAVfyBziMgyFzH8qxisLwmwwdS1")]
|
#[frozen_abi(digest = "GcfJc94Hb3s7gzF7Uh4YxLSiQf1MvUtMmtU45BvinkVT")]
|
||||||
pub type BankSlotDelta = SlotDelta<Result<()>>;
|
pub type BankSlotDelta = SlotDelta<Result<()>>;
|
||||||
|
|
||||||
// Eager rent collection repeats in cyclic manner.
|
// Eager rent collection repeats in cyclic manner.
|
||||||
@ -3525,7 +3525,8 @@ impl Bank {
|
|||||||
Some(index)
|
Some(index)
|
||||||
}
|
}
|
||||||
Err(TransactionError::WouldExceedMaxBlockCostLimit)
|
Err(TransactionError::WouldExceedMaxBlockCostLimit)
|
||||||
| Err(TransactionError::WouldExceedMaxAccountCostLimit) => Some(index),
|
| Err(TransactionError::WouldExceedMaxAccountCostLimit)
|
||||||
|
| Err(TransactionError::WouldExceedMaxAccountDataCostLimit) => Some(index),
|
||||||
Err(_) => None,
|
Err(_) => None,
|
||||||
Ok(_) => None,
|
Ok(_) => None,
|
||||||
})
|
})
|
||||||
|
@ -58,3 +58,6 @@ pub const MAX_BLOCK_UNITS: u64 =
|
|||||||
/// limit is to prevent too many transactions write to same account, threrefore
|
/// limit is to prevent too many transactions write to same account, threrefore
|
||||||
/// reduce block's paralellism.
|
/// reduce block's paralellism.
|
||||||
pub const MAX_WRITABLE_ACCOUNT_UNITS: u64 = MAX_BLOCK_REPLAY_TIME_US * COMPUTE_UNIT_TO_US_RATIO;
|
pub const MAX_WRITABLE_ACCOUNT_UNITS: u64 = MAX_BLOCK_REPLAY_TIME_US * COMPUTE_UNIT_TO_US_RATIO;
|
||||||
|
|
||||||
|
/// max len of account data in a slot (bytes)
|
||||||
|
pub const MAX_ACCOUNT_DATA_LEN: u64 = 100_000_000;
|
||||||
|
@ -10,7 +10,10 @@ use {
|
|||||||
execute_cost_table::ExecuteCostTable,
|
execute_cost_table::ExecuteCostTable,
|
||||||
},
|
},
|
||||||
log::*,
|
log::*,
|
||||||
solana_sdk::{pubkey::Pubkey, transaction::SanitizedTransaction},
|
solana_sdk::{
|
||||||
|
instruction::CompiledInstruction, program_utils::limited_deserialize, pubkey::Pubkey,
|
||||||
|
system_instruction::SystemInstruction, system_program, transaction::SanitizedTransaction,
|
||||||
|
},
|
||||||
std::collections::HashMap,
|
std::collections::HashMap,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -27,6 +30,7 @@ pub struct TransactionCost {
|
|||||||
// `cost_weight` is a multiplier could be applied to transaction cost,
|
// `cost_weight` is a multiplier could be applied to transaction cost,
|
||||||
// if set to zero allows the transaction to bypass cost limit check.
|
// if set to zero allows the transaction to bypass cost limit check.
|
||||||
pub cost_weight: u32,
|
pub cost_weight: u32,
|
||||||
|
pub account_data_size: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TransactionCost {
|
impl Default for TransactionCost {
|
||||||
@ -38,6 +42,7 @@ impl Default for TransactionCost {
|
|||||||
data_bytes_cost: 0u64,
|
data_bytes_cost: 0u64,
|
||||||
execution_cost: 0u64,
|
execution_cost: 0u64,
|
||||||
cost_weight: 1u32,
|
cost_weight: 1u32,
|
||||||
|
account_data_size: 0u64,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,6 +123,7 @@ impl CostModel {
|
|||||||
tx_cost.data_bytes_cost = self.get_data_bytes_cost(transaction);
|
tx_cost.data_bytes_cost = self.get_data_bytes_cost(transaction);
|
||||||
tx_cost.execution_cost = self.get_transaction_cost(transaction);
|
tx_cost.execution_cost = self.get_transaction_cost(transaction);
|
||||||
tx_cost.cost_weight = self.calculate_cost_weight(transaction);
|
tx_cost.cost_weight = self.calculate_cost_weight(transaction);
|
||||||
|
tx_cost.account_data_size = self.calculate_account_data_size(transaction);
|
||||||
|
|
||||||
debug!("transaction {:?} has cost {:?}", transaction, tx_cost);
|
debug!("transaction {:?} has cost {:?}", transaction, tx_cost);
|
||||||
tx_cost
|
tx_cost
|
||||||
@ -201,6 +207,59 @@ impl CostModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn calculate_account_data_size_on_deserialized_system_instruction(
|
||||||
|
instruction: SystemInstruction,
|
||||||
|
) -> u64 {
|
||||||
|
match instruction {
|
||||||
|
SystemInstruction::CreateAccount {
|
||||||
|
lamports: _lamports,
|
||||||
|
space,
|
||||||
|
owner: _owner,
|
||||||
|
} => space,
|
||||||
|
SystemInstruction::CreateAccountWithSeed {
|
||||||
|
base: _base,
|
||||||
|
seed: _seed,
|
||||||
|
lamports: _lamports,
|
||||||
|
space,
|
||||||
|
owner: _owner,
|
||||||
|
} => space,
|
||||||
|
SystemInstruction::Allocate { space } => space,
|
||||||
|
SystemInstruction::AllocateWithSeed {
|
||||||
|
base: _base,
|
||||||
|
seed: _seed,
|
||||||
|
space,
|
||||||
|
owner: _owner,
|
||||||
|
} => space,
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_account_data_size_on_instruction(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
instruction: &CompiledInstruction,
|
||||||
|
) -> u64 {
|
||||||
|
if program_id == &system_program::id() {
|
||||||
|
if let Ok(instruction) = limited_deserialize(&instruction.data) {
|
||||||
|
return Self::calculate_account_data_size_on_deserialized_system_instruction(
|
||||||
|
instruction,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// eventually, potentially determine account data size of all writable accounts
|
||||||
|
/// at the moment, calculate account data size of account creation
|
||||||
|
fn calculate_account_data_size(&self, transaction: &SanitizedTransaction) -> u64 {
|
||||||
|
transaction
|
||||||
|
.message()
|
||||||
|
.program_instructions_iter()
|
||||||
|
.map(|(program_id, instruction)| {
|
||||||
|
Self::calculate_account_data_size_on_instruction(program_id, instruction)
|
||||||
|
})
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
fn calculate_cost_weight(&self, transaction: &SanitizedTransaction) -> u32 {
|
fn calculate_cost_weight(&self, transaction: &SanitizedTransaction) -> u32 {
|
||||||
if is_simple_vote_transaction(transaction) {
|
if is_simple_vote_transaction(transaction) {
|
||||||
// vote has zero cost weight, so it bypasses block cost limit checking
|
// vote has zero cost weight, so it bypasses block cost limit checking
|
||||||
@ -272,6 +331,53 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cost_model_data_len_cost() {
|
||||||
|
let lamports = 0;
|
||||||
|
let owner = Pubkey::default();
|
||||||
|
let seed = String::default();
|
||||||
|
let space = 100;
|
||||||
|
let base = Pubkey::default();
|
||||||
|
for instruction in [
|
||||||
|
SystemInstruction::CreateAccount {
|
||||||
|
lamports,
|
||||||
|
space,
|
||||||
|
owner,
|
||||||
|
},
|
||||||
|
SystemInstruction::CreateAccountWithSeed {
|
||||||
|
base,
|
||||||
|
seed: seed.clone(),
|
||||||
|
lamports,
|
||||||
|
space,
|
||||||
|
owner,
|
||||||
|
},
|
||||||
|
SystemInstruction::Allocate { space },
|
||||||
|
SystemInstruction::AllocateWithSeed {
|
||||||
|
base,
|
||||||
|
seed,
|
||||||
|
space,
|
||||||
|
owner,
|
||||||
|
},
|
||||||
|
] {
|
||||||
|
assert_eq!(
|
||||||
|
space,
|
||||||
|
CostModel::calculate_account_data_size_on_deserialized_system_instruction(
|
||||||
|
instruction
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
0,
|
||||||
|
CostModel::calculate_account_data_size_on_deserialized_system_instruction(
|
||||||
|
SystemInstruction::TransferWithSeed {
|
||||||
|
lamports,
|
||||||
|
from_seed: String::default(),
|
||||||
|
from_owner: Pubkey::default(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cost_model_simple_transaction() {
|
fn test_cost_model_simple_transaction() {
|
||||||
let (mint_keypair, start_hash) = test_setup();
|
let (mint_keypair, start_hash) = test_setup();
|
||||||
|
@ -18,6 +18,8 @@ pub enum CostTrackerError {
|
|||||||
|
|
||||||
/// would exceed account max limit
|
/// would exceed account max limit
|
||||||
WouldExceedAccountMaxLimit,
|
WouldExceedAccountMaxLimit,
|
||||||
|
|
||||||
|
WouldExceedAccountDataMaxLimit,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(AbiExample, Debug)]
|
#[derive(AbiExample, Debug)]
|
||||||
@ -27,6 +29,7 @@ pub struct CostTracker {
|
|||||||
cost_by_writable_accounts: HashMap<Pubkey, u64>,
|
cost_by_writable_accounts: HashMap<Pubkey, u64>,
|
||||||
block_cost: u64,
|
block_cost: u64,
|
||||||
transaction_count: u64,
|
transaction_count: u64,
|
||||||
|
account_data_size: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CostTracker {
|
impl Default for CostTracker {
|
||||||
@ -44,6 +47,7 @@ impl CostTracker {
|
|||||||
cost_by_writable_accounts: HashMap::with_capacity(WRITABLE_ACCOUNTS_PER_BLOCK),
|
cost_by_writable_accounts: HashMap::with_capacity(WRITABLE_ACCOUNTS_PER_BLOCK),
|
||||||
block_cost: 0,
|
block_cost: 0,
|
||||||
transaction_count: 0,
|
transaction_count: 0,
|
||||||
|
account_data_size: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +62,11 @@ impl CostTracker {
|
|||||||
_transaction: &SanitizedTransaction,
|
_transaction: &SanitizedTransaction,
|
||||||
tx_cost: &TransactionCost,
|
tx_cost: &TransactionCost,
|
||||||
) -> Result<(), CostTrackerError> {
|
) -> Result<(), CostTrackerError> {
|
||||||
self.would_fit(&tx_cost.writable_accounts, &tx_cost.sum())
|
self.would_fit(
|
||||||
|
&tx_cost.writable_accounts,
|
||||||
|
tx_cost.sum(),
|
||||||
|
tx_cost.account_data_size,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_transaction_cost(
|
pub fn add_transaction_cost(
|
||||||
@ -66,7 +74,11 @@ impl CostTracker {
|
|||||||
_transaction: &SanitizedTransaction,
|
_transaction: &SanitizedTransaction,
|
||||||
tx_cost: &TransactionCost,
|
tx_cost: &TransactionCost,
|
||||||
) {
|
) {
|
||||||
self.add_transaction(&tx_cost.writable_accounts, &tx_cost.sum());
|
self.add_transaction(
|
||||||
|
&tx_cost.writable_accounts,
|
||||||
|
tx_cost.sum(),
|
||||||
|
tx_cost.account_data_size,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_add(
|
pub fn try_add(
|
||||||
@ -75,8 +87,8 @@ impl CostTracker {
|
|||||||
tx_cost: &TransactionCost,
|
tx_cost: &TransactionCost,
|
||||||
) -> Result<u64, CostTrackerError> {
|
) -> Result<u64, CostTrackerError> {
|
||||||
let cost = tx_cost.sum() * tx_cost.cost_weight as u64;
|
let cost = tx_cost.sum() * tx_cost.cost_weight as u64;
|
||||||
self.would_fit(&tx_cost.writable_accounts, &cost)?;
|
self.would_fit(&tx_cost.writable_accounts, cost, tx_cost.account_data_size)?;
|
||||||
self.add_transaction(&tx_cost.writable_accounts, &cost);
|
self.add_transaction(&tx_cost.writable_accounts, cost, tx_cost.account_data_size);
|
||||||
Ok(self.block_cost)
|
Ok(self.block_cost)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,6 +112,7 @@ impl CostTracker {
|
|||||||
),
|
),
|
||||||
("costliest_account", costliest_account.to_string(), String),
|
("costliest_account", costliest_account.to_string(), String),
|
||||||
("costliest_account_cost", costliest_account_cost as i64, i64),
|
("costliest_account_cost", costliest_account_cost as i64, i64),
|
||||||
|
("account_data_size", self.account_data_size, i64),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,17 +129,26 @@ impl CostTracker {
|
|||||||
(costliest_account, costliest_account_cost)
|
(costliest_account, costliest_account_cost)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn would_fit(&self, keys: &[Pubkey], cost: &u64) -> Result<(), CostTrackerError> {
|
fn would_fit(
|
||||||
|
&self,
|
||||||
|
keys: &[Pubkey],
|
||||||
|
cost: u64,
|
||||||
|
account_data_len: u64,
|
||||||
|
) -> Result<(), CostTrackerError> {
|
||||||
// check against the total package cost
|
// check against the total package cost
|
||||||
if self.block_cost + cost > self.block_cost_limit {
|
if self.block_cost + cost > self.block_cost_limit {
|
||||||
return Err(CostTrackerError::WouldExceedBlockMaxLimit);
|
return Err(CostTrackerError::WouldExceedBlockMaxLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the transaction itself is more costly than the account_cost_limit
|
// check if the transaction itself is more costly than the account_cost_limit
|
||||||
if *cost > self.account_cost_limit {
|
if cost > self.account_cost_limit {
|
||||||
return Err(CostTrackerError::WouldExceedAccountMaxLimit);
|
return Err(CostTrackerError::WouldExceedAccountMaxLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.account_data_size.saturating_add(account_data_len) > MAX_ACCOUNT_DATA_LEN {
|
||||||
|
return Err(CostTrackerError::WouldExceedAccountDataMaxLimit);
|
||||||
|
}
|
||||||
|
|
||||||
// check each account against account_cost_limit,
|
// check each account against account_cost_limit,
|
||||||
for account_key in keys.iter() {
|
for account_key in keys.iter() {
|
||||||
match self.cost_by_writable_accounts.get(account_key) {
|
match self.cost_by_writable_accounts.get(account_key) {
|
||||||
@ -144,7 +166,7 @@ impl CostTracker {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_transaction(&mut self, keys: &[Pubkey], cost: &u64) {
|
fn add_transaction(&mut self, keys: &[Pubkey], cost: u64, account_data_size: u64) {
|
||||||
for account_key in keys.iter() {
|
for account_key in keys.iter() {
|
||||||
*self
|
*self
|
||||||
.cost_by_writable_accounts
|
.cost_by_writable_accounts
|
||||||
@ -153,6 +175,7 @@ impl CostTracker {
|
|||||||
}
|
}
|
||||||
self.block_cost += cost;
|
self.block_cost += cost;
|
||||||
self.transaction_count += 1;
|
self.transaction_count += 1;
|
||||||
|
self.account_data_size = self.account_data_size.saturating_add(account_data_size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,11 +235,24 @@ mod tests {
|
|||||||
|
|
||||||
// build testee to have capacity for one simple transaction
|
// build testee to have capacity for one simple transaction
|
||||||
let mut testee = CostTracker::new(cost, cost);
|
let mut testee = CostTracker::new(cost, cost);
|
||||||
assert!(testee.would_fit(&keys, &cost).is_ok());
|
assert!(testee.would_fit(&keys, cost, 0).is_ok());
|
||||||
testee.add_transaction(&keys, &cost);
|
testee.add_transaction(&keys, cost, 0);
|
||||||
assert_eq!(cost, testee.block_cost);
|
assert_eq!(cost, testee.block_cost);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cost_tracker_add_data() {
|
||||||
|
let (mint_keypair, start_hash) = test_setup();
|
||||||
|
let (_tx, keys, cost) = build_simple_transaction(&mint_keypair, &start_hash);
|
||||||
|
|
||||||
|
// build testee to have capacity for one simple transaction
|
||||||
|
let mut testee = CostTracker::new(cost, cost);
|
||||||
|
assert!(testee.would_fit(&keys, cost, 0).is_ok());
|
||||||
|
let old = testee.account_data_size;
|
||||||
|
testee.add_transaction(&keys, cost, 1);
|
||||||
|
assert_eq!(old + 1, testee.account_data_size);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cost_tracker_ok_add_two_same_accounts() {
|
fn test_cost_tracker_ok_add_two_same_accounts() {
|
||||||
let (mint_keypair, start_hash) = test_setup();
|
let (mint_keypair, start_hash) = test_setup();
|
||||||
@ -227,12 +263,12 @@ mod tests {
|
|||||||
// build testee to have capacity for two simple transactions, with same accounts
|
// build testee to have capacity for two simple transactions, with same accounts
|
||||||
let mut testee = CostTracker::new(cost1 + cost2, cost1 + cost2);
|
let mut testee = CostTracker::new(cost1 + cost2, cost1 + cost2);
|
||||||
{
|
{
|
||||||
assert!(testee.would_fit(&keys1, &cost1).is_ok());
|
assert!(testee.would_fit(&keys1, cost1, 0).is_ok());
|
||||||
testee.add_transaction(&keys1, &cost1);
|
testee.add_transaction(&keys1, cost1, 0);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
assert!(testee.would_fit(&keys2, &cost2).is_ok());
|
assert!(testee.would_fit(&keys2, cost2, 0).is_ok());
|
||||||
testee.add_transaction(&keys2, &cost2);
|
testee.add_transaction(&keys2, cost2, 0);
|
||||||
}
|
}
|
||||||
assert_eq!(cost1 + cost2, testee.block_cost);
|
assert_eq!(cost1 + cost2, testee.block_cost);
|
||||||
assert_eq!(1, testee.cost_by_writable_accounts.len());
|
assert_eq!(1, testee.cost_by_writable_accounts.len());
|
||||||
@ -249,12 +285,12 @@ mod tests {
|
|||||||
// build testee to have capacity for two simple transactions, with same accounts
|
// build testee to have capacity for two simple transactions, with same accounts
|
||||||
let mut testee = CostTracker::new(cmp::max(cost1, cost2), cost1 + cost2);
|
let mut testee = CostTracker::new(cmp::max(cost1, cost2), cost1 + cost2);
|
||||||
{
|
{
|
||||||
assert!(testee.would_fit(&keys1, &cost1).is_ok());
|
assert!(testee.would_fit(&keys1, cost1, 0).is_ok());
|
||||||
testee.add_transaction(&keys1, &cost1);
|
testee.add_transaction(&keys1, cost1, 0);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
assert!(testee.would_fit(&keys2, &cost2).is_ok());
|
assert!(testee.would_fit(&keys2, cost2, 0).is_ok());
|
||||||
testee.add_transaction(&keys2, &cost2);
|
testee.add_transaction(&keys2, cost2, 0);
|
||||||
}
|
}
|
||||||
assert_eq!(cost1 + cost2, testee.block_cost);
|
assert_eq!(cost1 + cost2, testee.block_cost);
|
||||||
assert_eq!(2, testee.cost_by_writable_accounts.len());
|
assert_eq!(2, testee.cost_by_writable_accounts.len());
|
||||||
@ -271,12 +307,12 @@ mod tests {
|
|||||||
let mut testee = CostTracker::new(cmp::min(cost1, cost2), cost1 + cost2);
|
let mut testee = CostTracker::new(cmp::min(cost1, cost2), cost1 + cost2);
|
||||||
// should have room for first transaction
|
// should have room for first transaction
|
||||||
{
|
{
|
||||||
assert!(testee.would_fit(&keys1, &cost1).is_ok());
|
assert!(testee.would_fit(&keys1, cost1, 0).is_ok());
|
||||||
testee.add_transaction(&keys1, &cost1);
|
testee.add_transaction(&keys1, cost1, 0);
|
||||||
}
|
}
|
||||||
// but no more sapce on the same chain (same signer account)
|
// but no more sapce on the same chain (same signer account)
|
||||||
{
|
{
|
||||||
assert!(testee.would_fit(&keys2, &cost2).is_err());
|
assert!(testee.would_fit(&keys2, cost2, 0).is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,15 +328,34 @@ mod tests {
|
|||||||
let mut testee = CostTracker::new(cmp::max(cost1, cost2), cost1 + cost2 - 1);
|
let mut testee = CostTracker::new(cmp::max(cost1, cost2), cost1 + cost2 - 1);
|
||||||
// should have room for first transaction
|
// should have room for first transaction
|
||||||
{
|
{
|
||||||
assert!(testee.would_fit(&keys1, &cost1).is_ok());
|
assert!(testee.would_fit(&keys1, cost1, 0).is_ok());
|
||||||
testee.add_transaction(&keys1, &cost1);
|
testee.add_transaction(&keys1, cost1, 0);
|
||||||
}
|
}
|
||||||
// but no more room for package as whole
|
// but no more room for package as whole
|
||||||
{
|
{
|
||||||
assert!(testee.would_fit(&keys2, &cost2).is_err());
|
assert!(testee.would_fit(&keys2, cost2, 0).is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cost_tracker_reach_data_limit() {
|
||||||
|
let (mint_keypair, start_hash) = test_setup();
|
||||||
|
// build two transactions with diff accounts
|
||||||
|
let (_tx1, _keys1, cost1) = build_simple_transaction(&mint_keypair, &start_hash);
|
||||||
|
let second_account = Keypair::new();
|
||||||
|
let (_tx2, keys2, cost2) = build_simple_transaction(&second_account, &start_hash);
|
||||||
|
|
||||||
|
// build testee that passes
|
||||||
|
let testee = CostTracker::new(cmp::max(cost1, cost2), cost1 + cost2 - 1);
|
||||||
|
assert!(testee
|
||||||
|
.would_fit(&keys2, cost2, MAX_ACCOUNT_DATA_LEN)
|
||||||
|
.is_ok());
|
||||||
|
// data is too big
|
||||||
|
assert!(testee
|
||||||
|
.would_fit(&keys2, cost2, MAX_ACCOUNT_DATA_LEN + 1)
|
||||||
|
.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cost_tracker_try_add_is_atomic() {
|
fn test_cost_tracker_try_add_is_atomic() {
|
||||||
let (mint_keypair, start_hash) = test_setup();
|
let (mint_keypair, start_hash) = test_setup();
|
||||||
|
@ -101,6 +101,10 @@ pub enum TransactionError {
|
|||||||
/// Transaction would exceed max account limit within the block
|
/// Transaction would exceed max account limit within the block
|
||||||
#[error("Transaction would exceed max account limit within the block")]
|
#[error("Transaction would exceed max account limit within the block")]
|
||||||
WouldExceedMaxAccountCostLimit,
|
WouldExceedMaxAccountCostLimit,
|
||||||
|
|
||||||
|
/// Transaction would exceed max account data limit within the block
|
||||||
|
#[error("Transaction would exceed max account data limit within the block")]
|
||||||
|
WouldExceedMaxAccountDataCostLimit,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SanitizeError> for TransactionError {
|
impl From<SanitizeError> for TransactionError {
|
||||||
|
@ -45,6 +45,7 @@ enum TransactionErrorType {
|
|||||||
UNSUPPORTED_VERSION = 18;
|
UNSUPPORTED_VERSION = 18;
|
||||||
INVALID_WRITABLE_ACCOUNT = 19;
|
INVALID_WRITABLE_ACCOUNT = 19;
|
||||||
WOULD_EXCEED_MAX_ACCOUNT_COST_LIMIT = 20;
|
WOULD_EXCEED_MAX_ACCOUNT_COST_LIMIT = 20;
|
||||||
|
WOULD_EXCEED_MAX_ACCOUNT_DATA_COST_LIMIT = 21;
|
||||||
}
|
}
|
||||||
|
|
||||||
message InstructionError {
|
message InstructionError {
|
||||||
|
@ -567,6 +567,7 @@ impl TryFrom<tx_by_addr::TransactionError> for TransactionError {
|
|||||||
18 => TransactionError::UnsupportedVersion,
|
18 => TransactionError::UnsupportedVersion,
|
||||||
19 => TransactionError::InvalidWritableAccount,
|
19 => TransactionError::InvalidWritableAccount,
|
||||||
20 => TransactionError::WouldExceedMaxAccountCostLimit,
|
20 => TransactionError::WouldExceedMaxAccountCostLimit,
|
||||||
|
21 => TransactionError::WouldExceedMaxAccountDataCostLimit,
|
||||||
_ => return Err("Invalid TransactionError"),
|
_ => return Err("Invalid TransactionError"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -637,6 +638,9 @@ impl From<TransactionError> for tx_by_addr::TransactionError {
|
|||||||
TransactionError::WouldExceedMaxAccountCostLimit => {
|
TransactionError::WouldExceedMaxAccountCostLimit => {
|
||||||
tx_by_addr::TransactionErrorType::WouldExceedMaxAccountCostLimit
|
tx_by_addr::TransactionErrorType::WouldExceedMaxAccountCostLimit
|
||||||
}
|
}
|
||||||
|
TransactionError::WouldExceedMaxAccountDataCostLimit => {
|
||||||
|
tx_by_addr::TransactionErrorType::WouldExceedMaxAccountDataCostLimit
|
||||||
|
}
|
||||||
} 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) => {
|
||||||
|
Reference in New Issue
Block a user