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