1use 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#[derive(Eq, PartialEq, Hash, Clone, Debug, Serialize, Deserialize)]
26pub struct ResourceControlPolicy {
27 pub wasm_fuel_unit: Amount,
29 pub evm_fuel_unit: Amount,
31 pub read_operation: Amount,
33 pub write_operation: Amount,
35 pub byte_runtime: Amount,
37 pub byte_read: Amount,
39 pub byte_written: Amount,
41 pub blob_read: Amount,
43 pub blob_published: Amount,
45 pub blob_byte_read: Amount,
47 pub blob_byte_published: Amount,
49 pub byte_stored: Amount,
52 pub operation: Amount,
54 pub operation_byte: Amount,
56 pub message: Amount,
58 pub message_byte: Amount,
60 pub service_as_oracle_query: Amount,
62 pub http_request: Amount,
64
65 pub maximum_wasm_fuel_per_block: u64,
69 pub maximum_evm_fuel_per_block: u64,
71 pub maximum_service_oracle_execution_ms: u64,
73 pub maximum_block_size: u64,
76 pub maximum_bytecode_size: u64,
78 pub maximum_blob_size: u64,
80 pub maximum_published_blobs: u64,
82 pub maximum_block_proposal_size: u64,
84 pub maximum_bytes_read_per_block: u64,
86 pub maximum_bytes_written_per_block: u64,
88 pub maximum_oracle_response_bytes: u64,
90 pub maximum_http_response_bytes: u64,
92 pub http_request_timeout_ms: u64,
94 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 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 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 #[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 #[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 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 #[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 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 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}