1use crate::util::batch::GroveDbOpBatch;
2use grovedb_costs::storage_cost::removal::Identifier;
3use grovedb_costs::storage_cost::removal::StorageRemovedBytes::{
4 BasicStorageRemoval, NoStorageRemoval, SectionedStorageRemoval,
5};
6use std::collections::BTreeMap;
7
8use enum_map::Enum;
9use grovedb::batch::key_info::KeyInfo;
10use grovedb::batch::KeyInfoPath;
11use grovedb::element::reference_path::ReferencePathType;
12use grovedb::element::MaxReferenceHop;
13use grovedb::{batch::QualifiedGroveDbOp, Element, ElementFlags, TreeType};
14use grovedb_costs::OperationCost;
15use itertools::Itertools;
16
17use crate::error::drive::DriveError;
18use crate::error::Error;
19use crate::fees::get_overflow_error;
20use crate::fees::op::LowLevelDriveOperation::{
21 CalculatedCostOperation, FunctionOperation, GroveOperation, PreCalculatedFeeResult,
22};
23use crate::util::batch::grovedb_op_batch::GroveDbOpBatchV0Methods;
24use crate::util::storage_flags::StorageFlags;
25use dpp::block::epoch::Epoch;
26use dpp::fee::default_costs::CachedEpochIndexFeeVersions;
27use dpp::fee::fee_result::refunds::FeeRefunds;
28use dpp::fee::fee_result::FeeResult;
29use dpp::fee::Credits;
30use platform_version::version::fee::FeeVersion;
31
32#[derive(Debug, Enum)]
34pub enum BaseOp {
35 Stop,
37 Add,
39 Mul,
41 Sub,
43 Div,
45 Sdiv,
47 Mod,
49 Smod,
51 Addmod,
53 Mulmod,
55 Signextend,
57 Lt,
59 Gt,
61 Slt,
63 Sgt,
65 Eq,
67 Iszero,
69 And,
71 Or,
73 Xor,
75 Not,
77 Byte,
79}
80
81impl BaseOp {
82 pub fn cost(&self) -> u64 {
84 match self {
85 BaseOp::Stop => 0,
86 BaseOp::Add => 12,
87 BaseOp::Mul => 20,
88 BaseOp::Sub => 12,
89 BaseOp::Div => 20,
90 BaseOp::Sdiv => 20,
91 BaseOp::Mod => 20,
92 BaseOp::Smod => 20,
93 BaseOp::Addmod => 32,
94 BaseOp::Mulmod => 32,
95 BaseOp::Signextend => 20,
96 BaseOp::Lt => 12,
97 BaseOp::Gt => 12,
98 BaseOp::Slt => 12,
99 BaseOp::Sgt => 12,
100 BaseOp::Eq => 12,
101 BaseOp::Iszero => 12,
102 BaseOp::And => 12,
103 BaseOp::Or => 12,
104 BaseOp::Xor => 12,
105 BaseOp::Not => 12,
106 BaseOp::Byte => 12,
107 }
108 }
109}
110
111#[derive(Debug, Enum, PartialEq, Eq)]
113pub enum HashFunction {
114 Sha256RipeMD160,
116 Sha256,
118 Sha256_2,
120 Blake3,
122}
123
124impl HashFunction {
125 fn block_size(&self) -> u16 {
126 match self {
127 HashFunction::Sha256 => 64,
128 HashFunction::Sha256_2 => 64,
129 HashFunction::Blake3 => 64,
130 HashFunction::Sha256RipeMD160 => 64,
131 }
132 }
133
134 fn rounds(&self) -> u16 {
135 match self {
136 HashFunction::Sha256 => 1,
137 HashFunction::Sha256_2 => 2,
138 HashFunction::Blake3 => 1,
139 HashFunction::Sha256RipeMD160 => 1,
140 }
141 }
142
143 fn block_cost(&self, fee_version: &FeeVersion) -> u64 {
144 match self {
145 HashFunction::Sha256 => fee_version.hashing.sha256_per_block,
146 HashFunction::Sha256_2 => fee_version.hashing.sha256_per_block,
147 HashFunction::Blake3 => fee_version.hashing.blake3_per_block,
148 HashFunction::Sha256RipeMD160 => fee_version.hashing.sha256_per_block,
149 }
150 }
151
152 fn base_cost(&self, fee_version: &FeeVersion) -> u64 {
153 match self {
154 HashFunction::Sha256 => fee_version.hashing.single_sha256_base,
155 HashFunction::Sha256_2 => fee_version.hashing.single_sha256_base,
158 HashFunction::Blake3 => fee_version.hashing.blake3_base,
159 HashFunction::Sha256RipeMD160 => fee_version.hashing.sha256_ripe_md160_base,
160 }
161 }
162}
163
164#[derive(Debug, PartialEq, Eq)]
166pub struct FunctionOp {
167 pub(crate) hash: HashFunction,
169 pub(crate) rounds: u32,
171}
172
173impl FunctionOp {
174 fn cost(&self, fee_version: &FeeVersion) -> Credits {
176 let block_cost = (self.rounds as u64).saturating_mul(self.hash.block_cost(fee_version));
177 self.hash.base_cost(fee_version).saturating_add(block_cost)
178 }
179
180 pub fn new_with_round_count(hash: HashFunction, rounds: u32) -> Self {
183 FunctionOp { hash, rounds }
184 }
185
186 pub fn new_with_byte_count(hash: HashFunction, byte_count: u16) -> Self {
189 let blocks = byte_count / hash.block_size() + 1;
190 let rounds = blocks + hash.rounds() - 1;
191 FunctionOp {
192 hash,
193 rounds: rounds as u32,
194 }
195 }
196}
197
198#[derive(Debug, Eq, PartialEq)]
200pub enum LowLevelDriveOperation {
201 GroveOperation(QualifiedGroveDbOp),
203 FunctionOperation(FunctionOp),
205 CalculatedCostOperation(OperationCost),
207 PreCalculatedFeeResult(FeeResult),
209}
210
211impl LowLevelDriveOperation {
212 pub fn consume_to_fees_v0(
215 drive_operations: Vec<LowLevelDriveOperation>,
216 epoch: &Epoch,
217 epochs_per_era: u16,
218 fee_version: &FeeVersion,
219 previous_fee_versions: Option<&CachedEpochIndexFeeVersions>,
220 ) -> Result<Vec<FeeResult>, Error> {
221 drive_operations
222 .into_iter()
223 .map(|operation| match operation {
224 PreCalculatedFeeResult(f) => Ok(f),
225 FunctionOperation(op) => Ok(FeeResult {
226 processing_fee: op.cost(fee_version),
227 ..Default::default()
228 }),
229 _ => {
230 let cost = operation.operation_cost()?;
231 let storage_fee = cost.storage_cost.added_bytes as u64 * fee_version.storage.storage_disk_usage_credit_per_byte;
234 let processing_fee = cost.ephemeral_cost(fee_version)?;
235 let (fee_refunds, removed_bytes_from_system) =
236 match cost.storage_cost.removed_bytes {
237 NoStorageRemoval => (FeeRefunds::default(), 0),
238 BasicStorageRemoval(amount) => {
239 (FeeRefunds::default(), amount)
241 }
242 SectionedStorageRemoval(mut removal_per_epoch_by_identifier) => {
243
244 let system_amount = removal_per_epoch_by_identifier
245 .remove(&Identifier::default())
246 .map_or(0, |a| a.values().sum());
247 if fee_version.fee_version_number == 1 {
248 (
249 FeeRefunds::from_storage_removal(
250 removal_per_epoch_by_identifier,
251 epoch.index,
252 epochs_per_era,
253 &BTreeMap::default(),
254 )?,
255 system_amount,
256 )
257 } else {
258 let previous_fee_versions = previous_fee_versions.ok_or(Error::Drive(DriveError::CorruptedCodeExecution("expected previous epoch index fee versions to be able to offer refunds")))?;
259 (
260 FeeRefunds::from_storage_removal(
261 removal_per_epoch_by_identifier,
262 epoch.index,
263 epochs_per_era,
264 previous_fee_versions,
265 )?,
266 system_amount,
267 )
268 }
269 }
270 };
271 Ok(FeeResult {
272 storage_fee,
273 processing_fee,
274 fee_refunds,
275 removed_bytes_from_system,
276 })
277 }
278 })
279 .collect()
280 }
281
282 pub fn operation_cost(self) -> Result<OperationCost, Error> {
284 match self {
285 GroveOperation(_) => Err(Error::Drive(DriveError::CorruptedCodeExecution(
286 "grove operations must be executed, not directly transformed to costs",
287 ))),
288 CalculatedCostOperation(c) => Ok(c),
289 PreCalculatedFeeResult(_) => Err(Error::Drive(DriveError::CorruptedCodeExecution(
290 "pre calculated fees should not be requested by operation costs",
291 ))),
292 FunctionOperation(_) => Err(Error::Drive(DriveError::CorruptedCodeExecution(
293 "function operations should not be requested by operation costs",
294 ))),
295 }
296 }
297
298 pub fn combine_cost_operations(operations: &[LowLevelDriveOperation]) -> OperationCost {
300 let mut cost = OperationCost::default();
301 operations.iter().for_each(|op| {
302 if let CalculatedCostOperation(operation_cost) = op {
303 cost += operation_cost.clone()
304 }
305 });
306 cost
307 }
308
309 pub fn grovedb_operations_batch(
311 insert_operations: &[LowLevelDriveOperation],
312 ) -> GroveDbOpBatch {
313 let operations = insert_operations
314 .iter()
315 .filter_map(|op| match op {
316 GroveOperation(grovedb_op) => Some(grovedb_op.clone()),
317 _ => None,
318 })
319 .collect();
320 GroveDbOpBatch::from_operations(operations)
321 }
322
323 pub fn grovedb_operations_batch_consume(
325 insert_operations: Vec<LowLevelDriveOperation>,
326 ) -> GroveDbOpBatch {
327 let operations = insert_operations
328 .into_iter()
329 .filter_map(|op| match op {
330 GroveOperation(grovedb_op) => Some(grovedb_op),
331 _ => None,
332 })
333 .collect();
334 GroveDbOpBatch::from_operations(operations)
335 }
336
337 pub fn grovedb_operations_batch_consume_with_leftovers(
339 insert_operations: Vec<LowLevelDriveOperation>,
340 ) -> (GroveDbOpBatch, Vec<LowLevelDriveOperation>) {
341 let (grove_operations, other_operations): (Vec<_>, Vec<_>) =
342 insert_operations.into_iter().partition_map(|op| match op {
343 GroveOperation(grovedb_op) => itertools::Either::Left(grovedb_op),
344 _ => itertools::Either::Right(op),
345 });
346
347 (
348 GroveDbOpBatch::from_operations(grove_operations),
349 other_operations,
350 )
351 }
352
353 pub fn grovedb_operations_consume(
355 insert_operations: Vec<LowLevelDriveOperation>,
356 ) -> Vec<QualifiedGroveDbOp> {
357 insert_operations
358 .into_iter()
359 .filter_map(|op| match op {
360 GroveOperation(grovedb_op) => Some(grovedb_op),
361 _ => None,
362 })
363 .collect()
364 }
365
366 pub fn for_known_path_key_empty_tree(
368 path: Vec<Vec<u8>>,
369 key: Vec<u8>,
370 storage_flags: Option<&StorageFlags>,
371 ) -> Self {
372 let tree = match storage_flags {
373 Some(storage_flags) => {
374 Element::empty_tree_with_flags(storage_flags.to_some_element_flags())
375 }
376 None => Element::empty_tree(),
377 };
378
379 LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
380 }
381
382 pub fn for_known_path_key_empty_sum_tree(
384 path: Vec<Vec<u8>>,
385 key: Vec<u8>,
386 storage_flags: Option<&StorageFlags>,
387 ) -> Self {
388 let tree = match storage_flags {
389 Some(storage_flags) => {
390 Element::empty_sum_tree_with_flags(storage_flags.to_some_element_flags())
391 }
392 None => Element::empty_sum_tree(),
393 };
394
395 LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
396 }
397
398 pub fn for_known_path_key_empty_big_sum_tree(
400 path: Vec<Vec<u8>>,
401 key: Vec<u8>,
402 storage_flags: Option<&StorageFlags>,
403 ) -> Self {
404 let tree = match storage_flags {
405 Some(storage_flags) => {
406 Element::new_big_sum_tree_with_flags(None, storage_flags.to_some_element_flags())
407 }
408 None => Element::empty_big_sum_tree(),
409 };
410
411 LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
412 }
413
414 pub fn for_known_path_key_empty_count_tree(
416 path: Vec<Vec<u8>>,
417 key: Vec<u8>,
418 storage_flags: Option<&StorageFlags>,
419 ) -> Self {
420 let tree = match storage_flags {
421 Some(storage_flags) => {
422 Element::new_count_tree_with_flags(None, storage_flags.to_some_element_flags())
423 }
424 None => Element::empty_count_tree(),
425 };
426
427 LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
428 }
429
430 pub fn for_known_path_key_empty_count_sum_tree(
432 path: Vec<Vec<u8>>,
433 key: Vec<u8>,
434 storage_flags: Option<&StorageFlags>,
435 ) -> Self {
436 let tree = match storage_flags {
437 Some(storage_flags) => {
438 Element::new_count_sum_tree_with_flags(None, storage_flags.to_some_element_flags())
439 }
440 None => Element::new_count_sum_tree(None),
441 };
442
443 LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
444 }
445
446 pub fn for_estimated_path_key_empty_tree(
448 path: KeyInfoPath,
449 key: KeyInfo,
450 storage_flags: Option<&StorageFlags>,
451 ) -> Self {
452 let tree = match storage_flags {
453 Some(storage_flags) => {
454 Element::empty_tree_with_flags(storage_flags.to_some_element_flags())
455 }
456 None => Element::empty_tree(),
457 };
458
459 LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
460 }
461
462 pub fn for_estimated_path_key_empty_sum_tree(
464 path: KeyInfoPath,
465 key: KeyInfo,
466 storage_flags: Option<&StorageFlags>,
467 ) -> Self {
468 let tree = match storage_flags {
469 Some(storage_flags) => {
470 Element::empty_sum_tree_with_flags(storage_flags.to_some_element_flags())
471 }
472 None => Element::empty_sum_tree(),
473 };
474
475 LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
476 }
477
478 pub fn insert_for_known_path_key_element(
480 path: Vec<Vec<u8>>,
481 key: Vec<u8>,
482 element: Element,
483 ) -> Self {
484 GroveOperation(QualifiedGroveDbOp::insert_or_replace_op(path, key, element))
485 }
486
487 pub fn replace_for_known_path_key_element(
489 path: Vec<Vec<u8>>,
490 key: Vec<u8>,
491 element: Element,
492 ) -> Self {
493 GroveOperation(QualifiedGroveDbOp::replace_op(path, key, element))
494 }
495
496 pub fn patch_for_known_path_key_element(
499 path: Vec<Vec<u8>>,
500 key: Vec<u8>,
501 element: Element,
502 change_in_bytes: i32,
503 ) -> Self {
504 GroveOperation(QualifiedGroveDbOp::patch_op(
505 path,
506 key,
507 element,
508 change_in_bytes,
509 ))
510 }
511
512 pub fn insert_for_estimated_path_key_element(
514 path: KeyInfoPath,
515 key: KeyInfo,
516 element: Element,
517 ) -> Self {
518 GroveOperation(QualifiedGroveDbOp::insert_estimated_op(path, key, element))
519 }
520
521 pub fn replace_for_estimated_path_key_element(
523 path: KeyInfoPath,
524 key: KeyInfo,
525 element: Element,
526 ) -> Self {
527 GroveOperation(QualifiedGroveDbOp::replace_estimated_op(path, key, element))
528 }
529
530 pub fn refresh_reference_for_known_path_key_reference_info(
532 path: Vec<Vec<u8>>,
533 key: Vec<u8>,
534 reference_path_type: ReferencePathType,
535 max_reference_hop: MaxReferenceHop,
536 flags: Option<ElementFlags>,
537 trust_refresh_reference: bool,
538 ) -> Self {
539 GroveOperation(QualifiedGroveDbOp::refresh_reference_op(
540 path,
541 key,
542 reference_path_type,
543 max_reference_hop,
544 flags,
545 trust_refresh_reference,
546 ))
547 }
548}
549
550pub trait LowLevelDriveOperationTreeTypeConverter {
552 fn empty_tree_operation_for_known_path_key(
554 &self,
555 path: Vec<Vec<u8>>,
556 key: Vec<u8>,
557 storage_flags: Option<&StorageFlags>,
558 ) -> Result<LowLevelDriveOperation, Error>;
559}
560
561impl LowLevelDriveOperationTreeTypeConverter for TreeType {
562 fn empty_tree_operation_for_known_path_key(
564 &self,
565 path: Vec<Vec<u8>>,
566 key: Vec<u8>,
567 storage_flags: Option<&StorageFlags>,
568 ) -> Result<LowLevelDriveOperation, Error> {
569 let element_flags = storage_flags.map(|storage_flags| storage_flags.to_element_flags());
570 let element = match self {
571 TreeType::NormalTree => Element::empty_tree_with_flags(element_flags),
572 TreeType::SumTree => Element::empty_sum_tree_with_flags(element_flags),
573 TreeType::BigSumTree => Element::empty_big_sum_tree_with_flags(element_flags),
574 TreeType::CountTree => Element::empty_count_tree_with_flags(element_flags),
575 TreeType::CountSumTree => Element::empty_count_sum_tree_with_flags(element_flags),
576 TreeType::ProvableCountTree => {
577 Element::empty_provable_count_tree_with_flags(element_flags)
578 }
579 TreeType::ProvableCountSumTree => {
580 Element::empty_provable_count_sum_tree_with_flags(element_flags)
581 }
582 TreeType::CommitmentTree(chunk_power) => {
583 Element::empty_commitment_tree_with_flags(*chunk_power, element_flags)?
584 }
585 TreeType::MmrTree => Element::empty_mmr_tree_with_flags(element_flags),
586 TreeType::BulkAppendTree(chunk_power) => {
587 Element::empty_bulk_append_tree_with_flags(*chunk_power, element_flags)?
588 }
589 TreeType::DenseAppendOnlyFixedSizeTree(chunk_power) => {
590 Element::empty_dense_tree_with_flags(*chunk_power, element_flags)
591 }
592 };
593
594 Ok(LowLevelDriveOperation::insert_for_known_path_key_element(
595 path, key, element,
596 ))
597 }
598}
599
600pub trait DriveCost {
602 fn ephemeral_cost(&self, fee_version: &FeeVersion) -> Result<u64, Error>;
604}
605
606impl DriveCost for OperationCost {
607 fn ephemeral_cost(&self, fee_version: &FeeVersion) -> Result<Credits, Error> {
609 let OperationCost {
610 seek_count,
611 storage_cost,
612 storage_loaded_bytes,
613 hash_node_calls,
614 sinsemilla_hash_calls,
615 } = self;
616 let epoch_cost_for_processing_credit_per_byte =
617 fee_version.storage.storage_processing_credit_per_byte;
618 let seek_cost = (*seek_count as u64)
619 .checked_mul(fee_version.storage.storage_seek_cost)
620 .ok_or_else(|| get_overflow_error("seek cost overflow"))?;
621 let storage_added_bytes_ephemeral_cost = (storage_cost.added_bytes as u64)
622 .checked_mul(epoch_cost_for_processing_credit_per_byte)
623 .ok_or_else(|| get_overflow_error("storage written bytes cost overflow"))?;
624 let storage_replaced_bytes_ephemeral_cost = (storage_cost.replaced_bytes as u64)
625 .checked_mul(epoch_cost_for_processing_credit_per_byte)
626 .ok_or_else(|| get_overflow_error("storage written bytes cost overflow"))?;
627 let storage_removed_bytes_ephemeral_cost =
628 (storage_cost.removed_bytes.total_removed_bytes() as u64)
629 .checked_mul(epoch_cost_for_processing_credit_per_byte)
630 .ok_or_else(|| get_overflow_error("storage written bytes cost overflow"))?;
631 let storage_loaded_bytes_cost = { *storage_loaded_bytes }
633 .checked_mul(fee_version.storage.storage_load_credit_per_byte)
634 .ok_or_else(|| get_overflow_error("storage loaded cost overflow"))?;
635
636 let blake3_total = fee_version.hashing.blake3_base + fee_version.hashing.blake3_per_block;
638 let hash_node_cost = blake3_total * (*hash_node_calls as u64);
640 let sinsemilla_cost = fee_version.hashing.sinsemilla_base * (*sinsemilla_hash_calls as u64);
641 seek_cost
642 .checked_add(storage_added_bytes_ephemeral_cost)
643 .and_then(|c| c.checked_add(storage_replaced_bytes_ephemeral_cost))
644 .and_then(|c| c.checked_add(storage_loaded_bytes_cost))
645 .and_then(|c| c.checked_add(storage_removed_bytes_ephemeral_cost))
646 .and_then(|c| c.checked_add(hash_node_cost))
647 .and_then(|c| c.checked_add(sinsemilla_cost))
648 .ok_or_else(|| get_overflow_error("ephemeral cost addition overflow"))
649 }
650}
651
652#[cfg(test)]
653#[allow(clippy::identity_op)]
654mod tests {
655 use super::*;
656 use grovedb_costs::storage_cost::removal::StorageRemovedBytes;
657 use grovedb_costs::storage_cost::StorageCost;
658 use platform_version::version::fee::storage::FeeStorageVersion;
659 use platform_version::version::fee::FeeVersion;
660
661 fn fee_version() -> &'static FeeVersion {
663 FeeVersion::first()
664 }
665
666 #[test]
671 fn base_op_stop_costs_zero() {
672 assert_eq!(BaseOp::Stop.cost(), 0);
673 }
674
675 #[test]
676 fn base_op_add_costs_12() {
677 assert_eq!(BaseOp::Add.cost(), 12);
678 }
679
680 #[test]
681 fn base_op_mul_costs_20() {
682 assert_eq!(BaseOp::Mul.cost(), 20);
683 }
684
685 #[test]
686 fn base_op_signextend_costs_20() {
687 assert_eq!(BaseOp::Signextend.cost(), 20);
688 }
689
690 #[test]
691 fn base_op_addmod_costs_32() {
692 assert_eq!(BaseOp::Addmod.cost(), 32);
693 }
694
695 #[test]
696 fn base_op_mulmod_costs_32() {
697 assert_eq!(BaseOp::Mulmod.cost(), 32);
698 }
699
700 #[test]
701 fn base_op_byte_costs_12() {
702 assert_eq!(BaseOp::Byte.cost(), 12);
703 }
704
705 #[test]
706 fn base_op_sub_costs_12() {
707 assert_eq!(BaseOp::Sub.cost(), 12);
708 }
709
710 #[test]
711 fn base_op_div_costs_20() {
712 assert_eq!(BaseOp::Div.cost(), 20);
713 }
714
715 #[test]
716 fn base_op_comparison_ops_all_cost_12() {
717 for op in [
718 BaseOp::Lt,
719 BaseOp::Gt,
720 BaseOp::Slt,
721 BaseOp::Sgt,
722 BaseOp::Eq,
723 BaseOp::Iszero,
724 ] {
725 assert_eq!(op.cost(), 12, "comparison op {:?} should cost 12", op);
726 }
727 }
728
729 #[test]
730 fn base_op_bitwise_ops_all_cost_12() {
731 for op in [BaseOp::And, BaseOp::Or, BaseOp::Xor, BaseOp::Not] {
732 assert_eq!(op.cost(), 12, "bitwise op {:?} should cost 12", op);
733 }
734 }
735
736 #[test]
741 fn hash_function_block_size_all_64() {
742 assert_eq!(HashFunction::Sha256.block_size(), 64);
744 assert_eq!(HashFunction::Sha256_2.block_size(), 64);
745 assert_eq!(HashFunction::Blake3.block_size(), 64);
746 assert_eq!(HashFunction::Sha256RipeMD160.block_size(), 64);
747 }
748
749 #[test]
750 fn hash_function_rounds() {
751 assert_eq!(HashFunction::Sha256.rounds(), 1);
752 assert_eq!(HashFunction::Sha256_2.rounds(), 2);
753 assert_eq!(HashFunction::Blake3.rounds(), 1);
754 assert_eq!(HashFunction::Sha256RipeMD160.rounds(), 1);
755 }
756
757 #[test]
758 fn hash_function_block_cost_sha256_variants_use_sha256_per_block() {
759 let fv = fee_version();
760 let expected = fv.hashing.sha256_per_block;
761 assert_eq!(HashFunction::Sha256.block_cost(fv), expected);
762 assert_eq!(HashFunction::Sha256_2.block_cost(fv), expected);
763 assert_eq!(HashFunction::Sha256RipeMD160.block_cost(fv), expected);
764 }
765
766 #[test]
767 fn hash_function_block_cost_blake3_uses_blake3_per_block() {
768 let fv = fee_version();
769 assert_eq!(
770 HashFunction::Blake3.block_cost(fv),
771 fv.hashing.blake3_per_block
772 );
773 }
774
775 #[test]
776 fn hash_function_base_cost_sha256() {
777 let fv = fee_version();
778 assert_eq!(
779 HashFunction::Sha256.base_cost(fv),
780 fv.hashing.single_sha256_base
781 );
782 }
783
784 #[test]
785 fn hash_function_base_cost_sha256_2_uses_single_sha256_base() {
786 let fv = fee_version();
787 assert_eq!(
789 HashFunction::Sha256_2.base_cost(fv),
790 fv.hashing.single_sha256_base
791 );
792 }
793
794 #[test]
795 fn hash_function_base_cost_blake3() {
796 let fv = fee_version();
797 assert_eq!(HashFunction::Blake3.base_cost(fv), fv.hashing.blake3_base);
798 }
799
800 #[test]
801 fn hash_function_base_cost_sha256_ripe_md160() {
802 let fv = fee_version();
803 assert_eq!(
804 HashFunction::Sha256RipeMD160.base_cost(fv),
805 fv.hashing.sha256_ripe_md160_base
806 );
807 }
808
809 #[test]
814 fn function_op_new_with_byte_count_small_sha256() {
815 let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 32);
817 assert_eq!(op.rounds, 1);
818 assert_eq!(op.hash, HashFunction::Sha256);
819 }
820
821 #[test]
822 fn function_op_new_with_byte_count_exact_block_boundary_sha256() {
823 let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 64);
825 assert_eq!(op.rounds, 2);
826 }
827
828 #[test]
829 fn function_op_new_with_byte_count_large_sha256() {
830 let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 200);
832 assert_eq!(op.rounds, 4);
833 }
834
835 #[test]
836 fn function_op_new_with_byte_count_sha256_2_has_extra_round() {
837 let op = FunctionOp::new_with_byte_count(HashFunction::Sha256_2, 32);
839 assert_eq!(op.rounds, 2);
840 }
841
842 #[test]
843 fn function_op_new_with_byte_count_sha256_2_large() {
844 let op = FunctionOp::new_with_byte_count(HashFunction::Sha256_2, 200);
846 assert_eq!(op.rounds, 5);
847 }
848
849 #[test]
850 fn function_op_new_with_byte_count_blake3_small() {
851 let op = FunctionOp::new_with_byte_count(HashFunction::Blake3, 10);
853 assert_eq!(op.rounds, 1);
854 assert_eq!(op.hash, HashFunction::Blake3);
855 }
856
857 #[test]
858 fn function_op_new_with_byte_count_blake3_large() {
859 let op = FunctionOp::new_with_byte_count(HashFunction::Blake3, 500);
861 assert_eq!(op.rounds, 8);
862 }
863
864 #[test]
865 fn function_op_new_with_byte_count_zero_bytes() {
866 let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 0);
868 assert_eq!(op.rounds, 1);
869 }
870
871 #[test]
872 fn function_op_new_with_byte_count_sha256_ripemd160() {
873 let op = FunctionOp::new_with_byte_count(HashFunction::Sha256RipeMD160, 20);
875 assert_eq!(op.rounds, 1);
876 assert_eq!(op.hash, HashFunction::Sha256RipeMD160);
877 }
878
879 #[test]
884 fn function_op_cost_sha256_one_round() {
885 let fv = fee_version();
886 let op = FunctionOp::new_with_round_count(HashFunction::Sha256, 1);
887 let expected = fv.hashing.single_sha256_base + 1 * fv.hashing.sha256_per_block;
889 assert_eq!(op.cost(fv), expected);
890 }
891
892 #[test]
893 fn function_op_cost_sha256_2_two_rounds() {
894 let fv = fee_version();
895 let op = FunctionOp::new_with_round_count(HashFunction::Sha256_2, 2);
896 let expected = fv.hashing.single_sha256_base + 2 * fv.hashing.sha256_per_block;
898 assert_eq!(op.cost(fv), expected);
899 }
900
901 #[test]
902 fn function_op_cost_blake3_one_round() {
903 let fv = fee_version();
904 let op = FunctionOp::new_with_round_count(HashFunction::Blake3, 1);
905 let expected = fv.hashing.blake3_base + 1 * fv.hashing.blake3_per_block;
907 assert_eq!(op.cost(fv), expected);
908 }
909
910 #[test]
911 fn function_op_cost_zero_rounds() {
912 let fv = fee_version();
913 let op = FunctionOp::new_with_round_count(HashFunction::Blake3, 0);
914 assert_eq!(op.cost(fv), fv.hashing.blake3_base);
916 }
917
918 #[test]
919 fn function_op_cost_from_byte_count_matches_manual_calc() {
920 let fv = fee_version();
921 let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 128);
923 assert_eq!(op.rounds, 3);
924 let expected = fv.hashing.single_sha256_base + 3 * fv.hashing.sha256_per_block;
925 assert_eq!(op.cost(fv), expected);
926 }
927
928 #[test]
929 fn function_op_cost_sha256_ripemd160() {
930 let fv = fee_version();
931 let op = FunctionOp::new_with_round_count(HashFunction::Sha256RipeMD160, 1);
932 let expected = fv.hashing.sha256_ripe_md160_base + 1 * fv.hashing.sha256_per_block;
933 assert_eq!(op.cost(fv), expected);
934 }
935
936 #[test]
937 fn function_op_cost_saturating_mul_does_not_panic_on_large_rounds() {
938 let fv = fee_version();
939 let op = FunctionOp::new_with_round_count(HashFunction::Sha256, u32::MAX);
940 let expected_block_cost = (u32::MAX as u64).saturating_mul(fv.hashing.sha256_per_block);
943 let expected = fv
944 .hashing
945 .single_sha256_base
946 .saturating_add(expected_block_cost);
947 assert_eq!(op.cost(fv), expected);
948 }
949
950 #[test]
951 fn function_op_cost_saturates_to_max_with_extreme_fee_version() {
952 let mut fv = fee_version().clone();
955 fv.hashing.sha256_per_block = u64::MAX;
956 let op = FunctionOp::new_with_round_count(HashFunction::Sha256, 2);
957 assert_eq!(op.cost(&fv), u64::MAX);
959 }
960
961 #[test]
966 fn operation_cost_calculated_cost_operation_returns_cost() {
967 let cost = OperationCost {
968 seek_count: 3,
969 storage_cost: StorageCost {
970 added_bytes: 100,
971 replaced_bytes: 50,
972 removed_bytes: StorageRemovedBytes::NoStorageRemoval,
973 },
974 storage_loaded_bytes: 200,
975 hash_node_calls: 5,
976 sinsemilla_hash_calls: 0,
977 };
978 let op = CalculatedCostOperation(cost.clone());
979 let result = op.operation_cost().expect("should return Ok");
980 assert_eq!(result, cost);
981 }
982
983 #[test]
984 fn operation_cost_grove_operation_returns_error() {
985 let grove_op = LowLevelDriveOperation::insert_for_known_path_key_element(
986 vec![vec![1, 2, 3]],
987 vec![4, 5, 6],
988 Element::empty_tree(),
989 );
990 let result = grove_op.operation_cost();
991 assert!(result.is_err());
992 let err_msg = format!("{:?}", result.unwrap_err());
993 assert!(
994 err_msg.contains("grove operations must be executed"),
995 "unexpected error: {}",
996 err_msg
997 );
998 }
999
1000 #[test]
1001 fn operation_cost_pre_calculated_fee_result_returns_error() {
1002 let fee = FeeResult {
1003 storage_fee: 100,
1004 processing_fee: 200,
1005 ..Default::default()
1006 };
1007 let op = PreCalculatedFeeResult(fee);
1008 let result = op.operation_cost();
1009 assert!(result.is_err());
1010 let err_msg = format!("{:?}", result.unwrap_err());
1011 assert!(
1012 err_msg.contains("pre calculated fees should not be requested"),
1013 "unexpected error: {}",
1014 err_msg
1015 );
1016 }
1017
1018 #[test]
1019 fn operation_cost_function_operation_returns_error() {
1020 let func_op = FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Blake3, 1));
1021 let result = func_op.operation_cost();
1022 assert!(result.is_err());
1023 let err_msg = format!("{:?}", result.unwrap_err());
1024 assert!(
1025 err_msg.contains("function operations should not be requested"),
1026 "unexpected error: {}",
1027 err_msg
1028 );
1029 }
1030
1031 #[test]
1036 fn combine_cost_operations_sums_calculated_costs_only() {
1037 let cost1 = OperationCost {
1038 seek_count: 2,
1039 storage_cost: StorageCost {
1040 added_bytes: 10,
1041 replaced_bytes: 0,
1042 removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1043 },
1044 storage_loaded_bytes: 50,
1045 hash_node_calls: 1,
1046 sinsemilla_hash_calls: 0,
1047 };
1048 let cost2 = OperationCost {
1049 seek_count: 3,
1050 storage_cost: StorageCost {
1051 added_bytes: 20,
1052 replaced_bytes: 5,
1053 removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1054 },
1055 storage_loaded_bytes: 100,
1056 hash_node_calls: 2,
1057 sinsemilla_hash_calls: 1,
1058 };
1059
1060 let operations = vec![
1061 CalculatedCostOperation(cost1.clone()),
1062 FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Sha256, 1)),
1064 CalculatedCostOperation(cost2.clone()),
1065 PreCalculatedFeeResult(FeeResult::default()),
1067 ];
1068
1069 let combined = LowLevelDriveOperation::combine_cost_operations(&operations);
1070 assert_eq!(combined.seek_count, 2 + 3);
1071 assert_eq!(combined.storage_cost.added_bytes, 10 + 20);
1072 assert_eq!(combined.storage_cost.replaced_bytes, 0 + 5);
1073 assert_eq!(combined.storage_loaded_bytes, 50 + 100);
1074 assert_eq!(combined.hash_node_calls, 1 + 2);
1075 assert_eq!(combined.sinsemilla_hash_calls, 0 + 1);
1076 }
1077
1078 #[test]
1079 fn combine_cost_operations_empty_list_returns_default() {
1080 let combined = LowLevelDriveOperation::combine_cost_operations(&[]);
1081 assert_eq!(combined, OperationCost::default());
1082 }
1083
1084 #[test]
1085 fn combine_cost_operations_no_calculated_costs_returns_default() {
1086 let operations = vec![
1087 FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Blake3, 2)),
1088 PreCalculatedFeeResult(FeeResult {
1089 processing_fee: 999,
1090 ..Default::default()
1091 }),
1092 ];
1093 let combined = LowLevelDriveOperation::combine_cost_operations(&operations);
1094 assert_eq!(combined, OperationCost::default());
1095 }
1096
1097 fn make_grove_op(key_byte: u8) -> LowLevelDriveOperation {
1103 LowLevelDriveOperation::insert_for_known_path_key_element(
1104 vec![vec![0]],
1105 vec![key_byte],
1106 Element::new_item(vec![key_byte]),
1107 )
1108 }
1109
1110 fn make_mixed_ops() -> Vec<LowLevelDriveOperation> {
1111 vec![
1112 make_grove_op(1),
1113 FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Sha256, 1)),
1114 make_grove_op(2),
1115 CalculatedCostOperation(OperationCost::default()),
1116 make_grove_op(3),
1117 ]
1118 }
1119
1120 #[test]
1121 fn grovedb_operations_batch_filters_grove_ops_from_ref() {
1122 let ops = make_mixed_ops();
1123 let batch = LowLevelDriveOperation::grovedb_operations_batch(&ops);
1124 assert_eq!(batch.len(), 3);
1125 }
1126
1127 #[test]
1128 fn grovedb_operations_batch_empty_input() {
1129 let batch = LowLevelDriveOperation::grovedb_operations_batch(&[]);
1130 assert!(batch.is_empty());
1131 }
1132
1133 #[test]
1134 fn grovedb_operations_batch_no_grove_ops() {
1135 let ops = vec![
1136 FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Blake3, 1)),
1137 CalculatedCostOperation(OperationCost::default()),
1138 ];
1139 let batch = LowLevelDriveOperation::grovedb_operations_batch(&ops);
1140 assert!(batch.is_empty());
1141 }
1142
1143 #[test]
1144 fn grovedb_operations_batch_consume_filters_grove_ops() {
1145 let ops = make_mixed_ops();
1146 let batch = LowLevelDriveOperation::grovedb_operations_batch_consume(ops);
1147 assert_eq!(batch.len(), 3);
1148 }
1149
1150 #[test]
1151 fn grovedb_operations_batch_consume_empty_input() {
1152 let batch = LowLevelDriveOperation::grovedb_operations_batch_consume(vec![]);
1153 assert!(batch.is_empty());
1154 }
1155
1156 #[test]
1157 fn grovedb_operations_batch_consume_with_leftovers_partitions_correctly() {
1158 let ops = make_mixed_ops();
1159 let (batch, leftovers) =
1160 LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(ops);
1161 assert_eq!(batch.len(), 3);
1162 assert_eq!(leftovers.len(), 2);
1163
1164 for leftover in &leftovers {
1166 assert!(
1167 !matches!(leftover, GroveOperation(_)),
1168 "leftovers should not contain GroveOperation variants"
1169 );
1170 }
1171 }
1172
1173 #[test]
1174 fn grovedb_operations_batch_consume_with_leftovers_all_grove() {
1175 let ops = vec![make_grove_op(10), make_grove_op(20)];
1176 let (batch, leftovers) =
1177 LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(ops);
1178 assert_eq!(batch.len(), 2);
1179 assert!(leftovers.is_empty());
1180 }
1181
1182 #[test]
1183 fn grovedb_operations_batch_consume_with_leftovers_no_grove() {
1184 let ops = vec![
1185 CalculatedCostOperation(OperationCost::default()),
1186 FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Sha256, 1)),
1187 ];
1188 let (batch, leftovers) =
1189 LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(ops);
1190 assert!(batch.is_empty());
1191 assert_eq!(leftovers.len(), 2);
1192 }
1193
1194 #[test]
1195 fn grovedb_operations_batch_consume_with_leftovers_empty() {
1196 let (batch, leftovers) =
1197 LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(vec![]);
1198 assert!(batch.is_empty());
1199 assert!(leftovers.is_empty());
1200 }
1201
1202 #[test]
1207 fn ephemeral_cost_zero_operation() {
1208 let fv = fee_version();
1209 let cost = OperationCost::default();
1210 let result = cost.ephemeral_cost(fv).expect("should not overflow");
1211 assert_eq!(result, 0);
1212 }
1213
1214 #[test]
1215 fn ephemeral_cost_seek_only() {
1216 let fv = fee_version();
1217 let cost = OperationCost {
1218 seek_count: 5,
1219 storage_cost: StorageCost::default(),
1220 storage_loaded_bytes: 0,
1221 hash_node_calls: 0,
1222 sinsemilla_hash_calls: 0,
1223 };
1224 let result = cost.ephemeral_cost(fv).expect("should not overflow");
1225 let expected = 5u64 * fv.storage.storage_seek_cost;
1226 assert_eq!(result, expected);
1227 }
1228
1229 #[test]
1230 fn ephemeral_cost_storage_added_bytes() {
1231 let fv = fee_version();
1232 let cost = OperationCost {
1233 seek_count: 0,
1234 storage_cost: StorageCost {
1235 added_bytes: 100,
1236 replaced_bytes: 0,
1237 removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1238 },
1239 storage_loaded_bytes: 0,
1240 hash_node_calls: 0,
1241 sinsemilla_hash_calls: 0,
1242 };
1243 let result = cost.ephemeral_cost(fv).expect("should not overflow");
1244 let expected = 100u64 * fv.storage.storage_processing_credit_per_byte;
1245 assert_eq!(result, expected);
1246 }
1247
1248 #[test]
1249 fn ephemeral_cost_storage_replaced_bytes() {
1250 let fv = fee_version();
1251 let cost = OperationCost {
1252 seek_count: 0,
1253 storage_cost: StorageCost {
1254 added_bytes: 0,
1255 replaced_bytes: 50,
1256 removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1257 },
1258 storage_loaded_bytes: 0,
1259 hash_node_calls: 0,
1260 sinsemilla_hash_calls: 0,
1261 };
1262 let result = cost.ephemeral_cost(fv).expect("should not overflow");
1263 let expected = 50u64 * fv.storage.storage_processing_credit_per_byte;
1264 assert_eq!(result, expected);
1265 }
1266
1267 #[test]
1268 fn ephemeral_cost_storage_removed_bytes_basic() {
1269 let fv = fee_version();
1270 let cost = OperationCost {
1271 seek_count: 0,
1272 storage_cost: StorageCost {
1273 added_bytes: 0,
1274 replaced_bytes: 0,
1275 removed_bytes: StorageRemovedBytes::BasicStorageRemoval(75),
1276 },
1277 storage_loaded_bytes: 0,
1278 hash_node_calls: 0,
1279 sinsemilla_hash_calls: 0,
1280 };
1281 let result = cost.ephemeral_cost(fv).expect("should not overflow");
1282 let expected = 75u64 * fv.storage.storage_processing_credit_per_byte;
1283 assert_eq!(result, expected);
1284 }
1285
1286 #[test]
1287 fn ephemeral_cost_loaded_bytes() {
1288 let fv = fee_version();
1289 let cost = OperationCost {
1290 seek_count: 0,
1291 storage_cost: StorageCost::default(),
1292 storage_loaded_bytes: 300,
1293 hash_node_calls: 0,
1294 sinsemilla_hash_calls: 0,
1295 };
1296 let result = cost.ephemeral_cost(fv).expect("should not overflow");
1297 let expected = 300u64 * fv.storage.storage_load_credit_per_byte;
1298 assert_eq!(result, expected);
1299 }
1300
1301 #[test]
1302 fn ephemeral_cost_hash_node_calls() {
1303 let fv = fee_version();
1304 let cost = OperationCost {
1305 seek_count: 0,
1306 storage_cost: StorageCost::default(),
1307 storage_loaded_bytes: 0,
1308 hash_node_calls: 10,
1309 sinsemilla_hash_calls: 0,
1310 };
1311 let result = cost.ephemeral_cost(fv).expect("should not overflow");
1312 let blake3_total = fv.hashing.blake3_base + fv.hashing.blake3_per_block;
1313 let expected = blake3_total * 10;
1314 assert_eq!(result, expected);
1315 }
1316
1317 #[test]
1318 fn ephemeral_cost_sinsemilla_hash_calls() {
1319 let fv = fee_version();
1320 let cost = OperationCost {
1321 seek_count: 0,
1322 storage_cost: StorageCost::default(),
1323 storage_loaded_bytes: 0,
1324 hash_node_calls: 0,
1325 sinsemilla_hash_calls: 3,
1326 };
1327 let result = cost.ephemeral_cost(fv).expect("should not overflow");
1328 let expected = fv.hashing.sinsemilla_base * 3;
1329 assert_eq!(result, expected);
1330 }
1331
1332 #[test]
1333 fn ephemeral_cost_all_components_combined() {
1334 let fv = fee_version();
1335 let cost = OperationCost {
1336 seek_count: 2,
1337 storage_cost: StorageCost {
1338 added_bytes: 10,
1339 replaced_bytes: 20,
1340 removed_bytes: StorageRemovedBytes::BasicStorageRemoval(30),
1341 },
1342 storage_loaded_bytes: 40,
1343 hash_node_calls: 5,
1344 sinsemilla_hash_calls: 1,
1345 };
1346 let result = cost.ephemeral_cost(fv).expect("should not overflow");
1347
1348 let seek_cost = 2u64 * fv.storage.storage_seek_cost;
1349 let processing_per_byte = fv.storage.storage_processing_credit_per_byte;
1350 let added_cost = 10u64 * processing_per_byte;
1351 let replaced_cost = 20u64 * processing_per_byte;
1352 let removed_cost = 30u64 * processing_per_byte;
1353 let loaded_cost = 40u64 * fv.storage.storage_load_credit_per_byte;
1354 let blake3_total = fv.hashing.blake3_base + fv.hashing.blake3_per_block;
1355 let hash_cost = blake3_total * 5;
1356 let sinsemilla_cost = fv.hashing.sinsemilla_base * 1;
1357
1358 let expected = seek_cost
1359 + added_cost
1360 + replaced_cost
1361 + loaded_cost
1362 + removed_cost
1363 + hash_cost
1364 + sinsemilla_cost;
1365 assert_eq!(result, expected);
1366 }
1367
1368 #[test]
1369 fn ephemeral_cost_overflow_seek_cost() {
1370 let fv = &FeeVersion {
1371 storage: FeeStorageVersion {
1372 storage_seek_cost: u64::MAX,
1373 ..fee_version().storage.clone()
1374 },
1375 ..fee_version().clone()
1376 };
1377 let cost = OperationCost {
1378 seek_count: 2, storage_cost: StorageCost::default(),
1380 storage_loaded_bytes: 0,
1381 hash_node_calls: 0,
1382 sinsemilla_hash_calls: 0,
1383 };
1384 let result = cost.ephemeral_cost(fv);
1385 assert!(result.is_err(), "expected overflow error for seek cost");
1386 }
1387
1388 #[test]
1389 fn ephemeral_cost_overflow_storage_written_bytes() {
1390 let fv = &FeeVersion {
1391 storage: FeeStorageVersion {
1392 storage_processing_credit_per_byte: u64::MAX,
1393 ..fee_version().storage.clone()
1394 },
1395 ..fee_version().clone()
1396 };
1397 let cost = OperationCost {
1398 seek_count: 0,
1399 storage_cost: StorageCost {
1400 added_bytes: 2, replaced_bytes: 0,
1402 removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1403 },
1404 storage_loaded_bytes: 0,
1405 hash_node_calls: 0,
1406 sinsemilla_hash_calls: 0,
1407 };
1408 let result = cost.ephemeral_cost(fv);
1409 assert!(
1410 result.is_err(),
1411 "expected overflow error for storage written bytes"
1412 );
1413 }
1414
1415 #[test]
1416 fn ephemeral_cost_overflow_loaded_bytes() {
1417 let fv = &FeeVersion {
1418 storage: FeeStorageVersion {
1419 storage_load_credit_per_byte: u64::MAX,
1420 ..fee_version().storage.clone()
1421 },
1422 ..fee_version().clone()
1423 };
1424 let cost = OperationCost {
1425 seek_count: 0,
1426 storage_cost: StorageCost::default(),
1427 storage_loaded_bytes: 2, hash_node_calls: 0,
1429 sinsemilla_hash_calls: 0,
1430 };
1431 let result = cost.ephemeral_cost(fv);
1432 assert!(
1433 result.is_err(),
1434 "expected overflow error for loaded bytes cost"
1435 );
1436 }
1437
1438 #[test]
1439 fn ephemeral_cost_overflow_in_addition_chain() {
1440 let fv = fee_version();
1442 let cost = OperationCost {
1443 seek_count: u32::MAX,
1444 storage_cost: StorageCost {
1445 added_bytes: u32::MAX,
1446 replaced_bytes: u32::MAX,
1447 removed_bytes: StorageRemovedBytes::BasicStorageRemoval(u32::MAX),
1448 },
1449 storage_loaded_bytes: u64::MAX,
1450 hash_node_calls: u32::MAX,
1451 sinsemilla_hash_calls: u32::MAX,
1452 };
1453 let result = cost.ephemeral_cost(fv);
1454 assert!(
1455 result.is_err(),
1456 "expected overflow error when summing large components"
1457 );
1458 }
1459}