linera_execution/
resources.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! This module tracks the resources used during the execution of a transaction.
5
6use std::{sync::Arc, time::Duration};
7
8use custom_debug_derive::Debug;
9use linera_base::{
10    data_types::{Amount, ArithmeticError, Blob},
11    ensure,
12    identifiers::AccountOwner,
13    vm::VmRuntime,
14};
15use linera_views::{context::Context, ViewError};
16use serde::Serialize;
17
18use crate::{ExecutionError, Message, Operation, ResourceControlPolicy, SystemExecutionStateView};
19
20#[derive(Clone, Debug, Default)]
21pub struct ResourceController<Account = Amount, Tracker = ResourceTracker> {
22    /// The (fixed) policy used to charge fees and control resource usage.
23    policy: Arc<ResourceControlPolicy>,
24    /// How the resources were used so far.
25    pub tracker: Tracker,
26    /// The account paying for the resource usage.
27    pub account: Account,
28}
29
30impl<Account, Tracker> ResourceController<Account, Tracker> {
31    /// Creates a new resource controller with the given policy and account.
32    pub fn new(policy: Arc<ResourceControlPolicy>, tracker: Tracker, account: Account) -> Self {
33        Self {
34            policy,
35            tracker,
36            account,
37        }
38    }
39
40    /// Returns a reference to the policy.
41    pub fn policy(&self) -> &Arc<ResourceControlPolicy> {
42        &self.policy
43    }
44
45    /// Returns a reference to the tracker.
46    pub fn tracker(&self) -> &Tracker {
47        &self.tracker
48    }
49}
50
51/// The resources used so far by an execution process.
52/// Acts as an accumulator for all resources consumed during
53/// a specific execution flow. This could be the execution of a block,
54/// the processing of a single message, or a specific phase within these
55/// broader operations.
56#[derive(Copy, Debug, Clone, Default)]
57pub struct ResourceTracker {
58    /// The total size of the block so far.
59    pub block_size: u64,
60    /// The EVM fuel used so far.
61    pub evm_fuel: u64,
62    /// The Wasm fuel used so far.
63    pub wasm_fuel: u64,
64    /// The number of read operations.
65    pub read_operations: u32,
66    /// The number of write operations.
67    pub write_operations: u32,
68    /// The number of bytes read.
69    pub bytes_read: u64,
70    /// The number of bytes written.
71    pub bytes_written: u64,
72    /// The number of blobs read.
73    pub blobs_read: u32,
74    /// The number of blobs published.
75    pub blobs_published: u32,
76    /// The number of blob bytes read.
77    pub blob_bytes_read: u64,
78    /// The number of blob bytes published.
79    pub blob_bytes_published: u64,
80    /// The change in the number of bytes being stored by user applications.
81    pub bytes_stored: i32,
82    /// The number of operations executed.
83    pub operations: u32,
84    /// The total size of the arguments of user operations.
85    pub operation_bytes: u64,
86    /// The number of outgoing messages created (system and user).
87    pub messages: u32,
88    /// The total size of the arguments of outgoing user messages.
89    pub message_bytes: u64,
90    /// The number of HTTP requests performed.
91    pub http_requests: u32,
92    /// The number of calls to services as oracles.
93    pub service_oracle_queries: u32,
94    /// The time spent executing services as oracles.
95    pub service_oracle_execution: Duration,
96    /// The amount allocated to message grants.
97    pub grants: Amount,
98}
99
100impl ResourceTracker {
101    fn fuel(&self, vm_runtime: VmRuntime) -> u64 {
102        match vm_runtime {
103            VmRuntime::Wasm => self.wasm_fuel,
104            VmRuntime::Evm => self.evm_fuel,
105        }
106    }
107}
108
109/// How to access the balance of an account.
110pub trait BalanceHolder {
111    fn balance(&self) -> Result<Amount, ArithmeticError>;
112
113    fn try_add_assign(&mut self, other: Amount) -> Result<(), ArithmeticError>;
114
115    fn try_sub_assign(&mut self, other: Amount) -> Result<(), ArithmeticError>;
116}
117
118// The main accounting functions for a ResourceController.
119impl<Account, Tracker> ResourceController<Account, Tracker>
120where
121    Account: BalanceHolder,
122    Tracker: AsRef<ResourceTracker> + AsMut<ResourceTracker>,
123{
124    /// Obtains the balance of the account. The only possible error is an arithmetic
125    /// overflow, which should not happen in practice due to final token supply.
126    pub fn balance(&self) -> Result<Amount, ArithmeticError> {
127        self.account.balance()
128    }
129
130    /// Operates a 3-way merge by transferring the difference between `initial`
131    /// and `other` to `self`.
132    pub fn merge_balance(&mut self, initial: Amount, other: Amount) -> Result<(), ExecutionError> {
133        if other <= initial {
134            let sub_amount = initial.try_sub(other).expect("other <= initial");
135            self.account.try_sub_assign(sub_amount).map_err(|_| {
136                ExecutionError::FeesExceedFunding {
137                    fees: sub_amount,
138                    balance: self.balance().unwrap_or(Amount::MAX),
139                }
140            })?;
141        } else {
142            self.account
143                .try_add_assign(other.try_sub(initial).expect("other > initial"))?;
144        }
145        Ok(())
146    }
147
148    /// Subtracts an amount from a balance and reports an error if that is impossible.
149    fn update_balance(&mut self, fees: Amount) -> Result<(), ExecutionError> {
150        self.account
151            .try_sub_assign(fees)
152            .map_err(|_| ExecutionError::FeesExceedFunding {
153                fees,
154                balance: self.balance().unwrap_or(Amount::MAX),
155            })?;
156        Ok(())
157    }
158
159    /// Obtains the amount of fuel that could be spent by consuming the entire balance.
160    pub(crate) fn remaining_fuel(&self, vm_runtime: VmRuntime) -> u64 {
161        let balance = self.balance().unwrap_or(Amount::MAX);
162        let fuel = self.tracker.as_ref().fuel(vm_runtime);
163        let maximum_fuel_per_block = self.policy.maximum_fuel_per_block(vm_runtime);
164        self.policy
165            .remaining_fuel(balance, vm_runtime)
166            .min(maximum_fuel_per_block.saturating_sub(fuel))
167    }
168
169    /// Tracks the allocation of a grant.
170    pub fn track_grant(&mut self, grant: Amount) -> Result<(), ExecutionError> {
171        self.tracker.as_mut().grants.try_add_assign(grant)?;
172        self.update_balance(grant)
173    }
174
175    /// Tracks the execution of an operation in block.
176    pub fn track_operation(&mut self, operation: &Operation) -> Result<(), ExecutionError> {
177        self.tracker.as_mut().operations = self
178            .tracker
179            .as_mut()
180            .operations
181            .checked_add(1)
182            .ok_or(ArithmeticError::Overflow)?;
183        self.update_balance(self.policy.operation)?;
184        match operation {
185            Operation::System(_) => Ok(()),
186            Operation::User { bytes, .. } => {
187                let size = bytes.len();
188                self.tracker.as_mut().operation_bytes = self
189                    .tracker
190                    .as_mut()
191                    .operation_bytes
192                    .checked_add(size as u64)
193                    .ok_or(ArithmeticError::Overflow)?;
194                self.update_balance(self.policy.operation_bytes_price(size as u64)?)?;
195                Ok(())
196            }
197        }
198    }
199
200    /// Tracks the creation of an outgoing message.
201    pub fn track_message(&mut self, message: &Message) -> Result<(), ExecutionError> {
202        self.tracker.as_mut().messages = self
203            .tracker
204            .as_mut()
205            .messages
206            .checked_add(1)
207            .ok_or(ArithmeticError::Overflow)?;
208        self.update_balance(self.policy.message)?;
209        match message {
210            Message::System(_) => Ok(()),
211            Message::User { bytes, .. } => {
212                let size = bytes.len();
213                self.tracker.as_mut().message_bytes = self
214                    .tracker
215                    .as_mut()
216                    .message_bytes
217                    .checked_add(size as u64)
218                    .ok_or(ArithmeticError::Overflow)?;
219                self.update_balance(self.policy.message_bytes_price(size as u64)?)?;
220                Ok(())
221            }
222        }
223    }
224
225    /// Tracks the execution of an HTTP request.
226    pub fn track_http_request(&mut self) -> Result<(), ExecutionError> {
227        self.tracker.as_mut().http_requests = self
228            .tracker
229            .as_ref()
230            .http_requests
231            .checked_add(1)
232            .ok_or(ArithmeticError::Overflow)?;
233        self.update_balance(self.policy.http_request)
234    }
235
236    /// Tracks a number of fuel units used.
237    pub(crate) fn track_fuel(
238        &mut self,
239        fuel: u64,
240        vm_runtime: VmRuntime,
241    ) -> Result<(), ExecutionError> {
242        match vm_runtime {
243            VmRuntime::Wasm => {
244                self.tracker.as_mut().wasm_fuel = self
245                    .tracker
246                    .as_ref()
247                    .wasm_fuel
248                    .checked_add(fuel)
249                    .ok_or(ArithmeticError::Overflow)?;
250                ensure!(
251                    self.tracker.as_ref().wasm_fuel <= self.policy.maximum_wasm_fuel_per_block,
252                    ExecutionError::MaximumFuelExceeded(vm_runtime)
253                );
254            }
255            VmRuntime::Evm => {
256                self.tracker.as_mut().evm_fuel = self
257                    .tracker
258                    .as_ref()
259                    .evm_fuel
260                    .checked_add(fuel)
261                    .ok_or(ArithmeticError::Overflow)?;
262                ensure!(
263                    self.tracker.as_ref().evm_fuel <= self.policy.maximum_evm_fuel_per_block,
264                    ExecutionError::MaximumFuelExceeded(vm_runtime)
265                );
266            }
267        }
268        self.update_balance(self.policy.fuel_price(fuel, vm_runtime)?)
269    }
270
271    /// Tracks a read operation.
272    pub(crate) fn track_read_operation(&mut self) -> Result<(), ExecutionError> {
273        self.tracker.as_mut().read_operations = self
274            .tracker
275            .as_mut()
276            .read_operations
277            .checked_add(1)
278            .ok_or(ArithmeticError::Overflow)?;
279        self.update_balance(self.policy.read_operations_price(1)?)
280    }
281
282    /// Tracks a write operation.
283    pub(crate) fn track_write_operations(&mut self, count: u32) -> Result<(), ExecutionError> {
284        self.tracker.as_mut().write_operations = self
285            .tracker
286            .as_mut()
287            .write_operations
288            .checked_add(count)
289            .ok_or(ArithmeticError::Overflow)?;
290        self.update_balance(self.policy.write_operations_price(count)?)
291    }
292
293    /// Tracks a number of bytes read.
294    pub(crate) fn track_bytes_read(&mut self, count: u64) -> Result<(), ExecutionError> {
295        self.tracker.as_mut().bytes_read = self
296            .tracker
297            .as_mut()
298            .bytes_read
299            .checked_add(count)
300            .ok_or(ArithmeticError::Overflow)?;
301        if self.tracker.as_mut().bytes_read >= self.policy.maximum_bytes_read_per_block {
302            return Err(ExecutionError::ExcessiveRead);
303        }
304        self.update_balance(self.policy.bytes_read_price(count)?)?;
305        Ok(())
306    }
307
308    /// Tracks a number of bytes written.
309    pub(crate) fn track_bytes_written(&mut self, count: u64) -> Result<(), ExecutionError> {
310        self.tracker.as_mut().bytes_written = self
311            .tracker
312            .as_mut()
313            .bytes_written
314            .checked_add(count)
315            .ok_or(ArithmeticError::Overflow)?;
316        if self.tracker.as_mut().bytes_written >= self.policy.maximum_bytes_written_per_block {
317            return Err(ExecutionError::ExcessiveWrite);
318        }
319        self.update_balance(self.policy.bytes_written_price(count)?)?;
320        Ok(())
321    }
322
323    /// Tracks a number of blob bytes written.
324    pub(crate) fn track_blob_read(&mut self, count: u64) -> Result<(), ExecutionError> {
325        {
326            let tracker = self.tracker.as_mut();
327            tracker.blob_bytes_read = tracker
328                .blob_bytes_read
329                .checked_add(count)
330                .ok_or(ArithmeticError::Overflow)?;
331            tracker.blobs_read = tracker
332                .blobs_read
333                .checked_add(1)
334                .ok_or(ArithmeticError::Overflow)?;
335        }
336        self.update_balance(self.policy.blob_read_price(count)?)?;
337        Ok(())
338    }
339
340    /// Tracks a number of blob bytes published.
341    pub fn track_blob_published(&mut self, blob: &Blob) -> Result<(), ExecutionError> {
342        self.policy.check_blob_size(blob.content())?;
343        let size = blob.content().bytes().len() as u64;
344        if blob.is_committee_blob() {
345            return Ok(());
346        }
347        {
348            let tracker = self.tracker.as_mut();
349            tracker.blob_bytes_published = tracker
350                .blob_bytes_published
351                .checked_add(size)
352                .ok_or(ArithmeticError::Overflow)?;
353            tracker.blobs_published = tracker
354                .blobs_published
355                .checked_add(1)
356                .ok_or(ArithmeticError::Overflow)?;
357        }
358        self.update_balance(self.policy.blob_published_price(size)?)?;
359        Ok(())
360    }
361
362    /// Tracks a change in the number of bytes stored.
363    // TODO(#1536): This is not fully implemented.
364    #[allow(dead_code)]
365    pub(crate) fn track_stored_bytes(&mut self, delta: i32) -> Result<(), ExecutionError> {
366        self.tracker.as_mut().bytes_stored = self
367            .tracker
368            .as_mut()
369            .bytes_stored
370            .checked_add(delta)
371            .ok_or(ArithmeticError::Overflow)?;
372        Ok(())
373    }
374
375    /// Returns the remaining time services can spend executing as oracles.
376    pub(crate) fn remaining_service_oracle_execution_time(
377        &self,
378    ) -> Result<Duration, ExecutionError> {
379        let tracker = self.tracker.as_ref();
380        let spent_execution_time = tracker.service_oracle_execution;
381        let limit = Duration::from_millis(self.policy.maximum_service_oracle_execution_ms);
382
383        limit
384            .checked_sub(spent_execution_time)
385            .ok_or(ExecutionError::MaximumServiceOracleExecutionTimeExceeded)
386    }
387
388    /// Tracks a call to a service to run as an oracle.
389    pub(crate) fn track_service_oracle_call(&mut self) -> Result<(), ExecutionError> {
390        self.tracker.as_mut().service_oracle_queries = self
391            .tracker
392            .as_mut()
393            .service_oracle_queries
394            .checked_add(1)
395            .ok_or(ArithmeticError::Overflow)?;
396        self.update_balance(self.policy.service_as_oracle_query)
397    }
398
399    /// Tracks the time spent executing the service as an oracle.
400    pub(crate) fn track_service_oracle_execution(
401        &mut self,
402        execution_time: Duration,
403    ) -> Result<(), ExecutionError> {
404        let tracker = self.tracker.as_mut();
405        let spent_execution_time = &mut tracker.service_oracle_execution;
406        let limit = Duration::from_millis(self.policy.maximum_service_oracle_execution_ms);
407
408        *spent_execution_time = spent_execution_time.saturating_add(execution_time);
409
410        ensure!(
411            *spent_execution_time < limit,
412            ExecutionError::MaximumServiceOracleExecutionTimeExceeded
413        );
414
415        Ok(())
416    }
417
418    /// Tracks the size of a response produced by an oracle.
419    pub(crate) fn track_service_oracle_response(
420        &mut self,
421        response_bytes: usize,
422    ) -> Result<(), ExecutionError> {
423        ensure!(
424            response_bytes as u64 <= self.policy.maximum_oracle_response_bytes,
425            ExecutionError::ServiceOracleResponseTooLarge
426        );
427
428        Ok(())
429    }
430}
431
432impl<Account, Tracker> ResourceController<Account, Tracker>
433where
434    Tracker: AsMut<ResourceTracker>,
435{
436    /// Tracks the serialized size of a block, or parts of it.
437    pub fn track_block_size_of(&mut self, data: &impl Serialize) -> Result<(), ExecutionError> {
438        self.track_block_size(bcs::serialized_size(data)?)
439    }
440
441    /// Tracks the serialized size of a block, or parts of it.
442    pub fn track_block_size(&mut self, size: usize) -> Result<(), ExecutionError> {
443        let tracker = self.tracker.as_mut();
444        tracker.block_size = u64::try_from(size)
445            .ok()
446            .and_then(|size| tracker.block_size.checked_add(size))
447            .ok_or(ExecutionError::BlockTooLarge)?;
448        ensure!(
449            tracker.block_size <= self.policy.maximum_block_size,
450            ExecutionError::BlockTooLarge
451        );
452        Ok(())
453    }
454}
455
456impl ResourceController<Option<AccountOwner>, ResourceTracker> {
457    /// Provides a reference to the current execution state and obtains a temporary object
458    /// where the accounting functions of [`ResourceController`] are available.
459    pub async fn with_state<'a, C>(
460        &mut self,
461        view: &'a mut SystemExecutionStateView<C>,
462    ) -> Result<ResourceController<Sources<'a>, &mut ResourceTracker>, ViewError>
463    where
464        C: Context + Clone + Send + Sync + 'static,
465    {
466        self.with_state_and_grant(view, None).await
467    }
468
469    /// Provides a reference to the current execution state as well as an optional grant,
470    /// and obtains a temporary object where the accounting functions of
471    /// [`ResourceController`] are available.
472    pub async fn with_state_and_grant<'a, C>(
473        &mut self,
474        view: &'a mut SystemExecutionStateView<C>,
475        grant: Option<&'a mut Amount>,
476    ) -> Result<ResourceController<Sources<'a>, &mut ResourceTracker>, ViewError>
477    where
478        C: Context + Clone + Send + Sync + 'static,
479    {
480        let mut sources = Vec::new();
481        // First, use the grant (e.g. for messages) and otherwise use the chain account
482        // (e.g. for blocks and operations).
483        if let Some(grant) = grant {
484            sources.push(grant);
485        } else {
486            sources.push(view.balance.get_mut());
487        }
488        // Then the local account, if any. Currently, any negative fee (e.g. storage
489        // refund) goes preferably to this account.
490        if let Some(owner) = &self.account {
491            if let Some(balance) = view.balances.get_mut(owner).await? {
492                sources.push(balance);
493            }
494        }
495
496        Ok(ResourceController {
497            policy: self.policy.clone(),
498            tracker: &mut self.tracker,
499            account: Sources { sources },
500        })
501    }
502}
503
504// The simplest `BalanceHolder` is an `Amount`.
505impl BalanceHolder for Amount {
506    fn balance(&self) -> Result<Amount, ArithmeticError> {
507        Ok(*self)
508    }
509
510    fn try_add_assign(&mut self, other: Amount) -> Result<(), ArithmeticError> {
511        self.try_add_assign(other)
512    }
513
514    fn try_sub_assign(&mut self, other: Amount) -> Result<(), ArithmeticError> {
515        self.try_sub_assign(other)
516    }
517}
518
519// This is also needed for the default instantiation `ResourceController<Amount, ResourceTracker>`.
520// See https://doc.rust-lang.org/std/convert/trait.AsMut.html#reflexivity for general context.
521impl AsMut<ResourceTracker> for ResourceTracker {
522    fn as_mut(&mut self) -> &mut Self {
523        self
524    }
525}
526
527impl AsRef<ResourceTracker> for ResourceTracker {
528    fn as_ref(&self) -> &Self {
529        self
530    }
531}
532
533/// A temporary object holding a number of references to funding sources.
534pub struct Sources<'a> {
535    sources: Vec<&'a mut Amount>,
536}
537
538impl BalanceHolder for Sources<'_> {
539    fn balance(&self) -> Result<Amount, ArithmeticError> {
540        let mut amount = Amount::ZERO;
541        for source in self.sources.iter() {
542            amount.try_add_assign(**source)?;
543        }
544        Ok(amount)
545    }
546
547    fn try_add_assign(&mut self, other: Amount) -> Result<(), ArithmeticError> {
548        // Try to credit the owner account first.
549        // TODO(#1648): This may need some additional design work.
550        let source = self.sources.last_mut().expect("at least one source");
551        source.try_add_assign(other)
552    }
553
554    fn try_sub_assign(&mut self, mut other: Amount) -> Result<(), ArithmeticError> {
555        for source in self.sources.iter_mut() {
556            if source.try_sub_assign(other).is_ok() {
557                return Ok(());
558            }
559            other.try_sub_assign(**source).expect("*source < other");
560            **source = Amount::ZERO;
561        }
562        if other > Amount::ZERO {
563            Err(ArithmeticError::Underflow)
564        } else {
565            Ok(())
566        }
567    }
568}