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