use std::{sync::Arc, time::Duration};
use custom_debug_derive::Debug;
use linera_base::{
data_types::{Amount, ArithmeticError, BlobContent},
ensure,
identifiers::AccountOwner,
};
use linera_views::{context::Context, views::ViewError};
use serde::Serialize;
use crate::{ExecutionError, Message, Operation, ResourceControlPolicy, SystemExecutionStateView};
#[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 blobs_read: u32,
pub blobs_published: u32,
pub blob_bytes_read: u64,
pub blob_bytes_published: u64,
pub bytes_stored: i32,
pub operations: u32,
pub operation_bytes: u64,
pub messages: u32,
pub message_bytes: u64,
pub http_requests: u32,
pub service_oracle_queries: u32,
pub service_oracle_execution: Duration,
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(|_| ExecutionError::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(|_| {
ExecutionError::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 fn track_http_request(&mut self) -> Result<(), ExecutionError> {
self.tracker.as_mut().http_requests = self
.tracker
.as_ref()
.http_requests
.checked_add(1)
.ok_or(ArithmeticError::Overflow)?;
self.update_balance(self.policy.http_request)
}
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(())
}
pub(crate) fn track_blob_read(&mut self, count: u64) -> Result<(), ExecutionError> {
{
let tracker = self.tracker.as_mut();
tracker.blob_bytes_read = tracker
.blob_bytes_read
.checked_add(count)
.ok_or(ArithmeticError::Overflow)?;
tracker.blobs_read = tracker
.blobs_read
.checked_add(1)
.ok_or(ArithmeticError::Overflow)?;
}
self.update_balance(self.policy.blob_read_price(count)?)?;
Ok(())
}
pub fn track_blob_published(&mut self, content: &BlobContent) -> Result<(), ExecutionError> {
self.policy.check_blob_size(content)?;
let size = content.bytes().len() as u64;
{
let tracker = self.tracker.as_mut();
tracker.blob_bytes_published = tracker
.blob_bytes_published
.checked_add(size)
.ok_or(ArithmeticError::Overflow)?;
tracker.blobs_published = tracker
.blobs_published
.checked_add(1)
.ok_or(ArithmeticError::Overflow)?;
}
self.update_balance(self.policy.blob_published_price(size)?)?;
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(())
}
pub(crate) fn remaining_service_oracle_execution_time(
&self,
) -> Result<Duration, ExecutionError> {
let tracker = self.tracker.as_ref();
let spent_execution_time = tracker.service_oracle_execution;
let limit = Duration::from_millis(self.policy.maximum_service_oracle_execution_ms);
limit
.checked_sub(spent_execution_time)
.ok_or(ExecutionError::MaximumServiceOracleExecutionTimeExceeded)
}
pub(crate) fn track_service_oracle_call(&mut self) -> Result<(), ExecutionError> {
self.tracker.as_mut().service_oracle_queries = self
.tracker
.as_mut()
.service_oracle_queries
.checked_add(1)
.ok_or(ArithmeticError::Overflow)?;
self.update_balance(self.policy.service_as_oracle_query)
}
pub(crate) fn track_service_oracle_execution(
&mut self,
execution_time: Duration,
) -> Result<(), ExecutionError> {
let tracker = self.tracker.as_mut();
let spent_execution_time = &mut tracker.service_oracle_execution;
let limit = Duration::from_millis(self.policy.maximum_service_oracle_execution_ms);
*spent_execution_time = spent_execution_time.saturating_add(execution_time);
ensure!(
*spent_execution_time < limit,
ExecutionError::MaximumServiceOracleExecutionTimeExceeded
);
Ok(())
}
pub(crate) fn track_service_oracle_response(
&mut self,
response_bytes: usize,
) -> Result<(), ExecutionError> {
ensure!(
response_bytes as u64 <= self.policy.maximum_oracle_response_bytes,
ExecutionError::ServiceOracleResponseTooLarge
);
Ok(())
}
}
impl<Account, Tracker> ResourceController<Account, Tracker>
where
Tracker: AsMut<ResourceTracker>,
{
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::BlockTooLarge)?;
ensure!(
tracker.block_size <= self.policy.maximum_block_size,
ExecutionError::BlockTooLarge
);
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<AccountOwner>, ResourceTracker> {
pub async fn with_state<'a, C>(
&mut self,
view: &'a mut SystemExecutionStateView<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 SystemExecutionStateView<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.balance.get_mut());
}
if let Some(owner) = &self.account {
if let Some(balance) = view.balances.get_mut(owner).await? {
sources.push(balance);
}
}
Ok(ResourceController {
policy: self.policy.clone(),
tracker: &mut self.tracker,
account: Sources { sources },
})
}
}