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
6use std::fmt;
7
8use async_graphql::InputObject;
9use linera_base::data_types::{Amount, ArithmeticError, Resources};
10use serde::{Deserialize, Serialize};
11
12/// A collection of prices and limits associated with block execution.
13#[derive(Eq, PartialEq, Hash, Clone, Debug, Serialize, Deserialize, InputObject)]
14pub struct ResourceControlPolicy {
15    /// The base price for creating a new block.
16    pub block: Amount,
17    /// The price per unit of fuel (aka gas) for VM execution.
18    pub fuel_unit: Amount,
19    /// The price of one read operation.
20    pub read_operation: Amount,
21    /// The price of one write operation.
22    pub write_operation: Amount,
23    /// The price of reading a byte.
24    pub byte_read: Amount,
25    /// The price of writing a byte
26    pub byte_written: Amount,
27    /// The price of increasing storage by a byte.
28    // TODO(#1536): This is not fully supported.
29    pub byte_stored: Amount,
30    /// The base price of adding an operation to a block.
31    pub operation: Amount,
32    /// The additional price for each byte in the argument of a user operation.
33    pub operation_byte: Amount,
34    /// The base price of sending a message from a block.
35    pub message: Amount,
36    /// The additional price for each byte in the argument of a user message.
37    pub message_byte: Amount,
38
39    // TODO(#1538): Cap the number of transactions per block and the total size of their
40    // arguments.
41    /// The maximum amount of fuel a block can consume.
42    pub maximum_fuel_per_block: u64,
43    /// The maximum size of an executed block. This includes the block proposal itself as well as
44    /// the execution outcome.
45    pub maximum_executed_block_size: u64,
46    /// The maximum data to read per block
47    pub maximum_bytes_read_per_block: u64,
48    /// The maximum data to write per block
49    pub maximum_bytes_written_per_block: u64,
50}
51
52impl fmt::Display for ResourceControlPolicy {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        let ResourceControlPolicy {
55            block,
56            fuel_unit,
57            read_operation,
58            write_operation,
59            byte_read,
60            byte_written,
61            byte_stored,
62            operation,
63            operation_byte,
64            message,
65            message_byte,
66            maximum_fuel_per_block,
67            maximum_executed_block_size,
68            maximum_bytes_read_per_block,
69            maximum_bytes_written_per_block,
70        } = self;
71        write!(
72            f,
73            "Resource control policy:\n\
74            {block:.2} base cost per block\n\
75            {fuel_unit:.2} cost per fuel unit\n\
76            {read_operation:.2} cost per read operation\n\
77            {write_operation:.2} cost per write operation\n\
78            {byte_read:.2} cost per byte read\n\
79            {byte_written:.2} cost per byte written\n\
80            {byte_stored:.2} cost per byte stored\n\
81            {operation:.2} per operation\n\
82            {operation_byte:.2} per byte in the argument of an operation\n\
83            {message:.2} per outgoing messages\n\
84            {message_byte:.2} per byte in the argument of an outgoing messages\n\
85            {maximum_fuel_per_block} maximum fuel per block\n\
86            {maximum_executed_block_size} maximum size of an executed block\n\
87            {maximum_bytes_read_per_block} maximum number bytes read per block\n\
88            {maximum_bytes_written_per_block} maximum number bytes written per block",
89        )
90    }
91}
92
93impl Default for ResourceControlPolicy {
94    fn default() -> Self {
95        Self {
96            block: Amount::default(),
97            fuel_unit: Amount::default(),
98            read_operation: Amount::default(),
99            write_operation: Amount::default(),
100            byte_read: Amount::default(),
101            byte_written: Amount::default(),
102            byte_stored: Amount::default(),
103            operation: Amount::default(),
104            operation_byte: Amount::default(),
105            message: Amount::default(),
106            message_byte: Amount::default(),
107            maximum_fuel_per_block: u64::MAX,
108            maximum_executed_block_size: u64::MAX,
109            maximum_bytes_read_per_block: u64::MAX,
110            maximum_bytes_written_per_block: u64::MAX,
111        }
112    }
113}
114
115impl ResourceControlPolicy {
116    pub fn block_price(&self) -> Amount {
117        self.block
118    }
119
120    pub fn total_price(&self, resources: &Resources) -> Result<Amount, ArithmeticError> {
121        let mut amount = Amount::ZERO;
122        amount.try_add_assign(self.fuel_price(resources.fuel)?)?;
123        amount.try_add_assign(self.read_operations_price(resources.read_operations)?)?;
124        amount.try_add_assign(self.write_operations_price(resources.write_operations)?)?;
125        amount.try_add_assign(self.bytes_read_price(resources.bytes_to_read as u64)?)?;
126        amount.try_add_assign(self.bytes_written_price(resources.bytes_to_write as u64)?)?;
127        amount.try_add_assign(self.message.try_mul(resources.messages as u128)?)?;
128        amount.try_add_assign(self.message_bytes_price(resources.message_size as u64)?)?;
129        amount.try_add_assign(self.bytes_stored_price(resources.storage_size_delta as u64)?)?;
130        Ok(amount)
131    }
132
133    pub(crate) fn operation_bytes_price(&self, size: u64) -> Result<Amount, ArithmeticError> {
134        self.operation_byte.try_mul(size as u128)
135    }
136
137    pub(crate) fn message_bytes_price(&self, size: u64) -> Result<Amount, ArithmeticError> {
138        self.message_byte.try_mul(size as u128)
139    }
140
141    pub(crate) fn read_operations_price(&self, count: u32) -> Result<Amount, ArithmeticError> {
142        self.read_operation.try_mul(count as u128)
143    }
144
145    pub(crate) fn write_operations_price(&self, count: u32) -> Result<Amount, ArithmeticError> {
146        self.write_operation.try_mul(count as u128)
147    }
148
149    pub(crate) fn bytes_read_price(&self, count: u64) -> Result<Amount, ArithmeticError> {
150        self.byte_read.try_mul(count as u128)
151    }
152
153    pub(crate) fn bytes_written_price(&self, count: u64) -> Result<Amount, ArithmeticError> {
154        self.byte_written.try_mul(count as u128)
155    }
156
157    // TODO(#1536): This is not fully implemented.
158    #[allow(dead_code)]
159    pub(crate) fn bytes_stored_price(&self, count: u64) -> Result<Amount, ArithmeticError> {
160        self.byte_stored.try_mul(count as u128)
161    }
162
163    pub(crate) fn fuel_price(&self, fuel: u64) -> Result<Amount, ArithmeticError> {
164        self.fuel_unit.try_mul(u128::from(fuel))
165    }
166
167    /// Returns how much fuel can be paid with the given balance.
168    pub(crate) fn remaining_fuel(&self, balance: Amount) -> u64 {
169        u64::try_from(balance.saturating_div(self.fuel_unit)).unwrap_or(u64::MAX)
170    }
171}
172
173impl ResourceControlPolicy {
174    /// Creates a policy with no cost for anything except fuel.
175    ///
176    /// This can be used in tests that need whole numbers in their chain balance and don't expect
177    /// to execute any Wasm code.
178    pub fn only_fuel() -> Self {
179        Self {
180            fuel_unit: Amount::from_micros(1),
181            ..Self::default()
182        }
183    }
184
185    /// Creates a policy with no cost for anything except fuel, and 0.001 per block.
186    ///
187    /// This can be used in tests that don't expect to execute any Wasm code, and that keep track of
188    /// how many blocks were created.
189    pub fn fuel_and_block() -> Self {
190        Self {
191            block: Amount::from_millis(1),
192            fuel_unit: Amount::from_micros(1),
193            ..Self::default()
194        }
195    }
196
197    /// Creates a policy where all categories have a small non-zero cost.
198    pub fn all_categories() -> Self {
199        Self {
200            block: Amount::from_millis(1),
201            fuel_unit: Amount::from_nanos(1),
202            byte_read: Amount::from_attos(100),
203            byte_written: Amount::from_attos(1_000),
204            operation: Amount::from_attos(10),
205            operation_byte: Amount::from_attos(1),
206            message: Amount::from_attos(10),
207            message_byte: Amount::from_attos(1),
208            ..Self::default()
209        }
210    }
211
212    /// Creates a policy that matches the Devnet.
213    pub fn devnet() -> Self {
214        Self {
215            block: Amount::from_millis(1),
216            fuel_unit: Amount::from_nanos(10),
217            byte_read: Amount::from_nanos(10),
218            byte_written: Amount::from_nanos(100),
219            read_operation: Amount::from_micros(10),
220            write_operation: Amount::from_micros(20),
221            byte_stored: Amount::from_nanos(10),
222            message_byte: Amount::from_nanos(100),
223            operation_byte: Amount::from_nanos(10),
224            operation: Amount::from_micros(10),
225            message: Amount::from_micros(10),
226            maximum_fuel_per_block: 100_000_000,
227            maximum_executed_block_size: 1_000_000,
228            maximum_bytes_read_per_block: 100_000_000,
229            maximum_bytes_written_per_block: 10_000_000,
230        }
231    }
232}