1use 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#[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#[derive(Eq, PartialEq, Hash, Clone, Debug, Serialize, Deserialize, Allocative)]
53pub struct ResourceControlPolicy {
54 pub wasm_fuel_unit: Amount,
56 pub evm_fuel_unit: Amount,
58 pub read_operation: Amount,
60 pub write_operation: Amount,
62 pub byte_runtime: Amount,
64 pub byte_read: Amount,
66 pub byte_written: Amount,
68 pub blob_read: Amount,
70 pub blob_published: Amount,
72 pub blob_byte_read: Amount,
74 pub blob_byte_published: Amount,
76 pub operation: Amount,
78 pub operation_byte: Amount,
80 pub message: Amount,
82 pub message_byte: Amount,
84 pub service_as_oracle_query: Amount,
86 pub http_request: Amount,
88
89 pub maximum_wasm_fuel_per_block: u64,
93 pub maximum_evm_fuel_per_block: u64,
95 pub maximum_service_oracle_execution_ms: u64,
97 pub maximum_block_size: u64,
100 pub maximum_bytecode_size: u64,
102 pub maximum_blob_size: u64,
104 pub maximum_published_blobs: u64,
106 pub maximum_block_proposal_size: u64,
108 pub maximum_bytes_read_per_block: u64,
110 pub maximum_bytes_written_per_block: u64,
112 pub maximum_oracle_response_bytes: u64,
114 pub maximum_http_response_bytes: u64,
116 pub http_request_timeout_ms: u64,
118 pub http_request_allow_list: BTreeSet<String>,
120 pub free_application_ids: BTreeSet<ApplicationId>,
122 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 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 pub fn is_free_app(&self, app_id: &ApplicationId) -> bool {
255 self.free_application_ids.contains(app_id)
256 }
257
258 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 #[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 #[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 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 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 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}