use std::{fmt, sync::Arc, time::Duration};
use custom_debug_derive::Debug;
use linera_base::{
data_types::{Amount, ApplicationDescription, ArithmeticError, Blob},
ensure,
identifiers::AccountOwner,
ownership::ChainOwnership,
vm::VmRuntime,
};
use linera_views::{context::Context, ViewError};
use serde::Serialize;
use crate::{ExecutionError, Message, Operation, ResourceControlPolicy, SystemExecutionStateView};
#[derive(Clone, Debug, Default)]
pub struct ResourceController<Account = Amount, Tracker = ResourceTracker> {
policy: Arc<ResourceControlPolicy>,
pub tracker: Tracker,
pub account: Account,
pub is_free: bool,
}
impl<Account, Tracker> ResourceController<Account, Tracker> {
pub fn new(policy: Arc<ResourceControlPolicy>, tracker: Tracker, account: Account) -> Self {
Self {
policy,
tracker,
account,
is_free: false,
}
}
pub fn policy(&self) -> &Arc<ResourceControlPolicy> {
&self.policy
}
pub fn tracker(&self) -> &Tracker {
&self.tracker
}
}
pub const RUNTIME_AMOUNT_SIZE: u32 = 16;
pub const RUNTIME_APPLICATION_ID_SIZE: u32 = 32;
pub const RUNTIME_BLOCK_HEIGHT_SIZE: u32 = 8;
pub const RUNTIME_CHAIN_ID_SIZE: u32 = 32;
pub const RUNTIME_TIMESTAMP_SIZE: u32 = 8;
pub const RUNTIME_OWNER_WEIGHT_SIZE: u32 = 8;
pub const RUNTIME_CONSTANT_CHAIN_OWNERSHIP_SIZE: u32 = 4 + 4 * 8;
pub const RUNTIME_CRYPTO_HASH_SIZE: u32 = 32;
pub const RUNTIME_VM_RUNTIME_SIZE: u32 = 1;
pub const RUNTIME_CONSTANT_APPLICATION_DESCRIPTION_SIZE: u32 = 2 * RUNTIME_CRYPTO_HASH_SIZE + RUNTIME_VM_RUNTIME_SIZE + RUNTIME_CHAIN_ID_SIZE + RUNTIME_BLOCK_HEIGHT_SIZE + 4;
#[cfg(test)]
mod tests {
use std::mem::size_of;
use linera_base::{
data_types::{Amount, ApplicationDescription, BlockHeight, Timestamp},
identifiers::{ApplicationId, ChainId, ModuleId},
};
use crate::resources::{
RUNTIME_AMOUNT_SIZE, RUNTIME_APPLICATION_ID_SIZE, RUNTIME_BLOCK_HEIGHT_SIZE,
RUNTIME_CHAIN_ID_SIZE, RUNTIME_CONSTANT_APPLICATION_DESCRIPTION_SIZE,
RUNTIME_OWNER_WEIGHT_SIZE, RUNTIME_TIMESTAMP_SIZE,
};
#[test]
fn test_size_of_runtime_operations() {
assert_eq!(RUNTIME_AMOUNT_SIZE as usize, size_of::<Amount>());
assert_eq!(
RUNTIME_APPLICATION_ID_SIZE as usize,
size_of::<ApplicationId>()
);
assert_eq!(RUNTIME_BLOCK_HEIGHT_SIZE as usize, size_of::<BlockHeight>());
assert_eq!(RUNTIME_CHAIN_ID_SIZE as usize, size_of::<ChainId>());
assert_eq!(RUNTIME_TIMESTAMP_SIZE as usize, size_of::<Timestamp>());
assert_eq!(RUNTIME_OWNER_WEIGHT_SIZE as usize, size_of::<u64>());
}
#[test]
fn test_application_description_size() {
let description = ApplicationDescription {
module_id: ModuleId::default(),
creator_chain_id: ChainId::default(),
block_height: BlockHeight::default(),
application_index: 0,
parameters: vec![],
required_application_ids: vec![],
};
let serialized = bcs::to_bytes(&description).expect("serialization should succeed");
assert_eq!(
serialized.len(),
RUNTIME_CONSTANT_APPLICATION_DESCRIPTION_SIZE as usize + 2
);
}
}
#[derive(Copy, Debug, Clone, Default)]
pub struct ResourceTracker {
pub block_size: u64,
pub evm_fuel: u64,
pub wasm_fuel: u64,
pub read_operations: u32,
pub write_operations: u32,
pub bytes_runtime: 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,
}
impl ResourceTracker {
fn fuel(&self, vm_runtime: VmRuntime) -> u64 {
match vm_runtime {
VmRuntime::Wasm => self.wasm_fuel,
VmRuntime::Evm => self.evm_fuel,
}
}
}
impl fmt::Display for ResourceTracker {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut lines = Vec::new();
let mut block_parts = Vec::new();
if self.block_size != 0 {
block_parts.push(format!("size={}", self.block_size));
}
if self.operations != 0 {
block_parts.push(format!("operations={}", self.operations));
}
if self.operation_bytes != 0 {
block_parts.push(format!("operation_bytes={}", self.operation_bytes));
}
if !block_parts.is_empty() {
lines.push(format!("block: {}", block_parts.join(", ")));
}
let mut fuel_parts = Vec::new();
if self.wasm_fuel != 0 {
fuel_parts.push(format!("wasm={}", self.wasm_fuel));
}
if self.evm_fuel != 0 {
fuel_parts.push(format!("evm={}", self.evm_fuel));
}
if !fuel_parts.is_empty() {
lines.push(format!("fuel: {}", fuel_parts.join(", ")));
}
let mut storage_parts = Vec::new();
if self.read_operations != 0 {
storage_parts.push(format!("reads={}", self.read_operations));
}
if self.write_operations != 0 {
storage_parts.push(format!("writes={}", self.write_operations));
}
if self.bytes_runtime != 0 {
storage_parts.push(format!("runtime_bytes={}", self.bytes_runtime));
}
if self.bytes_read != 0 {
storage_parts.push(format!("bytes_read={}", self.bytes_read));
}
if self.bytes_written != 0 {
storage_parts.push(format!("bytes_written={}", self.bytes_written));
}
if self.bytes_stored != 0 {
storage_parts.push(format!("bytes_stored={}", self.bytes_stored));
}
if !storage_parts.is_empty() {
lines.push(format!("storage: {}", storage_parts.join(", ")));
}
let mut blob_parts = Vec::new();
if self.blobs_read != 0 {
blob_parts.push(format!("read={}", self.blobs_read));
}
if self.blobs_published != 0 {
blob_parts.push(format!("published={}", self.blobs_published));
}
if self.blob_bytes_read != 0 {
blob_parts.push(format!("bytes_read={}", self.blob_bytes_read));
}
if self.blob_bytes_published != 0 {
blob_parts.push(format!("bytes_published={}", self.blob_bytes_published));
}
if !blob_parts.is_empty() {
lines.push(format!("blobs: {}", blob_parts.join(", ")));
}
let mut message_parts = Vec::new();
if self.messages != 0 {
message_parts.push(format!("count={}", self.messages));
}
if self.message_bytes != 0 {
message_parts.push(format!("bytes={}", self.message_bytes));
}
if self.grants != Amount::ZERO {
message_parts.push(format!("grants={}", self.grants));
}
if !message_parts.is_empty() {
lines.push(format!("messages: {}", message_parts.join(", ")));
}
let mut http_service_parts = Vec::new();
if self.http_requests != 0 {
http_service_parts.push(format!("http_requests={}", self.http_requests));
}
if self.service_oracle_queries != 0 {
http_service_parts.push(format!("service_queries={}", self.service_oracle_queries));
}
if self.service_oracle_execution != Duration::ZERO {
http_service_parts.push(format!(
"service_execution={:?}",
self.service_oracle_execution
));
}
if !http_service_parts.is_empty() {
lines.push(format!("http/service: {}", http_service_parts.join(", ")));
}
let mut lines_iter = lines.into_iter();
if let Some(first) = lines_iter.next() {
write!(f, "{}", first)?;
for line in lines_iter {
write!(f, "\n {}", line)?;
}
}
Ok(())
}
}
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 {
let sub_amount = initial.try_sub(other).expect("other <= initial");
self.account.try_sub_assign(sub_amount).map_err(|_| {
ExecutionError::FeesExceedFunding {
fees: sub_amount,
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> {
if self.is_free {
return Ok(());
}
self.account
.try_sub_assign(fees)
.map_err(|_| ExecutionError::FeesExceedFunding {
fees,
balance: self.balance().unwrap_or(Amount::MAX),
})?;
Ok(())
}
pub(crate) fn remaining_fuel(&self, vm_runtime: VmRuntime) -> u64 {
let fuel = self.tracker.as_ref().fuel(vm_runtime);
let maximum_fuel_per_block = self.policy.maximum_fuel_per_block(vm_runtime);
if self.is_free {
return maximum_fuel_per_block.saturating_sub(fuel);
}
let balance = self.balance().unwrap_or(Amount::MAX);
self.policy
.remaining_fuel(balance, vm_runtime)
.min(maximum_fuel_per_block.saturating_sub(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_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,
vm_runtime: VmRuntime,
) -> Result<(), ExecutionError> {
match vm_runtime {
VmRuntime::Wasm => {
self.tracker.as_mut().wasm_fuel = self
.tracker
.as_ref()
.wasm_fuel
.checked_add(fuel)
.ok_or(ArithmeticError::Overflow)?;
ensure!(
self.tracker.as_ref().wasm_fuel <= self.policy.maximum_wasm_fuel_per_block,
ExecutionError::MaximumFuelExceeded(vm_runtime)
);
}
VmRuntime::Evm => {
self.tracker.as_mut().evm_fuel = self
.tracker
.as_ref()
.evm_fuel
.checked_add(fuel)
.ok_or(ArithmeticError::Overflow)?;
ensure!(
self.tracker.as_ref().evm_fuel <= self.policy.maximum_evm_fuel_per_block,
ExecutionError::MaximumFuelExceeded(vm_runtime)
);
}
}
self.update_balance(self.policy.fuel_price(fuel, vm_runtime)?)
}
pub(crate) fn track_runtime_chain_id(&mut self) -> Result<(), ExecutionError> {
self.track_size_runtime_operations(RUNTIME_CHAIN_ID_SIZE)
}
pub(crate) fn track_runtime_block_height(&mut self) -> Result<(), ExecutionError> {
self.track_size_runtime_operations(RUNTIME_BLOCK_HEIGHT_SIZE)
}
pub(crate) fn track_runtime_application_id(&mut self) -> Result<(), ExecutionError> {
self.track_size_runtime_operations(RUNTIME_APPLICATION_ID_SIZE)
}
pub(crate) fn track_runtime_application_parameters(
&mut self,
parameters: &[u8],
) -> Result<(), ExecutionError> {
let parameters_len = parameters.len() as u32;
self.track_size_runtime_operations(parameters_len)
}
pub(crate) fn track_runtime_timestamp(&mut self) -> Result<(), ExecutionError> {
self.track_size_runtime_operations(RUNTIME_TIMESTAMP_SIZE)
}
pub(crate) fn track_runtime_balance(&mut self) -> Result<(), ExecutionError> {
self.track_size_runtime_operations(RUNTIME_AMOUNT_SIZE)
}
pub(crate) fn track_runtime_owner_balances(
&mut self,
owner_balances: &[(AccountOwner, Amount)],
) -> Result<(), ExecutionError> {
let mut size = 0;
for (account_owner, _) in owner_balances {
size += account_owner.size() + RUNTIME_AMOUNT_SIZE;
}
self.track_size_runtime_operations(size)
}
pub(crate) fn track_runtime_owners(
&mut self,
owners: &[AccountOwner],
) -> Result<(), ExecutionError> {
let mut size = 0;
for owner in owners {
size += owner.size();
}
self.track_size_runtime_operations(size)
}
pub(crate) fn track_runtime_chain_ownership(
&mut self,
chain_ownership: &ChainOwnership,
) -> Result<(), ExecutionError> {
let mut size = 0;
for account_owner in &chain_ownership.super_owners {
size += account_owner.size();
}
for account_owner in chain_ownership.owners.keys() {
size += account_owner.size() + RUNTIME_OWNER_WEIGHT_SIZE;
}
size += RUNTIME_CONSTANT_CHAIN_OWNERSHIP_SIZE;
self.track_size_runtime_operations(size)
}
pub(crate) fn track_runtime_application_description(
&mut self,
description: &ApplicationDescription,
) -> Result<(), ExecutionError> {
let parameters_size = description.parameters.len() as u32;
let required_apps_size =
description.required_application_ids.len() as u32 * RUNTIME_APPLICATION_ID_SIZE;
let size =
RUNTIME_CONSTANT_APPLICATION_DESCRIPTION_SIZE + parameters_size + required_apps_size;
self.track_size_runtime_operations(size)
}
fn track_size_runtime_operations(&mut self, size: u32) -> Result<(), ExecutionError> {
self.tracker.as_mut().bytes_runtime = self
.tracker
.as_mut()
.bytes_runtime
.checked_add(size)
.ok_or(ArithmeticError::Overflow)?;
self.update_balance(self.policy.bytes_runtime_price(size)?)
}
pub(crate) fn track_read_operation(&mut self) -> Result<(), ExecutionError> {
self.tracker.as_mut().read_operations = self
.tracker
.as_mut()
.read_operations
.checked_add(1)
.ok_or(ArithmeticError::Overflow)?;
self.update_balance(self.policy.read_operations_price(1)?)
}
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, blob: &Blob) -> Result<(), ExecutionError> {
self.policy.check_blob_size(blob.content())?;
let size = blob.content().bytes().len() as u64;
if blob.is_committee_blob() {
return Ok(());
}
{
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 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 + '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 + '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 },
is_free: self.is_free,
})
}
}
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 {
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 &mut self.sources {
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(())
}
}
}