Skip to main content

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::{fmt, sync::Arc, time::Duration};
7
8use custom_debug_derive::Debug;
9use linera_base::{
10    data_types::{Amount, ApplicationDescription, ArithmeticError, Blob},
11    ensure,
12    identifiers::AccountOwner,
13    ownership::ChainOwnership,
14    vm::VmRuntime,
15};
16use linera_views::{context::Context, ViewError};
17use serde::Serialize;
18
19use crate::{ExecutionError, Message, Operation, ResourceControlPolicy, SystemExecutionStateView};
20
21#[derive(Clone, Debug, Default)]
22pub struct ResourceController<Account = Amount, Tracker = ResourceTracker> {
23    /// The (fixed) policy used to charge fees and control resource usage.
24    policy: Arc<ResourceControlPolicy>,
25    /// How the resources were used so far.
26    pub tracker: Tracker,
27    /// The account paying for the resource usage.
28    pub account: Account,
29    /// When true, balance deductions are skipped (fees waived for free apps).
30    pub is_free: bool,
31}
32
33impl<Account, Tracker> ResourceController<Account, Tracker> {
34    /// Creates a new resource controller with the given policy and account.
35    pub fn new(policy: Arc<ResourceControlPolicy>, tracker: Tracker, account: Account) -> Self {
36        Self {
37            policy,
38            tracker,
39            account,
40            is_free: false,
41        }
42    }
43
44    /// Returns a reference to the policy.
45    pub fn policy(&self) -> &Arc<ResourceControlPolicy> {
46        &self.policy
47    }
48
49    /// Returns a reference to the tracker.
50    pub fn tracker(&self) -> &Tracker {
51        &self.tracker
52    }
53}
54
55/// The runtime size of an `Amount`.
56pub const RUNTIME_AMOUNT_SIZE: u32 = 16;
57
58/// The runtime size of a `ApplicationId`.
59pub const RUNTIME_APPLICATION_ID_SIZE: u32 = 32;
60
61/// The runtime size of a `BlockHeight`.
62pub const RUNTIME_BLOCK_HEIGHT_SIZE: u32 = 8;
63
64/// The runtime size of a `ChainId`.
65pub const RUNTIME_CHAIN_ID_SIZE: u32 = 32;
66
67/// The runtime size of a `Timestamp`.
68pub const RUNTIME_TIMESTAMP_SIZE: u32 = 8;
69
70/// The runtime size of the weight of an owner.
71pub const RUNTIME_OWNER_WEIGHT_SIZE: u32 = 8;
72
73/// The runtime constant part size of the `ChainOwnership`.
74/// It consists of one `u32` and four `TimeDelta` which are the constant part of
75/// the `ChainOwnership`. The way we do it is not optimal:
76/// TODO(#4164): Implement a procedure for computing naive sizes.
77pub const RUNTIME_CONSTANT_CHAIN_OWNERSHIP_SIZE: u32 = 4 + 4 * 8;
78
79/// The runtime size of a `CryptoHash`.
80pub const RUNTIME_CRYPTO_HASH_SIZE: u32 = 32;
81
82/// The runtime size of a `VmRuntime` enum.
83pub const RUNTIME_VM_RUNTIME_SIZE: u32 = 1;
84
85/// The runtime constant part size of an `ApplicationDescription`.
86/// This includes: `ModuleId` (2 hashes + VmRuntime) + `ChainId` + `BlockHeight` + `u32`.
87/// Variable parts (`parameters` and `required_application_ids`) are calculated separately.
88pub const RUNTIME_CONSTANT_APPLICATION_DESCRIPTION_SIZE: u32 = 2 * RUNTIME_CRYPTO_HASH_SIZE + RUNTIME_VM_RUNTIME_SIZE  // ModuleId
89    + RUNTIME_CHAIN_ID_SIZE                                  // creator_chain_id
90    + RUNTIME_BLOCK_HEIGHT_SIZE                              // block_height
91    + 4; // application_index (u32)
92
93#[cfg(test)]
94mod tests {
95    use std::mem::size_of;
96
97    use linera_base::{
98        data_types::{Amount, ApplicationDescription, BlockHeight, Timestamp},
99        identifiers::{ApplicationId, ChainId, ModuleId},
100    };
101
102    use crate::resources::{
103        RUNTIME_AMOUNT_SIZE, RUNTIME_APPLICATION_ID_SIZE, RUNTIME_BLOCK_HEIGHT_SIZE,
104        RUNTIME_CHAIN_ID_SIZE, RUNTIME_CONSTANT_APPLICATION_DESCRIPTION_SIZE,
105        RUNTIME_OWNER_WEIGHT_SIZE, RUNTIME_TIMESTAMP_SIZE,
106    };
107
108    #[test]
109    fn test_size_of_runtime_operations() {
110        assert_eq!(RUNTIME_AMOUNT_SIZE as usize, size_of::<Amount>());
111        assert_eq!(
112            RUNTIME_APPLICATION_ID_SIZE as usize,
113            size_of::<ApplicationId>()
114        );
115        assert_eq!(RUNTIME_BLOCK_HEIGHT_SIZE as usize, size_of::<BlockHeight>());
116        assert_eq!(RUNTIME_CHAIN_ID_SIZE as usize, size_of::<ChainId>());
117        assert_eq!(RUNTIME_TIMESTAMP_SIZE as usize, size_of::<Timestamp>());
118        assert_eq!(RUNTIME_OWNER_WEIGHT_SIZE as usize, size_of::<u64>());
119    }
120
121    /// Verifies that `RUNTIME_CONSTANT_APPLICATION_DESCRIPTION_SIZE` matches the actual
122    /// structure of `ApplicationDescription`. This test will fail if a new fixed-size
123    /// field is added to the struct.
124    #[test]
125    fn test_application_description_size() {
126        // Verify using BCS serialization, which is architecture-independent.
127        // BCS encodes Vec length as ULEB128, so empty vectors add 1 byte each.
128        let description = ApplicationDescription {
129            module_id: ModuleId::default(),
130            creator_chain_id: ChainId::default(),
131            block_height: BlockHeight::default(),
132            application_index: 0,
133            parameters: vec![],
134            required_application_ids: vec![],
135        };
136        let serialized = bcs::to_bytes(&description).expect("serialization should succeed");
137        // Serialized size = fixed fields + 2 bytes for empty vectors (1 byte each for ULEB128 length).
138        assert_eq!(
139            serialized.len(),
140            RUNTIME_CONSTANT_APPLICATION_DESCRIPTION_SIZE as usize + 2
141        );
142    }
143}
144
145/// The resources used so far by an execution process.
146/// Acts as an accumulator for all resources consumed during
147/// a specific execution flow. This could be the execution of a block,
148/// the processing of a single message, or a specific phase within these
149/// broader operations.
150#[derive(Copy, Debug, Clone, Default)]
151pub struct ResourceTracker {
152    /// The total size of the block so far.
153    pub block_size: u64,
154    /// The EVM fuel used so far.
155    pub evm_fuel: u64,
156    /// The Wasm fuel used so far.
157    pub wasm_fuel: u64,
158    /// The number of read operations.
159    pub read_operations: u32,
160    /// The number of write operations.
161    pub write_operations: u32,
162    /// The size of bytes read from runtime.
163    pub bytes_runtime: u32,
164    /// The number of bytes read.
165    pub bytes_read: u64,
166    /// The number of bytes written.
167    pub bytes_written: u64,
168    /// The number of blobs read.
169    pub blobs_read: u32,
170    /// The number of blobs published.
171    pub blobs_published: u32,
172    /// The number of blob bytes read.
173    pub blob_bytes_read: u64,
174    /// The number of blob bytes published.
175    pub blob_bytes_published: u64,
176    /// The change in the number of bytes being stored by user applications.
177    pub bytes_stored: i32,
178    /// The number of operations executed.
179    pub operations: u32,
180    /// The total size of the arguments of user operations.
181    pub operation_bytes: u64,
182    /// The number of outgoing messages created (system and user).
183    pub messages: u32,
184    /// The total size of the arguments of outgoing user messages.
185    pub message_bytes: u64,
186    /// The number of HTTP requests performed.
187    pub http_requests: u32,
188    /// The number of calls to services as oracles.
189    pub service_oracle_queries: u32,
190    /// The time spent executing services as oracles.
191    pub service_oracle_execution: Duration,
192    /// The amount allocated to message grants.
193    pub grants: Amount,
194}
195
196impl ResourceTracker {
197    fn fuel(&self, vm_runtime: VmRuntime) -> u64 {
198        match vm_runtime {
199            VmRuntime::Wasm => self.wasm_fuel,
200            VmRuntime::Evm => self.evm_fuel,
201        }
202    }
203}
204
205impl fmt::Display for ResourceTracker {
206    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207        let mut lines = Vec::new();
208
209        let mut block_parts = Vec::new();
210        if self.block_size != 0 {
211            block_parts.push(format!("size={}", self.block_size));
212        }
213        if self.operations != 0 {
214            block_parts.push(format!("operations={}", self.operations));
215        }
216        if self.operation_bytes != 0 {
217            block_parts.push(format!("operation_bytes={}", self.operation_bytes));
218        }
219        if !block_parts.is_empty() {
220            lines.push(format!("block: {}", block_parts.join(", ")));
221        }
222
223        let mut fuel_parts = Vec::new();
224        if self.wasm_fuel != 0 {
225            fuel_parts.push(format!("wasm={}", self.wasm_fuel));
226        }
227        if self.evm_fuel != 0 {
228            fuel_parts.push(format!("evm={}", self.evm_fuel));
229        }
230        if !fuel_parts.is_empty() {
231            lines.push(format!("fuel: {}", fuel_parts.join(", ")));
232        }
233
234        let mut storage_parts = Vec::new();
235        if self.read_operations != 0 {
236            storage_parts.push(format!("reads={}", self.read_operations));
237        }
238        if self.write_operations != 0 {
239            storage_parts.push(format!("writes={}", self.write_operations));
240        }
241        if self.bytes_runtime != 0 {
242            storage_parts.push(format!("runtime_bytes={}", self.bytes_runtime));
243        }
244        if self.bytes_read != 0 {
245            storage_parts.push(format!("bytes_read={}", self.bytes_read));
246        }
247        if self.bytes_written != 0 {
248            storage_parts.push(format!("bytes_written={}", self.bytes_written));
249        }
250        if self.bytes_stored != 0 {
251            storage_parts.push(format!("bytes_stored={}", self.bytes_stored));
252        }
253        if !storage_parts.is_empty() {
254            lines.push(format!("storage: {}", storage_parts.join(", ")));
255        }
256
257        let mut blob_parts = Vec::new();
258        if self.blobs_read != 0 {
259            blob_parts.push(format!("read={}", self.blobs_read));
260        }
261        if self.blobs_published != 0 {
262            blob_parts.push(format!("published={}", self.blobs_published));
263        }
264        if self.blob_bytes_read != 0 {
265            blob_parts.push(format!("bytes_read={}", self.blob_bytes_read));
266        }
267        if self.blob_bytes_published != 0 {
268            blob_parts.push(format!("bytes_published={}", self.blob_bytes_published));
269        }
270        if !blob_parts.is_empty() {
271            lines.push(format!("blobs: {}", blob_parts.join(", ")));
272        }
273
274        let mut message_parts = Vec::new();
275        if self.messages != 0 {
276            message_parts.push(format!("count={}", self.messages));
277        }
278        if self.message_bytes != 0 {
279            message_parts.push(format!("bytes={}", self.message_bytes));
280        }
281        if self.grants != Amount::ZERO {
282            message_parts.push(format!("grants={}", self.grants));
283        }
284        if !message_parts.is_empty() {
285            lines.push(format!("messages: {}", message_parts.join(", ")));
286        }
287
288        let mut http_service_parts = Vec::new();
289        if self.http_requests != 0 {
290            http_service_parts.push(format!("http_requests={}", self.http_requests));
291        }
292        if self.service_oracle_queries != 0 {
293            http_service_parts.push(format!("service_queries={}", self.service_oracle_queries));
294        }
295        if self.service_oracle_execution != Duration::ZERO {
296            http_service_parts.push(format!(
297                "service_execution={:?}",
298                self.service_oracle_execution
299            ));
300        }
301        if !http_service_parts.is_empty() {
302            lines.push(format!("http/service: {}", http_service_parts.join(", ")));
303        }
304
305        let mut lines_iter = lines.into_iter();
306        if let Some(first) = lines_iter.next() {
307            write!(f, "{}", first)?;
308            for line in lines_iter {
309                write!(f, "\n  {}", line)?;
310            }
311        }
312
313        Ok(())
314    }
315}
316
317/// How to access the balance of an account.
318pub trait BalanceHolder {
319    fn balance(&self) -> Result<Amount, ArithmeticError>;
320
321    fn try_add_assign(&mut self, other: Amount) -> Result<(), ArithmeticError>;
322
323    fn try_sub_assign(&mut self, other: Amount) -> Result<(), ArithmeticError>;
324}
325
326// The main accounting functions for a ResourceController.
327impl<Account, Tracker> ResourceController<Account, Tracker>
328where
329    Account: BalanceHolder,
330    Tracker: AsRef<ResourceTracker> + AsMut<ResourceTracker>,
331{
332    /// Obtains the balance of the account. The only possible error is an arithmetic
333    /// overflow, which should not happen in practice due to final token supply.
334    pub fn balance(&self) -> Result<Amount, ArithmeticError> {
335        self.account.balance()
336    }
337
338    /// Operates a 3-way merge by transferring the difference between `initial`
339    /// and `other` to `self`.
340    pub fn merge_balance(&mut self, initial: Amount, other: Amount) -> Result<(), ExecutionError> {
341        if other <= initial {
342            let sub_amount = initial.try_sub(other).expect("other <= initial");
343            self.account.try_sub_assign(sub_amount).map_err(|_| {
344                ExecutionError::FeesExceedFunding {
345                    fees: sub_amount,
346                    balance: self.balance().unwrap_or(Amount::MAX),
347                }
348            })?;
349        } else {
350            self.account
351                .try_add_assign(other.try_sub(initial).expect("other > initial"))?;
352        }
353        Ok(())
354    }
355
356    /// Subtracts an amount from a balance and reports an error if that is impossible.
357    /// When `is_free` is set, balance deductions are skipped (fees waived).
358    fn update_balance(&mut self, fees: Amount) -> Result<(), ExecutionError> {
359        if self.is_free {
360            return Ok(());
361        }
362        self.account
363            .try_sub_assign(fees)
364            .map_err(|_| ExecutionError::FeesExceedFunding {
365                fees,
366                balance: self.balance().unwrap_or(Amount::MAX),
367            })?;
368        Ok(())
369    }
370
371    /// Obtains the amount of fuel that could be spent by consuming the entire balance.
372    pub(crate) fn remaining_fuel(&self, vm_runtime: VmRuntime) -> u64 {
373        let fuel = self.tracker.as_ref().fuel(vm_runtime);
374        let maximum_fuel_per_block = self.policy.maximum_fuel_per_block(vm_runtime);
375        if self.is_free {
376            return maximum_fuel_per_block.saturating_sub(fuel);
377        }
378        let balance = self.balance().unwrap_or(Amount::MAX);
379        self.policy
380            .remaining_fuel(balance, vm_runtime)
381            .min(maximum_fuel_per_block.saturating_sub(fuel))
382    }
383
384    /// Tracks the allocation of a grant.
385    pub fn track_grant(&mut self, grant: Amount) -> Result<(), ExecutionError> {
386        self.tracker.as_mut().grants.try_add_assign(grant)?;
387        self.update_balance(grant)
388    }
389
390    /// Tracks the execution of an operation in block.
391    pub fn track_operation(&mut self, operation: &Operation) -> Result<(), ExecutionError> {
392        self.tracker.as_mut().operations = self
393            .tracker
394            .as_mut()
395            .operations
396            .checked_add(1)
397            .ok_or(ArithmeticError::Overflow)?;
398        self.update_balance(self.policy.operation)?;
399        match operation {
400            Operation::System(_) => Ok(()),
401            Operation::User { bytes, .. } => {
402                let size = bytes.len();
403                self.tracker.as_mut().operation_bytes = self
404                    .tracker
405                    .as_mut()
406                    .operation_bytes
407                    .checked_add(size as u64)
408                    .ok_or(ArithmeticError::Overflow)?;
409                self.update_balance(self.policy.operation_bytes_price(size as u64)?)?;
410                Ok(())
411            }
412        }
413    }
414
415    /// Tracks the creation of an outgoing message.
416    pub fn track_message(&mut self, message: &Message) -> Result<(), ExecutionError> {
417        self.tracker.as_mut().messages = self
418            .tracker
419            .as_mut()
420            .messages
421            .checked_add(1)
422            .ok_or(ArithmeticError::Overflow)?;
423        self.update_balance(self.policy.message)?;
424        match message {
425            Message::System(_) => Ok(()),
426            Message::User { bytes, .. } => {
427                let size = bytes.len();
428                self.tracker.as_mut().message_bytes = self
429                    .tracker
430                    .as_mut()
431                    .message_bytes
432                    .checked_add(size as u64)
433                    .ok_or(ArithmeticError::Overflow)?;
434                self.update_balance(self.policy.message_bytes_price(size as u64)?)?;
435                Ok(())
436            }
437        }
438    }
439
440    /// Tracks the execution of an HTTP request.
441    pub fn track_http_request(&mut self) -> Result<(), ExecutionError> {
442        self.tracker.as_mut().http_requests = self
443            .tracker
444            .as_ref()
445            .http_requests
446            .checked_add(1)
447            .ok_or(ArithmeticError::Overflow)?;
448        self.update_balance(self.policy.http_request)
449    }
450
451    /// Tracks a number of fuel units used.
452    pub(crate) fn track_fuel(
453        &mut self,
454        fuel: u64,
455        vm_runtime: VmRuntime,
456    ) -> Result<(), ExecutionError> {
457        match vm_runtime {
458            VmRuntime::Wasm => {
459                self.tracker.as_mut().wasm_fuel = self
460                    .tracker
461                    .as_ref()
462                    .wasm_fuel
463                    .checked_add(fuel)
464                    .ok_or(ArithmeticError::Overflow)?;
465                ensure!(
466                    self.tracker.as_ref().wasm_fuel <= self.policy.maximum_wasm_fuel_per_block,
467                    ExecutionError::MaximumFuelExceeded(vm_runtime)
468                );
469            }
470            VmRuntime::Evm => {
471                self.tracker.as_mut().evm_fuel = self
472                    .tracker
473                    .as_ref()
474                    .evm_fuel
475                    .checked_add(fuel)
476                    .ok_or(ArithmeticError::Overflow)?;
477                ensure!(
478                    self.tracker.as_ref().evm_fuel <= self.policy.maximum_evm_fuel_per_block,
479                    ExecutionError::MaximumFuelExceeded(vm_runtime)
480                );
481            }
482        }
483        self.update_balance(self.policy.fuel_price(fuel, vm_runtime)?)
484    }
485
486    /// Tracks runtime reading of `ChainId`
487    pub(crate) fn track_runtime_chain_id(&mut self) -> Result<(), ExecutionError> {
488        self.track_size_runtime_operations(RUNTIME_CHAIN_ID_SIZE)
489    }
490
491    /// Tracks runtime reading of `BlockHeight`
492    pub(crate) fn track_runtime_block_height(&mut self) -> Result<(), ExecutionError> {
493        self.track_size_runtime_operations(RUNTIME_BLOCK_HEIGHT_SIZE)
494    }
495
496    /// Tracks runtime reading of `ApplicationId`
497    pub(crate) fn track_runtime_application_id(&mut self) -> Result<(), ExecutionError> {
498        self.track_size_runtime_operations(RUNTIME_APPLICATION_ID_SIZE)
499    }
500
501    /// Tracks runtime reading of application parameters.
502    pub(crate) fn track_runtime_application_parameters(
503        &mut self,
504        parameters: &[u8],
505    ) -> Result<(), ExecutionError> {
506        let parameters_len = parameters.len() as u32;
507        self.track_size_runtime_operations(parameters_len)
508    }
509
510    /// Tracks runtime reading of `Timestamp`
511    pub(crate) fn track_runtime_timestamp(&mut self) -> Result<(), ExecutionError> {
512        self.track_size_runtime_operations(RUNTIME_TIMESTAMP_SIZE)
513    }
514
515    /// Tracks runtime reading of balance
516    pub(crate) fn track_runtime_balance(&mut self) -> Result<(), ExecutionError> {
517        self.track_size_runtime_operations(RUNTIME_AMOUNT_SIZE)
518    }
519
520    /// Tracks runtime reading of owner balances
521    pub(crate) fn track_runtime_owner_balances(
522        &mut self,
523        owner_balances: &[(AccountOwner, Amount)],
524    ) -> Result<(), ExecutionError> {
525        let mut size = 0;
526        for (account_owner, _) in owner_balances {
527            size += account_owner.size() + RUNTIME_AMOUNT_SIZE;
528        }
529        self.track_size_runtime_operations(size)
530    }
531
532    /// Tracks runtime reading of owners
533    pub(crate) fn track_runtime_owners(
534        &mut self,
535        owners: &[AccountOwner],
536    ) -> Result<(), ExecutionError> {
537        let mut size = 0;
538        for owner in owners {
539            size += owner.size();
540        }
541        self.track_size_runtime_operations(size)
542    }
543
544    /// Tracks runtime reading of owners
545    pub(crate) fn track_runtime_chain_ownership(
546        &mut self,
547        chain_ownership: &ChainOwnership,
548    ) -> Result<(), ExecutionError> {
549        let mut size = 0;
550        for account_owner in &chain_ownership.super_owners {
551            size += account_owner.size();
552        }
553        for account_owner in chain_ownership.owners.keys() {
554            size += account_owner.size() + RUNTIME_OWNER_WEIGHT_SIZE;
555        }
556        size += RUNTIME_CONSTANT_CHAIN_OWNERSHIP_SIZE;
557        self.track_size_runtime_operations(size)
558    }
559
560    /// Tracks runtime reading of an application description.
561    pub(crate) fn track_runtime_application_description(
562        &mut self,
563        description: &ApplicationDescription,
564    ) -> Result<(), ExecutionError> {
565        let parameters_size = description.parameters.len() as u32;
566        let required_apps_size =
567            description.required_application_ids.len() as u32 * RUNTIME_APPLICATION_ID_SIZE;
568        let size =
569            RUNTIME_CONSTANT_APPLICATION_DESCRIPTION_SIZE + parameters_size + required_apps_size;
570        self.track_size_runtime_operations(size)
571    }
572
573    /// Tracks runtime operations.
574    fn track_size_runtime_operations(&mut self, size: u32) -> Result<(), ExecutionError> {
575        self.tracker.as_mut().bytes_runtime = self
576            .tracker
577            .as_mut()
578            .bytes_runtime
579            .checked_add(size)
580            .ok_or(ArithmeticError::Overflow)?;
581        self.update_balance(self.policy.bytes_runtime_price(size)?)
582    }
583
584    /// Tracks a read operation.
585    pub(crate) fn track_read_operation(&mut self) -> Result<(), ExecutionError> {
586        self.tracker.as_mut().read_operations = self
587            .tracker
588            .as_mut()
589            .read_operations
590            .checked_add(1)
591            .ok_or(ArithmeticError::Overflow)?;
592        self.update_balance(self.policy.read_operations_price(1)?)
593    }
594
595    /// Tracks a write operation.
596    pub(crate) fn track_write_operations(&mut self, count: u32) -> Result<(), ExecutionError> {
597        self.tracker.as_mut().write_operations = self
598            .tracker
599            .as_mut()
600            .write_operations
601            .checked_add(count)
602            .ok_or(ArithmeticError::Overflow)?;
603        self.update_balance(self.policy.write_operations_price(count)?)
604    }
605
606    /// Tracks a number of bytes read.
607    pub(crate) fn track_bytes_read(&mut self, count: u64) -> Result<(), ExecutionError> {
608        self.tracker.as_mut().bytes_read = self
609            .tracker
610            .as_mut()
611            .bytes_read
612            .checked_add(count)
613            .ok_or(ArithmeticError::Overflow)?;
614        if self.tracker.as_mut().bytes_read >= self.policy.maximum_bytes_read_per_block {
615            return Err(ExecutionError::ExcessiveRead);
616        }
617        self.update_balance(self.policy.bytes_read_price(count)?)?;
618        Ok(())
619    }
620
621    /// Tracks a number of bytes written.
622    pub(crate) fn track_bytes_written(&mut self, count: u64) -> Result<(), ExecutionError> {
623        self.tracker.as_mut().bytes_written = self
624            .tracker
625            .as_mut()
626            .bytes_written
627            .checked_add(count)
628            .ok_or(ArithmeticError::Overflow)?;
629        if self.tracker.as_mut().bytes_written >= self.policy.maximum_bytes_written_per_block {
630            return Err(ExecutionError::ExcessiveWrite);
631        }
632        self.update_balance(self.policy.bytes_written_price(count)?)?;
633        Ok(())
634    }
635
636    /// Tracks a number of blob bytes written.
637    pub(crate) fn track_blob_read(&mut self, count: u64) -> Result<(), ExecutionError> {
638        {
639            let tracker = self.tracker.as_mut();
640            tracker.blob_bytes_read = tracker
641                .blob_bytes_read
642                .checked_add(count)
643                .ok_or(ArithmeticError::Overflow)?;
644            tracker.blobs_read = tracker
645                .blobs_read
646                .checked_add(1)
647                .ok_or(ArithmeticError::Overflow)?;
648        }
649        self.update_balance(self.policy.blob_read_price(count)?)?;
650        Ok(())
651    }
652
653    /// Tracks a number of blob bytes published.
654    pub fn track_blob_published(&mut self, blob: &Blob) -> Result<(), ExecutionError> {
655        self.policy.check_blob_size(blob.content())?;
656        let size = blob.content().bytes().len() as u64;
657        if blob.is_committee_blob() {
658            return Ok(());
659        }
660        {
661            let tracker = self.tracker.as_mut();
662            tracker.blob_bytes_published = tracker
663                .blob_bytes_published
664                .checked_add(size)
665                .ok_or(ArithmeticError::Overflow)?;
666            tracker.blobs_published = tracker
667                .blobs_published
668                .checked_add(1)
669                .ok_or(ArithmeticError::Overflow)?;
670        }
671        self.update_balance(self.policy.blob_published_price(size)?)?;
672        Ok(())
673    }
674
675    /// Tracks a change in the number of bytes stored.
676    // TODO(#1536): This is not fully implemented.
677    #[allow(dead_code)]
678    pub(crate) fn track_stored_bytes(&mut self, delta: i32) -> Result<(), ExecutionError> {
679        self.tracker.as_mut().bytes_stored = self
680            .tracker
681            .as_mut()
682            .bytes_stored
683            .checked_add(delta)
684            .ok_or(ArithmeticError::Overflow)?;
685        Ok(())
686    }
687
688    /// Returns the remaining time services can spend executing as oracles.
689    pub(crate) fn remaining_service_oracle_execution_time(
690        &self,
691    ) -> Result<Duration, ExecutionError> {
692        let tracker = self.tracker.as_ref();
693        let spent_execution_time = tracker.service_oracle_execution;
694        let limit = Duration::from_millis(self.policy.maximum_service_oracle_execution_ms);
695
696        limit
697            .checked_sub(spent_execution_time)
698            .ok_or(ExecutionError::MaximumServiceOracleExecutionTimeExceeded)
699    }
700
701    /// Tracks a call to a service to run as an oracle.
702    pub(crate) fn track_service_oracle_call(&mut self) -> Result<(), ExecutionError> {
703        self.tracker.as_mut().service_oracle_queries = self
704            .tracker
705            .as_mut()
706            .service_oracle_queries
707            .checked_add(1)
708            .ok_or(ArithmeticError::Overflow)?;
709        self.update_balance(self.policy.service_as_oracle_query)
710    }
711
712    /// Tracks the time spent executing the service as an oracle.
713    pub(crate) fn track_service_oracle_execution(
714        &mut self,
715        execution_time: Duration,
716    ) -> Result<(), ExecutionError> {
717        let tracker = self.tracker.as_mut();
718        let spent_execution_time = &mut tracker.service_oracle_execution;
719        let limit = Duration::from_millis(self.policy.maximum_service_oracle_execution_ms);
720
721        *spent_execution_time = spent_execution_time.saturating_add(execution_time);
722
723        ensure!(
724            *spent_execution_time < limit,
725            ExecutionError::MaximumServiceOracleExecutionTimeExceeded
726        );
727
728        Ok(())
729    }
730
731    /// Tracks the size of a response produced by an oracle.
732    pub(crate) fn track_service_oracle_response(
733        &mut self,
734        response_bytes: usize,
735    ) -> Result<(), ExecutionError> {
736        ensure!(
737            response_bytes as u64 <= self.policy.maximum_oracle_response_bytes,
738            ExecutionError::ServiceOracleResponseTooLarge
739        );
740
741        Ok(())
742    }
743}
744
745impl<Account, Tracker> ResourceController<Account, Tracker>
746where
747    Tracker: AsMut<ResourceTracker>,
748{
749    /// Tracks the serialized size of a block, or parts of it.
750    pub fn track_block_size_of(&mut self, data: &impl Serialize) -> Result<(), ExecutionError> {
751        self.track_block_size(bcs::serialized_size(data)?)
752    }
753
754    /// Tracks the serialized size of a block, or parts of it.
755    pub fn track_block_size(&mut self, size: usize) -> Result<(), ExecutionError> {
756        let tracker = self.tracker.as_mut();
757        tracker.block_size = u64::try_from(size)
758            .ok()
759            .and_then(|size| tracker.block_size.checked_add(size))
760            .ok_or(ExecutionError::BlockTooLarge)?;
761        ensure!(
762            tracker.block_size <= self.policy.maximum_block_size,
763            ExecutionError::BlockTooLarge
764        );
765        Ok(())
766    }
767}
768
769impl ResourceController<Option<AccountOwner>, ResourceTracker> {
770    /// Provides a reference to the current execution state and obtains a temporary object
771    /// where the accounting functions of [`ResourceController`] are available.
772    pub async fn with_state<'a, C>(
773        &mut self,
774        view: &'a mut SystemExecutionStateView<C>,
775    ) -> Result<ResourceController<Sources<'a>, &mut ResourceTracker>, ViewError>
776    where
777        C: Context + Clone + 'static,
778    {
779        self.with_state_and_grant(view, None).await
780    }
781
782    /// Provides a reference to the current execution state as well as an optional grant,
783    /// and obtains a temporary object where the accounting functions of
784    /// [`ResourceController`] are available.
785    pub async fn with_state_and_grant<'a, C>(
786        &mut self,
787        view: &'a mut SystemExecutionStateView<C>,
788        grant: Option<&'a mut Amount>,
789    ) -> Result<ResourceController<Sources<'a>, &mut ResourceTracker>, ViewError>
790    where
791        C: Context + Clone + 'static,
792    {
793        let mut sources = Vec::new();
794        // First, use the grant (e.g. for messages) and otherwise use the chain account
795        // (e.g. for blocks and operations).
796        if let Some(grant) = grant {
797            sources.push(grant);
798        } else {
799            sources.push(view.balance.get_mut());
800        }
801        // Then the local account, if any. Currently, any negative fee (e.g. storage
802        // refund) goes preferably to this account.
803        if let Some(owner) = &self.account {
804            if let Some(balance) = view.balances.get_mut(owner).await? {
805                sources.push(balance);
806            }
807        }
808
809        Ok(ResourceController {
810            policy: self.policy.clone(),
811            tracker: &mut self.tracker,
812            account: Sources { sources },
813            is_free: self.is_free,
814        })
815    }
816}
817
818// The simplest `BalanceHolder` is an `Amount`.
819impl BalanceHolder for Amount {
820    fn balance(&self) -> Result<Amount, ArithmeticError> {
821        Ok(*self)
822    }
823
824    fn try_add_assign(&mut self, other: Amount) -> Result<(), ArithmeticError> {
825        self.try_add_assign(other)
826    }
827
828    fn try_sub_assign(&mut self, other: Amount) -> Result<(), ArithmeticError> {
829        self.try_sub_assign(other)
830    }
831}
832
833// This is also needed for the default instantiation `ResourceController<Amount, ResourceTracker>`.
834// See https://doc.rust-lang.org/std/convert/trait.AsMut.html#reflexivity for general context.
835impl AsMut<ResourceTracker> for ResourceTracker {
836    fn as_mut(&mut self) -> &mut Self {
837        self
838    }
839}
840
841impl AsRef<ResourceTracker> for ResourceTracker {
842    fn as_ref(&self) -> &Self {
843        self
844    }
845}
846
847/// A temporary object holding a number of references to funding sources.
848pub struct Sources<'a> {
849    sources: Vec<&'a mut Amount>,
850}
851
852impl BalanceHolder for Sources<'_> {
853    fn balance(&self) -> Result<Amount, ArithmeticError> {
854        let mut amount = Amount::ZERO;
855        for source in &self.sources {
856            amount.try_add_assign(**source)?;
857        }
858        Ok(amount)
859    }
860
861    fn try_add_assign(&mut self, other: Amount) -> Result<(), ArithmeticError> {
862        // Try to credit the owner account first.
863        // TODO(#1648): This may need some additional design work.
864        let source = self.sources.last_mut().expect("at least one source");
865        source.try_add_assign(other)
866    }
867
868    fn try_sub_assign(&mut self, mut other: Amount) -> Result<(), ArithmeticError> {
869        for source in &mut self.sources {
870            if source.try_sub_assign(other).is_ok() {
871                return Ok(());
872            }
873            other.try_sub_assign(**source).expect("*source < other");
874            **source = Amount::ZERO;
875        }
876        if other > Amount::ZERO {
877            Err(ArithmeticError::Underflow)
878        } else {
879            Ok(())
880        }
881    }
882}