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