Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions program-runtime/src/accounts_data_meter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
//! The accounts data space has a maximum size it is permitted to grow to. This module contains
//! the constants and types for tracking and metering the accounts data space during program
//! runtime.
use solana_sdk::instruction::InstructionError;

/// The maximum allowed size, in bytes, of the accounts data
/// 128 GB was chosen because it is the RAM amount listed under Hardware Recommendations on
/// [Validator Requirements](https://docs.solana.com/running-validator/validator-reqs), and
/// validators often put the ledger on a RAM disk (i.e. tmpfs).
pub const MAX_ACCOUNTS_DATA_LEN: u64 = 128_000_000_000;

/// Meter and track the amount of available accounts data space
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
pub struct AccountsDataMeter {
/// The maximum amount of accounts data space that can be used (in bytes)
maximum: u64,

/// The current amount of accounts data space used (in bytes)
current: u64,
}

impl AccountsDataMeter {
/// Make a new AccountsDataMeter
pub fn new(current_accounts_data_len: u64) -> Self {
let accounts_data_meter = Self {
maximum: MAX_ACCOUNTS_DATA_LEN,
current: current_accounts_data_len,
};
debug_assert!(accounts_data_meter.current <= accounts_data_meter.maximum);
accounts_data_meter
}

/// Return the maximum amount of accounts data space that can be used (in bytes)
pub fn maximum(&self) -> u64 {
self.maximum
}

/// Return the current amount of accounts data space used (in bytes)
pub fn current(&self) -> u64 {
self.current
}

/// Get the remaining amount of accounts data space (in bytes)
pub fn remaining(&self) -> u64 {
self.maximum.saturating_sub(self.current)
}

/// Consume accounts data space, in bytes. If `amount` is positive, we are *increasing* the
/// amount of accounts data space used. If `amount` is negative, we are *decreasing* the
/// amount of accounts data space used.
pub fn consume(&mut self, amount: i64) -> Result<(), InstructionError> {
if amount == 0 {
// nothing to do here; lets us skip doing unnecessary work in the 'else' case
return Ok(());
}

if amount.is_positive() {
let amount = amount as u64;
if amount > self.remaining() {
return Err(InstructionError::AccountsDataBudgetExceeded);
}
self.current = self.current.saturating_add(amount);
} else {
let amount = amount.abs() as u64;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edge case (with potential panic) which is not covered?

https://doc.rust-lang.org/std/primitive.i64.html#method.abs

Copy link
Copy Markdown
Contributor Author

@brooksprumo brooksprumo Dec 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point! I figure since we'll never be able to deallocate more than the maximum that can be allocated, amount should never be less than -MAX_ACCOUNTS_DATA_LEN, which means .abs() here will be safe. Is that sufficient? Would you like a comment and/or a debug_assert!()?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, makes sense. There should be an implicit debug_assert!() in .abs() already.

self.current = self.current.saturating_sub(amount);
}
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_new() {
let current = 1234;
let accounts_data_meter = AccountsDataMeter::new(current);
assert_eq!(accounts_data_meter.maximum, MAX_ACCOUNTS_DATA_LEN);
assert_eq!(accounts_data_meter.current, current);
}

#[test]
fn test_new_can_use_max_len() {
let _ = AccountsDataMeter::new(MAX_ACCOUNTS_DATA_LEN);
}

#[test]
#[should_panic]
fn test_new_panics_if_current_len_too_big() {
let _ = AccountsDataMeter::new(MAX_ACCOUNTS_DATA_LEN + 1);
}

#[test]
fn test_remaining() {
let current_accounts_data_len = 0;
let accounts_data_meter = AccountsDataMeter::new(current_accounts_data_len);
assert_eq!(accounts_data_meter.remaining(), MAX_ACCOUNTS_DATA_LEN);
}

#[test]
fn test_remaining_saturates() {
let current_accounts_data_len = 0;
let mut accounts_data_meter = AccountsDataMeter::new(current_accounts_data_len);
// To test that remaining() saturates, need to break the invariant that current <= maximum
accounts_data_meter.current = MAX_ACCOUNTS_DATA_LEN + 1;
assert_eq!(accounts_data_meter.remaining(), 0);
}

#[test]
fn test_consume() {
let current_accounts_data_len = 0;
let mut accounts_data_meter = AccountsDataMeter::new(current_accounts_data_len);

// Test: simple, positive numbers
let result = accounts_data_meter.consume(0);
assert!(result.is_ok());
let result = accounts_data_meter.consume(1);
assert!(result.is_ok());
let result = accounts_data_meter.consume(4);
assert!(result.is_ok());
let result = accounts_data_meter.consume(9);
assert!(result.is_ok());

// Test: can consume the remaining amount
let remaining = accounts_data_meter.remaining() as i64;
let result = accounts_data_meter.consume(remaining);
assert!(result.is_ok());
assert_eq!(accounts_data_meter.remaining(), 0);
}

#[test]
fn test_consume_deallocate() {
let current_accounts_data_len = 10_000;
let mut accounts_data_meter = AccountsDataMeter::new(current_accounts_data_len);
let remaining_before = accounts_data_meter.remaining();

let amount = (current_accounts_data_len / 2) as i64;
let amount = -amount;
let result = accounts_data_meter.consume(amount);
assert!(result.is_ok());
let remaining_after = accounts_data_meter.remaining();
assert_eq!(remaining_after, remaining_before + amount.abs() as u64);
}

#[test]
fn test_consume_too_much() {
let current_accounts_data_len = 0;
let mut accounts_data_meter = AccountsDataMeter::new(current_accounts_data_len);

// Test: consuming more than what's available (1) returns an error, (2) does not consume
let remaining = accounts_data_meter.remaining();
let result = accounts_data_meter.consume(remaining as i64 + 1);
assert!(result.is_err());
assert_eq!(accounts_data_meter.remaining(), remaining);
}

#[test]
fn test_consume_zero() {
// Pre-condition: set up the accounts data meter such that there is no remaining space
let current_accounts_data_len = 1234;
let mut accounts_data_meter = AccountsDataMeter::new(current_accounts_data_len);
accounts_data_meter.maximum = current_accounts_data_len;
assert_eq!(accounts_data_meter.remaining(), 0);

// Test: can always consume zero, even if there is no remaining space
let result = accounts_data_meter.consume(0);
assert!(result.is_ok());
}
}
15 changes: 12 additions & 3 deletions program-runtime/src/invoke_context.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use {
crate::{
ic_logger_msg, ic_msg, instruction_recorder::InstructionRecorder,
log_collector::LogCollector, native_loader::NativeLoader, pre_account::PreAccount,
timings::ExecuteDetailsTimings,
accounts_data_meter::AccountsDataMeter, ic_logger_msg, ic_msg,
instruction_recorder::InstructionRecorder, log_collector::LogCollector,
native_loader::NativeLoader, pre_account::PreAccount, timings::ExecuteDetailsTimings,
},
solana_sdk::{
account::{AccountSharedData, ReadableAccount},
Expand Down Expand Up @@ -154,6 +154,7 @@ pub struct InvokeContext<'a> {
compute_budget: ComputeBudget,
current_compute_budget: ComputeBudget,
compute_meter: Rc<RefCell<ComputeMeter>>,
accounts_data_meter: AccountsDataMeter,
executors: Rc<RefCell<Executors>>,
pub instruction_recorder: Option<Rc<RefCell<InstructionRecorder>>>,
pub feature_set: Arc<FeatureSet>,
Expand All @@ -177,6 +178,7 @@ impl<'a> InvokeContext<'a> {
feature_set: Arc<FeatureSet>,
blockhash: Hash,
lamports_per_signature: u64,
current_accounts_data_len: u64,
) -> Self {
Self {
invoke_stack: Vec::with_capacity(compute_budget.max_invoke_depth),
Expand All @@ -189,6 +191,7 @@ impl<'a> InvokeContext<'a> {
current_compute_budget: compute_budget,
compute_budget,
compute_meter: ComputeMeter::new_ref(compute_budget.max_units),
accounts_data_meter: AccountsDataMeter::new(current_accounts_data_len),
executors,
instruction_recorder,
feature_set,
Expand All @@ -215,6 +218,7 @@ impl<'a> InvokeContext<'a> {
Arc::new(FeatureSet::all_enabled()),
Hash::default(),
0,
0,
)
}

Expand Down Expand Up @@ -841,6 +845,11 @@ impl<'a> InvokeContext<'a> {
self.compute_meter.clone()
}

/// Get this invocation's AccountsDataMeter
pub fn get_accounts_data_meter(&self) -> &AccountsDataMeter {
&self.accounts_data_meter
}

/// Loaders may need to do work in order to execute a program. Cache
/// the work that can be re-used across executions
pub fn add_executor(&self, pubkey: &Pubkey, executor: Arc<dyn Executor>) {
Expand Down
1 change: 1 addition & 0 deletions program-runtime/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(min_specialization))]

pub mod accounts_data_meter;
pub mod instruction_recorder;
pub mod invoke_context;
pub mod log_collector;
Expand Down
17 changes: 14 additions & 3 deletions programs/bpf_loader/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,16 @@ use {
clock::Clock,
entrypoint::{HEAP_LENGTH, SUCCESS},
feature_set::{
do_support_realloc, reduce_required_deploy_balance, reject_all_elf_rw,
reject_deployment_of_unresolved_syscalls,
cap_accounts_data_len, do_support_realloc, reduce_required_deploy_balance,
reject_all_elf_rw, reject_deployment_of_unresolved_syscalls,
reject_section_virtual_address_file_offset_mismatch, requestable_heap_size,
start_verify_shift32_imm, stop_verify_mul64_imm_nonzero,
},
instruction::{AccountMeta, InstructionError},
keyed_account::{from_keyed_account, keyed_account_at_index, KeyedAccount},
loader_instruction::LoaderInstruction,
loader_upgradeable_instruction::UpgradeableLoaderInstruction,
program_error::ACCOUNTS_DATA_BUDGET_EXCEEDED,
program_utils::limited_deserialize,
pubkey::Pubkey,
rent::Rent,
Expand Down Expand Up @@ -1041,7 +1042,17 @@ impl Executor for BpfExecutor {
}
match result {
Ok(status) if status != SUCCESS => {
let error: InstructionError = status.into();
let error: InstructionError = if status == ACCOUNTS_DATA_BUDGET_EXCEEDED
&& !invoke_context
.feature_set
.is_active(&cap_accounts_data_len::id())
{
// Until the cap_accounts_data_len feature is enabled, map the
// ACCOUNTS_DATA_BUDGET_EXCEEDED error to InvalidError
InstructionError::InvalidError
} else {
status.into()
};
stable_log::program_failure(&log_collector, &program_id, &error);
Err(error)
}
Expand Down
32 changes: 14 additions & 18 deletions runtime/src/bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ use {
sync::{
atomic::{
AtomicBool, AtomicU64,
Ordering::{AcqRel, Acquire, Relaxed, Release},
Ordering::{Acquire, Relaxed, Release},
},
Arc, LockResult, RwLock, RwLockReadGuard, RwLockWriteGuard,
},
Expand Down Expand Up @@ -240,7 +240,7 @@ impl ExecuteTimings {
}

type BankStatusCache = StatusCache<Result<()>>;
#[frozen_abi(digest = "GcfJc94Hb3s7gzF7Uh4YxLSiQf1MvUtMmtU45BvinkVT")]
#[frozen_abi(digest = "2pPboTQ9ixNuR1hvRt7McJriam5EHfd3vpBWfxnVbmF3")]
Comment thread
brooksprumo marked this conversation as resolved.
pub type BankSlotDelta = SlotDelta<Result<()>>;

// Eager rent collection repeats in cyclic manner.
Expand Down Expand Up @@ -1194,8 +1194,7 @@ impl Bank {
};

let total_accounts_stats = bank.get_total_accounts_stats().unwrap();
bank.accounts_data_len
.store(total_accounts_stats.data_len as u64, Release);
bank.store_accounts_data_len(total_accounts_stats.data_len as u64);

bank
}
Expand Down Expand Up @@ -1443,7 +1442,7 @@ impl Bank {
freeze_started: AtomicBool::new(false),
cost_tracker: RwLock::new(CostTracker::default()),
sysvar_cache: RwLock::new(Vec::new()),
accounts_data_len: AtomicU64::new(parent.accounts_data_len.load(Acquire)),
accounts_data_len: AtomicU64::new(parent.load_accounts_data_len()),
};

let mut ancestors = Vec::with_capacity(1 + new.parents().len());
Expand Down Expand Up @@ -3609,11 +3608,10 @@ impl Bank {
&*self.sysvar_cache.read().unwrap(),
blockhash,
lamports_per_signature,
self.accounts_data_len.load(Acquire),
)
.map(|process_result| {
self.update_accounts_data_len(
process_result.accounts_data_len_delta,
)
self.store_accounts_data_len(process_result.accounts_data_len)
});
} else {
// TODO: support versioned messages
Expand Down Expand Up @@ -3773,16 +3771,14 @@ impl Bank {
)
}

/// Update the bank's accounts_data_len field based on the `delta`.
fn update_accounts_data_len(&self, delta: i64) {
if delta == 0 {
return;
}
if delta > 0 {
self.accounts_data_len.fetch_add(delta as u64, AcqRel);
} else {
self.accounts_data_len.fetch_sub(delta.abs() as u64, AcqRel);
}
/// Load the accounts data len
fn load_accounts_data_len(&self) -> u64 {
self.accounts_data_len.load(Acquire)
}

/// Store a new value to the accounts data len
fn store_accounts_data_len(&self, accounts_data_len: u64) {
self.accounts_data_len.store(accounts_data_len, Release)
}

/// Calculate fee for `SanitizedMessage`
Expand Down
Loading