use crate::{
policy::ResourceControlPolicy, system::SystemExecutionError, ExecutionError,
ExecutionStateView, Message, Operation,
};
use custom_debug_derive::Debug;
use futures::FutureExt;
use linera_base::{
data_types::{Amount, ArithmeticError},
identifiers::Owner,
};
use linera_views::{common::Context, views::ViewError};
use std::sync::Arc;
#[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 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 trait BalanceHolder {
fn as_amount(&self) -> Amount;
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: AsMut<ResourceTracker>,
{
pub fn balance(&self) -> Amount {
self.account.as_amount()
}
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::InsufficientFunding {
current_balance: self.balance(),
})?;
} 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::InsufficientFunding {
current_balance: self.balance(),
}
})?;
Ok(())
}
pub(crate) fn remaining_fuel(&self) -> u64 {
self.policy.remaining_fuel(self.balance())
}
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_byte_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_byte_price(size as u64)?)?;
Ok(())
}
}
}
pub(crate) fn track_fuel(&mut self, fuel: u64) -> Result<(), ExecutionError> {
self.tracker.as_mut().fuel = self
.tracker
.as_mut()
.fuel
.checked_add(fuel)
.ok_or(ArithmeticError::Overflow)?;
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 BalanceHolder for Amount {
fn as_amount(&self) -> Amount {
*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
}
}
pub struct OwnedView<'a, C> {
owner: Option<Owner>,
view: &'a mut ExecutionStateView<C>,
}
impl<'a, C> OwnedView<'a, C>
where
C: Context + Clone + Send + Sync + 'static,
ViewError: From<C::Error>,
{
fn get_owner_balance(&self, owner: &Owner) -> Amount {
self.view
.system
.balances
.get(owner)
.now_or_never()
.expect("The map entry was previously loaded by ResourceController::with")
.expect("Account was created there as well")
.expect("No I/O can fail here")
}
fn get_owner_balance_mut(&mut self, owner: &Owner) -> &mut Amount {
self.view
.system
.balances
.get_mut(owner)
.now_or_never()
.expect("The map entry was previously loaded by ResourceController::with")
.expect("Account was created there as well")
.expect("No I/O can fail here")
}
}
impl<C> BalanceHolder for OwnedView<'_, C>
where
C: Context + Clone + Send + Sync + 'static,
ViewError: From<C::Error>,
{
fn as_amount(&self) -> Amount {
match &self.owner {
None => *self.view.system.balance.get(),
Some(owner) => self
.view
.system
.balance
.get()
.try_add(self.get_owner_balance(owner))
.expect("Overflow was tested in `ResourceController::with` and `add_assign`"),
}
}
fn try_add_assign(&mut self, other: Amount) -> Result<(), ArithmeticError> {
match self.owner {
None => self.view.system.balance.get_mut().try_add_assign(other),
Some(owner) => {
let balance = self.get_owner_balance_mut(&owner);
balance.try_add_assign(other)?;
balance.try_add(*self.view.system.balance.get())?;
Ok(())
}
}
}
fn try_sub_assign(&mut self, other: Amount) -> Result<(), ArithmeticError> {
match self.owner {
None => self.view.system.balance.get_mut().try_sub_assign(other),
Some(owner) => {
if self
.get_owner_balance_mut(&owner)
.try_sub_assign(other)
.is_err()
{
let balance = self.get_owner_balance(&owner);
let delta = other.try_sub(balance).expect("balance < other");
self.view.system.balance.get_mut().try_sub_assign(delta)?;
*self.get_owner_balance_mut(&owner) = Amount::ZERO;
}
Ok(())
}
}
}
}
impl ResourceController<Option<Owner>, ResourceTracker> {
pub async fn with<'a, C>(
&mut self,
view: &'a mut ExecutionStateView<C>,
) -> Result<ResourceController<OwnedView<'a, C>, &mut ResourceTracker>, ViewError>
where
C: Context + Clone + Send + Sync + 'static,
ViewError: From<C::Error>,
{
if let Some(owner) = &self.account {
let balance = view.system.balances.get_mut(owner).await?;
if let Some(balance) = balance {
view.system.balance.get().try_add(*balance)?;
} else {
view.system.balances.insert(owner, Amount::ZERO)?;
}
}
Ok(ResourceController {
policy: self.policy.clone(),
tracker: &mut self.tracker,
account: OwnedView {
owner: self.account,
view,
},
})
}
}