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