diff --git a/CLI.md b/CLI.md index 918974506262..0ad27f133633 100644 --- a/CLI.md +++ b/CLI.md @@ -249,6 +249,7 @@ View or update the resource control policy * `--block ` — Set the base price for creating a block * `--fuel-unit ` — Set the price per unit of fuel * `--read-operation ` — Set the price per read operation +* `--write-operation ` — Set the price per write operation * `--byte-read ` — Set the price per byte read * `--byte-written ` — Set the price per byte written * `--byte-stored ` — Set the price per byte stored @@ -290,6 +291,9 @@ Create genesis configuration for a Linera deployment. Create initial user chains Default value: `0` * `--read-operation-price ` — Set the price per read operation + Default value: `0` +* `--write-operation-price ` — Set the price per write operation + Default value: `0` * `--byte-read-price ` — Set the price per byte read diff --git a/linera-base/src/data_types.rs b/linera-base/src/data_types.rs index 8a16c8612760..612318d4b71a 100644 --- a/linera-base/src/data_types.rs +++ b/linera-base/src/data_types.rs @@ -153,7 +153,6 @@ pub struct Resources { /// A number of read operations to be executed. pub read_operations: u32, /// A number of write operations to be executed. - // TODO(#1530): This is not used at the moment. pub write_operations: u32, /// A number of bytes to read. pub bytes_to_read: u32, diff --git a/linera-chain/src/chain.rs b/linera-chain/src/chain.rs index 36b39ccdbabe..a626ce7e2367 100644 --- a/linera-chain/src/chain.rs +++ b/linera-chain/src/chain.rs @@ -14,18 +14,17 @@ use async_graphql::SimpleObject; use futures::stream::{self, StreamExt, TryStreamExt}; use linera_base::{ crypto::CryptoHash, - data_types::{Amount, ArithmeticError, BlockHeight, Timestamp}, + data_types::{ArithmeticError, BlockHeight, Timestamp}, ensure, identifiers::{ChainId, Destination, MessageId}, prometheus_util, sync::Lazy, }; use linera_execution::{ - system::{SystemExecutionError, SystemMessage}, - ExecutionError, ExecutionOutcome, ExecutionRuntimeContext, ExecutionStateView, + system::SystemMessage, ExecutionOutcome, ExecutionRuntimeContext, ExecutionStateView, GenericApplicationId, Message, MessageContext, OperationContext, Query, QueryContext, - RawExecutionOutcome, RawOutgoingMessage, ResourceTracker, Response, UserApplicationDescription, - UserApplicationId, + RawExecutionOutcome, RawOutgoingMessage, ResourceController, ResourceTracker, Response, + UserApplicationDescription, UserApplicationId, }; use linera_views::{ common::Context, @@ -39,6 +38,7 @@ use prometheus::{HistogramVec, IntCounterVec}; use serde::{Deserialize, Serialize}; use std::{ collections::{BTreeMap, HashSet}, + sync::Arc, time::Instant, }; @@ -271,22 +271,6 @@ where ViewError: From, C::Extra: ExecutionRuntimeContext, { - /// Substracts an amount from a balance and reports an error if that is impossible - fn sub_assign_fees( - balance: &mut Amount, - fees: Amount, - chain_execution_context: ChainExecutionContext, - ) -> Result<(), ChainError> { - balance.try_sub_assign(fees).map_err(|_| { - ChainError::ExecutionError( - ExecutionError::SystemError(SystemExecutionError::InsufficientFunding { - current_balance: *balance, - }), - chain_execution_context, - ) - }) - } - pub fn chain_id(&self) -> ChainId { self.context().extra().chain_id() } @@ -602,11 +586,15 @@ where let Some((_, committee)) = self.execution_state.system.current_committee() else { return Err(ChainError::InactiveChain(chain_id)); }; - - let policy = committee.policy().clone(); + let mut resource_controller = ResourceController { + policy: Arc::new(committee.policy().clone()), + tracker: ResourceTracker::default(), + // TODO(#1537): Allow using the personal account of the block producer. + account: None, + }; let mut messages = Vec::new(); let mut message_counts = Vec::new(); - let mut tracker = ResourceTracker::default(); + // The first incoming message of any child chain must be `OpenChain`. A root chain must // already be initialized if block.height == BlockHeight::ZERO @@ -654,8 +642,7 @@ where .execute_message( context, message.event.message.clone(), - &policy, - &mut tracker, + &mut resource_controller, ) .await .map_err(|err| ChainError::ExecutionError(err, chain_execution_context))?, @@ -686,13 +673,12 @@ where .process_execution_outcomes(context.height, outcomes) .await?; if let MessageAction::Accept = message.action { - let balance = self.execution_state.system.balance.get_mut(); for message_out in &messages_out { - Self::sub_assign_fees( - balance, - policy.message_price(&message_out.message)?, - chain_execution_context, - )?; + resource_controller + .with(&mut self.execution_state) + .await? + .track_message(&message_out.message) + .map_err(|err| ChainError::ExecutionError(err, chain_execution_context))?; } } messages.append(&mut messages_out); @@ -714,31 +700,35 @@ where }; let outcomes = self .execution_state - .execute_operation(context, operation.clone(), &policy, &mut tracker) + .execute_operation(context, operation.clone(), &mut resource_controller) .await .map_err(|err| ChainError::ExecutionError(err, chain_execution_context))?; let mut messages_out = self .process_execution_outcomes(context.height, outcomes) .await?; - let balance = self.execution_state.system.balance.get_mut(); - Self::sub_assign_fees( - balance, - policy.operation_price(operation)?, - chain_execution_context, - )?; + resource_controller + .with(&mut self.execution_state) + .await? + .track_operation(operation) + .map_err(|err| ChainError::ExecutionError(err, chain_execution_context))?; for message_out in &messages_out { - Self::sub_assign_fees( - balance, - policy.message_price(&message_out.message)?, - chain_execution_context, - )?; + resource_controller + .with(&mut self.execution_state) + .await? + .track_message(&message_out.message) + .map_err(|err| ChainError::ExecutionError(err, chain_execution_context))?; } messages.append(&mut messages_out); message_counts .push(u32::try_from(messages.len()).map_err(|_| ArithmeticError::Overflow)?); } - let balance = self.execution_state.system.balance.get_mut(); - Self::sub_assign_fees(balance, policy.block_price(), ChainExecutionContext::Block)?; + + // Finally, charge for the block fee. + resource_controller + .with(&mut self.execution_state) + .await? + .track_block() + .map_err(|err| ChainError::ExecutionError(err, ChainExecutionContext::Block))?; // Recompute the state hash. let state_hash = self.execution_state.crypto_hash().await?; @@ -757,16 +747,16 @@ where .observe(start_time.elapsed().as_secs_f64() * 1000.0); WASM_FUEL_USED_PER_BLOCK .with_label_values(&[]) - .observe(tracker.used_fuel as f64); + .observe(resource_controller.tracker.fuel as f64); WASM_NUM_READS_PER_BLOCK .with_label_values(&[]) - .observe(tracker.num_reads as f64); + .observe(resource_controller.tracker.read_operations as f64); WASM_BYTES_READ_PER_BLOCK .with_label_values(&[]) - .observe(tracker.bytes_read as f64); + .observe(resource_controller.tracker.bytes_read as f64); WASM_BYTES_WRITTEN_PER_BLOCK .with_label_values(&[]) - .observe(tracker.bytes_written as f64); + .observe(resource_controller.tracker.bytes_written as f64); assert_eq!( message_counts.len(), diff --git a/linera-chain/src/lib.rs b/linera-chain/src/lib.rs index 231bd72d6ff5..88a34c4b4d4d 100644 --- a/linera-chain/src/lib.rs +++ b/linera-chain/src/lib.rs @@ -20,7 +20,7 @@ use linera_base::{ data_types::{ArithmeticError, BlockHeight, Round, Timestamp}, identifiers::ChainId, }; -use linera_execution::{policy::PricingError, ExecutionError}; +use linera_execution::ExecutionError; use linera_views::views::ViewError; use rand_distr::WeightedError; use thiserror::Error; @@ -35,8 +35,6 @@ pub enum ChainError { ViewError(#[from] ViewError), #[error("Execution error: {0} during {1:?}")] ExecutionError(ExecutionError, ChainExecutionContext), - #[error("Pricing error: {0}")] - PricingError(#[from] PricingError), #[error("The chain being queried is not active {0:?}")] InactiveChain(ChainId), diff --git a/linera-core/src/unit_tests/wasm_worker_tests.rs b/linera-core/src/unit_tests/wasm_worker_tests.rs index 892414641790..210b186134e0 100644 --- a/linera-core/src/unit_tests/wasm_worker_tests.rs +++ b/linera-core/src/unit_tests/wasm_worker_tests.rs @@ -26,11 +26,10 @@ use linera_chain::{ }; use linera_execution::{ committee::Epoch, - policy::ResourceControlPolicy, system::{SystemChannel, SystemMessage, SystemOperation}, Bytecode, BytecodeLocation, ChainOwnership, ChannelSubscription, ExecutionRuntimeConfig, ExecutionStateView, GenericApplicationId, Message, MessageKind, Operation, OperationContext, - ResourceTracker, SystemExecutionState, UserApplicationDescription, UserApplicationId, + ResourceController, SystemExecutionState, UserApplicationDescription, UserApplicationId, WasmContractModule, WasmRuntime, }; use linera_storage::{MemoryStorage, Storage}; @@ -430,8 +429,7 @@ where index: 0, next_message_index: 0, }; - let mut tracker = ResourceTracker::default(); - let policy = ResourceControlPolicy::default(); + let mut controller = ResourceController::default(); creator_state .execute_operation( operation_context, @@ -439,8 +437,7 @@ where application_id, bytes: user_operation, }, - &policy, - &mut tracker, + &mut controller, ) .await?; creator_state.system.timestamp.set(Timestamp::from(5)); diff --git a/linera-execution/src/execution.rs b/linera-execution/src/execution.rs index 0e2cbe7f0bf2..a8274257e68c 100644 --- a/linera-execution/src/execution.rs +++ b/linera-execution/src/execution.rs @@ -2,11 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - policy::ResourceControlPolicy, resources::ResourceTracker, system::SystemExecutionStateView, - ContractSyncRuntime, ExecutionError, ExecutionOutcome, ExecutionRuntimeConfig, - ExecutionRuntimeContext, Message, MessageContext, MessageKind, Operation, OperationContext, - Query, QueryContext, RawExecutionOutcome, RawOutgoingMessage, Response, ServiceSyncRuntime, - SystemMessage, UserApplicationDescription, UserApplicationId, + resources::ResourceController, system::SystemExecutionStateView, ContractSyncRuntime, + ExecutionError, ExecutionOutcome, ExecutionRuntimeConfig, ExecutionRuntimeContext, Message, + MessageContext, MessageKind, Operation, OperationContext, Query, QueryContext, + RawExecutionOutcome, RawOutgoingMessage, Response, ServiceSyncRuntime, SystemMessage, + UserApplicationDescription, UserApplicationId, }; use futures::{stream::FuturesUnordered, StreamExt, TryStreamExt}; use linera_base::identifiers::{ChainId, Destination, Owner}; @@ -21,7 +21,10 @@ use std::collections::{BTreeSet, HashMap}; #[cfg(any(test, feature = "test"))] use { - crate::{system::SystemExecutionState, TestExecutionRuntimeContext, UserContractCode}, + crate::{ + policy::ResourceControlPolicy, system::SystemExecutionState, ResourceTracker, + TestExecutionRuntimeContext, UserContractCode, + }, async_lock::Mutex, linera_views::memory::{MemoryContext, TEST_MEMORY_MAX_STREAM_QUERIES}, std::collections::BTreeMap, @@ -128,9 +131,14 @@ where .user_contracts() .insert(application_id, contract); - let mut tracker = ResourceTracker::default(); + let tracker = ResourceTracker::default(); let policy = ResourceControlPolicy::default(); - self.run_user_action(application_id, chain_id, action, &policy, &mut tracker) + let mut resource_controller = ResourceController { + policy: Arc::new(policy), + tracker, + account: None, + }; + self.run_user_action(application_id, chain_id, action, &mut resource_controller) .await?; Ok(()) @@ -165,8 +173,7 @@ where application_id: UserApplicationId, chain_id: ChainId, action: UserAction, - policy: &ResourceControlPolicy, - tracker: &mut ResourceTracker, + resource_controller: &mut ResourceController>, ) -> Result, ExecutionError> { let execution_outcomes = match self.context().extra().execution_runtime_config() { ExecutionRuntimeConfig::Synchronous => { @@ -174,8 +181,7 @@ where application_id, chain_id, action, - policy, - tracker, + resource_controller, ) .await? } @@ -189,12 +195,13 @@ where application_id: UserApplicationId, chain_id: ChainId, action: UserAction, - policy: &ResourceControlPolicy, - tracker: &mut ResourceTracker, + resource_controller: &mut ResourceController>, ) -> Result, ExecutionError> { - let balance = self.system.balance.get(); - let runtime_limits = tracker.limits(policy, balance); - let initial_remaining_fuel = policy.remaining_fuel(*balance); + let controller = ResourceController { + policy: resource_controller.policy.clone(), + tracker: resource_controller.tracker, + account: resource_controller.with(self).await?.balance(), + }; let (execution_state_sender, mut execution_state_receiver) = futures::channel::mpsc::unbounded(); let execution_outcomes_future = tokio::task::spawn_blocking(move || { @@ -202,17 +209,16 @@ where execution_state_sender, application_id, chain_id, - runtime_limits, - initial_remaining_fuel, + controller, action, ) }); while let Some(request) = execution_state_receiver.next().await { self.handle_request(request).await?; } - let (execution_outcomes, runtime_counts) = execution_outcomes_future.await??; - let balance = self.system.balance.get_mut(); - tracker.update_limits(balance, policy, runtime_counts)?; + let (execution_outcomes, controller) = execution_outcomes_future.await??; + *resource_controller.with(self).await?.balance_mut() = controller.account; + resource_controller.tracker = controller.tracker; Ok(execution_outcomes) } @@ -281,8 +287,7 @@ where &mut self, context: OperationContext, operation: Operation, - policy: &ResourceControlPolicy, - tracker: &mut ResourceTracker, + resource_controller: &mut ResourceController>, ) -> Result, ExecutionError> { assert_eq!(context.chain_id, self.context().extra().chain_id()); match operation { @@ -298,8 +303,7 @@ where application_id, context.chain_id, user_action, - policy, - tracker, + resource_controller, ) .await?, ); @@ -314,8 +318,7 @@ where application_id, context.chain_id, UserAction::Operation(context, bytes), - policy, - tracker, + resource_controller, ) .await } @@ -326,8 +329,7 @@ where &mut self, context: MessageContext, message: Message, - policy: &ResourceControlPolicy, - tracker: &mut ResourceTracker, + resource_controller: &mut ResourceController>, ) -> Result, ExecutionError> { assert_eq!(context.chain_id, self.context().extra().chain_id()); match message { @@ -343,8 +345,7 @@ where application_id, context.chain_id, UserAction::Message(context, bytes), - policy, - tracker, + resource_controller, ) .await } diff --git a/linera-execution/src/lib.rs b/linera-execution/src/lib.rs index 27685c2f4464..0a7c7bfd8b08 100644 --- a/linera-execution/src/lib.rs +++ b/linera-execution/src/lib.rs @@ -23,7 +23,7 @@ pub use applications::{ }; pub use execution::ExecutionStateView; pub use ownership::{ChainOwnership, TimeoutConfig}; -pub use resources::ResourceTracker; +pub use resources::{ResourceController, ResourceTracker}; pub use system::{ SystemExecutionError, SystemExecutionStateView, SystemMessage, SystemOperation, SystemQuery, SystemResponse, @@ -38,7 +38,6 @@ pub use wasm::{WasmContractModule, WasmExecutionError, WasmServiceModule}; #[cfg(any(test, feature = "test"))] pub use {applications::ApplicationRegistry, system::SystemExecutionState}; -use crate::policy::PricingError; use async_graphql::SimpleObject; use async_trait::async_trait; use custom_debug_derive::Debug; @@ -123,10 +122,6 @@ pub enum ExecutionError { #[error("Failed to load bytecode from storage {0:?}")] ApplicationBytecodeNotFound(Box), - #[error("Pricing error: {0}")] - PricingError(#[from] PricingError), - #[error("Excessive number of read queries from storage")] - ExcessiveNumReads, #[error("Excessive number of bytes read from storage")] ExcessiveRead, #[error("Excessive number of bytes written to storage")] diff --git a/linera-execution/src/policy.rs b/linera-execution/src/policy.rs index b5085a6a76cb..d9ece8bae77a 100644 --- a/linera-execution/src/policy.rs +++ b/linera-execution/src/policy.rs @@ -3,11 +3,9 @@ //! This module contains types related to fees and pricing. -use crate::{Message, Operation}; use async_graphql::InputObject; use linera_base::data_types::{Amount, ArithmeticError}; use serde::{Deserialize, Serialize}; -use thiserror::Error; /// A collection of prices and limits associated with block execution. #[derive(Eq, PartialEq, Hash, Clone, Debug, Serialize, Deserialize, InputObject)] @@ -18,12 +16,14 @@ pub struct ResourceControlPolicy { pub fuel_unit: Amount, /// The price of one read operation. pub read_operation: Amount, - // TODO(#1530): Write operation. + /// The price of one write operation. + pub write_operation: Amount, /// The price of reading a byte. pub byte_read: Amount, - /// The price to writting a byte + /// The price of writing a byte pub byte_written: Amount, /// The price of increasing storage by a byte. + // TODO(#1536): This is not fully supported. pub byte_stored: Amount, /// The base price of adding an operation to a block. pub operation: Amount, @@ -34,6 +34,8 @@ pub struct ResourceControlPolicy { /// The additional price for each byte in the argument of a user message. pub message_byte: Amount, + // TODO(#1538): Cap the number of transactions per block and the total size of their + // arguments. /// The maximum data to read per block pub maximum_bytes_read_per_block: u64, /// The maximum data to write per block @@ -46,6 +48,7 @@ impl Default for ResourceControlPolicy { block: Amount::default(), fuel_unit: Amount::default(), read_operation: Amount::default(), + write_operation: Amount::default(), byte_read: Amount::default(), byte_written: Amount::default(), byte_stored: Amount::default(), @@ -64,56 +67,42 @@ impl ResourceControlPolicy { self.block } - pub fn operation_price(&self, operation: &Operation) -> Result { - match operation { - Operation::System(_) => Ok(self.operation), - Operation::User { bytes, .. } => { - let size = bytes.len(); - let price = self - .operation_byte - .try_mul(size as u128)? - .try_add(self.operation)?; - Ok(price) - } - } + pub(crate) fn operation_byte_price(&self, size: u64) -> Result { + self.operation_byte.try_mul(size as u128) } - pub fn message_price(&self, message: &Message) -> Result { - match message { - Message::System(_) => Ok(self.message), - Message::User { bytes, .. } => { - let size = bytes.len(); - let price = self - .message_byte - .try_mul(size as u128)? - .try_add(self.message)?; - Ok(price) - } - } + pub(crate) fn message_byte_price(&self, size: u64) -> Result { + self.message_byte.try_mul(size as u128) + } + + pub(crate) fn read_operations_price(&self, count: u32) -> Result { + self.read_operation.try_mul(count as u128) } - pub fn storage_num_reads_price(&self, count: u64) -> Result { - Ok(self.read_operation.try_mul(count as u128)?) + pub(crate) fn write_operations_price(&self, count: u32) -> Result { + self.write_operation.try_mul(count as u128) } - pub fn storage_bytes_read_price(&self, count: u64) -> Result { - Ok(self.byte_read.try_mul(count as u128)?) + pub(crate) fn bytes_read_price(&self, count: u64) -> Result { + self.byte_read.try_mul(count as u128) } - pub fn storage_bytes_written_price(&self, count: u64) -> Result { - Ok(self.byte_written.try_mul(count as u128)?) + pub(crate) fn bytes_written_price(&self, count: u64) -> Result { + self.byte_written.try_mul(count as u128) } - pub fn storage_bytes_stored_price(&self, count: u64) -> Result { - Ok(self.byte_stored.try_mul(count as u128)?) + // TODO(#1536): This is not fully implemented. + #[allow(dead_code)] + pub(crate) fn bytes_stored_price(&self, count: u64) -> Result { + self.byte_stored.try_mul(count as u128) } - pub fn fuel_price(&self, fuel: u64) -> Result { - Ok(self.fuel_unit.try_mul(u128::from(fuel))?) + pub(crate) fn fuel_price(&self, fuel: u64) -> Result { + self.fuel_unit.try_mul(u128::from(fuel)) } /// Returns how much fuel can be paid with the given balance. - pub fn remaining_fuel(&self, balance: Amount) -> u64 { + pub(crate) fn remaining_fuel(&self, balance: Amount) -> u64 { u64::try_from(balance.saturating_div(self.fuel_unit)).unwrap_or(u64::MAX) } @@ -158,9 +147,3 @@ impl ResourceControlPolicy { } } } - -#[derive(Error, Debug)] -pub enum PricingError { - #[error(transparent)] - ArithmeticError(#[from] ArithmeticError), -} diff --git a/linera-execution/src/resources.rs b/linera-execution/src/resources.rs index 8cfe0524caff..8e45e43a71ad 100644 --- a/linera-execution/src/resources.rs +++ b/linera-execution/src/resources.rs @@ -3,194 +3,331 @@ //! This module tracks the resources used during the execution of a transaction. -use crate::{policy::ResourceControlPolicy, system::SystemExecutionError, ExecutionError}; - +use crate::{ + policy::ResourceControlPolicy, system::SystemExecutionError, ExecutionError, + ExecutionStateView, Message, Operation, +}; use custom_debug_derive::Debug; -use linera_base::data_types::Amount; - -/// The resource constraints applicable to an execution process. -#[derive(Copy, Debug, Clone)] -pub struct RuntimeLimits { - /// The maximum read requests per block - pub max_budget_num_reads: u64, - /// The maximum number of bytes that can be read per block - pub max_budget_bytes_read: u64, - /// The maximum number of bytes that can be written per block - pub max_budget_bytes_written: u64, - /// The maximum size of read allowed per block - pub maximum_bytes_left_to_read: u64, - /// The maximum size of write allowed per block - pub maximum_bytes_left_to_write: u64, +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 { + /// The (fixed) policy used to charge fees and control resource usage. + pub policy: Arc, + /// How the resources were used so far. + pub tracker: Tracker, + /// The account paying for the resource usage. + pub account: Account, } /// The resources used so far by an execution process. #[derive(Copy, Debug, Clone, Default)] pub struct ResourceTracker { - /// The used fuel in the computation - pub used_fuel: u64, - /// The number of reads in the computation - pub num_reads: u64, - /// The number of writes in the computation - pub num_writes: u64, - /// The total number of bytes read + /// The number of blocks created. + pub blocks: u32, + /// The fuel used so far. + pub fuel: u64, + /// The number of read operations. + pub read_operations: u32, + /// The number of write operations. + pub write_operations: u32, + /// The number of bytes read. pub bytes_read: u64, - /// The total number of bytes written + /// The number of bytes written. pub bytes_written: u64, - /// The change in the total data being stored - pub stored_size_delta: i32, + /// The change in the number of bytes being stored by user applications. + pub bytes_stored: i32, + /// The number of operations executed. + pub operations: u32, + /// The total size of the arguments of user operations. + pub operation_bytes: u64, + /// The number of outgoing messages created (system and user). + pub messages: u32, + /// The total size of the arguments of outgoing user messages. + pub message_bytes: u64, } -impl Default for RuntimeLimits { - fn default() -> Self { - RuntimeLimits { - max_budget_num_reads: u64::MAX / 2, - max_budget_bytes_read: u64::MAX / 2, - max_budget_bytes_written: u64::MAX / 2, - maximum_bytes_left_to_read: u64::MAX / 2, - maximum_bytes_left_to_write: u64::MAX / 2, - } +/// How to access the balance of an account. +pub trait BalanceHolder { + fn as_amount(&self) -> Amount; + + fn as_amount_mut(&mut self) -> &mut Amount; + + fn try_sub_assign(&mut self, other: Amount) -> Result<(), ArithmeticError> { + self.as_amount_mut().try_sub_assign(other) } } -impl ResourceTracker { - /// Subtracts an amount from a balance and reports an error if that is impossible - fn sub_assign_fees(balance: &mut Amount, fees: Amount) -> Result<(), SystemExecutionError> { - balance - .try_sub_assign(fees) - .map_err(|_| SystemExecutionError::InsufficientFunding { - current_balance: *balance, - }) +// The main accounting functions for a ResourceController. +impl ResourceController +where + Account: BalanceHolder, + Tracker: AsMut, +{ + /// Obtains the balance of the account. + pub fn balance(&mut self) -> Amount { + self.account.as_amount() } - /// Updates the limits for the maximum and updates the balance. - pub fn update_limits( - &mut self, - balance: &mut Amount, - policy: &ResourceControlPolicy, - runtime_counts: RuntimeCounts, - ) -> Result<(), ExecutionError> { - // The fuel being used - let initial_fuel = policy.remaining_fuel(*balance); - let used_fuel = initial_fuel.saturating_sub(runtime_counts.remaining_fuel); - self.used_fuel += used_fuel; - Self::sub_assign_fees(balance, policy.fuel_price(used_fuel)?)?; - - // The number of reads - Self::sub_assign_fees( - balance, - policy.storage_num_reads_price(runtime_counts.num_reads)?, - )?; - self.num_reads += runtime_counts.num_reads; - - // The number of bytes read - let bytes_read = runtime_counts.bytes_read; - self.bytes_read += runtime_counts.bytes_read; - Self::sub_assign_fees(balance, policy.storage_bytes_read_price(bytes_read)?)?; - - // The number of bytes written - let bytes_written = runtime_counts.bytes_written; - self.bytes_written += bytes_written; - Self::sub_assign_fees(balance, policy.storage_bytes_written_price(bytes_written)?)?; + /// Obtains a mutable reference on the balance of the account. + pub fn balance_mut(&mut self) -> &mut Amount { + self.account.as_amount_mut() + } + /// Subtracts an amount from a balance and reports an error if that is impossible. + fn update_balance(&mut self, fees: Amount) -> Result<(), ExecutionError> { + self.account.try_sub_assign(fees).map_err(|_| { + SystemExecutionError::InsufficientFunding { + current_balance: self.account.as_amount(), + } + })?; Ok(()) } - /// Obtain the limits for the running of the system - pub fn limits(&self, policy: &ResourceControlPolicy, balance: &Amount) -> RuntimeLimits { - let max_budget_num_reads = - u64::try_from(balance.saturating_div(policy.read_operation)).unwrap_or(u64::MAX); - let max_budget_bytes_read = - u64::try_from(balance.saturating_div(policy.byte_read)).unwrap_or(u64::MAX); - let max_budget_bytes_written = - u64::try_from(balance.saturating_div(policy.byte_written)).unwrap_or(u64::MAX); - let maximum_bytes_left_to_read = policy - .maximum_bytes_read_per_block - .saturating_sub(self.bytes_read); - let maximum_bytes_left_to_write = policy - .maximum_bytes_written_per_block - .saturating_sub(self.bytes_written); - RuntimeLimits { - max_budget_num_reads, - max_budget_bytes_read, - max_budget_bytes_written, - maximum_bytes_left_to_read, - maximum_bytes_left_to_write, - } + /// Obtains the amount of fuel that could be spent by consuming the entire balance. + pub(crate) fn remaining_fuel(&self) -> u64 { + self.policy.remaining_fuel(self.account.as_amount()) } -} -/// The entries of the runtime related to fuel and storage -#[derive(Copy, Debug, Clone, Default)] -pub struct RuntimeCounts { - /// The remaining fuel available - pub remaining_fuel: u64, - /// The number of read operations - pub num_reads: u64, - /// The number of write operations - pub num_writes: u64, - /// The bytes that have been read - pub bytes_read: u64, - /// The bytes that have been written - pub bytes_written: u64, - /// The change in the total data stored - pub stored_size_delta: i32, -} + /// Tracks the creation of a block. + 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) + } -impl RuntimeCounts { - pub fn increment_num_reads(&mut self, limits: &RuntimeLimits) -> Result<(), ExecutionError> { - self.num_reads += 1; - if self.num_reads >= limits.max_budget_num_reads { - return Err(ExecutionError::ExcessiveNumReads); + /// Tracks the execution of an operation in 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(()) + } } - Ok(()) } - /// Increments the number of executed write operations. - pub fn increment_num_writes( - &mut self, - _limits: &RuntimeLimits, - amount: u64, - ) -> Result<(), ExecutionError> { - self.num_writes += amount; - Ok(()) + /// Tracks the creation of an outgoing message. + 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 fn increment_bytes_read( - &mut self, - limits: &RuntimeLimits, - increment: u64, - ) -> Result<(), ExecutionError> { - self.bytes_read = self + /// Tracks a number of fuel units used. + 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)?) + } + + /// Tracks a read operation. + 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)?) + } + + /// Tracks a write operation. + 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)?) + } + + /// Tracks a number of bytes read. + 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(increment) - .ok_or(ExecutionError::ExcessiveRead)?; - if self.bytes_read >= limits.max_budget_bytes_read - || self.bytes_read >= limits.maximum_bytes_left_to_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 fn increment_bytes_written( - &mut self, - limits: &RuntimeLimits, - increment: u64, - ) -> Result<(), ExecutionError> { - self.bytes_written = self + /// Tracks a number of bytes written. + 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(increment) - .ok_or(ExecutionError::ExcessiveWrite)?; - if self.bytes_written >= limits.max_budget_bytes_written - || self.bytes_written >= limits.maximum_bytes_left_to_write - { + .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 fn update_stored_size(&mut self, delta: i32) -> Result<(), ExecutionError> { - self.stored_size_delta += delta; + /// Tracks a change in the number of bytes stored. + // TODO(#1536): This is not fully implemented. + #[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(()) } } + +// The simplest `BalanceHolder` is an `Amount`. +impl BalanceHolder for Amount { + fn as_amount(&self) -> Amount { + *self + } + + fn as_amount_mut(&mut self) -> &mut Amount { + self + } +} + +// This is also needed for the default instantiation `ResourceController`. +// See https://doc.rust-lang.org/std/convert/trait.AsMut.html#reflexivity for general context. +impl AsMut for ResourceTracker { + fn as_mut(&mut self) -> &mut Self { + self + } +} + +/// A temporary object made of an optional [`Owner`] together with a mutable reference +/// on an execution state view [`ExecutionStateView`]. +/// +/// This type is meant to implement [`BalanceHolder`] and make the accounting functions of +/// [`ResourceController`] available to temporary objects of type +/// `ResourceController, &'a mut ResourceTracker>`. +/// +/// Such temporary objects can be obtained from values of type +/// `ResourceController>` using the method `with` to provide the missing +/// reference on [`ExecutionStateView`]. +pub struct OwnedView<'a, C> { + owner: Option, + view: &'a mut ExecutionStateView, +} + +impl BalanceHolder for OwnedView<'_, C> +where + C: Context + Clone + Send + Sync + 'static, + ViewError: From, +{ + fn as_amount(&self) -> Amount { + match &self.owner { + None => *self.view.system.balance.get(), + Some(owner) => self + .view + .system + .balances + .get(owner) + .now_or_never() + .expect("The map entry was previously loaded by OwnedView::with") + .expect("Account was created there as well") + .expect("No I/O can fail here"), + } + } + + fn as_amount_mut(&mut self) -> &mut Amount { + match &self.owner { + None => self.view.system.balance.get_mut(), + Some(owner) => self + .view + .system + .balances + .get_mut(owner) + .now_or_never() + .expect("The map entry was previously loaded by OwnedView::with") + .expect("Account was created there as well") + .expect("No I/O can fail here"), + } + } +} + +impl ResourceController, ResourceTracker> { + /// Provides a reference to the current execution state and obtains a temporary object + /// where the accounting functions of [`ResourceController`] are available. + pub async fn with<'a, C>( + &mut self, + view: &'a mut ExecutionStateView, + ) -> Result, &mut ResourceTracker>, ViewError> + where + C: Context + Clone + Send + Sync + 'static, + ViewError: From, + { + if let Some(owner) = &self.account { + // Make sure `owner` has an account and that the account is loaded in memory. + let balance = view.system.balances.get_mut(owner).await?; + if balance.is_none() { + view.system.balances.insert(owner, Amount::ZERO)?; + } + } + Ok(ResourceController { + policy: self.policy.clone(), + tracker: &mut self.tracker, + account: OwnedView { + owner: self.account, + view, + }, + }) + } +} diff --git a/linera-execution/src/runtime.rs b/linera-execution/src/runtime.rs index cdd7e204e23e..d64e60cb4faa 100644 --- a/linera-execution/src/runtime.rs +++ b/linera-execution/src/runtime.rs @@ -4,7 +4,7 @@ use crate::{ execution::UserAction, execution_state_actor::{ExecutionStateSender, Request}, - resources::{RuntimeCounts, RuntimeLimits}, + resources::ResourceController, util::{ReceiverExt, UnboundedSenderExt}, ApplicationCallOutcome, BaseRuntime, CallOutcome, CalleeContext, ContractRuntime, ExecutionError, ExecutionOutcome, ServiceRuntime, SessionId, UserApplicationDescription, @@ -58,10 +58,8 @@ pub struct SyncRuntimeInternal { /// Track application states (view case). view_user_states: BTreeMap, - /// Counters to track fuel and storage consumption. - runtime_counts: RuntimeCounts, - /// The runtime limits. - runtime_limits: RuntimeLimits, + /// Controller to track fuel and storage consumption. + resource_controller: ResourceController, } /// The runtime status of an application. @@ -232,13 +230,8 @@ impl SyncRuntimeInternal { fn new( chain_id: ChainId, execution_state_sender: ExecutionStateSender, - runtime_limits: RuntimeLimits, - remaining_fuel: u64, + resource_controller: ResourceController, ) -> Self { - let runtime_counts = RuntimeCounts { - remaining_fuel, - ..Default::default() - }; Self { chain_id, execution_state_sender, @@ -249,8 +242,7 @@ impl SyncRuntimeInternal { session_manager: SessionManager::default(), simple_user_states: BTreeMap::default(), view_user_states: BTreeMap::default(), - runtime_counts, - runtime_limits, + resource_controller, } } @@ -708,10 +700,14 @@ impl BaseRuntime for SyncRuntimeInternal { let id = self.application_id()?; let state = self.view_user_states.entry(id).or_default(); state.force_all_pending_queries()?; - self.runtime_counts - .increment_num_writes(&self.runtime_limits, batch.num_operations() as u64)?; - self.runtime_counts - .increment_bytes_written(&self.runtime_limits, batch.size() as u64)?; + self.resource_controller.track_write_operations( + batch + .num_operations() + .try_into() + .map_err(|_| ExecutionError::from(ArithmeticError::Overflow))?, + )?; + self.resource_controller + .track_bytes_written(batch.size() as u64)?; self.execution_state_sender .send_request(|callback| Request::WriteBatch { id, @@ -725,8 +721,7 @@ impl BaseRuntime for SyncRuntimeInternal { fn contains_key_new(&mut self, key: Vec) -> Result { let id = self.application_id()?; let state = self.view_user_states.entry(id).or_default(); - self.runtime_counts - .increment_num_reads(&self.runtime_limits)?; + self.resource_controller.track_read_operations(1)?; let receiver = self .execution_state_sender .send_request(move |callback| Request::ContainsKey { id, key, callback })?; @@ -746,8 +741,7 @@ impl BaseRuntime for SyncRuntimeInternal { ) -> Result { let id = self.application_id()?; let state = self.view_user_states.entry(id).or_default(); - self.runtime_counts - .increment_num_reads(&self.runtime_limits)?; + self.resource_controller.track_read_operations(1)?; let receiver = self .execution_state_sender .send_request(move |callback| Request::ReadMultiValuesBytes { id, keys, callback })?; @@ -763,8 +757,8 @@ impl BaseRuntime for SyncRuntimeInternal { let values = state.read_multi_values_queries.wait(*promise)?; for value in &values { if let Some(value) = &value { - self.runtime_counts - .increment_bytes_read(&self.runtime_limits, value.len() as u64)?; + self.resource_controller + .track_bytes_read(value.len() as u64)?; } } Ok(values) @@ -776,8 +770,7 @@ impl BaseRuntime for SyncRuntimeInternal { ) -> Result { let id = self.application_id()?; let state = self.view_user_states.entry(id).or_default(); - self.runtime_counts - .increment_num_reads(&self.runtime_limits)?; + self.resource_controller.track_read_operations(1)?; let receiver = self .execution_state_sender .send_request(move |callback| Request::ReadValueBytes { id, key, callback })?; @@ -792,8 +785,8 @@ impl BaseRuntime for SyncRuntimeInternal { let state = self.view_user_states.entry(id).or_default(); let value = state.read_value_queries.wait(*promise)?; if let Some(value) = &value { - self.runtime_counts - .increment_bytes_read(&self.runtime_limits, value.len() as u64)?; + self.resource_controller + .track_bytes_read(value.len() as u64)?; } Ok(value) } @@ -804,8 +797,7 @@ impl BaseRuntime for SyncRuntimeInternal { ) -> Result { let id = self.application_id()?; let state = self.view_user_states.entry(id).or_default(); - self.runtime_counts - .increment_num_reads(&self.runtime_limits)?; + self.resource_controller.track_read_operations(1)?; let receiver = self.execution_state_sender.send_request(move |callback| { Request::FindKeysByPrefix { id, @@ -827,8 +819,8 @@ impl BaseRuntime for SyncRuntimeInternal { for key in &keys { read_size += key.len(); } - self.runtime_counts - .increment_bytes_read(&self.runtime_limits, read_size as u64)?; + self.resource_controller + .track_bytes_read(read_size as u64)?; Ok(keys) } @@ -838,8 +830,7 @@ impl BaseRuntime for SyncRuntimeInternal { ) -> Result { let id = self.application_id()?; let state = self.view_user_states.entry(id).or_default(); - self.runtime_counts - .increment_num_reads(&self.runtime_limits)?; + self.resource_controller.track_read_operations(1)?; let receiver = self.execution_state_sender.send_request(move |callback| { Request::FindKeyValuesByPrefix { id, @@ -861,8 +852,8 @@ impl BaseRuntime for SyncRuntimeInternal { for (key, value) in &key_values { read_size += key.len() + value.len(); } - self.runtime_counts - .increment_bytes_read(&self.runtime_limits, read_size as u64)?; + self.resource_controller + .track_bytes_read(read_size as u64)?; Ok(key_values) } } @@ -879,16 +870,11 @@ impl ContractSyncRuntime { execution_state_sender: ExecutionStateSender, application_id: UserApplicationId, chain_id: ChainId, - runtime_limits: RuntimeLimits, - initial_remaining_fuel: u64, + resource_controller: ResourceController, action: UserAction, - ) -> Result<(Vec, RuntimeCounts), ExecutionError> { - let mut runtime = SyncRuntimeInternal::new( - chain_id, - execution_state_sender, - runtime_limits, - initial_remaining_fuel, - ); + ) -> Result<(Vec, ResourceController), ExecutionError> { + let mut runtime = + SyncRuntimeInternal::new(chain_id, execution_state_sender, resource_controller); let (code, description) = runtime.load_contract(application_id)?; let signer = action.signer(); runtime.push_application(ApplicationStatus { @@ -927,18 +913,21 @@ impl ContractSyncRuntime { application_id, execution_outcome.with_authenticated_signer(signer), )); - Ok((runtime.execution_outcomes, runtime.runtime_counts)) + Ok((runtime.execution_outcomes, runtime.resource_controller)) } } impl ContractRuntime for ContractSyncRuntime { fn remaining_fuel(&mut self) -> Result { - Ok(self.inner().runtime_counts.remaining_fuel) + Ok(self.inner().resource_controller.remaining_fuel()) } fn set_remaining_fuel(&mut self, remaining_fuel: u64) -> Result<(), ExecutionError> { - self.inner().runtime_counts.remaining_fuel = remaining_fuel; - Ok(()) + let mut this = self.inner(); + let previous_fuel = this.resource_controller.remaining_fuel(); + assert!(previous_fuel >= remaining_fuel); + this.resource_controller + .track_fuel(previous_fuel - remaining_fuel) } fn try_call_application( @@ -1022,8 +1011,7 @@ impl ServiceSyncRuntime { let runtime_internal = SyncRuntimeInternal::new( context.chain_id, execution_state_sender, - RuntimeLimits::default(), - 0, + ResourceController::default(), ); let mut runtime = ServiceSyncRuntime::new(runtime_internal); diff --git a/linera-execution/src/unit_tests/runtime_tests.rs b/linera-execution/src/unit_tests/runtime_tests.rs index b78e9e575650..a33eeb84c02c 100644 --- a/linera-execution/src/unit_tests/runtime_tests.rs +++ b/linera-execution/src/unit_tests/runtime_tests.rs @@ -5,7 +5,7 @@ use super::{ApplicationStatus, SyncRuntimeInternal}; use crate::{ - execution_state_actor::Request, resources::RuntimeLimits, BaseRuntime, UserContractInstance, + execution_state_actor::Request, runtime::ResourceController, BaseRuntime, UserContractInstance, }; use futures::{channel::mpsc, StreamExt}; use linera_base::{ @@ -72,11 +72,11 @@ fn test_write_batch() { .expect("Failed to write test batch"); assert_eq!( - runtime.runtime_counts.num_writes, - expected_write_count as u64 + runtime.resource_controller.tracker.write_operations, + expected_write_count as u32 ); assert_eq!( - runtime.runtime_counts.bytes_written, + runtime.resource_controller.tracker.bytes_written, expected_bytes_count as u64 ); } @@ -89,9 +89,10 @@ fn create_contract_runtime() -> ( ) { let chain_id = ChainDescription::Root(0).into(); let (execution_state_sender, execution_state_receiver) = mpsc::unbounded(); - let limits = RuntimeLimits::default(); + let resource_controller = ResourceController::default(); - let mut runtime = SyncRuntimeInternal::new(chain_id, execution_state_sender, limits, 0); + let mut runtime = + SyncRuntimeInternal::new(chain_id, execution_state_sender, resource_controller); runtime.push_application(create_dummy_application()); diff --git a/linera-execution/tests/test_execution.rs b/linera-execution/tests/test_execution.rs index 4d90116d88a9..81b46eaea336 100644 --- a/linera-execution/tests/test_execution.rs +++ b/linera-execution/tests/test_execution.rs @@ -12,12 +12,12 @@ use linera_base::{ identifiers::{ChainDescription, ChainId, Destination, Owner}, }; use linera_execution::{ - policy::ResourceControlPolicy, system::SystemMessage, ApplicationCallOutcome, - ApplicationRegistryView, BaseRuntime, ContractRuntime, ExecutionError, ExecutionOutcome, - ExecutionRuntimeConfig, ExecutionRuntimeContext, ExecutionStateView, MessageKind, Operation, - OperationContext, Query, QueryContext, RawExecutionOutcome, RawOutgoingMessage, - ResourceTracker, Response, SessionCallOutcome, SystemExecutionState, - TestExecutionRuntimeContext, UserApplicationDescription, UserApplicationId, + system::SystemMessage, ApplicationCallOutcome, ApplicationRegistryView, BaseRuntime, + ContractRuntime, ExecutionError, ExecutionOutcome, ExecutionRuntimeConfig, + ExecutionRuntimeContext, ExecutionStateView, MessageKind, Operation, OperationContext, Query, + QueryContext, RawExecutionOutcome, RawOutgoingMessage, ResourceController, Response, + SessionCallOutcome, SystemExecutionState, TestExecutionRuntimeContext, + UserApplicationDescription, UserApplicationId, }; use linera_views::{ batch::Batch, @@ -48,8 +48,7 @@ async fn test_missing_bytecode_for_user_application() -> anyhow::Result<()> { authenticated_signer: None, next_message_index: 0, }; - let mut tracker = ResourceTracker::default(); - let policy = ResourceControlPolicy::default(); + let mut controller = ResourceController::default(); let result = view .execute_operation( context, @@ -57,8 +56,7 @@ async fn test_missing_bytecode_for_user_application() -> anyhow::Result<()> { application_id: *app_id, bytes: vec![], }, - &policy, - &mut tracker, + &mut controller, ) .await; @@ -164,8 +162,7 @@ async fn test_simple_user_operation() -> anyhow::Result<()> { authenticated_signer: Some(owner), next_message_index: 0, }; - let mut tracker = ResourceTracker::default(); - let policy = ResourceControlPolicy::default(); + let mut controller = ResourceController::default(); let outcomes = view .execute_operation( context, @@ -173,8 +170,7 @@ async fn test_simple_user_operation() -> anyhow::Result<()> { application_id: caller_id, bytes: dummy_operation.clone(), }, - &policy, - &mut tracker, + &mut controller, ) .await .unwrap(); @@ -259,8 +255,7 @@ async fn test_leaking_session() -> anyhow::Result<()> { authenticated_signer: None, next_message_index: 0, }; - let mut tracker = ResourceTracker::default(); - let policy = ResourceControlPolicy::default(); + let mut controller = ResourceController::default(); let result = view .execute_operation( context, @@ -268,8 +263,7 @@ async fn test_leaking_session() -> anyhow::Result<()> { application_id: caller_id, bytes: vec![], }, - &policy, - &mut tracker, + &mut controller, ) .await; @@ -336,8 +330,7 @@ async fn test_simple_session() -> anyhow::Result<()> { authenticated_signer: None, next_message_index: 0, }; - let mut tracker = ResourceTracker::default(); - let policy = ResourceControlPolicy::default(); + let mut controller = ResourceController::default(); let outcomes = view .execute_operation( context, @@ -345,8 +338,7 @@ async fn test_simple_session() -> anyhow::Result<()> { application_id: caller_id, bytes: vec![], }, - &policy, - &mut tracker, + &mut controller, ) .await?; @@ -411,8 +403,7 @@ async fn test_cross_application_error() -> anyhow::Result<()> { authenticated_signer: None, next_message_index: 0, }; - let mut tracker = ResourceTracker::default(); - let policy = ResourceControlPolicy::default(); + let mut controller = ResourceController::default(); assert!(matches!( view.execute_operation( context, @@ -420,8 +411,7 @@ async fn test_cross_application_error() -> anyhow::Result<()> { application_id: caller_id, bytes: vec![], }, - &policy, - &mut tracker, + &mut controller, ) .await, Err(ExecutionError::UserError(message)) if message == error_message @@ -470,8 +460,7 @@ async fn test_simple_message() -> anyhow::Result<()> { authenticated_signer: None, next_message_index: 0, }; - let mut tracker = ResourceTracker::default(); - let policy = ResourceControlPolicy::default(); + let mut controller = ResourceController::default(); let outcomes = view .execute_operation( context, @@ -479,8 +468,7 @@ async fn test_simple_message() -> anyhow::Result<()> { application_id, bytes: vec![], }, - &policy, - &mut tracker, + &mut controller, ) .await?; @@ -573,8 +561,7 @@ async fn test_message_from_cross_application_call() -> anyhow::Result<()> { authenticated_signer: None, next_message_index: 0, }; - let mut tracker = ResourceTracker::default(); - let policy = ResourceControlPolicy::default(); + let mut controller = ResourceController::default(); let outcomes = view .execute_operation( context, @@ -582,8 +569,7 @@ async fn test_message_from_cross_application_call() -> anyhow::Result<()> { application_id: caller_id, bytes: vec![], }, - &policy, - &mut tracker, + &mut controller, ) .await?; @@ -705,8 +691,7 @@ async fn test_message_from_session_call() -> anyhow::Result<()> { authenticated_signer: None, next_message_index: 0, }; - let mut tracker = ResourceTracker::default(); - let policy = ResourceControlPolicy::default(); + let mut controller = ResourceController::default(); let outcomes = view .execute_operation( context, @@ -714,8 +699,7 @@ async fn test_message_from_session_call() -> anyhow::Result<()> { application_id: caller_id, bytes: vec![], }, - &policy, - &mut tracker, + &mut controller, ) .await?; @@ -848,8 +832,7 @@ async fn test_multiple_messages_from_different_applications() -> anyhow::Result< authenticated_signer: None, next_message_index: 0, }; - let mut tracker = ResourceTracker::default(); - let policy = ResourceControlPolicy::default(); + let mut controller = ResourceController::default(); let mut outcomes = view .execute_operation( context, @@ -857,8 +840,7 @@ async fn test_multiple_messages_from_different_applications() -> anyhow::Result< application_id: caller_id, bytes: vec![], }, - &policy, - &mut tracker, + &mut controller, ) .await?; diff --git a/linera-execution/tests/test_system_execution.rs b/linera-execution/tests/test_system_execution.rs index 97f8cc0fa2b9..8252ffc7e9d0 100644 --- a/linera-execution/tests/test_system_execution.rs +++ b/linera-execution/tests/test_system_execution.rs @@ -9,10 +9,9 @@ use linera_base::{ identifiers::{ChainDescription, ChainId, MessageId}, }; use linera_execution::{ - policy::ResourceControlPolicy, system::{Recipient, UserData}, ExecutionOutcome, ExecutionStateView, Message, MessageContext, Operation, OperationContext, - Query, QueryContext, RawExecutionOutcome, ResourceTracker, Response, SystemExecutionState, + Query, QueryContext, RawExecutionOutcome, ResourceController, Response, SystemExecutionState, SystemMessage, SystemOperation, SystemQuery, SystemResponse, TestExecutionRuntimeContext, }; use linera_views::memory::MemoryContext; @@ -42,10 +41,9 @@ async fn test_simple_system_operation() -> anyhow::Result<()> { authenticated_signer: None, next_message_index: 0, }; - let mut tracker = ResourceTracker::default(); - let policy = ResourceControlPolicy::default(); + let mut controller = ResourceController::default(); let outcomes = view - .execute_operation(context, Operation::System(operation), &policy, &mut tracker) + .execute_operation(context, Operation::System(operation), &mut controller) .await .unwrap(); assert_eq!(view.system.balance.get(), &Amount::ZERO); @@ -88,10 +86,9 @@ async fn test_simple_system_message() -> anyhow::Result<()> { }, authenticated_signer: None, }; - let mut tracker = ResourceTracker::default(); - let policy = ResourceControlPolicy::default(); + let mut controller = ResourceController::default(); let outcomes = view - .execute_message(context, Message::System(message), &policy, &mut tracker) + .execute_message(context, Message::System(message), &mut controller) .await .unwrap(); assert_eq!(view.system.balance.get(), &Amount::from_tokens(4)); diff --git a/linera-execution/tests/wasm.rs b/linera-execution/tests/wasm.rs index 60a1748aba32..c2bba660aed9 100644 --- a/linera-execution/tests/wasm.rs +++ b/linera-execution/tests/wasm.rs @@ -14,7 +14,7 @@ use linera_base::{ use linera_execution::{ policy::ResourceControlPolicy, ExecutionOutcome, ExecutionRuntimeConfig, ExecutionRuntimeContext, ExecutionStateView, Operation, OperationContext, Query, QueryContext, - RawExecutionOutcome, ResourceTracker, Response, SystemExecutionState, + RawExecutionOutcome, ResourceController, ResourceTracker, Response, SystemExecutionState, TestExecutionRuntimeContext, WasmContractModule, WasmRuntime, WasmServiceModule, }; use linera_views::{memory::MemoryContext, views::View}; @@ -81,16 +81,19 @@ async fn test_fuel_for_counter_wasm_application( fuel_unit: Amount::from_atto(1), ..ResourceControlPolicy::default() }; - let mut tracker = ResourceTracker::default(); let amount = Amount::from_tokens(1); *view.system.balance.get_mut() = amount; + let mut controller = ResourceController { + policy: Arc::new(policy), + tracker: ResourceTracker::default(), + account: None, + }; for increment in &increments { let outcomes = view .execute_operation( context, Operation::user(app_id, increment).unwrap(), - &policy, - &mut tracker, + &mut controller, ) .await?; assert_eq!( @@ -101,7 +104,13 @@ async fn test_fuel_for_counter_wasm_application( )] ); } - assert_eq!(tracker.used_fuel, expected_fuel); + assert_eq!(controller.tracker.fuel, expected_fuel); + assert_eq!( + controller.with(&mut view).await?.balance(), + Amount::ONE + .try_sub(Amount::from_atto(expected_fuel as u128)) + .unwrap() + ); let context = QueryContext { chain_id: ChainId::root(0), diff --git a/linera-rpc/tests/staged/formats.yaml b/linera-rpc/tests/staged/formats.yaml index 314e74f38df1..dfbd01bffcc2 100644 --- a/linera-rpc/tests/staged/formats.yaml +++ b/linera-rpc/tests/staged/formats.yaml @@ -603,6 +603,8 @@ ResourceControlPolicy: TYPENAME: Amount - read_operation: TYPENAME: Amount + - write_operation: + TYPENAME: Amount - byte_read: TYPENAME: Amount - byte_written: diff --git a/linera-service-graphql-client/gql/service_schema.graphql b/linera-service-graphql-client/gql/service_schema.graphql index 916a0f86537d..d0726271c84f 100644 --- a/linera-service-graphql-client/gql/service_schema.graphql +++ b/linera-service-graphql-client/gql/service_schema.graphql @@ -633,11 +633,15 @@ input ResourceControlPolicy { """ readOperation: Amount! """ + The price of one write operation. + """ + writeOperation: Amount! + """ The price of reading a byte. """ byteRead: Amount! """ - The price to writting a byte + The price of writing a byte """ byteWritten: Amount! """ diff --git a/linera-service/src/linera/client_options.rs b/linera-service/src/linera/client_options.rs index 781aeb127228..82d102e12410 100644 --- a/linera-service/src/linera/client_options.rs +++ b/linera-service/src/linera/client_options.rs @@ -300,6 +300,10 @@ pub enum ClientCommand { #[arg(long)] read_operation: Option, + /// Set the price per write operation. + #[arg(long)] + write_operation: Option, + /// Set the price per byte read. #[arg(long)] byte_read: Option, @@ -397,6 +401,10 @@ pub enum ClientCommand { #[arg(long, default_value = "0")] read_operation_price: Amount, + /// Set the price per write operation. + #[arg(long, default_value = "0")] + write_operation_price: Amount, + /// Set the price per byte read. #[arg(long, default_value = "0")] byte_read_price: Amount, diff --git a/linera-service/src/linera/main.rs b/linera-service/src/linera/main.rs index 2ea3abc788ca..f84932b632cf 100644 --- a/linera-service/src/linera/main.rs +++ b/linera-service/src/linera/main.rs @@ -359,6 +359,7 @@ impl Runnable for Job { block, fuel_unit, read_operation, + write_operation, byte_read, byte_written, byte_stored, @@ -378,6 +379,9 @@ impl Runnable for Job { if let Some(read_operation) = read_operation { policy.read_operation = read_operation; } + if let Some(write_operation) = write_operation { + policy.write_operation = write_operation; + } if let Some(byte_read) = byte_read { policy.byte_read = byte_read; } @@ -416,6 +420,7 @@ impl Runnable for Job { {:.2} base cost per block\n\ {:.2} cost per fuel unit\n\ {:.2} cost per read operation\n\ + {:.2} cost per write operation\n\ {:.2} cost per byte read\n\ {:.2} cost per byte written\n\ {:.2} cost per byte stored\n\ @@ -428,6 +433,7 @@ impl Runnable for Job { policy.block, policy.fuel_unit, policy.read_operation, + policy.write_operation, policy.byte_read, policy.byte_written, policy.byte_stored, @@ -441,6 +447,7 @@ impl Runnable for Job { if block.is_none() && fuel_unit.is_none() && read_operation.is_none() + && write_operation.is_none() && byte_read.is_none() && byte_written.is_none() && byte_stored.is_none() @@ -1177,6 +1184,7 @@ async fn run(options: ClientOptions) -> Result<(), anyhow::Error> { block_price, fuel_unit_price, read_operation_price, + write_operation_price, byte_read_price, byte_written_price, byte_stored_price, @@ -1203,6 +1211,7 @@ async fn run(options: ClientOptions) -> Result<(), anyhow::Error> { block: *block_price, fuel_unit: *fuel_unit_price, read_operation: *read_operation_price, + write_operation: *write_operation_price, byte_read: *byte_read_price, byte_written: *byte_written_price, byte_stored: *byte_stored_price,