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