Skip to main content

drive/fees/
op.rs

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/// Base ops
33#[derive(Debug, Enum)]
34pub enum BaseOp {
35    /// Stop
36    Stop,
37    /// Add
38    Add,
39    /// Multiply
40    Mul,
41    /// Subtract
42    Sub,
43    /// Divide
44    Div,
45    /// Sdiv
46    Sdiv,
47    /// Modulo
48    Mod,
49    /// Smod
50    Smod,
51    /// Addmod
52    Addmod,
53    /// Mulmod
54    Mulmod,
55    /// Signextend
56    Signextend,
57    /// Less than
58    Lt,
59    /// Greater than
60    Gt,
61    /// Slt
62    Slt,
63    /// Sgt
64    Sgt,
65    /// Equals
66    Eq,
67    /// Is zero
68    Iszero,
69    /// And
70    And,
71    /// Or
72    Or,
73    /// Xor
74    Xor,
75    /// Not
76    Not,
77    /// Byte
78    Byte,
79}
80
81impl BaseOp {
82    /// Match the op and get the cost
83    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/// Supported Hash Functions
112#[derive(Debug, Enum, PartialEq, Eq)]
113pub enum HashFunction {
114    /// Used for crypto addresses
115    Sha256RipeMD160,
116    /// Single Sha256
117    Sha256,
118    /// Double Sha256
119    Sha256_2,
120    /// Single Blake3
121    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            // It's normal that the base cost for a sha256 will have a single sha256 base
156            // But it has an extra block
157            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/// A Hash Function Operation
165#[derive(Debug, PartialEq, Eq)]
166pub struct FunctionOp {
167    /// hash
168    pub(crate) hash: HashFunction,
169    /// rounds
170    pub(crate) rounds: u32,
171}
172
173impl FunctionOp {
174    /// The cost of the function
175    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    /// Create a new function operation with the following hash knowing the rounds it will take
181    /// in advance
182    pub fn new_with_round_count(hash: HashFunction, rounds: u32) -> Self {
183        FunctionOp { hash, rounds }
184    }
185
186    /// Create a new function operation with the following hash knowing the number of bytes
187    /// it will hash
188    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/// Drive operation
199#[derive(Debug, Eq, PartialEq)]
200pub enum LowLevelDriveOperation {
201    /// Grove operation
202    GroveOperation(QualifiedGroveDbOp),
203    /// A drive operation
204    FunctionOperation(FunctionOp),
205    /// Calculated cost operation
206    CalculatedCostOperation(OperationCost),
207    /// Pre Calculated Fee Result
208    PreCalculatedFeeResult(FeeResult),
209}
210
211impl LowLevelDriveOperation {
212    /// Returns a list of the costs of the Drive operations.
213    /// Should only be used by Calculate fee
214    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                    // There is no need for a checked multiply here because added bytes are u64 and
232                    // storage disk usage credit per byte should never be high enough to cause an overflow
233                    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                                // this is not always considered an error
240                                (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    /// Returns the cost of this operation
283    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    /// Filters the groveDB ops from a list of operations and puts them in a `GroveDbOpBatch`.
299    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    /// Filters the groveDB ops from a list of operations and puts them in a `GroveDbOpBatch`.
310    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    /// Filters the groveDB ops from a list of operations and puts them in a `GroveDbOpBatch`.
324    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    /// Filters the groveDB ops from a list of operations and puts them in a `GroveDbOpBatch`.
338    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    /// Filters the groveDB ops from a list of operations and collects them in a `Vec<QualifiedGroveDbOp>`.
354    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    /// Sets `GroveOperation` for inserting an empty tree at the given path and key
367    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    /// Sets `GroveOperation` for inserting an empty sum tree at the given path and key
383    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    /// Sets `GroveOperation` for inserting an empty sum tree at the given path and key
399    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    /// Sets `GroveOperation` for inserting an empty count tree at the given path and key
415    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    /// Sets `GroveOperation` for inserting an empty count tree at the given path and key
431    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    /// Sets `GroveOperation` for inserting an empty `NormalTree` wrapped in
447    /// `Element::NonCounted` at the given path and key. The wrapper makes
448    /// the inserted subtree contribute 0 to a parent count tree's aggregate
449    /// (per grovedb #654). Used by the index-walker for sibling continuations
450    /// inside a `range_countable` value tree, so e.g. a compound `byColorShape`
451    /// continuation under a `byColor` value tree (which is a `CountTree`)
452    /// doesn't pollute the byColor count.
453    pub fn for_known_path_key_empty_non_counted_normal_tree(
454        path: Vec<Vec<u8>>,
455        key: Vec<u8>,
456        storage_flags: Option<&StorageFlags>,
457    ) -> Self {
458        Self::for_known_path_key_empty_non_counted_tree(
459            path,
460            key,
461            TreeType::NormalTree,
462            storage_flags,
463        )
464        .expect("NormalTree NonCounted wrapping never fails")
465    }
466
467    /// Sets `GroveOperation` for inserting an empty tree of the given
468    /// `tree_type` wrapped in `Element::NonCounted`. The wrapper makes the
469    /// inserted subtree contribute 0 to a parent count tree's aggregate
470    /// count (per grovedb #654), regardless of the inner tree variant.
471    ///
472    /// Used by the index walker for sibling continuations inside a
473    /// `range_countable` value tree (a `CountTree`). Most continuations are
474    /// plain `NormalTree`, but in nested-`range_countable` cases (e.g. an
475    /// index `[color]` is range-countable AND a deeper compound index
476    /// `[color, size]` is also range-countable), the continuation
477    /// property-name tree at `"size"` is itself a `ProvableCountTree` and
478    /// must still contribute 0 to the parent `<c1>` `CountTree`.
479    ///
480    /// Returns an error for tree variants whose `NonCounted` wrapping
481    /// hasn't been validated end-to-end yet (currently anything outside
482    /// `NormalTree` / `CountTree` / `ProvableCountTree`).
483    pub fn for_known_path_key_empty_non_counted_tree(
484        path: Vec<Vec<u8>>,
485        key: Vec<u8>,
486        tree_type: TreeType,
487        storage_flags: Option<&StorageFlags>,
488    ) -> Result<Self, Error> {
489        let element_flags = storage_flags.map(|s| s.to_element_flags());
490        let inner = match tree_type {
491            TreeType::NormalTree => Element::empty_tree_with_flags(element_flags),
492            TreeType::CountTree => Element::empty_count_tree_with_flags(element_flags),
493            TreeType::ProvableCountTree => {
494                Element::empty_provable_count_tree_with_flags(element_flags)
495            }
496            _ => {
497                return Err(Error::Drive(DriveError::NotSupported(
498                    "NonCounted-wrapping is only supported for NormalTree, CountTree, and ProvableCountTree",
499                )));
500            }
501        };
502        let tree = Element::new_non_counted(inner)
503            .expect("new_non_counted only fails when wrapping another NonCounted");
504        Ok(LowLevelDriveOperation::insert_for_known_path_key_element(
505            path, key, tree,
506        ))
507    }
508
509    /// Sets `GroveOperation` for inserting an empty provable count tree at the given path and key
510    pub fn for_known_path_key_empty_provable_count_tree(
511        path: Vec<Vec<u8>>,
512        key: Vec<u8>,
513        storage_flags: Option<&StorageFlags>,
514    ) -> Self {
515        let tree = match storage_flags {
516            Some(storage_flags) => Element::new_provable_count_tree_with_flags(
517                None,
518                storage_flags.to_some_element_flags(),
519            ),
520            None => Element::empty_provable_count_tree(),
521        };
522
523        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
524    }
525
526    /// Sets `GroveOperation` for inserting an empty tree at the given path and key
527    pub fn for_estimated_path_key_empty_tree(
528        path: KeyInfoPath,
529        key: KeyInfo,
530        storage_flags: Option<&StorageFlags>,
531    ) -> Self {
532        let tree = match storage_flags {
533            Some(storage_flags) => {
534                Element::empty_tree_with_flags(storage_flags.to_some_element_flags())
535            }
536            None => Element::empty_tree(),
537        };
538
539        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
540    }
541
542    /// Sets `GroveOperation` for inserting an empty sum tree at the given path and key
543    pub fn for_estimated_path_key_empty_sum_tree(
544        path: KeyInfoPath,
545        key: KeyInfo,
546        storage_flags: Option<&StorageFlags>,
547    ) -> Self {
548        let tree = match storage_flags {
549            Some(storage_flags) => {
550                Element::empty_sum_tree_with_flags(storage_flags.to_some_element_flags())
551            }
552            None => Element::empty_sum_tree(),
553        };
554
555        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
556    }
557
558    /// Sets `GroveOperation` for inserting an empty count tree at the given (estimated) path and key
559    pub fn for_estimated_path_key_empty_count_tree(
560        path: KeyInfoPath,
561        key: KeyInfo,
562        storage_flags: Option<&StorageFlags>,
563    ) -> Self {
564        let tree = match storage_flags {
565            Some(storage_flags) => {
566                Element::empty_count_tree_with_flags(storage_flags.to_some_element_flags())
567            }
568            None => Element::empty_count_tree(),
569        };
570
571        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
572    }
573
574    /// Sets `GroveOperation` for inserting an empty provable count tree at the given (estimated) path and key
575    pub fn for_estimated_path_key_empty_provable_count_tree(
576        path: KeyInfoPath,
577        key: KeyInfo,
578        storage_flags: Option<&StorageFlags>,
579    ) -> Self {
580        let tree = match storage_flags {
581            Some(storage_flags) => {
582                Element::empty_provable_count_tree_with_flags(storage_flags.to_some_element_flags())
583            }
584            None => Element::empty_provable_count_tree(),
585        };
586
587        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
588    }
589
590    /// Sets `GroveOperation` for inserting an element at the given path and key
591    pub fn insert_for_known_path_key_element(
592        path: Vec<Vec<u8>>,
593        key: Vec<u8>,
594        element: Element,
595    ) -> Self {
596        GroveOperation(QualifiedGroveDbOp::insert_or_replace_op(path, key, element))
597    }
598
599    /// Sets `GroveOperation` for replacement of an element at the given path and key
600    pub fn replace_for_known_path_key_element(
601        path: Vec<Vec<u8>>,
602        key: Vec<u8>,
603        element: Element,
604    ) -> Self {
605        GroveOperation(QualifiedGroveDbOp::replace_op(path, key, element))
606    }
607
608    /// Sets `GroveOperation` for patching of an element at the given path and key
609    /// This is different from replacement which does not add or delete bytes
610    pub fn patch_for_known_path_key_element(
611        path: Vec<Vec<u8>>,
612        key: Vec<u8>,
613        element: Element,
614        change_in_bytes: i32,
615    ) -> Self {
616        GroveOperation(QualifiedGroveDbOp::patch_op(
617            path,
618            key,
619            element,
620            change_in_bytes,
621        ))
622    }
623
624    /// Sets `GroveOperation` for inserting an element at an unknown estimated path and key
625    pub fn insert_for_estimated_path_key_element(
626        path: KeyInfoPath,
627        key: KeyInfo,
628        element: Element,
629    ) -> Self {
630        GroveOperation(QualifiedGroveDbOp::insert_estimated_op(path, key, element))
631    }
632
633    /// Sets `GroveOperation` for replacement of an element at an unknown estimated path and key
634    pub fn replace_for_estimated_path_key_element(
635        path: KeyInfoPath,
636        key: KeyInfo,
637        element: Element,
638    ) -> Self {
639        GroveOperation(QualifiedGroveDbOp::replace_estimated_op(path, key, element))
640    }
641
642    /// Sets `GroveOperation` for refresh of a reference at the given path and key
643    pub fn refresh_reference_for_known_path_key_reference_info(
644        path: Vec<Vec<u8>>,
645        key: Vec<u8>,
646        reference_path_type: ReferencePathType,
647        max_reference_hop: MaxReferenceHop,
648        flags: Option<ElementFlags>,
649        trust_refresh_reference: bool,
650    ) -> Self {
651        GroveOperation(QualifiedGroveDbOp::refresh_reference_op(
652            path,
653            key,
654            reference_path_type,
655            max_reference_hop,
656            flags,
657            trust_refresh_reference,
658        ))
659    }
660}
661
662/// A trait for getting an empty tree operation based on the tree type
663pub trait LowLevelDriveOperationTreeTypeConverter {
664    /// Sets `GroveOperation` for inserting an empty tree at the given path and key
665    fn empty_tree_operation_for_known_path_key(
666        &self,
667        path: Vec<Vec<u8>>,
668        key: Vec<u8>,
669        storage_flags: Option<&StorageFlags>,
670    ) -> Result<LowLevelDriveOperation, Error>;
671}
672
673impl LowLevelDriveOperationTreeTypeConverter for TreeType {
674    /// Sets `GroveOperation` for inserting an empty tree at the given path and key
675    fn empty_tree_operation_for_known_path_key(
676        &self,
677        path: Vec<Vec<u8>>,
678        key: Vec<u8>,
679        storage_flags: Option<&StorageFlags>,
680    ) -> Result<LowLevelDriveOperation, Error> {
681        let element_flags = storage_flags.map(|storage_flags| storage_flags.to_element_flags());
682        let element = match self {
683            TreeType::NormalTree => Element::empty_tree_with_flags(element_flags),
684            TreeType::SumTree => Element::empty_sum_tree_with_flags(element_flags),
685            TreeType::BigSumTree => Element::empty_big_sum_tree_with_flags(element_flags),
686            TreeType::CountTree => Element::empty_count_tree_with_flags(element_flags),
687            TreeType::CountSumTree => Element::empty_count_sum_tree_with_flags(element_flags),
688            TreeType::ProvableCountTree => {
689                Element::empty_provable_count_tree_with_flags(element_flags)
690            }
691            TreeType::ProvableCountSumTree => {
692                Element::empty_provable_count_sum_tree_with_flags(element_flags)
693            }
694            TreeType::CommitmentTree(chunk_power) => {
695                Element::empty_commitment_tree_with_flags(*chunk_power, element_flags)?
696            }
697            TreeType::MmrTree => Element::empty_mmr_tree_with_flags(element_flags),
698            TreeType::BulkAppendTree(chunk_power) => {
699                Element::empty_bulk_append_tree_with_flags(*chunk_power, element_flags)?
700            }
701            TreeType::DenseAppendOnlyFixedSizeTree(chunk_power) => {
702                Element::empty_dense_tree_with_flags(*chunk_power, element_flags)
703            }
704        };
705
706        Ok(LowLevelDriveOperation::insert_for_known_path_key_element(
707            path, key, element,
708        ))
709    }
710}
711
712/// Drive cost trait
713pub trait DriveCost {
714    /// Ephemeral cost
715    fn ephemeral_cost(&self, fee_version: &FeeVersion) -> Result<u64, Error>;
716}
717
718impl DriveCost for OperationCost {
719    /// Return the ephemeral cost from the operation
720    fn ephemeral_cost(&self, fee_version: &FeeVersion) -> Result<Credits, Error> {
721        let OperationCost {
722            seek_count,
723            storage_cost,
724            storage_loaded_bytes,
725            hash_node_calls,
726            sinsemilla_hash_calls,
727        } = self;
728        let epoch_cost_for_processing_credit_per_byte =
729            fee_version.storage.storage_processing_credit_per_byte;
730        let seek_cost = (*seek_count as u64)
731            .checked_mul(fee_version.storage.storage_seek_cost)
732            .ok_or_else(|| get_overflow_error("seek cost overflow"))?;
733        let storage_added_bytes_ephemeral_cost = (storage_cost.added_bytes as u64)
734            .checked_mul(epoch_cost_for_processing_credit_per_byte)
735            .ok_or_else(|| get_overflow_error("storage written bytes cost overflow"))?;
736        let storage_replaced_bytes_ephemeral_cost = (storage_cost.replaced_bytes as u64)
737            .checked_mul(epoch_cost_for_processing_credit_per_byte)
738            .ok_or_else(|| get_overflow_error("storage written bytes cost overflow"))?;
739        let storage_removed_bytes_ephemeral_cost =
740            (storage_cost.removed_bytes.total_removed_bytes() as u64)
741                .checked_mul(epoch_cost_for_processing_credit_per_byte)
742                .ok_or_else(|| get_overflow_error("storage written bytes cost overflow"))?;
743        // not accessible
744        let storage_loaded_bytes_cost = { *storage_loaded_bytes }
745            .checked_mul(fee_version.storage.storage_load_credit_per_byte)
746            .ok_or_else(|| get_overflow_error("storage loaded cost overflow"))?;
747
748        // There is one block per hash node call
749        let blake3_total = fee_version.hashing.blake3_base + fee_version.hashing.blake3_per_block;
750        // this can't overflow
751        let hash_node_cost = blake3_total * (*hash_node_calls as u64);
752        let sinsemilla_cost = fee_version.hashing.sinsemilla_base * (*sinsemilla_hash_calls as u64);
753        seek_cost
754            .checked_add(storage_added_bytes_ephemeral_cost)
755            .and_then(|c| c.checked_add(storage_replaced_bytes_ephemeral_cost))
756            .and_then(|c| c.checked_add(storage_loaded_bytes_cost))
757            .and_then(|c| c.checked_add(storage_removed_bytes_ephemeral_cost))
758            .and_then(|c| c.checked_add(hash_node_cost))
759            .and_then(|c| c.checked_add(sinsemilla_cost))
760            .ok_or_else(|| get_overflow_error("ephemeral cost addition overflow"))
761    }
762}
763
764#[cfg(test)]
765#[allow(clippy::identity_op)]
766mod tests {
767    use super::*;
768    use grovedb_costs::storage_cost::removal::StorageRemovedBytes;
769    use grovedb_costs::storage_cost::StorageCost;
770    use platform_version::version::fee::storage::FeeStorageVersion;
771    use platform_version::version::fee::FeeVersion;
772
773    /// Helper to get the canonical fee version used across these tests.
774    fn fee_version() -> &'static FeeVersion {
775        FeeVersion::first()
776    }
777
778    // ---------------------------------------------------------------
779    // 1. BaseOp::cost() — spot-check several opcodes
780    // ---------------------------------------------------------------
781
782    #[test]
783    fn base_op_stop_costs_zero() {
784        assert_eq!(BaseOp::Stop.cost(), 0);
785    }
786
787    #[test]
788    fn base_op_add_costs_12() {
789        assert_eq!(BaseOp::Add.cost(), 12);
790    }
791
792    #[test]
793    fn base_op_mul_costs_20() {
794        assert_eq!(BaseOp::Mul.cost(), 20);
795    }
796
797    #[test]
798    fn base_op_signextend_costs_20() {
799        assert_eq!(BaseOp::Signextend.cost(), 20);
800    }
801
802    #[test]
803    fn base_op_addmod_costs_32() {
804        assert_eq!(BaseOp::Addmod.cost(), 32);
805    }
806
807    #[test]
808    fn base_op_mulmod_costs_32() {
809        assert_eq!(BaseOp::Mulmod.cost(), 32);
810    }
811
812    #[test]
813    fn base_op_byte_costs_12() {
814        assert_eq!(BaseOp::Byte.cost(), 12);
815    }
816
817    #[test]
818    fn base_op_sub_costs_12() {
819        assert_eq!(BaseOp::Sub.cost(), 12);
820    }
821
822    #[test]
823    fn base_op_div_costs_20() {
824        assert_eq!(BaseOp::Div.cost(), 20);
825    }
826
827    #[test]
828    fn base_op_comparison_ops_all_cost_12() {
829        for op in [
830            BaseOp::Lt,
831            BaseOp::Gt,
832            BaseOp::Slt,
833            BaseOp::Sgt,
834            BaseOp::Eq,
835            BaseOp::Iszero,
836        ] {
837            assert_eq!(op.cost(), 12, "comparison op {:?} should cost 12", op);
838        }
839    }
840
841    #[test]
842    fn base_op_bitwise_ops_all_cost_12() {
843        for op in [BaseOp::And, BaseOp::Or, BaseOp::Xor, BaseOp::Not] {
844            assert_eq!(op.cost(), 12, "bitwise op {:?} should cost 12", op);
845        }
846    }
847
848    // ---------------------------------------------------------------
849    // 2. HashFunction — block_size / rounds / block_cost / base_cost
850    // ---------------------------------------------------------------
851
852    #[test]
853    fn hash_function_block_size_all_64() {
854        // All four hash functions currently have a 64-byte block size.
855        assert_eq!(HashFunction::Sha256.block_size(), 64);
856        assert_eq!(HashFunction::Sha256_2.block_size(), 64);
857        assert_eq!(HashFunction::Blake3.block_size(), 64);
858        assert_eq!(HashFunction::Sha256RipeMD160.block_size(), 64);
859    }
860
861    #[test]
862    fn hash_function_rounds() {
863        assert_eq!(HashFunction::Sha256.rounds(), 1);
864        assert_eq!(HashFunction::Sha256_2.rounds(), 2);
865        assert_eq!(HashFunction::Blake3.rounds(), 1);
866        assert_eq!(HashFunction::Sha256RipeMD160.rounds(), 1);
867    }
868
869    #[test]
870    fn hash_function_block_cost_sha256_variants_use_sha256_per_block() {
871        let fv = fee_version();
872        let expected = fv.hashing.sha256_per_block;
873        assert_eq!(HashFunction::Sha256.block_cost(fv), expected);
874        assert_eq!(HashFunction::Sha256_2.block_cost(fv), expected);
875        assert_eq!(HashFunction::Sha256RipeMD160.block_cost(fv), expected);
876    }
877
878    #[test]
879    fn hash_function_block_cost_blake3_uses_blake3_per_block() {
880        let fv = fee_version();
881        assert_eq!(
882            HashFunction::Blake3.block_cost(fv),
883            fv.hashing.blake3_per_block
884        );
885    }
886
887    #[test]
888    fn hash_function_base_cost_sha256() {
889        let fv = fee_version();
890        assert_eq!(
891            HashFunction::Sha256.base_cost(fv),
892            fv.hashing.single_sha256_base
893        );
894    }
895
896    #[test]
897    fn hash_function_base_cost_sha256_2_uses_single_sha256_base() {
898        let fv = fee_version();
899        // Sha256_2 intentionally uses single_sha256_base (extra rounds handle the double hash).
900        assert_eq!(
901            HashFunction::Sha256_2.base_cost(fv),
902            fv.hashing.single_sha256_base
903        );
904    }
905
906    #[test]
907    fn hash_function_base_cost_blake3() {
908        let fv = fee_version();
909        assert_eq!(HashFunction::Blake3.base_cost(fv), fv.hashing.blake3_base);
910    }
911
912    #[test]
913    fn hash_function_base_cost_sha256_ripe_md160() {
914        let fv = fee_version();
915        assert_eq!(
916            HashFunction::Sha256RipeMD160.base_cost(fv),
917            fv.hashing.sha256_ripe_md160_base
918        );
919    }
920
921    // ---------------------------------------------------------------
922    // 3. FunctionOp::new_with_byte_count — verify blocks/rounds calc
923    // ---------------------------------------------------------------
924
925    #[test]
926    fn function_op_new_with_byte_count_small_sha256() {
927        // 32 bytes => blocks = 32/64 + 1 = 1, rounds = 1 + 1 - 1 = 1
928        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 32);
929        assert_eq!(op.rounds, 1);
930        assert_eq!(op.hash, HashFunction::Sha256);
931    }
932
933    #[test]
934    fn function_op_new_with_byte_count_exact_block_boundary_sha256() {
935        // 64 bytes => blocks = 64/64 + 1 = 2, rounds = 2 + 1 - 1 = 2
936        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 64);
937        assert_eq!(op.rounds, 2);
938    }
939
940    #[test]
941    fn function_op_new_with_byte_count_large_sha256() {
942        // 200 bytes => blocks = 200/64 + 1 = 3 + 1 = 4, rounds = 4 + 1 - 1 = 4
943        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 200);
944        assert_eq!(op.rounds, 4);
945    }
946
947    #[test]
948    fn function_op_new_with_byte_count_sha256_2_has_extra_round() {
949        // 32 bytes => blocks = 32/64 + 1 = 1, rounds = 1 + 2 - 1 = 2
950        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256_2, 32);
951        assert_eq!(op.rounds, 2);
952    }
953
954    #[test]
955    fn function_op_new_with_byte_count_sha256_2_large() {
956        // 200 bytes => blocks = 200/64 + 1 = 4, rounds = 4 + 2 - 1 = 5
957        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256_2, 200);
958        assert_eq!(op.rounds, 5);
959    }
960
961    #[test]
962    fn function_op_new_with_byte_count_blake3_small() {
963        // 10 bytes => blocks = 10/64 + 1 = 1, rounds = 1 + 1 - 1 = 1
964        let op = FunctionOp::new_with_byte_count(HashFunction::Blake3, 10);
965        assert_eq!(op.rounds, 1);
966        assert_eq!(op.hash, HashFunction::Blake3);
967    }
968
969    #[test]
970    fn function_op_new_with_byte_count_blake3_large() {
971        // 500 bytes => blocks = 500/64 + 1 = 7 + 1 = 8, rounds = 8 + 1 - 1 = 8
972        let op = FunctionOp::new_with_byte_count(HashFunction::Blake3, 500);
973        assert_eq!(op.rounds, 8);
974    }
975
976    #[test]
977    fn function_op_new_with_byte_count_zero_bytes() {
978        // 0 bytes => blocks = 0/64 + 1 = 1, rounds = 1 + 1 - 1 = 1
979        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 0);
980        assert_eq!(op.rounds, 1);
981    }
982
983    #[test]
984    fn function_op_new_with_byte_count_sha256_ripemd160() {
985        // 20 bytes => blocks = 20/64 + 1 = 1, rounds = 1 + 1 - 1 = 1
986        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256RipeMD160, 20);
987        assert_eq!(op.rounds, 1);
988        assert_eq!(op.hash, HashFunction::Sha256RipeMD160);
989    }
990
991    // ---------------------------------------------------------------
992    // 4. FunctionOp::cost — verify rounds * block_cost + base_cost
993    // ---------------------------------------------------------------
994
995    #[test]
996    fn function_op_cost_sha256_one_round() {
997        let fv = fee_version();
998        let op = FunctionOp::new_with_round_count(HashFunction::Sha256, 1);
999        // cost = base + rounds * block_cost = 100 + 1 * 5000 = 5100
1000        let expected = fv.hashing.single_sha256_base + 1 * fv.hashing.sha256_per_block;
1001        assert_eq!(op.cost(fv), expected);
1002    }
1003
1004    #[test]
1005    fn function_op_cost_sha256_2_two_rounds() {
1006        let fv = fee_version();
1007        let op = FunctionOp::new_with_round_count(HashFunction::Sha256_2, 2);
1008        // cost = base + rounds * block_cost = 100 + 2 * 5000 = 10100
1009        let expected = fv.hashing.single_sha256_base + 2 * fv.hashing.sha256_per_block;
1010        assert_eq!(op.cost(fv), expected);
1011    }
1012
1013    #[test]
1014    fn function_op_cost_blake3_one_round() {
1015        let fv = fee_version();
1016        let op = FunctionOp::new_with_round_count(HashFunction::Blake3, 1);
1017        // cost = blake3_base + 1 * blake3_per_block = 100 + 300 = 400
1018        let expected = fv.hashing.blake3_base + 1 * fv.hashing.blake3_per_block;
1019        assert_eq!(op.cost(fv), expected);
1020    }
1021
1022    #[test]
1023    fn function_op_cost_zero_rounds() {
1024        let fv = fee_version();
1025        let op = FunctionOp::new_with_round_count(HashFunction::Blake3, 0);
1026        // cost = blake3_base + 0 * blake3_per_block = blake3_base
1027        assert_eq!(op.cost(fv), fv.hashing.blake3_base);
1028    }
1029
1030    #[test]
1031    fn function_op_cost_from_byte_count_matches_manual_calc() {
1032        let fv = fee_version();
1033        // 128 bytes of SHA256: blocks = 128/64 + 1 = 3, rounds = 3 + 1 - 1 = 3
1034        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 128);
1035        assert_eq!(op.rounds, 3);
1036        let expected = fv.hashing.single_sha256_base + 3 * fv.hashing.sha256_per_block;
1037        assert_eq!(op.cost(fv), expected);
1038    }
1039
1040    #[test]
1041    fn function_op_cost_sha256_ripemd160() {
1042        let fv = fee_version();
1043        let op = FunctionOp::new_with_round_count(HashFunction::Sha256RipeMD160, 1);
1044        let expected = fv.hashing.sha256_ripe_md160_base + 1 * fv.hashing.sha256_per_block;
1045        assert_eq!(op.cost(fv), expected);
1046    }
1047
1048    #[test]
1049    fn function_op_cost_saturating_mul_does_not_panic_on_large_rounds() {
1050        let fv = fee_version();
1051        let op = FunctionOp::new_with_round_count(HashFunction::Sha256, u32::MAX);
1052        // u32::MAX as u64 * sha256_per_block (5000) fits in u64 without overflow,
1053        // so cost = base + rounds * block_cost, computed via saturating ops.
1054        let expected_block_cost = (u32::MAX as u64).saturating_mul(fv.hashing.sha256_per_block);
1055        let expected = fv
1056            .hashing
1057            .single_sha256_base
1058            .saturating_add(expected_block_cost);
1059        assert_eq!(op.cost(fv), expected);
1060    }
1061
1062    #[test]
1063    fn function_op_cost_saturates_to_max_with_extreme_fee_version() {
1064        // Construct a fee version where block_cost is large enough that
1065        // u32::MAX * block_cost overflows u64, triggering saturation.
1066        let mut fv = fee_version().clone();
1067        fv.hashing.sha256_per_block = u64::MAX;
1068        let op = FunctionOp::new_with_round_count(HashFunction::Sha256, 2);
1069        // 2 * u64::MAX saturates to u64::MAX, then base.saturating_add(u64::MAX) = u64::MAX.
1070        assert_eq!(op.cost(&fv), u64::MAX);
1071    }
1072
1073    // ---------------------------------------------------------------
1074    // 5. operation_cost() — test all 4 match arms
1075    // ---------------------------------------------------------------
1076
1077    #[test]
1078    fn operation_cost_calculated_cost_operation_returns_cost() {
1079        let cost = OperationCost {
1080            seek_count: 3,
1081            storage_cost: StorageCost {
1082                added_bytes: 100,
1083                replaced_bytes: 50,
1084                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1085            },
1086            storage_loaded_bytes: 200,
1087            hash_node_calls: 5,
1088            sinsemilla_hash_calls: 0,
1089        };
1090        let op = CalculatedCostOperation(cost.clone());
1091        let result = op.operation_cost().expect("should return Ok");
1092        assert_eq!(result, cost);
1093    }
1094
1095    #[test]
1096    fn operation_cost_grove_operation_returns_error() {
1097        let grove_op = LowLevelDriveOperation::insert_for_known_path_key_element(
1098            vec![vec![1, 2, 3]],
1099            vec![4, 5, 6],
1100            Element::empty_tree(),
1101        );
1102        let result = grove_op.operation_cost();
1103        assert!(result.is_err());
1104        let err_msg = format!("{:?}", result.unwrap_err());
1105        assert!(
1106            err_msg.contains("grove operations must be executed"),
1107            "unexpected error: {}",
1108            err_msg
1109        );
1110    }
1111
1112    #[test]
1113    fn operation_cost_pre_calculated_fee_result_returns_error() {
1114        let fee = FeeResult {
1115            storage_fee: 100,
1116            processing_fee: 200,
1117            ..Default::default()
1118        };
1119        let op = PreCalculatedFeeResult(fee);
1120        let result = op.operation_cost();
1121        assert!(result.is_err());
1122        let err_msg = format!("{:?}", result.unwrap_err());
1123        assert!(
1124            err_msg.contains("pre calculated fees should not be requested"),
1125            "unexpected error: {}",
1126            err_msg
1127        );
1128    }
1129
1130    #[test]
1131    fn operation_cost_function_operation_returns_error() {
1132        let func_op = FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Blake3, 1));
1133        let result = func_op.operation_cost();
1134        assert!(result.is_err());
1135        let err_msg = format!("{:?}", result.unwrap_err());
1136        assert!(
1137            err_msg.contains("function operations should not be requested"),
1138            "unexpected error: {}",
1139            err_msg
1140        );
1141    }
1142
1143    // ---------------------------------------------------------------
1144    // 6. combine_cost_operations — filter and sum
1145    // ---------------------------------------------------------------
1146
1147    #[test]
1148    fn combine_cost_operations_sums_calculated_costs_only() {
1149        let cost1 = OperationCost {
1150            seek_count: 2,
1151            storage_cost: StorageCost {
1152                added_bytes: 10,
1153                replaced_bytes: 0,
1154                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1155            },
1156            storage_loaded_bytes: 50,
1157            hash_node_calls: 1,
1158            sinsemilla_hash_calls: 0,
1159        };
1160        let cost2 = OperationCost {
1161            seek_count: 3,
1162            storage_cost: StorageCost {
1163                added_bytes: 20,
1164                replaced_bytes: 5,
1165                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1166            },
1167            storage_loaded_bytes: 100,
1168            hash_node_calls: 2,
1169            sinsemilla_hash_calls: 1,
1170        };
1171
1172        let operations = vec![
1173            CalculatedCostOperation(cost1.clone()),
1174            // This FunctionOperation should be ignored by combine_cost_operations
1175            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Sha256, 1)),
1176            CalculatedCostOperation(cost2.clone()),
1177            // PreCalculatedFeeResult should also be ignored
1178            PreCalculatedFeeResult(FeeResult::default()),
1179        ];
1180
1181        let combined = LowLevelDriveOperation::combine_cost_operations(&operations);
1182        assert_eq!(combined.seek_count, 2 + 3);
1183        assert_eq!(combined.storage_cost.added_bytes, 10 + 20);
1184        assert_eq!(combined.storage_cost.replaced_bytes, 0 + 5);
1185        assert_eq!(combined.storage_loaded_bytes, 50 + 100);
1186        assert_eq!(combined.hash_node_calls, 1 + 2);
1187        assert_eq!(combined.sinsemilla_hash_calls, 0 + 1);
1188    }
1189
1190    #[test]
1191    fn combine_cost_operations_empty_list_returns_default() {
1192        let combined = LowLevelDriveOperation::combine_cost_operations(&[]);
1193        assert_eq!(combined, OperationCost::default());
1194    }
1195
1196    #[test]
1197    fn combine_cost_operations_no_calculated_costs_returns_default() {
1198        let operations = vec![
1199            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Blake3, 2)),
1200            PreCalculatedFeeResult(FeeResult {
1201                processing_fee: 999,
1202                ..Default::default()
1203            }),
1204        ];
1205        let combined = LowLevelDriveOperation::combine_cost_operations(&operations);
1206        assert_eq!(combined, OperationCost::default());
1207    }
1208
1209    // ---------------------------------------------------------------
1210    // 7. grovedb_operations_batch / _consume / _consume_with_leftovers
1211    // ---------------------------------------------------------------
1212
1213    /// Helper: creates a GroveOperation variant (insert_or_replace).
1214    fn make_grove_op(key_byte: u8) -> LowLevelDriveOperation {
1215        LowLevelDriveOperation::insert_for_known_path_key_element(
1216            vec![vec![0]],
1217            vec![key_byte],
1218            Element::new_item(vec![key_byte]),
1219        )
1220    }
1221
1222    fn make_mixed_ops() -> Vec<LowLevelDriveOperation> {
1223        vec![
1224            make_grove_op(1),
1225            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Sha256, 1)),
1226            make_grove_op(2),
1227            CalculatedCostOperation(OperationCost::default()),
1228            make_grove_op(3),
1229        ]
1230    }
1231
1232    #[test]
1233    fn grovedb_operations_batch_filters_grove_ops_from_ref() {
1234        let ops = make_mixed_ops();
1235        let batch = LowLevelDriveOperation::grovedb_operations_batch(&ops);
1236        assert_eq!(batch.len(), 3);
1237    }
1238
1239    #[test]
1240    fn grovedb_operations_batch_empty_input() {
1241        let batch = LowLevelDriveOperation::grovedb_operations_batch(&[]);
1242        assert!(batch.is_empty());
1243    }
1244
1245    #[test]
1246    fn grovedb_operations_batch_no_grove_ops() {
1247        let ops = vec![
1248            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Blake3, 1)),
1249            CalculatedCostOperation(OperationCost::default()),
1250        ];
1251        let batch = LowLevelDriveOperation::grovedb_operations_batch(&ops);
1252        assert!(batch.is_empty());
1253    }
1254
1255    #[test]
1256    fn grovedb_operations_batch_consume_filters_grove_ops() {
1257        let ops = make_mixed_ops();
1258        let batch = LowLevelDriveOperation::grovedb_operations_batch_consume(ops);
1259        assert_eq!(batch.len(), 3);
1260    }
1261
1262    #[test]
1263    fn grovedb_operations_batch_consume_empty_input() {
1264        let batch = LowLevelDriveOperation::grovedb_operations_batch_consume(vec![]);
1265        assert!(batch.is_empty());
1266    }
1267
1268    #[test]
1269    fn grovedb_operations_batch_consume_with_leftovers_partitions_correctly() {
1270        let ops = make_mixed_ops();
1271        let (batch, leftovers) =
1272            LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(ops);
1273        assert_eq!(batch.len(), 3);
1274        assert_eq!(leftovers.len(), 2);
1275
1276        // Verify leftovers contain the non-grove operations.
1277        for leftover in &leftovers {
1278            assert!(
1279                !matches!(leftover, GroveOperation(_)),
1280                "leftovers should not contain GroveOperation variants"
1281            );
1282        }
1283    }
1284
1285    #[test]
1286    fn grovedb_operations_batch_consume_with_leftovers_all_grove() {
1287        let ops = vec![make_grove_op(10), make_grove_op(20)];
1288        let (batch, leftovers) =
1289            LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(ops);
1290        assert_eq!(batch.len(), 2);
1291        assert!(leftovers.is_empty());
1292    }
1293
1294    #[test]
1295    fn grovedb_operations_batch_consume_with_leftovers_no_grove() {
1296        let ops = vec![
1297            CalculatedCostOperation(OperationCost::default()),
1298            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Sha256, 1)),
1299        ];
1300        let (batch, leftovers) =
1301            LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(ops);
1302        assert!(batch.is_empty());
1303        assert_eq!(leftovers.len(), 2);
1304    }
1305
1306    #[test]
1307    fn grovedb_operations_batch_consume_with_leftovers_empty() {
1308        let (batch, leftovers) =
1309            LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(vec![]);
1310        assert!(batch.is_empty());
1311        assert!(leftovers.is_empty());
1312    }
1313
1314    // ---------------------------------------------------------------
1315    // 8. DriveCost::ephemeral_cost — various scenarios
1316    // ---------------------------------------------------------------
1317
1318    #[test]
1319    fn ephemeral_cost_zero_operation() {
1320        let fv = fee_version();
1321        let cost = OperationCost::default();
1322        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1323        assert_eq!(result, 0);
1324    }
1325
1326    #[test]
1327    fn ephemeral_cost_seek_only() {
1328        let fv = fee_version();
1329        let cost = OperationCost {
1330            seek_count: 5,
1331            storage_cost: StorageCost::default(),
1332            storage_loaded_bytes: 0,
1333            hash_node_calls: 0,
1334            sinsemilla_hash_calls: 0,
1335        };
1336        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1337        let expected = 5u64 * fv.storage.storage_seek_cost;
1338        assert_eq!(result, expected);
1339    }
1340
1341    #[test]
1342    fn ephemeral_cost_storage_added_bytes() {
1343        let fv = fee_version();
1344        let cost = OperationCost {
1345            seek_count: 0,
1346            storage_cost: StorageCost {
1347                added_bytes: 100,
1348                replaced_bytes: 0,
1349                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1350            },
1351            storage_loaded_bytes: 0,
1352            hash_node_calls: 0,
1353            sinsemilla_hash_calls: 0,
1354        };
1355        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1356        let expected = 100u64 * fv.storage.storage_processing_credit_per_byte;
1357        assert_eq!(result, expected);
1358    }
1359
1360    #[test]
1361    fn ephemeral_cost_storage_replaced_bytes() {
1362        let fv = fee_version();
1363        let cost = OperationCost {
1364            seek_count: 0,
1365            storage_cost: StorageCost {
1366                added_bytes: 0,
1367                replaced_bytes: 50,
1368                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1369            },
1370            storage_loaded_bytes: 0,
1371            hash_node_calls: 0,
1372            sinsemilla_hash_calls: 0,
1373        };
1374        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1375        let expected = 50u64 * fv.storage.storage_processing_credit_per_byte;
1376        assert_eq!(result, expected);
1377    }
1378
1379    #[test]
1380    fn ephemeral_cost_storage_removed_bytes_basic() {
1381        let fv = fee_version();
1382        let cost = OperationCost {
1383            seek_count: 0,
1384            storage_cost: StorageCost {
1385                added_bytes: 0,
1386                replaced_bytes: 0,
1387                removed_bytes: StorageRemovedBytes::BasicStorageRemoval(75),
1388            },
1389            storage_loaded_bytes: 0,
1390            hash_node_calls: 0,
1391            sinsemilla_hash_calls: 0,
1392        };
1393        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1394        let expected = 75u64 * fv.storage.storage_processing_credit_per_byte;
1395        assert_eq!(result, expected);
1396    }
1397
1398    #[test]
1399    fn ephemeral_cost_loaded_bytes() {
1400        let fv = fee_version();
1401        let cost = OperationCost {
1402            seek_count: 0,
1403            storage_cost: StorageCost::default(),
1404            storage_loaded_bytes: 300,
1405            hash_node_calls: 0,
1406            sinsemilla_hash_calls: 0,
1407        };
1408        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1409        let expected = 300u64 * fv.storage.storage_load_credit_per_byte;
1410        assert_eq!(result, expected);
1411    }
1412
1413    #[test]
1414    fn ephemeral_cost_hash_node_calls() {
1415        let fv = fee_version();
1416        let cost = OperationCost {
1417            seek_count: 0,
1418            storage_cost: StorageCost::default(),
1419            storage_loaded_bytes: 0,
1420            hash_node_calls: 10,
1421            sinsemilla_hash_calls: 0,
1422        };
1423        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1424        let blake3_total = fv.hashing.blake3_base + fv.hashing.blake3_per_block;
1425        let expected = blake3_total * 10;
1426        assert_eq!(result, expected);
1427    }
1428
1429    #[test]
1430    fn ephemeral_cost_sinsemilla_hash_calls() {
1431        let fv = fee_version();
1432        let cost = OperationCost {
1433            seek_count: 0,
1434            storage_cost: StorageCost::default(),
1435            storage_loaded_bytes: 0,
1436            hash_node_calls: 0,
1437            sinsemilla_hash_calls: 3,
1438        };
1439        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1440        let expected = fv.hashing.sinsemilla_base * 3;
1441        assert_eq!(result, expected);
1442    }
1443
1444    #[test]
1445    fn ephemeral_cost_all_components_combined() {
1446        let fv = fee_version();
1447        let cost = OperationCost {
1448            seek_count: 2,
1449            storage_cost: StorageCost {
1450                added_bytes: 10,
1451                replaced_bytes: 20,
1452                removed_bytes: StorageRemovedBytes::BasicStorageRemoval(30),
1453            },
1454            storage_loaded_bytes: 40,
1455            hash_node_calls: 5,
1456            sinsemilla_hash_calls: 1,
1457        };
1458        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1459
1460        let seek_cost = 2u64 * fv.storage.storage_seek_cost;
1461        let processing_per_byte = fv.storage.storage_processing_credit_per_byte;
1462        let added_cost = 10u64 * processing_per_byte;
1463        let replaced_cost = 20u64 * processing_per_byte;
1464        let removed_cost = 30u64 * processing_per_byte;
1465        let loaded_cost = 40u64 * fv.storage.storage_load_credit_per_byte;
1466        let blake3_total = fv.hashing.blake3_base + fv.hashing.blake3_per_block;
1467        let hash_cost = blake3_total * 5;
1468        let sinsemilla_cost = fv.hashing.sinsemilla_base * 1;
1469
1470        let expected = seek_cost
1471            + added_cost
1472            + replaced_cost
1473            + loaded_cost
1474            + removed_cost
1475            + hash_cost
1476            + sinsemilla_cost;
1477        assert_eq!(result, expected);
1478    }
1479
1480    #[test]
1481    fn ephemeral_cost_overflow_seek_cost() {
1482        let fv = &FeeVersion {
1483            storage: FeeStorageVersion {
1484                storage_seek_cost: u64::MAX,
1485                ..fee_version().storage.clone()
1486            },
1487            ..fee_version().clone()
1488        };
1489        let cost = OperationCost {
1490            seek_count: 2, // 2 * u64::MAX overflows
1491            storage_cost: StorageCost::default(),
1492            storage_loaded_bytes: 0,
1493            hash_node_calls: 0,
1494            sinsemilla_hash_calls: 0,
1495        };
1496        let result = cost.ephemeral_cost(fv);
1497        assert!(result.is_err(), "expected overflow error for seek cost");
1498    }
1499
1500    #[test]
1501    fn ephemeral_cost_overflow_storage_written_bytes() {
1502        let fv = &FeeVersion {
1503            storage: FeeStorageVersion {
1504                storage_processing_credit_per_byte: u64::MAX,
1505                ..fee_version().storage.clone()
1506            },
1507            ..fee_version().clone()
1508        };
1509        let cost = OperationCost {
1510            seek_count: 0,
1511            storage_cost: StorageCost {
1512                added_bytes: 2, // 2 * u64::MAX overflows
1513                replaced_bytes: 0,
1514                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1515            },
1516            storage_loaded_bytes: 0,
1517            hash_node_calls: 0,
1518            sinsemilla_hash_calls: 0,
1519        };
1520        let result = cost.ephemeral_cost(fv);
1521        assert!(
1522            result.is_err(),
1523            "expected overflow error for storage written bytes"
1524        );
1525    }
1526
1527    #[test]
1528    fn ephemeral_cost_overflow_loaded_bytes() {
1529        let fv = &FeeVersion {
1530            storage: FeeStorageVersion {
1531                storage_load_credit_per_byte: u64::MAX,
1532                ..fee_version().storage.clone()
1533            },
1534            ..fee_version().clone()
1535        };
1536        let cost = OperationCost {
1537            seek_count: 0,
1538            storage_cost: StorageCost::default(),
1539            storage_loaded_bytes: 2, // 2 * u64::MAX overflows
1540            hash_node_calls: 0,
1541            sinsemilla_hash_calls: 0,
1542        };
1543        let result = cost.ephemeral_cost(fv);
1544        assert!(
1545            result.is_err(),
1546            "expected overflow error for loaded bytes cost"
1547        );
1548    }
1549
1550    #[test]
1551    fn ephemeral_cost_overflow_in_addition_chain() {
1552        // Use values that individually do not overflow but whose sum does.
1553        let fv = fee_version();
1554        let cost = OperationCost {
1555            seek_count: u32::MAX,
1556            storage_cost: StorageCost {
1557                added_bytes: u32::MAX,
1558                replaced_bytes: u32::MAX,
1559                removed_bytes: StorageRemovedBytes::BasicStorageRemoval(u32::MAX),
1560            },
1561            storage_loaded_bytes: u64::MAX,
1562            hash_node_calls: u32::MAX,
1563            sinsemilla_hash_calls: u32::MAX,
1564        };
1565        let result = cost.ephemeral_cost(fv);
1566        assert!(
1567            result.is_err(),
1568            "expected overflow error when summing large components"
1569        );
1570    }
1571}