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)]
653mod tests {
654 use super::*;
655 use grovedb_costs::storage_cost::removal::StorageRemovedBytes;
656 use grovedb_costs::storage_cost::StorageCost;
657 use platform_version::version::fee::storage::FeeStorageVersion;
658 use platform_version::version::fee::FeeVersion;
659
660 fn fee_version() -> &'static FeeVersion {
662 FeeVersion::first()
663 }
664
665 #[test]
670 fn base_op_stop_costs_zero() {
671 assert_eq!(BaseOp::Stop.cost(), 0);
672 }
673
674 #[test]
675 fn base_op_add_costs_12() {
676 assert_eq!(BaseOp::Add.cost(), 12);
677 }
678
679 #[test]
680 fn base_op_mul_costs_20() {
681 assert_eq!(BaseOp::Mul.cost(), 20);
682 }
683
684 #[test]
685 fn base_op_signextend_costs_20() {
686 assert_eq!(BaseOp::Signextend.cost(), 20);
687 }
688
689 #[test]
690 fn base_op_addmod_costs_32() {
691 assert_eq!(BaseOp::Addmod.cost(), 32);
692 }
693
694 #[test]
695 fn base_op_mulmod_costs_32() {
696 assert_eq!(BaseOp::Mulmod.cost(), 32);
697 }
698
699 #[test]
700 fn base_op_byte_costs_12() {
701 assert_eq!(BaseOp::Byte.cost(), 12);
702 }
703
704 #[test]
705 fn base_op_sub_costs_12() {
706 assert_eq!(BaseOp::Sub.cost(), 12);
707 }
708
709 #[test]
710 fn base_op_div_costs_20() {
711 assert_eq!(BaseOp::Div.cost(), 20);
712 }
713
714 #[test]
715 fn base_op_comparison_ops_all_cost_12() {
716 for op in [
717 BaseOp::Lt,
718 BaseOp::Gt,
719 BaseOp::Slt,
720 BaseOp::Sgt,
721 BaseOp::Eq,
722 BaseOp::Iszero,
723 ] {
724 assert_eq!(op.cost(), 12, "comparison op {:?} should cost 12", op);
725 }
726 }
727
728 #[test]
729 fn base_op_bitwise_ops_all_cost_12() {
730 for op in [BaseOp::And, BaseOp::Or, BaseOp::Xor, BaseOp::Not] {
731 assert_eq!(op.cost(), 12, "bitwise op {:?} should cost 12", op);
732 }
733 }
734
735 #[test]
740 fn hash_function_block_size_all_64() {
741 assert_eq!(HashFunction::Sha256.block_size(), 64);
743 assert_eq!(HashFunction::Sha256_2.block_size(), 64);
744 assert_eq!(HashFunction::Blake3.block_size(), 64);
745 assert_eq!(HashFunction::Sha256RipeMD160.block_size(), 64);
746 }
747
748 #[test]
749 fn hash_function_rounds() {
750 assert_eq!(HashFunction::Sha256.rounds(), 1);
751 assert_eq!(HashFunction::Sha256_2.rounds(), 2);
752 assert_eq!(HashFunction::Blake3.rounds(), 1);
753 assert_eq!(HashFunction::Sha256RipeMD160.rounds(), 1);
754 }
755
756 #[test]
757 fn hash_function_block_cost_sha256_variants_use_sha256_per_block() {
758 let fv = fee_version();
759 let expected = fv.hashing.sha256_per_block;
760 assert_eq!(HashFunction::Sha256.block_cost(fv), expected);
761 assert_eq!(HashFunction::Sha256_2.block_cost(fv), expected);
762 assert_eq!(HashFunction::Sha256RipeMD160.block_cost(fv), expected);
763 }
764
765 #[test]
766 fn hash_function_block_cost_blake3_uses_blake3_per_block() {
767 let fv = fee_version();
768 assert_eq!(
769 HashFunction::Blake3.block_cost(fv),
770 fv.hashing.blake3_per_block
771 );
772 }
773
774 #[test]
775 fn hash_function_base_cost_sha256() {
776 let fv = fee_version();
777 assert_eq!(
778 HashFunction::Sha256.base_cost(fv),
779 fv.hashing.single_sha256_base
780 );
781 }
782
783 #[test]
784 fn hash_function_base_cost_sha256_2_uses_single_sha256_base() {
785 let fv = fee_version();
786 assert_eq!(
788 HashFunction::Sha256_2.base_cost(fv),
789 fv.hashing.single_sha256_base
790 );
791 }
792
793 #[test]
794 fn hash_function_base_cost_blake3() {
795 let fv = fee_version();
796 assert_eq!(HashFunction::Blake3.base_cost(fv), fv.hashing.blake3_base);
797 }
798
799 #[test]
800 fn hash_function_base_cost_sha256_ripe_md160() {
801 let fv = fee_version();
802 assert_eq!(
803 HashFunction::Sha256RipeMD160.base_cost(fv),
804 fv.hashing.sha256_ripe_md160_base
805 );
806 }
807
808 #[test]
813 fn function_op_new_with_byte_count_small_sha256() {
814 let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 32);
816 assert_eq!(op.rounds, 1);
817 assert_eq!(op.hash, HashFunction::Sha256);
818 }
819
820 #[test]
821 fn function_op_new_with_byte_count_exact_block_boundary_sha256() {
822 let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 64);
824 assert_eq!(op.rounds, 2);
825 }
826
827 #[test]
828 fn function_op_new_with_byte_count_large_sha256() {
829 let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 200);
831 assert_eq!(op.rounds, 4);
832 }
833
834 #[test]
835 fn function_op_new_with_byte_count_sha256_2_has_extra_round() {
836 let op = FunctionOp::new_with_byte_count(HashFunction::Sha256_2, 32);
838 assert_eq!(op.rounds, 2);
839 }
840
841 #[test]
842 fn function_op_new_with_byte_count_sha256_2_large() {
843 let op = FunctionOp::new_with_byte_count(HashFunction::Sha256_2, 200);
845 assert_eq!(op.rounds, 5);
846 }
847
848 #[test]
849 fn function_op_new_with_byte_count_blake3_small() {
850 let op = FunctionOp::new_with_byte_count(HashFunction::Blake3, 10);
852 assert_eq!(op.rounds, 1);
853 assert_eq!(op.hash, HashFunction::Blake3);
854 }
855
856 #[test]
857 fn function_op_new_with_byte_count_blake3_large() {
858 let op = FunctionOp::new_with_byte_count(HashFunction::Blake3, 500);
860 assert_eq!(op.rounds, 8);
861 }
862
863 #[test]
864 fn function_op_new_with_byte_count_zero_bytes() {
865 let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 0);
867 assert_eq!(op.rounds, 1);
868 }
869
870 #[test]
871 fn function_op_new_with_byte_count_sha256_ripemd160() {
872 let op = FunctionOp::new_with_byte_count(HashFunction::Sha256RipeMD160, 20);
874 assert_eq!(op.rounds, 1);
875 assert_eq!(op.hash, HashFunction::Sha256RipeMD160);
876 }
877
878 #[test]
883 fn function_op_cost_sha256_one_round() {
884 let fv = fee_version();
885 let op = FunctionOp::new_with_round_count(HashFunction::Sha256, 1);
886 let expected = fv.hashing.single_sha256_base + 1 * fv.hashing.sha256_per_block;
888 assert_eq!(op.cost(fv), expected);
889 }
890
891 #[test]
892 fn function_op_cost_sha256_2_two_rounds() {
893 let fv = fee_version();
894 let op = FunctionOp::new_with_round_count(HashFunction::Sha256_2, 2);
895 let expected = fv.hashing.single_sha256_base + 2 * fv.hashing.sha256_per_block;
897 assert_eq!(op.cost(fv), expected);
898 }
899
900 #[test]
901 fn function_op_cost_blake3_one_round() {
902 let fv = fee_version();
903 let op = FunctionOp::new_with_round_count(HashFunction::Blake3, 1);
904 let expected = fv.hashing.blake3_base + 1 * fv.hashing.blake3_per_block;
906 assert_eq!(op.cost(fv), expected);
907 }
908
909 #[test]
910 fn function_op_cost_zero_rounds() {
911 let fv = fee_version();
912 let op = FunctionOp::new_with_round_count(HashFunction::Blake3, 0);
913 assert_eq!(op.cost(fv), fv.hashing.blake3_base);
915 }
916
917 #[test]
918 fn function_op_cost_from_byte_count_matches_manual_calc() {
919 let fv = fee_version();
920 let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 128);
922 assert_eq!(op.rounds, 3);
923 let expected = fv.hashing.single_sha256_base + 3 * fv.hashing.sha256_per_block;
924 assert_eq!(op.cost(fv), expected);
925 }
926
927 #[test]
928 fn function_op_cost_sha256_ripemd160() {
929 let fv = fee_version();
930 let op = FunctionOp::new_with_round_count(HashFunction::Sha256RipeMD160, 1);
931 let expected = fv.hashing.sha256_ripe_md160_base + 1 * fv.hashing.sha256_per_block;
932 assert_eq!(op.cost(fv), expected);
933 }
934
935 #[test]
936 fn function_op_cost_saturating_mul_does_not_panic_on_large_rounds() {
937 let fv = fee_version();
938 let op = FunctionOp::new_with_round_count(HashFunction::Sha256, u32::MAX);
939 let expected_block_cost = (u32::MAX as u64).saturating_mul(fv.hashing.sha256_per_block);
942 let expected = fv
943 .hashing
944 .single_sha256_base
945 .saturating_add(expected_block_cost);
946 assert_eq!(op.cost(fv), expected);
947 }
948
949 #[test]
950 fn function_op_cost_saturates_to_max_with_extreme_fee_version() {
951 let mut fv = fee_version().clone();
954 fv.hashing.sha256_per_block = u64::MAX;
955 let op = FunctionOp::new_with_round_count(HashFunction::Sha256, 2);
956 assert_eq!(op.cost(&fv), u64::MAX);
958 }
959
960 #[test]
965 fn operation_cost_calculated_cost_operation_returns_cost() {
966 let cost = OperationCost {
967 seek_count: 3,
968 storage_cost: StorageCost {
969 added_bytes: 100,
970 replaced_bytes: 50,
971 removed_bytes: StorageRemovedBytes::NoStorageRemoval,
972 },
973 storage_loaded_bytes: 200,
974 hash_node_calls: 5,
975 sinsemilla_hash_calls: 0,
976 };
977 let op = CalculatedCostOperation(cost.clone());
978 let result = op.operation_cost().expect("should return Ok");
979 assert_eq!(result, cost);
980 }
981
982 #[test]
983 fn operation_cost_grove_operation_returns_error() {
984 let grove_op = LowLevelDriveOperation::insert_for_known_path_key_element(
985 vec![vec![1, 2, 3]],
986 vec![4, 5, 6],
987 Element::empty_tree(),
988 );
989 let result = grove_op.operation_cost();
990 assert!(result.is_err());
991 let err_msg = format!("{:?}", result.unwrap_err());
992 assert!(
993 err_msg.contains("grove operations must be executed"),
994 "unexpected error: {}",
995 err_msg
996 );
997 }
998
999 #[test]
1000 fn operation_cost_pre_calculated_fee_result_returns_error() {
1001 let fee = FeeResult {
1002 storage_fee: 100,
1003 processing_fee: 200,
1004 ..Default::default()
1005 };
1006 let op = PreCalculatedFeeResult(fee);
1007 let result = op.operation_cost();
1008 assert!(result.is_err());
1009 let err_msg = format!("{:?}", result.unwrap_err());
1010 assert!(
1011 err_msg.contains("pre calculated fees should not be requested"),
1012 "unexpected error: {}",
1013 err_msg
1014 );
1015 }
1016
1017 #[test]
1018 fn operation_cost_function_operation_returns_error() {
1019 let func_op = FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Blake3, 1));
1020 let result = func_op.operation_cost();
1021 assert!(result.is_err());
1022 let err_msg = format!("{:?}", result.unwrap_err());
1023 assert!(
1024 err_msg.contains("function operations should not be requested"),
1025 "unexpected error: {}",
1026 err_msg
1027 );
1028 }
1029
1030 #[test]
1035 fn combine_cost_operations_sums_calculated_costs_only() {
1036 let cost1 = OperationCost {
1037 seek_count: 2,
1038 storage_cost: StorageCost {
1039 added_bytes: 10,
1040 replaced_bytes: 0,
1041 removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1042 },
1043 storage_loaded_bytes: 50,
1044 hash_node_calls: 1,
1045 sinsemilla_hash_calls: 0,
1046 };
1047 let cost2 = OperationCost {
1048 seek_count: 3,
1049 storage_cost: StorageCost {
1050 added_bytes: 20,
1051 replaced_bytes: 5,
1052 removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1053 },
1054 storage_loaded_bytes: 100,
1055 hash_node_calls: 2,
1056 sinsemilla_hash_calls: 1,
1057 };
1058
1059 let operations = vec![
1060 CalculatedCostOperation(cost1.clone()),
1061 FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Sha256, 1)),
1063 CalculatedCostOperation(cost2.clone()),
1064 PreCalculatedFeeResult(FeeResult::default()),
1066 ];
1067
1068 let combined = LowLevelDriveOperation::combine_cost_operations(&operations);
1069 assert_eq!(combined.seek_count, 2 + 3);
1070 assert_eq!(combined.storage_cost.added_bytes, 10 + 20);
1071 assert_eq!(combined.storage_cost.replaced_bytes, 0 + 5);
1072 assert_eq!(combined.storage_loaded_bytes, 50 + 100);
1073 assert_eq!(combined.hash_node_calls, 1 + 2);
1074 assert_eq!(combined.sinsemilla_hash_calls, 0 + 1);
1075 }
1076
1077 #[test]
1078 fn combine_cost_operations_empty_list_returns_default() {
1079 let combined = LowLevelDriveOperation::combine_cost_operations(&[]);
1080 assert_eq!(combined, OperationCost::default());
1081 }
1082
1083 #[test]
1084 fn combine_cost_operations_no_calculated_costs_returns_default() {
1085 let operations = vec![
1086 FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Blake3, 2)),
1087 PreCalculatedFeeResult(FeeResult {
1088 processing_fee: 999,
1089 ..Default::default()
1090 }),
1091 ];
1092 let combined = LowLevelDriveOperation::combine_cost_operations(&operations);
1093 assert_eq!(combined, OperationCost::default());
1094 }
1095
1096 fn make_grove_op(key_byte: u8) -> LowLevelDriveOperation {
1102 LowLevelDriveOperation::insert_for_known_path_key_element(
1103 vec![vec![0]],
1104 vec![key_byte],
1105 Element::new_item(vec![key_byte]),
1106 )
1107 }
1108
1109 fn make_mixed_ops() -> Vec<LowLevelDriveOperation> {
1110 vec![
1111 make_grove_op(1),
1112 FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Sha256, 1)),
1113 make_grove_op(2),
1114 CalculatedCostOperation(OperationCost::default()),
1115 make_grove_op(3),
1116 ]
1117 }
1118
1119 #[test]
1120 fn grovedb_operations_batch_filters_grove_ops_from_ref() {
1121 let ops = make_mixed_ops();
1122 let batch = LowLevelDriveOperation::grovedb_operations_batch(&ops);
1123 assert_eq!(batch.len(), 3);
1124 }
1125
1126 #[test]
1127 fn grovedb_operations_batch_empty_input() {
1128 let batch = LowLevelDriveOperation::grovedb_operations_batch(&[]);
1129 assert!(batch.is_empty());
1130 }
1131
1132 #[test]
1133 fn grovedb_operations_batch_no_grove_ops() {
1134 let ops = vec![
1135 FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Blake3, 1)),
1136 CalculatedCostOperation(OperationCost::default()),
1137 ];
1138 let batch = LowLevelDriveOperation::grovedb_operations_batch(&ops);
1139 assert!(batch.is_empty());
1140 }
1141
1142 #[test]
1143 fn grovedb_operations_batch_consume_filters_grove_ops() {
1144 let ops = make_mixed_ops();
1145 let batch = LowLevelDriveOperation::grovedb_operations_batch_consume(ops);
1146 assert_eq!(batch.len(), 3);
1147 }
1148
1149 #[test]
1150 fn grovedb_operations_batch_consume_empty_input() {
1151 let batch = LowLevelDriveOperation::grovedb_operations_batch_consume(vec![]);
1152 assert!(batch.is_empty());
1153 }
1154
1155 #[test]
1156 fn grovedb_operations_batch_consume_with_leftovers_partitions_correctly() {
1157 let ops = make_mixed_ops();
1158 let (batch, leftovers) =
1159 LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(ops);
1160 assert_eq!(batch.len(), 3);
1161 assert_eq!(leftovers.len(), 2);
1162
1163 for leftover in &leftovers {
1165 assert!(
1166 !matches!(leftover, GroveOperation(_)),
1167 "leftovers should not contain GroveOperation variants"
1168 );
1169 }
1170 }
1171
1172 #[test]
1173 fn grovedb_operations_batch_consume_with_leftovers_all_grove() {
1174 let ops = vec![make_grove_op(10), make_grove_op(20)];
1175 let (batch, leftovers) =
1176 LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(ops);
1177 assert_eq!(batch.len(), 2);
1178 assert!(leftovers.is_empty());
1179 }
1180
1181 #[test]
1182 fn grovedb_operations_batch_consume_with_leftovers_no_grove() {
1183 let ops = vec![
1184 CalculatedCostOperation(OperationCost::default()),
1185 FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Sha256, 1)),
1186 ];
1187 let (batch, leftovers) =
1188 LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(ops);
1189 assert!(batch.is_empty());
1190 assert_eq!(leftovers.len(), 2);
1191 }
1192
1193 #[test]
1194 fn grovedb_operations_batch_consume_with_leftovers_empty() {
1195 let (batch, leftovers) =
1196 LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(vec![]);
1197 assert!(batch.is_empty());
1198 assert!(leftovers.is_empty());
1199 }
1200
1201 #[test]
1206 fn ephemeral_cost_zero_operation() {
1207 let fv = fee_version();
1208 let cost = OperationCost::default();
1209 let result = cost.ephemeral_cost(fv).expect("should not overflow");
1210 assert_eq!(result, 0);
1211 }
1212
1213 #[test]
1214 fn ephemeral_cost_seek_only() {
1215 let fv = fee_version();
1216 let cost = OperationCost {
1217 seek_count: 5,
1218 storage_cost: StorageCost::default(),
1219 storage_loaded_bytes: 0,
1220 hash_node_calls: 0,
1221 sinsemilla_hash_calls: 0,
1222 };
1223 let result = cost.ephemeral_cost(fv).expect("should not overflow");
1224 let expected = 5u64 * fv.storage.storage_seek_cost;
1225 assert_eq!(result, expected);
1226 }
1227
1228 #[test]
1229 fn ephemeral_cost_storage_added_bytes() {
1230 let fv = fee_version();
1231 let cost = OperationCost {
1232 seek_count: 0,
1233 storage_cost: StorageCost {
1234 added_bytes: 100,
1235 replaced_bytes: 0,
1236 removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1237 },
1238 storage_loaded_bytes: 0,
1239 hash_node_calls: 0,
1240 sinsemilla_hash_calls: 0,
1241 };
1242 let result = cost.ephemeral_cost(fv).expect("should not overflow");
1243 let expected = 100u64 * fv.storage.storage_processing_credit_per_byte;
1244 assert_eq!(result, expected);
1245 }
1246
1247 #[test]
1248 fn ephemeral_cost_storage_replaced_bytes() {
1249 let fv = fee_version();
1250 let cost = OperationCost {
1251 seek_count: 0,
1252 storage_cost: StorageCost {
1253 added_bytes: 0,
1254 replaced_bytes: 50,
1255 removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1256 },
1257 storage_loaded_bytes: 0,
1258 hash_node_calls: 0,
1259 sinsemilla_hash_calls: 0,
1260 };
1261 let result = cost.ephemeral_cost(fv).expect("should not overflow");
1262 let expected = 50u64 * fv.storage.storage_processing_credit_per_byte;
1263 assert_eq!(result, expected);
1264 }
1265
1266 #[test]
1267 fn ephemeral_cost_storage_removed_bytes_basic() {
1268 let fv = fee_version();
1269 let cost = OperationCost {
1270 seek_count: 0,
1271 storage_cost: StorageCost {
1272 added_bytes: 0,
1273 replaced_bytes: 0,
1274 removed_bytes: StorageRemovedBytes::BasicStorageRemoval(75),
1275 },
1276 storage_loaded_bytes: 0,
1277 hash_node_calls: 0,
1278 sinsemilla_hash_calls: 0,
1279 };
1280 let result = cost.ephemeral_cost(fv).expect("should not overflow");
1281 let expected = 75u64 * fv.storage.storage_processing_credit_per_byte;
1282 assert_eq!(result, expected);
1283 }
1284
1285 #[test]
1286 fn ephemeral_cost_loaded_bytes() {
1287 let fv = fee_version();
1288 let cost = OperationCost {
1289 seek_count: 0,
1290 storage_cost: StorageCost::default(),
1291 storage_loaded_bytes: 300,
1292 hash_node_calls: 0,
1293 sinsemilla_hash_calls: 0,
1294 };
1295 let result = cost.ephemeral_cost(fv).expect("should not overflow");
1296 let expected = 300u64 * fv.storage.storage_load_credit_per_byte;
1297 assert_eq!(result, expected);
1298 }
1299
1300 #[test]
1301 fn ephemeral_cost_hash_node_calls() {
1302 let fv = fee_version();
1303 let cost = OperationCost {
1304 seek_count: 0,
1305 storage_cost: StorageCost::default(),
1306 storage_loaded_bytes: 0,
1307 hash_node_calls: 10,
1308 sinsemilla_hash_calls: 0,
1309 };
1310 let result = cost.ephemeral_cost(fv).expect("should not overflow");
1311 let blake3_total = fv.hashing.blake3_base + fv.hashing.blake3_per_block;
1312 let expected = blake3_total * 10;
1313 assert_eq!(result, expected);
1314 }
1315
1316 #[test]
1317 fn ephemeral_cost_sinsemilla_hash_calls() {
1318 let fv = fee_version();
1319 let cost = OperationCost {
1320 seek_count: 0,
1321 storage_cost: StorageCost::default(),
1322 storage_loaded_bytes: 0,
1323 hash_node_calls: 0,
1324 sinsemilla_hash_calls: 3,
1325 };
1326 let result = cost.ephemeral_cost(fv).expect("should not overflow");
1327 let expected = fv.hashing.sinsemilla_base * 3;
1328 assert_eq!(result, expected);
1329 }
1330
1331 #[test]
1332 fn ephemeral_cost_all_components_combined() {
1333 let fv = fee_version();
1334 let cost = OperationCost {
1335 seek_count: 2,
1336 storage_cost: StorageCost {
1337 added_bytes: 10,
1338 replaced_bytes: 20,
1339 removed_bytes: StorageRemovedBytes::BasicStorageRemoval(30),
1340 },
1341 storage_loaded_bytes: 40,
1342 hash_node_calls: 5,
1343 sinsemilla_hash_calls: 1,
1344 };
1345 let result = cost.ephemeral_cost(fv).expect("should not overflow");
1346
1347 let seek_cost = 2u64 * fv.storage.storage_seek_cost;
1348 let processing_per_byte = fv.storage.storage_processing_credit_per_byte;
1349 let added_cost = 10u64 * processing_per_byte;
1350 let replaced_cost = 20u64 * processing_per_byte;
1351 let removed_cost = 30u64 * processing_per_byte;
1352 let loaded_cost = 40u64 * fv.storage.storage_load_credit_per_byte;
1353 let blake3_total = fv.hashing.blake3_base + fv.hashing.blake3_per_block;
1354 let hash_cost = blake3_total * 5;
1355 let sinsemilla_cost = fv.hashing.sinsemilla_base * 1;
1356
1357 let expected = seek_cost
1358 + added_cost
1359 + replaced_cost
1360 + loaded_cost
1361 + removed_cost
1362 + hash_cost
1363 + sinsemilla_cost;
1364 assert_eq!(result, expected);
1365 }
1366
1367 #[test]
1368 fn ephemeral_cost_overflow_seek_cost() {
1369 let fv = &FeeVersion {
1370 storage: FeeStorageVersion {
1371 storage_seek_cost: u64::MAX,
1372 ..fee_version().storage.clone()
1373 },
1374 ..fee_version().clone()
1375 };
1376 let cost = OperationCost {
1377 seek_count: 2, storage_cost: StorageCost::default(),
1379 storage_loaded_bytes: 0,
1380 hash_node_calls: 0,
1381 sinsemilla_hash_calls: 0,
1382 };
1383 let result = cost.ephemeral_cost(fv);
1384 assert!(result.is_err(), "expected overflow error for seek cost");
1385 }
1386
1387 #[test]
1388 fn ephemeral_cost_overflow_storage_written_bytes() {
1389 let fv = &FeeVersion {
1390 storage: FeeStorageVersion {
1391 storage_processing_credit_per_byte: u64::MAX,
1392 ..fee_version().storage.clone()
1393 },
1394 ..fee_version().clone()
1395 };
1396 let cost = OperationCost {
1397 seek_count: 0,
1398 storage_cost: StorageCost {
1399 added_bytes: 2, replaced_bytes: 0,
1401 removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1402 },
1403 storage_loaded_bytes: 0,
1404 hash_node_calls: 0,
1405 sinsemilla_hash_calls: 0,
1406 };
1407 let result = cost.ephemeral_cost(fv);
1408 assert!(
1409 result.is_err(),
1410 "expected overflow error for storage written bytes"
1411 );
1412 }
1413
1414 #[test]
1415 fn ephemeral_cost_overflow_loaded_bytes() {
1416 let fv = &FeeVersion {
1417 storage: FeeStorageVersion {
1418 storage_load_credit_per_byte: u64::MAX,
1419 ..fee_version().storage.clone()
1420 },
1421 ..fee_version().clone()
1422 };
1423 let cost = OperationCost {
1424 seek_count: 0,
1425 storage_cost: StorageCost::default(),
1426 storage_loaded_bytes: 2, hash_node_calls: 0,
1428 sinsemilla_hash_calls: 0,
1429 };
1430 let result = cost.ephemeral_cost(fv);
1431 assert!(
1432 result.is_err(),
1433 "expected overflow error for loaded bytes cost"
1434 );
1435 }
1436
1437 #[test]
1438 fn ephemeral_cost_overflow_in_addition_chain() {
1439 let fv = fee_version();
1441 let cost = OperationCost {
1442 seek_count: u32::MAX,
1443 storage_cost: StorageCost {
1444 added_bytes: u32::MAX,
1445 replaced_bytes: u32::MAX,
1446 removed_bytes: StorageRemovedBytes::BasicStorageRemoval(u32::MAX),
1447 },
1448 storage_loaded_bytes: u64::MAX,
1449 hash_node_calls: u32::MAX,
1450 sinsemilla_hash_calls: u32::MAX,
1451 };
1452 let result = cost.ephemeral_cost(fv);
1453 assert!(
1454 result.is_err(),
1455 "expected overflow error when summing large components"
1456 );
1457 }
1458}