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