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