use std::sync::Arc;
use custom_debug_derive::Debug;
use linera_base::{
data_types::{Amount, ArithmeticError},
ensure,
identifiers::{AccountOwner, Owner},
};
use linera_views::{context::Context, views::ViewError};
use serde::Serialize;
use crate::{
system::SystemExecutionError, ExecutionError, ExecutionStateView, Message, Operation,
ResourceControlPolicy,
};
#[derive(Clone, Debug, Default)]
pub struct ResourceController<Account = Amount, Tracker = ResourceTracker> {
pub policy: Arc<ResourceControlPolicy>,
pub tracker: Tracker,
pub account: Account,
}
#[derive(Copy, Debug, Clone, Default)]
pub struct ResourceTracker {
pub blocks: u32,
pub block_size: u64,
pub fuel: u64,
pub read_operations: u32,
pub write_operations: u32,
pub bytes_read: u64,
pub bytes_written: u64,
pub bytes_stored: i32,
pub operations: u32,
pub operation_bytes: u64,
pub messages: u32,
pub message_bytes: u64,
pub grants: Amount,
}
pub trait BalanceHolder {
fn balance(&self) -> Result<Amount, ArithmeticError>;
fn try_add_assign(&mut self, other: Amount) -> Result<(), ArithmeticError>;
fn try_sub_assign(&mut self, other: Amount) -> Result<(), ArithmeticError>;
}
impl<Account, Tracker> ResourceController<Account, Tracker>
where
Account: BalanceHolder,
Tracker: AsRef<ResourceTracker> + AsMut<ResourceTracker>,
{
pub fn balance(&self) -> Result<Amount, ArithmeticError> {
self.account.balance()
}
pub fn merge_balance(&mut self, initial: Amount, other: Amount) -> Result<(), ExecutionError> {
if other <= initial {
self.account
.try_sub_assign(initial.try_sub(other).expect("other <= initial"))
.map_err(|_| SystemExecutionError::InsufficientFundingForFees {
balance: self.balance().unwrap_or(Amount::MAX),
})?;
} else {
self.account
.try_add_assign(other.try_sub(initial).expect("other > initial"))?;
}
Ok(())
}
fn update_balance(&mut self, fees: Amount) -> Result<(), ExecutionError> {
self.account.try_sub_assign(fees).map_err(|_| {
SystemExecutionError::InsufficientFundingForFees {
balance: self.balance().unwrap_or(Amount::MAX),
}
})?;
Ok(())
}
pub(crate) fn remaining_fuel(&self) -> u64 {
self.policy
.remaining_fuel(self.balance().unwrap_or(Amount::MAX))
.min(
self.policy
.maximum_fuel_per_block
.saturating_sub(self.tracker.as_ref().fuel),
)
}
pub fn track_grant(&mut self, grant: Amount) -> Result<(), ExecutionError> {
self.tracker.as_mut().grants.try_add_assign(grant)?;
self.update_balance(grant)
}
pub fn track_block(&mut self) -> Result<(), ExecutionError> {
self.tracker.as_mut().blocks = self
.tracker
.as_mut()
.blocks
.checked_add(1)
.ok_or(ArithmeticError::Overflow)?;
self.update_balance(self.policy.block)
}
pub fn track_operation(&mut self, operation: &Operation) -> Result<(), ExecutionError> {
self.tracker.as_mut().operations = self
.tracker
.as_mut()
.operations
.checked_add(1)
.ok_or(ArithmeticError::Overflow)?;
self.update_balance(self.policy.operation)?;
match operation {
Operation::System(_) => Ok(()),
Operation::User { bytes, .. } => {
let size = bytes.len();
self.tracker.as_mut().operation_bytes = self
.tracker
.as_mut()
.operation_bytes
.checked_add(size as u64)
.ok_or(ArithmeticError::Overflow)?;
self.update_balance(self.policy.operation_bytes_price(size as u64)?)?;
Ok(())
}
}
}
pub fn track_message(&mut self, message: &Message) -> Result<(), ExecutionError> {
self.tracker.as_mut().messages = self
.tracker
.as_mut()
.messages
.checked_add(1)
.ok_or(ArithmeticError::Overflow)?;
self.update_balance(self.policy.message)?;
match message {
Message::System(_) => Ok(()),
Message::User { bytes, .. } => {
let size = bytes.len();
self.tracker.as_mut().message_bytes = self
.tracker
.as_mut()
.message_bytes
.checked_add(size as u64)
.ok_or(ArithmeticError::Overflow)?;
self.update_balance(self.policy.message_bytes_price(size as u64)?)?;
Ok(())
}
}
}
pub(crate) fn track_fuel(&mut self, fuel: u64) -> Result<(), ExecutionError> {
self.tracker.as_mut().fuel = self
.tracker
.as_ref()
.fuel
.checked_add(fuel)
.ok_or(ArithmeticError::Overflow)?;
ensure!(
self.tracker.as_ref().fuel <= self.policy.maximum_fuel_per_block,
ExecutionError::MaximumFuelExceeded
);
self.update_balance(self.policy.fuel_price(fuel)?)
}
pub(crate) fn track_read_operations(&mut self, count: u32) -> Result<(), ExecutionError> {
self.tracker.as_mut().read_operations = self
.tracker
.as_mut()
.read_operations
.checked_add(count)
.ok_or(ArithmeticError::Overflow)?;
self.update_balance(self.policy.read_operations_price(count)?)
}
pub(crate) fn track_write_operations(&mut self, count: u32) -> Result<(), ExecutionError> {
self.tracker.as_mut().write_operations = self
.tracker
.as_mut()
.write_operations
.checked_add(count)
.ok_or(ArithmeticError::Overflow)?;
self.update_balance(self.policy.write_operations_price(count)?)
}
pub(crate) fn track_bytes_read(&mut self, count: u64) -> Result<(), ExecutionError> {
self.tracker.as_mut().bytes_read = self
.tracker
.as_mut()
.bytes_read
.checked_add(count)
.ok_or(ArithmeticError::Overflow)?;
if self.tracker.as_mut().bytes_read >= self.policy.maximum_bytes_read_per_block {
return Err(ExecutionError::ExcessiveRead);
}
self.update_balance(self.policy.bytes_read_price(count)?)?;
Ok(())
}
pub(crate) fn track_bytes_written(&mut self, count: u64) -> Result<(), ExecutionError> {
self.tracker.as_mut().bytes_written = self
.tracker
.as_mut()
.bytes_written
.checked_add(count)
.ok_or(ArithmeticError::Overflow)?;
if self.tracker.as_mut().bytes_written >= self.policy.maximum_bytes_written_per_block {
return Err(ExecutionError::ExcessiveWrite);
}
self.update_balance(self.policy.bytes_written_price(count)?)?;
Ok(())
}
#[allow(dead_code)]
pub(crate) fn track_stored_bytes(&mut self, delta: i32) -> Result<(), ExecutionError> {
self.tracker.as_mut().bytes_stored = self
.tracker
.as_mut()
.bytes_stored
.checked_add(delta)
.ok_or(ArithmeticError::Overflow)?;
Ok(())
}
}
impl<Account, Tracker> ResourceController<Account, Tracker>
where
Tracker: AsMut<ResourceTracker>,
{
pub fn track_executed_block_size_sequence_extension(
&mut self,
old_len: usize,
delta: usize,
) -> Result<(), ExecutionError> {
if delta == 0 {
return Ok(());
}
let new_len = old_len + delta;
let old_size = ((usize::BITS - old_len.leading_zeros()) / 7).max(1);
let new_size = ((usize::BITS - new_len.leading_zeros()) / 7).max(1);
if new_size > old_size {
self.track_block_size((new_size - old_size) as usize)?;
}
Ok(())
}
pub fn track_block_size_of(&mut self, data: &impl Serialize) -> Result<(), ExecutionError> {
self.track_block_size(bcs::serialized_size(data)?)
}
pub fn track_block_size(&mut self, size: usize) -> Result<(), ExecutionError> {
let tracker = self.tracker.as_mut();
tracker.block_size = u64::try_from(size)
.ok()
.and_then(|size| tracker.block_size.checked_add(size))
.ok_or(ExecutionError::ExecutedBlockTooLarge)?;
ensure!(
tracker.block_size <= self.policy.maximum_executed_block_size,
ExecutionError::ExecutedBlockTooLarge
);
Ok(())
}
}
impl BalanceHolder for Amount {
fn balance(&self) -> Result<Amount, ArithmeticError> {
Ok(*self)
}
fn try_add_assign(&mut self, other: Amount) -> Result<(), ArithmeticError> {
self.try_add_assign(other)
}
fn try_sub_assign(&mut self, other: Amount) -> Result<(), ArithmeticError> {
self.try_sub_assign(other)
}
}
impl AsMut<ResourceTracker> for ResourceTracker {
fn as_mut(&mut self) -> &mut Self {
self
}
}
impl AsRef<ResourceTracker> for ResourceTracker {
fn as_ref(&self) -> &Self {
self
}
}
pub struct Sources<'a> {
sources: Vec<&'a mut Amount>,
}
impl BalanceHolder for Sources<'_> {
fn balance(&self) -> Result<Amount, ArithmeticError> {
let mut amount = Amount::ZERO;
for source in self.sources.iter() {
amount.try_add_assign(**source)?;
}
Ok(amount)
}
fn try_add_assign(&mut self, other: Amount) -> Result<(), ArithmeticError> {
let source = self.sources.last_mut().expect("at least one source");
source.try_add_assign(other)
}
fn try_sub_assign(&mut self, mut other: Amount) -> Result<(), ArithmeticError> {
for source in self.sources.iter_mut() {
if source.try_sub_assign(other).is_ok() {
return Ok(());
}
other.try_sub_assign(**source).expect("*source < other");
**source = Amount::ZERO;
}
if other > Amount::ZERO {
Err(ArithmeticError::Underflow)
} else {
Ok(())
}
}
}
impl ResourceController<Option<Owner>, ResourceTracker> {
pub async fn with_state<'a, C>(
&mut self,
view: &'a mut ExecutionStateView<C>,
) -> Result<ResourceController<Sources<'a>, &mut ResourceTracker>, ViewError>
where
C: Context + Clone + Send + Sync + 'static,
{
self.with_state_and_grant(view, None).await
}
pub async fn with_state_and_grant<'a, C>(
&mut self,
view: &'a mut ExecutionStateView<C>,
grant: Option<&'a mut Amount>,
) -> Result<ResourceController<Sources<'a>, &mut ResourceTracker>, ViewError>
where
C: Context + Clone + Send + Sync + 'static,
{
let mut sources = Vec::new();
if let Some(grant) = grant {
sources.push(grant);
} else {
sources.push(view.system.balance.get_mut());
}
if let Some(owner) = &self.account {
if let Some(balance) = view
.system
.balances
.get_mut(&AccountOwner::User(*owner))
.await?
{
sources.push(balance);
}
}
Ok(ResourceController {
policy: self.policy.clone(),
tracker: &mut self.tracker,
account: Sources { sources },
})
}
}