Skip to main content

linera_execution/
policy.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! This module contains types related to fees and pricing.
5//! Defines the economic parameters and hard limits for resource consumption
6//! within the Linera network. It specifies prices for fundamental units like fuel,
7//! individual read/write operations, costs per byte read/written,
8//! base costs for messages and operations, and costs associated with publishing blobs.
9//! It also sets overarching limits such as the maximum fuel allowed per block,
10//! the maximum block size, and limits on concurrent operations.
11
12use std::{collections::BTreeSet, fmt};
13
14use allocative::Allocative;
15use linera_base::{
16    data_types::{Amount, ArithmeticError, BlobContent, CompressedBytecode, Resources},
17    ensure,
18    identifiers::{ApplicationId, BlobType},
19    vm::VmRuntime,
20};
21use serde::{Deserialize, Serialize};
22
23use crate::ExecutionError;
24
25/// A flag that enables an optional protocol feature.
26///
27/// Flags are stored in [`ResourceControlPolicy::flags`] so that new features can be activated
28/// in future testnets or on mainnet by updating the policy, without breaking compatibility
29/// with chains and validators that don't have the feature enabled.
30#[repr(u32)]
31#[derive(
32    Eq,
33    PartialEq,
34    Ord,
35    PartialOrd,
36    Hash,
37    Clone,
38    Copy,
39    Debug,
40    Serialize,
41    Deserialize,
42    Allocative,
43    strum::Display,
44    strum::EnumString,
45)]
46pub enum ProtocolFlag {
47    #[doc(hidden)]
48    _Reserved = 0,
49}
50
51/// A collection of prices and limits associated with block execution.
52#[derive(Eq, PartialEq, Hash, Clone, Debug, Serialize, Deserialize, Allocative)]
53pub struct ResourceControlPolicy {
54    /// The price per unit of fuel (aka gas) for Wasm execution.
55    pub wasm_fuel_unit: Amount,
56    /// The price per unit of fuel (aka gas) for EVM execution.
57    pub evm_fuel_unit: Amount,
58    /// The price of one read operation.
59    pub read_operation: Amount,
60    /// The price of one write operation.
61    pub write_operation: Amount,
62    /// The price of accessing one byte from the runtime.
63    pub byte_runtime: Amount,
64    /// The price of reading a byte.
65    pub byte_read: Amount,
66    /// The price of writing a byte
67    pub byte_written: Amount,
68    /// The base price to read a blob.
69    pub blob_read: Amount,
70    /// The base price to publish a blob.
71    pub blob_published: Amount,
72    /// The price to read a blob, per byte.
73    pub blob_byte_read: Amount,
74    /// The price to publish a blob, per byte.
75    pub blob_byte_published: Amount,
76    /// The base price of adding an operation to a block.
77    pub operation: Amount,
78    /// The additional price for each byte in the argument of a user operation.
79    pub operation_byte: Amount,
80    /// The base price of sending a message from a block.
81    pub message: Amount,
82    /// The additional price for each byte in the argument of a user message.
83    pub message_byte: Amount,
84    /// The price per query to a service as an oracle.
85    pub service_as_oracle_query: Amount,
86    /// The price for a performing an HTTP request.
87    pub http_request: Amount,
88
89    // TODO(#1538): Cap the number of transactions per block and the total size of their
90    // arguments.
91    /// The maximum amount of Wasm fuel a block can consume.
92    pub maximum_wasm_fuel_per_block: u64,
93    /// The maximum amount of EVM fuel a block can consume.
94    pub maximum_evm_fuel_per_block: u64,
95    /// The maximum time in milliseconds that a block can spend executing services as oracles.
96    pub maximum_service_oracle_execution_ms: u64,
97    /// The maximum size of a block. This includes the block proposal itself as well as
98    /// the execution outcome.
99    pub maximum_block_size: u64,
100    /// The maximum size of decompressed contract or service bytecode, in bytes.
101    pub maximum_bytecode_size: u64,
102    /// The maximum size of a blob.
103    pub maximum_blob_size: u64,
104    /// The maximum number of published blobs per block.
105    pub maximum_published_blobs: u64,
106    /// The maximum size of a block proposal.
107    pub maximum_block_proposal_size: u64,
108    /// The maximum data to read per block
109    pub maximum_bytes_read_per_block: u64,
110    /// The maximum data to write per block
111    pub maximum_bytes_written_per_block: u64,
112    /// The maximum size in bytes of an oracle response.
113    pub maximum_oracle_response_bytes: u64,
114    /// The maximum size in bytes of a received HTTP response.
115    pub maximum_http_response_bytes: u64,
116    /// The maximum amount of time allowed to wait for an HTTP response.
117    pub http_request_timeout_ms: u64,
118    /// The list of hosts that contracts and services can send HTTP requests to.
119    pub http_request_allow_list: BTreeSet<String>,
120    /// The list of application IDs for which all message- and event-related fees are waived.
121    pub free_application_ids: BTreeSet<ApplicationId>,
122    /// The set of optional protocol features that are enabled.
123    pub flags: BTreeSet<ProtocolFlag>,
124}
125
126impl fmt::Display for ResourceControlPolicy {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        let ResourceControlPolicy {
129            wasm_fuel_unit,
130            evm_fuel_unit,
131            read_operation,
132            write_operation,
133            byte_runtime,
134            byte_read,
135            byte_written,
136            blob_read,
137            blob_published,
138            blob_byte_read,
139            blob_byte_published,
140            operation,
141            operation_byte,
142            message,
143            message_byte,
144            service_as_oracle_query,
145            http_request,
146            maximum_wasm_fuel_per_block,
147            maximum_evm_fuel_per_block,
148            maximum_service_oracle_execution_ms,
149            maximum_block_size,
150            maximum_blob_size,
151            maximum_published_blobs,
152            maximum_bytecode_size,
153            maximum_block_proposal_size,
154            maximum_bytes_read_per_block,
155            maximum_bytes_written_per_block,
156            maximum_oracle_response_bytes,
157            maximum_http_response_bytes,
158            http_request_allow_list,
159            http_request_timeout_ms,
160            free_application_ids,
161            flags,
162        } = self;
163        write!(
164            f,
165            "Resource control policy:\n\
166            {wasm_fuel_unit:.2} cost per Wasm fuel unit\n\
167            {evm_fuel_unit:.2} cost per EVM fuel unit\n\
168            {read_operation:.2} cost per read operation\n\
169            {write_operation:.2} cost per write operation\n\
170            {byte_runtime:.2} cost per runtime byte read operation\n\
171            {byte_read:.2} cost per byte read\n\
172            {byte_written:.2} cost per byte written\n\
173            {blob_read:.2} base cost per read blob\n\
174            {blob_published:.2} base cost per published blob\n\
175            {blob_byte_read:.2} cost of reading blobs, per byte\n\
176            {blob_byte_published:.2} cost of publishing blobs, per byte\n\
177            {operation:.2} per operation\n\
178            {operation_byte:.2} per byte in the argument of an operation\n\
179            {service_as_oracle_query:.2} per query to a service as an oracle\n\
180            {message:.2} per outgoing messages\n\
181            {message_byte:.2} per byte in the argument of an outgoing messages\n\
182            {http_request:.2} per HTTP request performed\n\
183            {maximum_wasm_fuel_per_block} maximum Wasm fuel per block\n\
184            {maximum_evm_fuel_per_block} maximum EVM fuel per block\n\
185            {maximum_service_oracle_execution_ms} ms maximum service-as-oracle execution time per \
186                block\n\
187            {maximum_block_size} maximum size of a block\n\
188            {maximum_blob_size} maximum size of a data blob, bytecode or other binary blob\n\
189            {maximum_published_blobs} maximum number of blobs published per block\n\
190            {maximum_bytecode_size} maximum size of service and contract bytecode\n\
191            {maximum_block_proposal_size} maximum size of a block proposal\n\
192            {maximum_bytes_read_per_block} maximum number of bytes read per block\n\
193            {maximum_bytes_written_per_block} maximum number of bytes written per block\n\
194            {maximum_oracle_response_bytes} maximum number of bytes of an oracle response\n\
195            {maximum_http_response_bytes} maximum number of bytes of an HTTP response\n\
196            {http_request_timeout_ms} ms timeout for HTTP requests\n\
197            HTTP hosts allowed for contracts and services: {http_request_allow_list:#?}\n\
198            Free application IDs: {free_application_ids:#?}\n\
199            Enabled protocol flags: {flags:#?}\n",
200        )?;
201        Ok(())
202    }
203}
204
205impl Default for ResourceControlPolicy {
206    fn default() -> Self {
207        Self::no_fees()
208    }
209}
210
211impl ResourceControlPolicy {
212    /// Creates a policy with no cost for anything.
213    ///
214    /// This can be used in tests or benchmarks.
215    pub fn no_fees() -> Self {
216        Self {
217            wasm_fuel_unit: Amount::ZERO,
218            evm_fuel_unit: Amount::ZERO,
219            read_operation: Amount::ZERO,
220            write_operation: Amount::ZERO,
221            byte_runtime: Amount::ZERO,
222            byte_read: Amount::ZERO,
223            byte_written: Amount::ZERO,
224            blob_read: Amount::ZERO,
225            blob_published: Amount::ZERO,
226            blob_byte_read: Amount::ZERO,
227            blob_byte_published: Amount::ZERO,
228            operation: Amount::ZERO,
229            operation_byte: Amount::ZERO,
230            message: Amount::ZERO,
231            message_byte: Amount::ZERO,
232            service_as_oracle_query: Amount::ZERO,
233            http_request: Amount::ZERO,
234            maximum_wasm_fuel_per_block: u64::MAX,
235            maximum_evm_fuel_per_block: u64::MAX,
236            maximum_service_oracle_execution_ms: u64::MAX,
237            maximum_block_size: u64::MAX,
238            maximum_blob_size: u64::MAX,
239            maximum_published_blobs: u64::MAX,
240            maximum_bytecode_size: u64::MAX,
241            maximum_block_proposal_size: u64::MAX,
242            maximum_bytes_read_per_block: u64::MAX,
243            maximum_bytes_written_per_block: u64::MAX,
244            maximum_oracle_response_bytes: u64::MAX,
245            maximum_http_response_bytes: u64::MAX,
246            http_request_timeout_ms: u64::MAX,
247            http_request_allow_list: BTreeSet::new(),
248            free_application_ids: BTreeSet::new(),
249            flags: BTreeSet::new(),
250        }
251    }
252
253    /// Returns whether the given application has its message- and event-related fees waived.
254    pub fn is_free_app(&self, app_id: &ApplicationId) -> bool {
255        self.free_application_ids.contains(app_id)
256    }
257
258    /// The maximum fuel per block according to the `VmRuntime`.
259    pub fn maximum_fuel_per_block(&self, vm_runtime: VmRuntime) -> u64 {
260        match vm_runtime {
261            VmRuntime::Wasm => self.maximum_wasm_fuel_per_block,
262            VmRuntime::Evm => self.maximum_evm_fuel_per_block,
263        }
264    }
265
266    /// Creates a policy with no cost for anything except fuel.
267    ///
268    /// This can be used in tests that need whole numbers in their chain balance.
269    #[cfg(with_testing)]
270    pub fn only_fuel() -> Self {
271        Self {
272            wasm_fuel_unit: Amount::from_micros(1),
273            evm_fuel_unit: Amount::from_micros(1),
274            ..Self::no_fees()
275        }
276    }
277
278    /// Creates a policy where all categories have a small non-zero cost.
279    #[cfg(with_testing)]
280    pub fn all_categories() -> Self {
281        Self {
282            wasm_fuel_unit: Amount::from_nanos(1),
283            evm_fuel_unit: Amount::from_nanos(1),
284            byte_read: Amount::from_attos(100),
285            byte_written: Amount::from_attos(1_000),
286            blob_read: Amount::from_nanos(1),
287            blob_published: Amount::from_nanos(10),
288            blob_byte_read: Amount::from_attos(100),
289            blob_byte_published: Amount::from_attos(1_000),
290            operation: Amount::from_attos(10),
291            operation_byte: Amount::from_attos(1),
292            message: Amount::from_attos(10),
293            message_byte: Amount::from_attos(1),
294            http_request: Amount::from_micros(1),
295            ..Self::no_fees()
296        }
297    }
298
299    /// Creates a policy that matches the Testnet.
300    pub fn testnet() -> Self {
301        Self {
302            wasm_fuel_unit: Amount::from_nanos(10),
303            evm_fuel_unit: Amount::from_nanos(10),
304            byte_runtime: Amount::from_nanos(1),
305            byte_read: Amount::from_nanos(10),
306            byte_written: Amount::from_nanos(100),
307            blob_read: Amount::from_nanos(100),
308            blob_published: Amount::from_nanos(1000),
309            blob_byte_read: Amount::from_nanos(10),
310            blob_byte_published: Amount::from_nanos(100),
311            read_operation: Amount::from_micros(10),
312            write_operation: Amount::from_micros(20),
313            message_byte: Amount::from_nanos(100),
314            operation_byte: Amount::from_nanos(10),
315            operation: Amount::from_micros(10),
316            message: Amount::from_micros(10),
317            service_as_oracle_query: Amount::from_millis(10),
318            http_request: Amount::from_micros(50),
319            maximum_wasm_fuel_per_block: 100_000_000,
320            maximum_evm_fuel_per_block: 100_000_000,
321            maximum_service_oracle_execution_ms: 10_000,
322            maximum_block_size: 1_000_000,
323            maximum_blob_size: 1_000_000,
324            maximum_published_blobs: 10,
325            maximum_bytecode_size: 10_000_000,
326            maximum_block_proposal_size: 13_000_000,
327            maximum_bytes_read_per_block: 100_000_000,
328            maximum_bytes_written_per_block: 10_000_000,
329            maximum_oracle_response_bytes: 10_000,
330            maximum_http_response_bytes: 10_000,
331            http_request_timeout_ms: 20_000,
332            http_request_allow_list: BTreeSet::new(),
333            free_application_ids: BTreeSet::new(),
334            flags: BTreeSet::new(),
335        }
336    }
337
338    pub fn total_price(&self, resources: &Resources) -> Result<Amount, ArithmeticError> {
339        let mut amount = Amount::ZERO;
340        amount.try_add_assign(self.fuel_price(resources.wasm_fuel, VmRuntime::Wasm)?)?;
341        amount.try_add_assign(self.fuel_price(resources.evm_fuel, VmRuntime::Evm)?)?;
342        amount.try_add_assign(self.read_operations_price(resources.read_operations)?)?;
343        amount.try_add_assign(self.bytes_runtime_price(resources.bytes_runtime)?)?;
344        amount.try_add_assign(self.write_operations_price(resources.write_operations)?)?;
345        amount.try_add_assign(self.bytes_read_price(resources.bytes_to_read as u64)?)?;
346        amount.try_add_assign(self.bytes_written_price(resources.bytes_to_write as u64)?)?;
347        amount.try_add_assign(
348            self.blob_byte_read
349                .try_mul(resources.blob_bytes_to_read as u128)?
350                .try_add(self.blob_read.try_mul(resources.blobs_to_read as u128)?)?,
351        )?;
352        amount.try_add_assign(
353            self.blob_byte_published
354                .try_mul(resources.blob_bytes_to_publish as u128)?
355                .try_add(
356                    self.blob_published
357                        .try_mul(resources.blobs_to_publish as u128)?,
358                )?,
359        )?;
360        amount.try_add_assign(self.message.try_mul(resources.messages as u128)?)?;
361        amount.try_add_assign(self.message_bytes_price(resources.message_size as u64)?)?;
362        amount.try_add_assign(
363            self.service_as_oracle_queries_price(resources.service_as_oracle_queries)?,
364        )?;
365        amount.try_add_assign(self.http_requests_price(resources.http_requests)?)?;
366        Ok(amount)
367    }
368
369    pub(crate) fn operation_bytes_price(&self, size: u64) -> Result<Amount, ArithmeticError> {
370        self.operation_byte.try_mul(size as u128)
371    }
372
373    pub(crate) fn message_bytes_price(&self, size: u64) -> Result<Amount, ArithmeticError> {
374        self.message_byte.try_mul(size as u128)
375    }
376
377    pub(crate) fn read_operations_price(&self, count: u32) -> Result<Amount, ArithmeticError> {
378        self.read_operation.try_mul(count as u128)
379    }
380
381    pub(crate) fn write_operations_price(&self, count: u32) -> Result<Amount, ArithmeticError> {
382        self.write_operation.try_mul(count as u128)
383    }
384
385    pub(crate) fn bytes_runtime_price(&self, count: u32) -> Result<Amount, ArithmeticError> {
386        self.byte_runtime.try_mul(count as u128)
387    }
388
389    pub(crate) fn bytes_read_price(&self, count: u64) -> Result<Amount, ArithmeticError> {
390        self.byte_read.try_mul(count as u128)
391    }
392
393    pub(crate) fn bytes_written_price(&self, count: u64) -> Result<Amount, ArithmeticError> {
394        self.byte_written.try_mul(count as u128)
395    }
396
397    pub(crate) fn blob_read_price(&self, count: u64) -> Result<Amount, ArithmeticError> {
398        self.blob_byte_read
399            .try_mul(count as u128)?
400            .try_add(self.blob_read)
401    }
402
403    pub(crate) fn blob_published_price(&self, count: u64) -> Result<Amount, ArithmeticError> {
404        self.blob_byte_published
405            .try_mul(count as u128)?
406            .try_add(self.blob_published)
407    }
408
409    /// Returns how much it would cost to perform `count` queries to services running as oracles.
410    pub(crate) fn service_as_oracle_queries_price(
411        &self,
412        count: u32,
413    ) -> Result<Amount, ArithmeticError> {
414        self.service_as_oracle_query.try_mul(count as u128)
415    }
416
417    pub(crate) fn http_requests_price(&self, count: u32) -> Result<Amount, ArithmeticError> {
418        self.http_request.try_mul(count as u128)
419    }
420
421    fn fuel_unit_price(&self, vm_runtime: VmRuntime) -> Amount {
422        match vm_runtime {
423            VmRuntime::Wasm => self.wasm_fuel_unit,
424            VmRuntime::Evm => self.evm_fuel_unit,
425        }
426    }
427
428    pub(crate) fn fuel_price(
429        &self,
430        fuel: u64,
431        vm_runtime: VmRuntime,
432    ) -> Result<Amount, ArithmeticError> {
433        self.fuel_unit_price(vm_runtime).try_mul(u128::from(fuel))
434    }
435
436    /// Returns how much fuel can be paid with the given balance.
437    pub(crate) fn remaining_fuel(&self, balance: Amount, vm_runtime: VmRuntime) -> u64 {
438        let fuel_unit = self.fuel_unit_price(vm_runtime);
439        u64::try_from(balance.saturating_ratio(fuel_unit)).unwrap_or(u64::MAX)
440    }
441
442    pub fn check_blob_size(&self, content: &BlobContent) -> Result<(), ExecutionError> {
443        ensure!(
444            u64::try_from(content.bytes().len())
445                .ok()
446                .is_some_and(|size| size <= self.maximum_blob_size),
447            ExecutionError::BlobTooLarge
448        );
449        match content.blob_type() {
450            BlobType::ContractBytecode | BlobType::ServiceBytecode | BlobType::EvmBytecode => {
451                ensure!(
452                    CompressedBytecode::decompressed_size_at_most(
453                        content.bytes(),
454                        self.maximum_bytecode_size
455                    )?,
456                    ExecutionError::BytecodeTooLarge
457                );
458            }
459            BlobType::Data
460            | BlobType::ApplicationDescription
461            | BlobType::ApplicationFormats
462            | BlobType::Committee
463            | BlobType::ChainDescription
464            | BlobType::CheckpointExecutionState => {}
465        }
466        Ok(())
467    }
468}