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::IndexAxis;
13use grovedb::element::MaxReferenceHop;
14use grovedb::{batch::QualifiedGroveDbOp, Element, ElementFlags, TreeType};
15use grovedb_costs::OperationCost;
16use itertools::Itertools;
17
18use crate::error::drive::DriveError;
19use crate::error::Error;
20use crate::fees::get_overflow_error;
21use crate::fees::op::LowLevelDriveOperation::{
22    CalculatedCostOperation, FunctionOperation, GroveOperation, PreCalculatedFeeResult,
23};
24use crate::util::batch::grovedb_op_batch::GroveDbOpBatchV0Methods;
25use crate::util::storage_flags::StorageFlags;
26use dpp::block::epoch::Epoch;
27use dpp::fee::default_costs::CachedEpochIndexFeeVersions;
28use dpp::fee::fee_result::refunds::FeeRefunds;
29use dpp::fee::fee_result::FeeResult;
30use dpp::fee::Credits;
31use platform_version::version::fee::FeeVersion;
32
33/// Base ops
34#[derive(Debug, Enum)]
35pub enum BaseOp {
36    /// Stop
37    Stop,
38    /// Add
39    Add,
40    /// Multiply
41    Mul,
42    /// Subtract
43    Sub,
44    /// Divide
45    Div,
46    /// Sdiv
47    Sdiv,
48    /// Modulo
49    Mod,
50    /// Smod
51    Smod,
52    /// Addmod
53    Addmod,
54    /// Mulmod
55    Mulmod,
56    /// Signextend
57    Signextend,
58    /// Less than
59    Lt,
60    /// Greater than
61    Gt,
62    /// Slt
63    Slt,
64    /// Sgt
65    Sgt,
66    /// Equals
67    Eq,
68    /// Is zero
69    Iszero,
70    /// And
71    And,
72    /// Or
73    Or,
74    /// Xor
75    Xor,
76    /// Not
77    Not,
78    /// Byte
79    Byte,
80}
81
82impl BaseOp {
83    /// Match the op and get the cost
84    pub fn cost(&self) -> u64 {
85        match self {
86            BaseOp::Stop => 0,
87            BaseOp::Add => 12,
88            BaseOp::Mul => 20,
89            BaseOp::Sub => 12,
90            BaseOp::Div => 20,
91            BaseOp::Sdiv => 20,
92            BaseOp::Mod => 20,
93            BaseOp::Smod => 20,
94            BaseOp::Addmod => 32,
95            BaseOp::Mulmod => 32,
96            BaseOp::Signextend => 20,
97            BaseOp::Lt => 12,
98            BaseOp::Gt => 12,
99            BaseOp::Slt => 12,
100            BaseOp::Sgt => 12,
101            BaseOp::Eq => 12,
102            BaseOp::Iszero => 12,
103            BaseOp::And => 12,
104            BaseOp::Or => 12,
105            BaseOp::Xor => 12,
106            BaseOp::Not => 12,
107            BaseOp::Byte => 12,
108        }
109    }
110}
111
112/// Supported Hash Functions
113#[derive(Debug, Enum, PartialEq, Eq)]
114pub enum HashFunction {
115    /// Used for crypto addresses
116    Sha256RipeMD160,
117    /// Single Sha256
118    Sha256,
119    /// Double Sha256
120    Sha256_2,
121    /// Single Blake3
122    Blake3,
123}
124
125impl HashFunction {
126    fn block_size(&self) -> u16 {
127        match self {
128            HashFunction::Sha256 => 64,
129            HashFunction::Sha256_2 => 64,
130            HashFunction::Blake3 => 64,
131            HashFunction::Sha256RipeMD160 => 64,
132        }
133    }
134
135    fn rounds(&self) -> u16 {
136        match self {
137            HashFunction::Sha256 => 1,
138            HashFunction::Sha256_2 => 2,
139            HashFunction::Blake3 => 1,
140            HashFunction::Sha256RipeMD160 => 1,
141        }
142    }
143
144    fn block_cost(&self, fee_version: &FeeVersion) -> u64 {
145        match self {
146            HashFunction::Sha256 => fee_version.hashing.sha256_per_block,
147            HashFunction::Sha256_2 => fee_version.hashing.sha256_per_block,
148            HashFunction::Blake3 => fee_version.hashing.blake3_per_block,
149            HashFunction::Sha256RipeMD160 => fee_version.hashing.sha256_per_block,
150        }
151    }
152
153    fn base_cost(&self, fee_version: &FeeVersion) -> u64 {
154        match self {
155            HashFunction::Sha256 => fee_version.hashing.single_sha256_base,
156            // It's normal that the base cost for a sha256 will have a single sha256 base
157            // But it has an extra block
158            HashFunction::Sha256_2 => fee_version.hashing.single_sha256_base,
159            HashFunction::Blake3 => fee_version.hashing.blake3_base,
160            HashFunction::Sha256RipeMD160 => fee_version.hashing.sha256_ripe_md160_base,
161        }
162    }
163}
164
165/// A Hash Function Operation
166#[derive(Debug, PartialEq, Eq)]
167pub struct FunctionOp {
168    /// hash
169    pub(crate) hash: HashFunction,
170    /// rounds
171    pub(crate) rounds: u32,
172}
173
174impl FunctionOp {
175    /// The cost of the function
176    fn cost(&self, fee_version: &FeeVersion) -> Credits {
177        let block_cost = (self.rounds as u64).saturating_mul(self.hash.block_cost(fee_version));
178        self.hash.base_cost(fee_version).saturating_add(block_cost)
179    }
180
181    /// Create a new function operation with the following hash knowing the rounds it will take
182    /// in advance
183    pub fn new_with_round_count(hash: HashFunction, rounds: u32) -> Self {
184        FunctionOp { hash, rounds }
185    }
186
187    /// Create a new function operation with the following hash knowing the number of bytes
188    /// it will hash
189    pub fn new_with_byte_count(hash: HashFunction, byte_count: u16) -> Self {
190        let blocks = byte_count / hash.block_size() + 1;
191        let rounds = blocks + hash.rounds() - 1;
192        FunctionOp {
193            hash,
194            rounds: rounds as u32,
195        }
196    }
197}
198
199/// Drive operation
200// GroveOperation dominates every op vec on the write path; boxing it would
201// trade one inline copy for a per-op heap allocation in consensus-critical
202// batching, so the size disparity against the small cost variants is accepted.
203#[allow(clippy::large_enum_variant)]
204#[derive(Debug, Eq, PartialEq)]
205pub enum LowLevelDriveOperation {
206    /// Grove operation
207    GroveOperation(QualifiedGroveDbOp),
208    /// A drive operation
209    FunctionOperation(FunctionOp),
210    /// Calculated cost operation
211    CalculatedCostOperation(OperationCost),
212    /// Pre Calculated Fee Result
213    PreCalculatedFeeResult(FeeResult),
214}
215
216/// Shared rejection message for the three `Element` wrappers
217/// (`NonCounted` / `NotSummed` / `NotCountedOrSummed`) when asked to wrap an
218/// indexed tree.
219///
220/// grovedb's wrapper constructors reject indexed inners outright, and for a
221/// structural reason rather than an oversight: the wrapper suppresses the
222/// wrapped subtree's contribution to its parent's aggregate, but an indexed
223/// primary's parent element is exactly where that aggregate — and the
224/// secondary root keys derived from it — is committed. A wrapped indexed tree
225/// would have nowhere to hang its secondaries.
226///
227/// The shape that would reach this — a ranked index whose terminal
228/// property-name tree sits inside a value tree that itself aggregates, i.e. a
229/// compound ranked index `[a, b]` on a doctype that ALSO declares an
230/// aggregating index terminating at `[a]` — is rejected at contract-parse
231/// time (`validate_no_ranked_prefix_overlap` in rs-dpp), so this is the
232/// fail-closed backstop behind that check. Failing closed here is deliberate
233/// — the alternative is silently writing a non-indexed tree and having
234/// ranked queries return nothing.
235const INDEXED_INNER_UNWRAPPABLE: &str =
236    "an indexed tree cannot be wrapped in NonCounted / NotSummed / NotCountedOrSummed: the \
237     wrapper suppresses the subtree's contribution to its parent's aggregate, but an indexed \
238     primary commits its aggregate (and the derived secondary root keys) through that very \
239     parent element. A ranked index's terminal property-name tree therefore cannot live inside \
240     an aggregating value tree — i.e. a ranked compound index [a, b] cannot coexist with a \
241     countable/summable index terminating at [a]; contracts declaring that pair are rejected \
242     at parse time.";
243
244impl LowLevelDriveOperation {
245    /// Returns a list of the costs of the Drive operations.
246    /// Should only be used by Calculate fee
247    pub fn consume_to_fees_v0(
248        drive_operations: Vec<LowLevelDriveOperation>,
249        epoch: &Epoch,
250        epochs_per_era: u16,
251        fee_version: &FeeVersion,
252        previous_fee_versions: Option<&CachedEpochIndexFeeVersions>,
253    ) -> Result<Vec<FeeResult>, Error> {
254        drive_operations
255            .into_iter()
256            .map(|operation| match operation {
257                PreCalculatedFeeResult(f) => Ok(f),
258                FunctionOperation(op) => Ok(FeeResult {
259                    processing_fee: op.cost(fee_version),
260                    ..Default::default()
261                }),
262                _ => {
263                    let cost = operation.operation_cost()?;
264                    // There is no need for a checked multiply here because added bytes are u64 and
265                    // storage disk usage credit per byte should never be high enough to cause an overflow
266                    let storage_fee = cost.storage_cost.added_bytes as u64 * fee_version.storage.storage_disk_usage_credit_per_byte;
267                    let processing_fee = cost.ephemeral_cost(fee_version)?;
268                    let (fee_refunds, removed_bytes_from_system) =
269                        match cost.storage_cost.removed_bytes {
270                            NoStorageRemoval => (FeeRefunds::default(), 0),
271                            BasicStorageRemoval(amount) => {
272                                // this is not always considered an error
273                                (FeeRefunds::default(), amount)
274                            }
275                            SectionedStorageRemoval(mut removal_per_epoch_by_identifier) => {
276
277                                let system_amount = removal_per_epoch_by_identifier
278                                    .remove(&Identifier::default())
279                                    .map_or(0, |a| a.values().sum());
280                                if fee_version.fee_version_number == 1 {
281                                    (
282                                        FeeRefunds::from_storage_removal(
283                                            removal_per_epoch_by_identifier,
284                                            epoch.index,
285                                            epochs_per_era,
286                                            &BTreeMap::default(),
287                                        )?,
288                                        system_amount,
289                                    )
290                                } else {
291                                    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")))?;
292                                    (
293                                        FeeRefunds::from_storage_removal(
294                                            removal_per_epoch_by_identifier,
295                                            epoch.index,
296                                            epochs_per_era,
297                                            previous_fee_versions,
298                                        )?,
299                                        system_amount,
300                                    )
301                                }
302                            }
303                        };
304                    Ok(FeeResult {
305                        storage_fee,
306                        processing_fee,
307                        fee_refunds,
308                        removed_bytes_from_system,
309                    })
310                }
311            })
312            .collect()
313    }
314
315    /// Returns the cost of this operation
316    pub fn operation_cost(self) -> Result<OperationCost, Error> {
317        match self {
318            GroveOperation(_) => Err(Error::Drive(DriveError::CorruptedCodeExecution(
319                "grove operations must be executed, not directly transformed to costs",
320            ))),
321            CalculatedCostOperation(c) => Ok(c),
322            PreCalculatedFeeResult(_) => Err(Error::Drive(DriveError::CorruptedCodeExecution(
323                "pre calculated fees should not be requested by operation costs",
324            ))),
325            FunctionOperation(_) => Err(Error::Drive(DriveError::CorruptedCodeExecution(
326                "function operations should not be requested by operation costs",
327            ))),
328        }
329    }
330
331    /// Filters the groveDB ops from a list of operations and puts them in a `GroveDbOpBatch`.
332    pub fn combine_cost_operations(operations: &[LowLevelDriveOperation]) -> OperationCost {
333        let mut cost = OperationCost::default();
334        operations.iter().for_each(|op| {
335            if let CalculatedCostOperation(operation_cost) = op {
336                cost += operation_cost.clone()
337            }
338        });
339        cost
340    }
341
342    /// Filters the groveDB ops from a list of operations and puts them in a `GroveDbOpBatch`.
343    pub fn grovedb_operations_batch(
344        insert_operations: &[LowLevelDriveOperation],
345    ) -> GroveDbOpBatch {
346        let operations = insert_operations
347            .iter()
348            .filter_map(|op| match op {
349                GroveOperation(grovedb_op) => Some(grovedb_op.clone()),
350                _ => None,
351            })
352            .collect();
353        GroveDbOpBatch::from_operations(operations)
354    }
355
356    /// Filters the groveDB ops from a list of operations and puts them in a `GroveDbOpBatch`.
357    pub fn grovedb_operations_batch_consume(
358        insert_operations: Vec<LowLevelDriveOperation>,
359    ) -> GroveDbOpBatch {
360        let operations = insert_operations
361            .into_iter()
362            .filter_map(|op| match op {
363                GroveOperation(grovedb_op) => Some(grovedb_op),
364                _ => None,
365            })
366            .collect();
367        GroveDbOpBatch::from_operations(operations)
368    }
369
370    /// Filters the groveDB ops from a list of operations and puts them in a `GroveDbOpBatch`.
371    pub fn grovedb_operations_batch_consume_with_leftovers(
372        insert_operations: Vec<LowLevelDriveOperation>,
373    ) -> (GroveDbOpBatch, Vec<LowLevelDriveOperation>) {
374        let (grove_operations, other_operations): (Vec<_>, Vec<_>) =
375            insert_operations.into_iter().partition_map(|op| match op {
376                GroveOperation(grovedb_op) => itertools::Either::Left(grovedb_op),
377                _ => itertools::Either::Right(op),
378            });
379
380        (
381            GroveDbOpBatch::from_operations(grove_operations),
382            other_operations,
383        )
384    }
385
386    /// Filters the groveDB ops from a list of operations and collects them in a `Vec<QualifiedGroveDbOp>`.
387    pub fn grovedb_operations_consume(
388        insert_operations: Vec<LowLevelDriveOperation>,
389    ) -> Vec<QualifiedGroveDbOp> {
390        insert_operations
391            .into_iter()
392            .filter_map(|op| match op {
393                GroveOperation(grovedb_op) => Some(grovedb_op),
394                _ => None,
395            })
396            .collect()
397    }
398
399    /// Sets `GroveOperation` for inserting an empty tree at the given path and key
400    pub fn for_known_path_key_empty_tree(
401        path: Vec<Vec<u8>>,
402        key: Vec<u8>,
403        storage_flags: Option<&StorageFlags>,
404    ) -> Self {
405        let tree = match storage_flags {
406            Some(storage_flags) => {
407                Element::empty_tree_with_flags(storage_flags.to_some_element_flags())
408            }
409            None => Element::empty_tree(),
410        };
411
412        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
413    }
414
415    /// Sets `GroveOperation` for inserting an empty sum tree at the given path and key
416    pub fn for_known_path_key_empty_sum_tree(
417        path: Vec<Vec<u8>>,
418        key: Vec<u8>,
419        storage_flags: Option<&StorageFlags>,
420    ) -> Self {
421        let tree = match storage_flags {
422            Some(storage_flags) => {
423                Element::empty_sum_tree_with_flags(storage_flags.to_some_element_flags())
424            }
425            None => Element::empty_sum_tree(),
426        };
427
428        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
429    }
430
431    /// Sets `GroveOperation` for inserting an empty sum tree at the given path and key
432    pub fn for_known_path_key_empty_big_sum_tree(
433        path: Vec<Vec<u8>>,
434        key: Vec<u8>,
435        storage_flags: Option<&StorageFlags>,
436    ) -> Self {
437        let tree = match storage_flags {
438            Some(storage_flags) => {
439                Element::new_big_sum_tree_with_flags(None, storage_flags.to_some_element_flags())
440            }
441            None => Element::empty_big_sum_tree(),
442        };
443
444        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
445    }
446
447    /// Sets `GroveOperation` for inserting an empty count tree at the given path and key
448    pub fn for_known_path_key_empty_count_tree(
449        path: Vec<Vec<u8>>,
450        key: Vec<u8>,
451        storage_flags: Option<&StorageFlags>,
452    ) -> Self {
453        let tree = match storage_flags {
454            Some(storage_flags) => {
455                Element::new_count_tree_with_flags(None, storage_flags.to_some_element_flags())
456            }
457            None => Element::empty_count_tree(),
458        };
459
460        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
461    }
462
463    /// Sets `GroveOperation` for inserting an empty count tree at the given path and key
464    pub fn for_known_path_key_empty_count_sum_tree(
465        path: Vec<Vec<u8>>,
466        key: Vec<u8>,
467        storage_flags: Option<&StorageFlags>,
468    ) -> Self {
469        let tree = match storage_flags {
470            Some(storage_flags) => {
471                Element::new_count_sum_tree_with_flags(None, storage_flags.to_some_element_flags())
472            }
473            None => Element::new_count_sum_tree(None),
474        };
475
476        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
477    }
478
479    /// Sets `GroveOperation` for inserting an empty `NormalTree` wrapped in
480    /// `Element::NonCounted` at the given path and key. The wrapper makes
481    /// the inserted subtree contribute 0 to a parent count tree's aggregate
482    /// (per grovedb #654). Used by the index-walker for sibling continuations
483    /// inside a `range_countable` value tree, so e.g. a compound `byColorShape`
484    /// continuation under a `byColor` value tree (which is a `CountTree`)
485    /// doesn't pollute the byColor count.
486    pub fn for_known_path_key_empty_non_counted_normal_tree(
487        path: Vec<Vec<u8>>,
488        key: Vec<u8>,
489        storage_flags: Option<&StorageFlags>,
490    ) -> Self {
491        Self::for_known_path_key_empty_non_counted_tree(
492            path,
493            key,
494            TreeType::NormalTree,
495            storage_flags,
496        )
497        .expect("NormalTree NonCounted wrapping never fails")
498    }
499
500    /// Sets `GroveOperation` for inserting an empty tree of the given
501    /// `tree_type` wrapped in `Element::NonCounted`. The wrapper makes the
502    /// inserted subtree contribute 0 to a parent count tree's aggregate
503    /// count (per grovedb #654), regardless of the inner tree variant.
504    ///
505    /// Used by the index walker for sibling continuations inside a
506    /// `range_countable` value tree (a `CountTree`). Most continuations are
507    /// plain `NormalTree`, but in nested-`range_countable` cases (e.g. an
508    /// index `[color]` is range-countable AND a deeper compound index
509    /// `[color, size]` is also range-countable), the continuation
510    /// property-name tree at `"size"` is itself a `ProvableCountTree` and
511    /// must still contribute 0 to the parent `<c1>` `CountTree`.
512    ///
513    /// Returns an error for tree variants whose `NonCounted` wrapping
514    /// hasn't been validated end-to-end yet (currently anything outside
515    /// `NormalTree` / `CountTree` / `ProvableCountTree`).
516    pub fn for_known_path_key_empty_non_counted_tree(
517        path: Vec<Vec<u8>>,
518        key: Vec<u8>,
519        tree_type: TreeType,
520        storage_flags: Option<&StorageFlags>,
521    ) -> Result<Self, Error> {
522        // Per grovedb PR 670, `Element::new_non_counted` only wraps
523        // count-bearing trees — provable-count parents reject the
524        // wrapper at the merk-layer insert guard, and sum-bearing
525        // trees use dedicated `NotSummed` / `NotCountedOrSummed`
526        // wrappers (see [`Self::for_known_path_key_empty_not_summed_tree`]
527        // / [`Self::for_known_path_key_empty_not_counted_or_summed_tree`]).
528        let element_flags = storage_flags.map(|s| s.to_element_flags());
529        let inner = match tree_type {
530            TreeType::NormalTree => Element::empty_tree_with_flags(element_flags),
531            TreeType::CountTree => Element::empty_count_tree_with_flags(element_flags),
532            TreeType::ProvableCountTree => {
533                Element::empty_provable_count_tree_with_flags(element_flags)
534            }
535            TreeType::ProvableSumIndexedTree
536            | TreeType::ProvableCountIndexedTree
537            | TreeType::ProvableCountProvableSumIndexedTree => {
538                return Err(Error::Drive(DriveError::NotSupported(
539                    INDEXED_INNER_UNWRAPPABLE,
540                )));
541            }
542            _ => {
543                return Err(Error::Drive(DriveError::NotSupported(
544                    "NonCounted-wrapping is only supported for NormalTree, CountTree, and \
545                     ProvableCountTree. For sum-bearing continuations under a sum or \
546                     count+sum parent, use `for_known_path_key_empty_not_summed_tree` or \
547                     `for_known_path_key_empty_not_counted_or_summed_tree` instead.",
548                )));
549            }
550        };
551        // Propagate the grovedb error as a typed Drive error rather
552        // than `.expect`-ing. The match above already restricts `inner`
553        // to NormalTree / CountTree / ProvableCountTree — all of which
554        // `new_non_counted` accepts at the head this PR pins
555        // (`packages/rs-drive/Cargo.toml`'s grovedb rev) — so in
556        // practice this `?` is a no-op. Keeping it as `?` means a
557        // future grovedb bump that tightens `new_non_counted`'s
558        // accepted-variant set lands a typed `Error::GroveDB` at the
559        // call site instead of a runtime panic. The `?` conversion
560        // uses `impl From<grovedb::element::error::ElementError>`
561        // defined in `crate::error::mod.rs`.
562        let tree = Element::new_non_counted(inner)?;
563        Ok(LowLevelDriveOperation::insert_for_known_path_key_element(
564            path, key, tree,
565        ))
566    }
567
568    /// Sets `GroveOperation` for inserting an empty sum-bearing tree
569    /// wrapped in `Element::NotSummed` (grovedb PR 670). The wrapper
570    /// makes the inserted subtree contribute 0 to a parent sum tree's
571    /// running sum while still allowing any count it carries to
572    /// propagate normally. Used by the index walker for continuation
573    /// property-name trees inside a `summable`-but-not-`countable`
574    /// value tree. For continuations under a count+sum parent, use
575    /// [`Self::for_known_path_key_empty_not_counted_or_summed_tree`].
576    pub fn for_known_path_key_empty_not_summed_tree(
577        path: Vec<Vec<u8>>,
578        key: Vec<u8>,
579        tree_type: TreeType,
580        storage_flags: Option<&StorageFlags>,
581    ) -> Result<Self, Error> {
582        let element_flags = storage_flags.map(|s| s.to_element_flags());
583        let inner = match tree_type {
584            TreeType::SumTree => Element::empty_sum_tree_with_flags(element_flags),
585            TreeType::BigSumTree => Element::empty_big_sum_tree_with_flags(element_flags),
586            TreeType::ProvableSumTree => Element::empty_provable_sum_tree_with_flags(element_flags),
587            TreeType::CountSumTree => Element::empty_count_sum_tree_with_flags(element_flags),
588            TreeType::ProvableCountSumTree => {
589                Element::empty_provable_count_sum_tree_with_flags(element_flags)
590            }
591            TreeType::ProvableCountProvableSumTree => {
592                Element::empty_provable_count_provable_sum_tree_with_flags(element_flags)
593            }
594            TreeType::ProvableSumIndexedTree
595            | TreeType::ProvableCountIndexedTree
596            | TreeType::ProvableCountProvableSumIndexedTree => {
597                return Err(Error::Drive(DriveError::NotSupported(
598                    INDEXED_INNER_UNWRAPPABLE,
599                )));
600            }
601            _ => {
602                return Err(Error::Drive(DriveError::NotSupported(
603                    "NotSummed-wrapping is only supported for the six sum-bearing tree \
604                     variants (SumTree, BigSumTree, ProvableSumTree, CountSumTree, \
605                     ProvableCountSumTree, ProvableCountProvableSumTree).",
606                )));
607            }
608        };
609        let tree = Element::new_not_summed(inner).map_err(|_| {
610            Error::Drive(DriveError::NotSupported(
611                "Element::new_not_summed rejected the inner tree (unreachable given the \
612                 match above).",
613            ))
614        })?;
615        Ok(LowLevelDriveOperation::insert_for_known_path_key_element(
616            path, key, tree,
617        ))
618    }
619
620    /// Sets `GroveOperation` for inserting an empty inner tree wrapped
621    /// in the wrapper variant appropriate for an `aggregating_parent_tree_type`.
622    ///
623    /// Dispatcher around the three concrete wrapper helpers
624    /// ([`Self::for_known_path_key_empty_non_counted_tree`] /
625    /// [`Self::for_known_path_key_empty_not_summed_tree`] /
626    /// [`Self::for_known_path_key_empty_not_counted_or_summed_tree`])
627    /// keyed on **the parent's** tree type — the wrapper exists to
628    /// suppress contribution to the parent's aggregate, so the parent's
629    /// kind picks the wrapper:
630    /// - Pure count parents (`CountTree` / `ProvableCountTree`) →
631    ///   `Element::NonCounted`.
632    /// - Pure sum parents (`SumTree` / `BigSumTree` / `ProvableSumTree`)
633    ///   → `Element::NotSummed`.
634    /// - Combined count+sum parents (`CountSumTree` /
635    ///   `ProvableCountSumTree` / `ProvableCountProvableSumTree`) →
636    ///   `Element::NotCountedOrSummed`.
637    /// - Non-aggregating parents (`NormalTree`, etc.) — no wrapping
638    ///   needed; caller should use
639    ///   [`crate::fees::op::LowLevelDriveOperationTreeTypeConverter::empty_tree_operation_for_known_path_key`]
640    ///   directly. This dispatcher rejects them with `NotSupported`
641    ///   so an upstream bug surfaces immediately rather than silently
642    ///   emitting an unwrapped child that pollutes a future parent.
643    ///
644    /// `inner_tree_type` is the tree variant being inserted under the
645    /// parent — typically a property-name continuation tree
646    /// (`NormalTree` / `CountTree` / `ProvableCountTree` / their
647    /// sum-bearing siblings).
648    pub fn wrap_in_non_aggregated_for_parent_tree_type(
649        path: Vec<Vec<u8>>,
650        key: Vec<u8>,
651        aggregating_parent_tree_type: TreeType,
652        inner_tree_type: TreeType,
653        storage_flags: Option<&StorageFlags>,
654    ) -> Result<Self, Error> {
655        match aggregating_parent_tree_type {
656            // Count-only parents — wrap so the inner contributes 0 to
657            // the parent's count. The inner can be plain or itself
658            // count-bearing; the helper validates accepted variants.
659            //
660            // `ProvableCountIndexedTree` is included because an indexed
661            // primary aggregates exactly like the tree it mirrors: the
662            // wrapper choice depends on which axes the parent commits, and
663            // PCIT commits the same single count axis as `ProvableCountTree`.
664            TreeType::CountTree
665            | TreeType::ProvableCountTree
666            | TreeType::ProvableCountIndexedTree => {
667                Self::for_known_path_key_empty_non_counted_tree(
668                    path,
669                    key,
670                    inner_tree_type,
671                    storage_flags,
672                )
673            }
674            // Sum-only parents — wrap so the inner contributes 0 to
675            // the parent's sum. Inner must be sum-bearing (see
676            // `for_known_path_key_empty_not_summed_tree`'s accepted set).
677            TreeType::SumTree
678            | TreeType::BigSumTree
679            | TreeType::ProvableSumTree
680            | TreeType::ProvableSumIndexedTree => Self::for_known_path_key_empty_not_summed_tree(
681                path,
682                key,
683                inner_tree_type,
684                storage_flags,
685            ),
686            // Combined count+sum parents — wrap so both axes contribute
687            // 0. Inner must be sum-bearing.
688            TreeType::CountSumTree
689            | TreeType::ProvableCountSumTree
690            | TreeType::ProvableCountProvableSumTree
691            | TreeType::ProvableCountProvableSumIndexedTree => {
692                Self::for_known_path_key_empty_not_counted_or_summed_tree(
693                    path,
694                    key,
695                    inner_tree_type,
696                    storage_flags,
697                )
698            }
699            _ => Err(Error::Drive(DriveError::NotSupported(
700                "wrap_in_non_aggregated_for_parent_tree_type called with a non-aggregating \
701                 parent tree type — caller should use the unwrapped \
702                 `empty_tree_operation_for_known_path_key` path instead.",
703            ))),
704        }
705    }
706
707    /// Sets `GroveOperation` for inserting an empty sum-bearing tree
708    /// wrapped in `Element::NotCountedOrSummed` (grovedb PR 670).
709    /// Suppresses BOTH count and sum propagation to the parent — used
710    /// for continuation property-name trees under a count+sum
711    /// aggregating value tree (CountSumTree / ProvableCountSumTree /
712    /// ProvableCountProvableSumTree). Same accepted inner-type set as
713    /// [`Self::for_known_path_key_empty_not_summed_tree`].
714    pub fn for_known_path_key_empty_not_counted_or_summed_tree(
715        path: Vec<Vec<u8>>,
716        key: Vec<u8>,
717        tree_type: TreeType,
718        storage_flags: Option<&StorageFlags>,
719    ) -> Result<Self, Error> {
720        let element_flags = storage_flags.map(|s| s.to_element_flags());
721        let inner = match tree_type {
722            TreeType::SumTree => Element::empty_sum_tree_with_flags(element_flags),
723            TreeType::BigSumTree => Element::empty_big_sum_tree_with_flags(element_flags),
724            TreeType::ProvableSumTree => Element::empty_provable_sum_tree_with_flags(element_flags),
725            TreeType::CountSumTree => Element::empty_count_sum_tree_with_flags(element_flags),
726            TreeType::ProvableCountSumTree => {
727                Element::empty_provable_count_sum_tree_with_flags(element_flags)
728            }
729            TreeType::ProvableCountProvableSumTree => {
730                Element::empty_provable_count_provable_sum_tree_with_flags(element_flags)
731            }
732            TreeType::ProvableSumIndexedTree
733            | TreeType::ProvableCountIndexedTree
734            | TreeType::ProvableCountProvableSumIndexedTree => {
735                return Err(Error::Drive(DriveError::NotSupported(
736                    INDEXED_INNER_UNWRAPPABLE,
737                )));
738            }
739            _ => {
740                return Err(Error::Drive(DriveError::NotSupported(
741                    "NotCountedOrSummed-wrapping is only supported for the six sum-bearing \
742                     tree variants — see `for_known_path_key_empty_not_summed_tree`.",
743                )));
744            }
745        };
746        let tree = Element::new_not_counted_or_summed(inner).map_err(|_| {
747            Error::Drive(DriveError::NotSupported(
748                "Element::new_not_counted_or_summed rejected the inner tree (unreachable \
749                 given the match above).",
750            ))
751        })?;
752        Ok(LowLevelDriveOperation::insert_for_known_path_key_element(
753            path, key, tree,
754        ))
755    }
756
757    /// Sets `GroveOperation` for inserting an empty continuation tree under an
758    /// aggregating parent so it contributes **zero to every axis the parent
759    /// aggregates** — the v2 index walkers' replacement for
760    /// [`Self::wrap_in_non_aggregated_for_parent_tree_type`].
761    ///
762    /// The v0 dispatcher above covers only the diagonal of the parent×inner
763    /// matrix (count parent + count-ish inner, sum parent + sum-bearing
764    /// inner, count+sum parent + sum-bearing inner) and errors on everything
765    /// else, which made shared-prefix aggregate contracts (e.g. a summable
766    /// `[a]` next to a plain compound `[a, b]`) reject every document
767    /// insert. This dispatcher completes the matrix using only combinations
768    /// grovedb accepts:
769    /// - `CountTree` parent → `Element::NonCounted(inner)` for any inner
770    ///   tree variant (a `NonCounted` child contributes 0 to the count; the
771    ///   parent has no sum axis).
772    /// - `CountSumTree` parent → sum-bearing inner:
773    ///   `Element::NotCountedOrSummed(inner)`; non-sum inner:
774    ///   `Element::NonCounted(inner)` (count suppressed by the wrapper, sum
775    ///   contribution of a non-sum inner is 0 by definition —
776    ///   `sum_value_or_default()` returns 0 for it).
777    /// - `SumTree` / `BigSumTree` / `ProvableSumTree` parent → sum-bearing
778    ///   inner: `Element::NotSummed(inner)`; non-sum inner: **no wrapper at
779    ///   all** — a non-sum child already contributes 0 to a sum-only
780    ///   parent, and grovedb has no `NotSummed(non-sum)` form.
781    /// - Provable count-bearing parents (`ProvableCountTree` /
782    ///   `ProvableCountSumTree` / `ProvableCountProvableSumTree`) →
783    ///   `NotSupported`. These commit their count into every node hash and
784    ///   reject count-suppressed children at grovedb's insert guards
785    ///   (`TreeType::accepts_non_counted_children` /
786    ///   `accepts_not_counted_or_summed_children`), so callers must demote
787    ///   the parent first — see
788    ///   `crate::drive::document::index_level_tree_types`.
789    /// - Non-aggregating parents → `NotSupported`; use
790    ///   [`crate::fees::op::LowLevelDriveOperationTreeTypeConverter::empty_tree_operation_for_known_path_key`]
791    ///   directly.
792    ///
793    /// Only reachable from the v2 index walkers (platform-version gated);
794    /// the v0 dispatcher stays byte-identical for the frozen v0/v1 walkers.
795    pub fn for_known_path_key_empty_tree_contributing_zero_to_parent(
796        path: Vec<Vec<u8>>,
797        key: Vec<u8>,
798        aggregating_parent_tree_type: TreeType,
799        inner_tree_type: TreeType,
800        storage_flags: Option<&StorageFlags>,
801    ) -> Result<Self, Error> {
802        // An indexed inner is rejected under every aggregating parent,
803        // including the sum-only ones whose non-sum fallback below is
804        // unwrapped: a ranked index's terminal property-name tree must
805        // not live inside an aggregating value tree at all (see
806        // `INDEXED_INNER_UNWRAPPABLE`), and letting the unwrapped
807        // fallback quietly accept one would create the exact shape
808        // rs-dpp's single-property ranked rule and the ranked query
809        // picker both refuse to serve.
810        if matches!(
811            inner_tree_type,
812            TreeType::ProvableSumIndexedTree
813                | TreeType::ProvableCountIndexedTree
814                | TreeType::ProvableCountProvableSumIndexedTree
815        ) {
816            return Err(Error::Drive(DriveError::NotSupported(
817                INDEXED_INNER_UNWRAPPABLE,
818            )));
819        }
820        let inner_is_sum_bearing = matches!(
821            inner_tree_type,
822            TreeType::SumTree
823                | TreeType::BigSumTree
824                | TreeType::ProvableSumTree
825                | TreeType::CountSumTree
826                | TreeType::ProvableCountSumTree
827                | TreeType::ProvableCountProvableSumTree
828        );
829        match aggregating_parent_tree_type {
830            TreeType::CountTree => Self::for_known_path_key_empty_non_counted_any_tree(
831                path,
832                key,
833                inner_tree_type,
834                storage_flags,
835            ),
836            TreeType::CountSumTree => {
837                if inner_is_sum_bearing {
838                    Self::for_known_path_key_empty_not_counted_or_summed_tree(
839                        path,
840                        key,
841                        inner_tree_type,
842                        storage_flags,
843                    )
844                } else {
845                    Self::for_known_path_key_empty_non_counted_any_tree(
846                        path,
847                        key,
848                        inner_tree_type,
849                        storage_flags,
850                    )
851                }
852            }
853            TreeType::SumTree | TreeType::BigSumTree | TreeType::ProvableSumTree => {
854                if inner_is_sum_bearing {
855                    Self::for_known_path_key_empty_not_summed_tree(
856                        path,
857                        key,
858                        inner_tree_type,
859                        storage_flags,
860                    )
861                } else {
862                    inner_tree_type.empty_tree_operation_for_known_path_key(
863                        path,
864                        key,
865                        storage_flags,
866                    )
867                }
868            }
869            // Indexed parents are structurally impossible here: the
870            // ranked upgrade applies to *property-name* trees, and this
871            // dispatcher is only ever called with a **value** tree as the
872            // parent. Rejected explicitly rather than through the
873            // non-aggregating catch-all so a future change that starts
874            // hanging continuations under an indexed tree reports the real
875            // reason (the indexed primary's secondaries are keyed by its
876            // children's aggregates, which a zero-contributing child would
877            // silently fall out of).
878            TreeType::ProvableCountIndexedTree
879            | TreeType::ProvableSumIndexedTree
880            | TreeType::ProvableCountProvableSumIndexedTree => {
881                Err(Error::Drive(DriveError::NotSupported(
882                    "indexed trees are property-name trees, never value trees, so they cannot \
883                     host zero-contributing continuation children — see \
884                     crate::drive::document::ranked_index_tree_type.",
885                )))
886            }
887            TreeType::ProvableCountTree
888            | TreeType::ProvableCountSumTree
889            | TreeType::ProvableCountProvableSumTree => {
890                Err(Error::Drive(DriveError::NotSupported(
891                    "provable count-bearing parents cannot host zero-contributing children — \
892                 grovedb commits their count into every node hash and rejects NonCounted / \
893                 NotCountedOrSummed children; the index walker must demote such value trees \
894                 to CountSumTree before hanging continuations under them (see \
895                 index_level_tree_types_with_continuation_demotion).",
896                )))
897            }
898            _ => Err(Error::Drive(DriveError::NotSupported(
899                "for_known_path_key_empty_tree_contributing_zero_to_parent called with a \
900                 non-aggregating parent tree type — caller should use the unwrapped \
901                 `empty_tree_operation_for_known_path_key` path instead.",
902            ))),
903        }
904    }
905
906    /// Sets `GroveOperation` for inserting an empty tree of any of the nine
907    /// standard merk tree variants wrapped in `Element::NonCounted`.
908    /// Extends [`Self::for_known_path_key_empty_non_counted_tree`]'s
909    /// accepted set (`NormalTree` / `CountTree` / `ProvableCountTree`) with
910    /// the six sum-bearing variants: `Element::new_non_counted` accepts any
911    /// non-wrapper inner, and under the only parents the v2 walkers use it
912    /// for (`CountTree`, `CountSumTree` — both without per-node count
913    /// commitments) the wrapper suppresses the count contribution while a
914    /// sum-bearing inner's sum still propagates on the parent's sum axis if
915    /// it has one — which is exactly the v0-diagonal behavior for
916    /// count-only parents, and unreachable for `CountSumTree` parents (the
917    /// zero-contribution dispatcher routes their sum-bearing inners through
918    /// `NotCountedOrSummed` instead).
919    ///
920    /// Kept separate from the frozen v0 helper so pre-v14 consensus
921    /// behavior stays byte-identical.
922    pub fn for_known_path_key_empty_non_counted_any_tree(
923        path: Vec<Vec<u8>>,
924        key: Vec<u8>,
925        tree_type: TreeType,
926        storage_flags: Option<&StorageFlags>,
927    ) -> Result<Self, Error> {
928        let element_flags = storage_flags.map(|s| s.to_element_flags());
929        let inner = match tree_type {
930            TreeType::NormalTree => Element::empty_tree_with_flags(element_flags),
931            TreeType::SumTree => Element::empty_sum_tree_with_flags(element_flags),
932            TreeType::BigSumTree => Element::empty_big_sum_tree_with_flags(element_flags),
933            TreeType::CountTree => Element::empty_count_tree_with_flags(element_flags),
934            TreeType::CountSumTree => Element::empty_count_sum_tree_with_flags(element_flags),
935            TreeType::ProvableCountTree => {
936                Element::empty_provable_count_tree_with_flags(element_flags)
937            }
938            TreeType::ProvableCountSumTree => {
939                Element::empty_provable_count_sum_tree_with_flags(element_flags)
940            }
941            TreeType::ProvableSumTree => Element::empty_provable_sum_tree_with_flags(element_flags),
942            TreeType::ProvableCountProvableSumTree => {
943                Element::empty_provable_count_provable_sum_tree_with_flags(element_flags)
944            }
945            TreeType::ProvableSumIndexedTree
946            | TreeType::ProvableCountIndexedTree
947            | TreeType::ProvableCountProvableSumIndexedTree => {
948                return Err(Error::Drive(DriveError::NotSupported(
949                    INDEXED_INNER_UNWRAPPABLE,
950                )));
951            }
952            _ => {
953                return Err(Error::Drive(DriveError::NotSupported(
954                    "NonCounted-wrapping is only supported for the nine standard merk tree \
955                     variants; special trees (commitment / MMR / bulk-append / dense) are \
956                     never index continuation trees.",
957                )));
958            }
959        };
960        let tree = Element::new_non_counted(inner)?;
961        Ok(LowLevelDriveOperation::insert_for_known_path_key_element(
962            path, key, tree,
963        ))
964    }
965
966    /// Sets `GroveOperation` for inserting an empty provable count tree at the given path and key
967    pub fn for_known_path_key_empty_provable_count_tree(
968        path: Vec<Vec<u8>>,
969        key: Vec<u8>,
970        storage_flags: Option<&StorageFlags>,
971    ) -> Self {
972        let tree = match storage_flags {
973            Some(storage_flags) => Element::new_provable_count_tree_with_flags(
974                None,
975                storage_flags.to_some_element_flags(),
976            ),
977            None => Element::empty_provable_count_tree(),
978        };
979
980        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
981    }
982
983    /// Sets `GroveOperation` for inserting an empty provable sum tree at
984    /// the given path and key. The provable variant commits aggregated
985    /// sub-sums to every internal merk node, enabling O(log n)
986    /// `AggregateSumOnRange` proofs over range queries on the property
987    /// whose values feed the tree.
988    ///
989    /// Used by the index walker for property-name trees of indexes that
990    /// declare `rangeSummable: true` (mirrors the count-side
991    /// [`Self::for_known_path_key_empty_provable_count_tree`]).
992    pub fn for_known_path_key_empty_provable_sum_tree(
993        path: Vec<Vec<u8>>,
994        key: Vec<u8>,
995        storage_flags: Option<&StorageFlags>,
996    ) -> Self {
997        let tree = match storage_flags {
998            Some(storage_flags) => Element::new_provable_sum_tree_with_flags(
999                None,
1000                storage_flags.to_some_element_flags(),
1001            ),
1002            None => Element::empty_provable_sum_tree(),
1003        };
1004
1005        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
1006    }
1007
1008    /// Sets `GroveOperation` for inserting an empty provable
1009    /// count-sum tree at the given path and key. **Pre-PR-670
1010    /// variant**: per-node counts committed to every internal merk
1011    /// node, but the sum is only carried at the root (not per-node).
1012    /// Use this when an index declares `rangeCountable: true` plus
1013    /// non-range `summable: "<prop>"` — count queries get the
1014    /// `AggregateCountOnRange` benefit while sum queries return only
1015    /// the root total.
1016    pub fn for_known_path_key_empty_provable_count_sum_tree(
1017        path: Vec<Vec<u8>>,
1018        key: Vec<u8>,
1019        storage_flags: Option<&StorageFlags>,
1020    ) -> Self {
1021        let tree = match storage_flags {
1022            Some(storage_flags) => Element::new_provable_count_sum_tree_with_flags(
1023                None,
1024                storage_flags.to_some_element_flags(),
1025            ),
1026            None => Element::empty_provable_count_sum_tree(),
1027        };
1028
1029        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
1030    }
1031
1032    /// Sets `GroveOperation` for inserting an empty
1033    /// **provable-count-provable-sum** tree (PCPS) at the given path
1034    /// and key. The grovedb PR 670 newcomer: **both** per-node counts
1035    /// AND per-node sums committed to every internal merk node, so a
1036    /// single tree can answer both `AggregateCountOnRange`,
1037    /// `AggregateSumOnRange`, AND the new
1038    /// `AggregateCountAndSumOnRange` (combined) range queries.
1039    ///
1040    /// Used by the index walker for property-name trees of indexes
1041    /// that declare BOTH `rangeCountable: true` AND `rangeSummable:
1042    /// true`, and for primary-key trees that declare both at the
1043    /// doctype level. The dispatch table in
1044    /// [`crate::drive::document::primary_key_tree_type`]'s v1 arm
1045    /// picks `TreeType::ProvableCountProvableSumTree` for these
1046    /// cases.
1047    pub fn for_known_path_key_empty_provable_count_provable_sum_tree(
1048        path: Vec<Vec<u8>>,
1049        key: Vec<u8>,
1050        storage_flags: Option<&StorageFlags>,
1051    ) -> Self {
1052        let tree = match storage_flags {
1053            Some(storage_flags) => Element::new_provable_count_provable_sum_tree_with_flags(
1054                None,
1055                storage_flags.to_some_element_flags(),
1056            ),
1057            None => Element::empty_provable_count_provable_sum_tree(),
1058        };
1059
1060        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
1061    }
1062
1063    /// Sets `GroveOperation` for inserting an empty tree at the given path and key
1064    pub fn for_estimated_path_key_empty_tree(
1065        path: KeyInfoPath,
1066        key: KeyInfo,
1067        storage_flags: Option<&StorageFlags>,
1068    ) -> Self {
1069        let tree = match storage_flags {
1070            Some(storage_flags) => {
1071                Element::empty_tree_with_flags(storage_flags.to_some_element_flags())
1072            }
1073            None => Element::empty_tree(),
1074        };
1075
1076        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
1077    }
1078
1079    /// Sets `GroveOperation` for inserting an empty sum tree at the given path and key
1080    pub fn for_estimated_path_key_empty_sum_tree(
1081        path: KeyInfoPath,
1082        key: KeyInfo,
1083        storage_flags: Option<&StorageFlags>,
1084    ) -> Self {
1085        let tree = match storage_flags {
1086            Some(storage_flags) => {
1087                Element::empty_sum_tree_with_flags(storage_flags.to_some_element_flags())
1088            }
1089            None => Element::empty_sum_tree(),
1090        };
1091
1092        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
1093    }
1094
1095    /// Sets `GroveOperation` for inserting an empty count tree at the given (estimated) path and key
1096    pub fn for_estimated_path_key_empty_count_tree(
1097        path: KeyInfoPath,
1098        key: KeyInfo,
1099        storage_flags: Option<&StorageFlags>,
1100    ) -> Self {
1101        let tree = match storage_flags {
1102            Some(storage_flags) => {
1103                Element::empty_count_tree_with_flags(storage_flags.to_some_element_flags())
1104            }
1105            None => Element::empty_count_tree(),
1106        };
1107
1108        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
1109    }
1110
1111    /// Sets `GroveOperation` for inserting an empty provable count tree at the given (estimated) path and key
1112    pub fn for_estimated_path_key_empty_provable_count_tree(
1113        path: KeyInfoPath,
1114        key: KeyInfo,
1115        storage_flags: Option<&StorageFlags>,
1116    ) -> Self {
1117        let tree = match storage_flags {
1118            Some(storage_flags) => {
1119                Element::empty_provable_count_tree_with_flags(storage_flags.to_some_element_flags())
1120            }
1121            None => Element::empty_provable_count_tree(),
1122        };
1123
1124        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
1125    }
1126
1127    /// Cost-estimation analog of
1128    /// [`Self::for_known_path_key_empty_provable_sum_tree`]. See its doc.
1129    pub fn for_estimated_path_key_empty_provable_sum_tree(
1130        path: KeyInfoPath,
1131        key: KeyInfo,
1132        storage_flags: Option<&StorageFlags>,
1133    ) -> Self {
1134        let tree = match storage_flags {
1135            Some(storage_flags) => {
1136                Element::empty_provable_sum_tree_with_flags(storage_flags.to_some_element_flags())
1137            }
1138            None => Element::empty_provable_sum_tree(),
1139        };
1140
1141        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
1142    }
1143
1144    /// Cost-estimation analog of
1145    /// [`Self::for_known_path_key_empty_count_sum_tree`]. See its doc.
1146    pub fn for_estimated_path_key_empty_count_sum_tree(
1147        path: KeyInfoPath,
1148        key: KeyInfo,
1149        storage_flags: Option<&StorageFlags>,
1150    ) -> Self {
1151        let tree = match storage_flags {
1152            Some(storage_flags) => {
1153                Element::empty_count_sum_tree_with_flags(storage_flags.to_some_element_flags())
1154            }
1155            None => Element::empty_count_sum_tree(),
1156        };
1157
1158        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
1159    }
1160
1161    /// Cost-estimation analog of
1162    /// [`Self::for_known_path_key_empty_provable_count_sum_tree`]. See its
1163    /// doc.
1164    pub fn for_estimated_path_key_empty_provable_count_sum_tree(
1165        path: KeyInfoPath,
1166        key: KeyInfo,
1167        storage_flags: Option<&StorageFlags>,
1168    ) -> Self {
1169        let tree = match storage_flags {
1170            Some(storage_flags) => Element::empty_provable_count_sum_tree_with_flags(
1171                storage_flags.to_some_element_flags(),
1172            ),
1173            None => Element::empty_provable_count_sum_tree(),
1174        };
1175
1176        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
1177    }
1178
1179    /// Cost-estimation analog of
1180    /// [`Self::for_known_path_key_empty_provable_count_provable_sum_tree`].
1181    /// See its doc.
1182    pub fn for_estimated_path_key_empty_provable_count_provable_sum_tree(
1183        path: KeyInfoPath,
1184        key: KeyInfo,
1185        storage_flags: Option<&StorageFlags>,
1186    ) -> Self {
1187        let tree = match storage_flags {
1188            Some(storage_flags) => Element::empty_provable_count_provable_sum_tree_with_flags(
1189                storage_flags.to_some_element_flags(),
1190            ),
1191            None => Element::empty_provable_count_provable_sum_tree(),
1192        };
1193
1194        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
1195    }
1196
1197    /// Sets `GroveOperation` for inserting an empty **provable
1198    /// count-indexed** tree (PCIT, grovedb PR 657) at the given path and
1199    /// key. The primary Merk is a byte-compatible mirror of
1200    /// `ProvableCountTree`, so every existing `AggregateCountOnRange` read
1201    /// keeps working against it; what the indexed variant adds is one
1202    /// ordered secondary Merk keyed by `(count_be ‖ child_key)`, which is
1203    /// what makes "top / bottom K groups by document count" O(log n + k)
1204    /// with a proof.
1205    ///
1206    /// Used at contract registration (and by the index walkers when a deeper
1207    /// level is materialized lazily) for an index that declares
1208    /// `rankedCountable: true` while its range layout is count-only. An index
1209    /// that also declares `rangeSummable` lays out as PCPS underneath and
1210    /// therefore takes the multi-axis
1211    /// [`Self::for_known_path_key_empty_provable_count_provable_sum_indexed_tree`]
1212    /// path instead, even when Count is its only ranking axis.
1213    pub fn for_known_path_key_empty_provable_count_indexed_tree(
1214        path: Vec<Vec<u8>>,
1215        key: Vec<u8>,
1216        storage_flags: Option<&StorageFlags>,
1217    ) -> Self {
1218        let tree = match storage_flags {
1219            Some(storage_flags) => Element::empty_provable_count_indexed_tree_with_flags(
1220                storage_flags.to_some_element_flags(),
1221            ),
1222            None => Element::empty_provable_count_indexed_tree(),
1223        };
1224
1225        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
1226    }
1227
1228    /// Sum-axis counterpart of
1229    /// [`Self::for_known_path_key_empty_provable_count_indexed_tree`]: an
1230    /// empty **provable sum-indexed** tree (PSIT) whose primary mirrors
1231    /// `ProvableSumTree` and whose single secondary is keyed by
1232    /// `(sum_sortable_be ‖ child_key)`.
1233    pub fn for_known_path_key_empty_provable_sum_indexed_tree(
1234        path: Vec<Vec<u8>>,
1235        key: Vec<u8>,
1236        storage_flags: Option<&StorageFlags>,
1237    ) -> Self {
1238        let tree = match storage_flags {
1239            Some(storage_flags) => Element::empty_provable_sum_indexed_tree_with_flags(
1240                storage_flags.to_some_element_flags(),
1241            ),
1242            None => Element::empty_provable_sum_indexed_tree(),
1243        };
1244
1245        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
1246    }
1247
1248    /// Sets `GroveOperation` for inserting an empty **provable count +
1249    /// provable sum indexed** tree (PCPSIT, grovedb PR 657) at the given path
1250    /// and key, carrying `ranked_axes` — the canonical
1251    /// `(axis_tag, secondary_root_key)` TLV, sorted ascending by tag with no
1252    /// duplicates and 1..=3 entries. Every secondary starts empty, so each
1253    /// root key is `None`.
1254    ///
1255    /// Unlike the two single-axis helpers this one returns a `Result`: the
1256    /// axes list is caller-supplied and grovedb validates it
1257    /// (`Element::validate_pcpsit_axes`), because an out-of-order, duplicated
1258    /// or empty TLV would still be hashed into the parent via `axes_digest`
1259    /// and produce a tree whose secondaries nothing can address.
1260    pub fn for_known_path_key_empty_provable_count_provable_sum_indexed_tree(
1261        path: Vec<Vec<u8>>,
1262        key: Vec<u8>,
1263        ranked_axes: Vec<(u8, Option<Vec<u8>>)>,
1264        storage_flags: Option<&StorageFlags>,
1265    ) -> Result<Self, Error> {
1266        let tree = match storage_flags {
1267            Some(storage_flags) => {
1268                Element::empty_provable_count_provable_sum_indexed_tree_with_flags(
1269                    ranked_axes,
1270                    storage_flags.to_some_element_flags(),
1271                )?
1272            }
1273            None => Element::empty_provable_count_provable_sum_indexed_tree(ranked_axes)?,
1274        };
1275
1276        Ok(LowLevelDriveOperation::insert_for_known_path_key_element(
1277            path, key, tree,
1278        ))
1279    }
1280
1281    /// Dispatcher over the three indexed-tree constructors, keyed on the
1282    /// resolved `tree_type` and the ranking axes that produced it (see
1283    /// [`crate::drive::document::ranked_index_tree_type`]).
1284    ///
1285    /// `ranked_axes` must be non-empty and canonical; the single-axis
1286    /// variants additionally require that the axis matches the variant, since
1287    /// their element shape hard-codes which aggregate the one secondary is
1288    /// keyed by. Any other combination is an upstream resolution bug and is
1289    /// rejected rather than silently narrowed.
1290    pub fn for_known_path_key_empty_indexed_tree(
1291        path: Vec<Vec<u8>>,
1292        key: Vec<u8>,
1293        tree_type: TreeType,
1294        ranked_axes: &[IndexAxis],
1295        storage_flags: Option<&StorageFlags>,
1296    ) -> Result<Self, Error> {
1297        match (tree_type, ranked_axes) {
1298            (TreeType::ProvableCountIndexedTree, [IndexAxis::Count]) => {
1299                Ok(Self::for_known_path_key_empty_provable_count_indexed_tree(
1300                    path,
1301                    key,
1302                    storage_flags,
1303                ))
1304            }
1305            (TreeType::ProvableSumIndexedTree, [IndexAxis::Sum]) => Ok(
1306                Self::for_known_path_key_empty_provable_sum_indexed_tree(path, key, storage_flags),
1307            ),
1308            (TreeType::ProvableCountProvableSumIndexedTree, axes) if !axes.is_empty() => {
1309                Self::for_known_path_key_empty_provable_count_provable_sum_indexed_tree(
1310                    path,
1311                    key,
1312                    axes.iter().map(|axis| (axis.tag(), None)).collect(),
1313                    storage_flags,
1314                )
1315            }
1316            _ => Err(Error::Drive(DriveError::NotSupported(
1317                "for_known_path_key_empty_indexed_tree called with a tree type / ranked-axis \
1318                 pair that does not describe an indexed tree — the single-axis PCIT / PSIT \
1319                 variants accept exactly their own axis and PCPSIT needs a non-empty axis list.",
1320            ))),
1321        }
1322    }
1323
1324    /// Cost-estimation analog of
1325    /// [`Self::for_known_path_key_empty_provable_count_indexed_tree`].
1326    pub fn for_estimated_path_key_empty_provable_count_indexed_tree(
1327        path: KeyInfoPath,
1328        key: KeyInfo,
1329        storage_flags: Option<&StorageFlags>,
1330    ) -> Self {
1331        let tree = match storage_flags {
1332            Some(storage_flags) => Element::empty_provable_count_indexed_tree_with_flags(
1333                storage_flags.to_some_element_flags(),
1334            ),
1335            None => Element::empty_provable_count_indexed_tree(),
1336        };
1337
1338        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
1339    }
1340
1341    /// Cost-estimation analog of
1342    /// [`Self::for_known_path_key_empty_provable_sum_indexed_tree`].
1343    pub fn for_estimated_path_key_empty_provable_sum_indexed_tree(
1344        path: KeyInfoPath,
1345        key: KeyInfo,
1346        storage_flags: Option<&StorageFlags>,
1347    ) -> Self {
1348        let tree = match storage_flags {
1349            Some(storage_flags) => Element::empty_provable_sum_indexed_tree_with_flags(
1350                storage_flags.to_some_element_flags(),
1351            ),
1352            None => Element::empty_provable_sum_indexed_tree(),
1353        };
1354
1355        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
1356    }
1357
1358    /// Cost-estimation analog of
1359    /// [`Self::for_known_path_key_empty_provable_count_provable_sum_indexed_tree`].
1360    pub fn for_estimated_path_key_empty_provable_count_provable_sum_indexed_tree(
1361        path: KeyInfoPath,
1362        key: KeyInfo,
1363        ranked_axes: Vec<(u8, Option<Vec<u8>>)>,
1364        storage_flags: Option<&StorageFlags>,
1365    ) -> Result<Self, Error> {
1366        let tree = match storage_flags {
1367            Some(storage_flags) => {
1368                Element::empty_provable_count_provable_sum_indexed_tree_with_flags(
1369                    ranked_axes,
1370                    storage_flags.to_some_element_flags(),
1371                )?
1372            }
1373            None => Element::empty_provable_count_provable_sum_indexed_tree(ranked_axes)?,
1374        };
1375
1376        Ok(LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree))
1377    }
1378
1379    /// Sets `GroveOperation` for inserting an element at the given path and key
1380    pub fn insert_for_known_path_key_element(
1381        path: Vec<Vec<u8>>,
1382        key: Vec<u8>,
1383        element: Element,
1384    ) -> Self {
1385        GroveOperation(QualifiedGroveDbOp::insert_or_replace_op(path, key, element))
1386    }
1387
1388    /// Sets `GroveOperation` for replacement of an element at the given path and key
1389    pub fn replace_for_known_path_key_element(
1390        path: Vec<Vec<u8>>,
1391        key: Vec<u8>,
1392        element: Element,
1393    ) -> Self {
1394        GroveOperation(QualifiedGroveDbOp::replace_op(path, key, element))
1395    }
1396
1397    /// Sets `GroveOperation` for patching of an element at the given path and key
1398    /// This is different from replacement which does not add or delete bytes
1399    pub fn patch_for_known_path_key_element(
1400        path: Vec<Vec<u8>>,
1401        key: Vec<u8>,
1402        element: Element,
1403        change_in_bytes: i32,
1404    ) -> Self {
1405        GroveOperation(QualifiedGroveDbOp::patch_op(
1406            path,
1407            key,
1408            element,
1409            change_in_bytes,
1410        ))
1411    }
1412
1413    /// Sets `GroveOperation` for inserting an element at an unknown estimated path and key
1414    pub fn insert_for_estimated_path_key_element(
1415        path: KeyInfoPath,
1416        key: KeyInfo,
1417        element: Element,
1418    ) -> Self {
1419        GroveOperation(QualifiedGroveDbOp::insert_estimated_op(path, key, element))
1420    }
1421
1422    /// Sets `GroveOperation` for replacement of an element at an unknown estimated path and key
1423    pub fn replace_for_estimated_path_key_element(
1424        path: KeyInfoPath,
1425        key: KeyInfo,
1426        element: Element,
1427    ) -> Self {
1428        GroveOperation(QualifiedGroveDbOp::replace_estimated_op(path, key, element))
1429    }
1430
1431    /// Sets `GroveOperation` for refresh of a reference at the given path and key
1432    pub fn refresh_reference_for_known_path_key_reference_info(
1433        path: Vec<Vec<u8>>,
1434        key: Vec<u8>,
1435        reference_path_type: ReferencePathType,
1436        max_reference_hop: MaxReferenceHop,
1437        flags: Option<ElementFlags>,
1438        trust_refresh_reference: bool,
1439    ) -> Self {
1440        GroveOperation(QualifiedGroveDbOp::refresh_reference_op(
1441            path,
1442            key,
1443            reference_path_type,
1444            max_reference_hop,
1445            flags,
1446            // `non_counted: false` — Drive's index references contribute to
1447            // count aggregates on `ProvableCountTree` / `CountTree` parents
1448            // (and to count × sum aggregates on the dual-axis combined
1449            // trees). The non-counted variant exists in grovedb for
1450            // siblings-of-summable-only-trees that must not bump count
1451            // aggregates; Drive never refreshes those.
1452            false,
1453            trust_refresh_reference,
1454        ))
1455    }
1456
1457    /// Sets `GroveOperation` for refresh of a
1458    /// [`grovedb::Element::ReferenceWithSumItem`] at the given path and
1459    /// key, **overriding** the carried sum with `sum_value`.
1460    ///
1461    /// Used by document-update paths on `summable` indexes: when the
1462    /// summed property's value changes but the index keys do not, the
1463    /// reference body stays the same but its sum contribution must be
1464    /// rewritten so ancestor `SumTree` / `ProvableCountSumTree` /
1465    /// `ProvableCountProvableSumTree` aggregates pick up the delta.
1466    ///
1467    /// Mirrors [`Self::refresh_reference_for_known_path_key_reference_info`]
1468    /// but emits a grovedb `RefreshReference` op in
1469    /// `SumItemReference*` mode instead of `PlainReference*` mode.
1470    pub fn refresh_reference_with_sum_item_for_known_path_key_reference_info(
1471        path: Vec<Vec<u8>>,
1472        key: Vec<u8>,
1473        reference_path_type: ReferencePathType,
1474        max_reference_hop: MaxReferenceHop,
1475        sum_value: i64,
1476        flags: Option<ElementFlags>,
1477        trust_refresh_reference: bool,
1478    ) -> Self {
1479        GroveOperation(QualifiedGroveDbOp::refresh_reference_with_sum_item_op(
1480            path,
1481            key,
1482            reference_path_type,
1483            max_reference_hop,
1484            sum_value,
1485            flags,
1486            // `non_counted: false` — see the count-tree rationale on the
1487            // plain-reference helper above. Same reasoning applies on the
1488            // sum side: index references always contribute to ancestor
1489            // count aggregates.
1490            false,
1491            trust_refresh_reference,
1492        ))
1493    }
1494}
1495
1496/// A trait for getting an empty tree operation based on the tree type
1497pub trait LowLevelDriveOperationTreeTypeConverter {
1498    /// Sets `GroveOperation` for inserting an empty tree at the given path and key
1499    fn empty_tree_operation_for_known_path_key(
1500        &self,
1501        path: Vec<Vec<u8>>,
1502        key: Vec<u8>,
1503        storage_flags: Option<&StorageFlags>,
1504    ) -> Result<LowLevelDriveOperation, Error>;
1505}
1506
1507impl LowLevelDriveOperationTreeTypeConverter for TreeType {
1508    /// Sets `GroveOperation` for inserting an empty tree at the given path and key
1509    fn empty_tree_operation_for_known_path_key(
1510        &self,
1511        path: Vec<Vec<u8>>,
1512        key: Vec<u8>,
1513        storage_flags: Option<&StorageFlags>,
1514    ) -> Result<LowLevelDriveOperation, Error> {
1515        let element_flags = storage_flags.map(|storage_flags| storage_flags.to_element_flags());
1516        let element = match self {
1517            TreeType::NormalTree => Element::empty_tree_with_flags(element_flags),
1518            TreeType::SumTree => Element::empty_sum_tree_with_flags(element_flags),
1519            TreeType::BigSumTree => Element::empty_big_sum_tree_with_flags(element_flags),
1520            TreeType::CountTree => Element::empty_count_tree_with_flags(element_flags),
1521            TreeType::CountSumTree => Element::empty_count_sum_tree_with_flags(element_flags),
1522            TreeType::ProvableCountTree => {
1523                Element::empty_provable_count_tree_with_flags(element_flags)
1524            }
1525            TreeType::ProvableCountSumTree => {
1526                Element::empty_provable_count_sum_tree_with_flags(element_flags)
1527            }
1528            TreeType::ProvableCountProvableSumTree => {
1529                Element::empty_provable_count_provable_sum_tree_with_flags(element_flags)
1530            }
1531            TreeType::ProvableSumTree => Element::empty_provable_sum_tree_with_flags(element_flags),
1532            TreeType::CommitmentTree(chunk_power) => {
1533                Element::empty_commitment_tree_with_flags(*chunk_power, element_flags)?
1534            }
1535            TreeType::MmrTree => Element::empty_mmr_tree_with_flags(element_flags),
1536            TreeType::BulkAppendTree(chunk_power) => {
1537                Element::empty_bulk_append_tree_with_flags(*chunk_power, element_flags)?
1538            }
1539            TreeType::DenseAppendOnlyFixedSizeTree(chunk_power) => {
1540                Element::empty_dense_tree_with_flags(*chunk_power, element_flags)
1541            }
1542            // Single-axis indexed trees (grovedb PR 657) carry no axis list
1543            // on the element — the one secondary Merk is implied by the
1544            // variant — so `TreeType` alone fully describes them and the
1545            // generic converter can build them.
1546            TreeType::ProvableSumIndexedTree => {
1547                Element::empty_provable_sum_indexed_tree_with_flags(element_flags)
1548            }
1549            TreeType::ProvableCountIndexedTree => {
1550                Element::empty_provable_count_indexed_tree_with_flags(element_flags)
1551            }
1552            // The multi-axis variant is the exception: its axes TLV lives
1553            // only on the `Element`, `TreeType` does not carry it, and an
1554            // empty TLV is rejected by grovedb's `validate_pcpsit_axes`. The
1555            // conversion is lossy by construction, not merely unimplemented,
1556            // so callers must route through
1557            // [`LowLevelDriveOperation::for_known_path_key_empty_indexed_tree`]
1558            // (or the dedicated
1559            // `batch_insert_empty_provable_count_provable_sum_indexed_tree`
1560            // helper) which take the axes explicitly. Erroring here keeps a
1561            // caller that forgot from silently emitting an axis-less indexed
1562            // tree whose secondaries nothing maintains.
1563            TreeType::ProvableCountProvableSumIndexedTree => {
1564                return Err(Error::Drive(DriveError::NotSupported(
1565                    "empty_tree_operation_for_known_path_key cannot create a \
1566                     ProvableCountProvableSumIndexedTree — the ranked axis set is not carried \
1567                     by TreeType; use for_known_path_key_empty_indexed_tree (or \
1568                     batch_insert_empty_provable_count_provable_sum_indexed_tree) instead.",
1569                )))
1570            }
1571            // A private document store's entry size lives only on the
1572            // `Element` (it does not affect Merk node layout), so `TreeType`
1573            // cannot describe the element to insert. Drive has no private
1574            // document store surface yet; when it does, creation must go
1575            // through a dedicated helper that takes the entry size.
1576            TreeType::PrivateDocumentStore(_) => {
1577                return Err(Error::Drive(DriveError::NotSupported(
1578                    "empty_tree_operation_for_known_path_key cannot create a \
1579                     PrivateDocumentStore — the entry size is not carried by TreeType",
1580                )))
1581            }
1582        };
1583
1584        Ok(LowLevelDriveOperation::insert_for_known_path_key_element(
1585            path, key, element,
1586        ))
1587    }
1588}
1589
1590/// Drive cost trait
1591pub trait DriveCost {
1592    /// Ephemeral cost
1593    fn ephemeral_cost(&self, fee_version: &FeeVersion) -> Result<u64, Error>;
1594}
1595
1596impl DriveCost for OperationCost {
1597    /// Return the ephemeral cost from the operation
1598    fn ephemeral_cost(&self, fee_version: &FeeVersion) -> Result<Credits, Error> {
1599        let OperationCost {
1600            seek_count,
1601            storage_cost,
1602            storage_loaded_bytes,
1603            hash_node_calls,
1604            sinsemilla_hash_calls,
1605        } = self;
1606        let epoch_cost_for_processing_credit_per_byte =
1607            fee_version.storage.storage_processing_credit_per_byte;
1608        let seek_cost = (*seek_count as u64)
1609            .checked_mul(fee_version.storage.storage_seek_cost)
1610            .ok_or_else(|| get_overflow_error("seek cost overflow"))?;
1611        let storage_added_bytes_ephemeral_cost = (storage_cost.added_bytes as u64)
1612            .checked_mul(epoch_cost_for_processing_credit_per_byte)
1613            .ok_or_else(|| get_overflow_error("storage written bytes cost overflow"))?;
1614        let storage_replaced_bytes_ephemeral_cost = (storage_cost.replaced_bytes as u64)
1615            .checked_mul(epoch_cost_for_processing_credit_per_byte)
1616            .ok_or_else(|| get_overflow_error("storage written bytes cost overflow"))?;
1617        let storage_removed_bytes_ephemeral_cost =
1618            (storage_cost.removed_bytes.total_removed_bytes() as u64)
1619                .checked_mul(epoch_cost_for_processing_credit_per_byte)
1620                .ok_or_else(|| get_overflow_error("storage written bytes cost overflow"))?;
1621        // not accessible
1622        let storage_loaded_bytes_cost = { *storage_loaded_bytes }
1623            .checked_mul(fee_version.storage.storage_load_credit_per_byte)
1624            .ok_or_else(|| get_overflow_error("storage loaded cost overflow"))?;
1625
1626        // There is one block per hash node call
1627        let blake3_total = fee_version.hashing.blake3_base + fee_version.hashing.blake3_per_block;
1628        // this can't overflow
1629        let hash_node_cost = blake3_total * (*hash_node_calls as u64);
1630        let sinsemilla_cost = fee_version.hashing.sinsemilla_base * (*sinsemilla_hash_calls as u64);
1631        seek_cost
1632            .checked_add(storage_added_bytes_ephemeral_cost)
1633            .and_then(|c| c.checked_add(storage_replaced_bytes_ephemeral_cost))
1634            .and_then(|c| c.checked_add(storage_loaded_bytes_cost))
1635            .and_then(|c| c.checked_add(storage_removed_bytes_ephemeral_cost))
1636            .and_then(|c| c.checked_add(hash_node_cost))
1637            .and_then(|c| c.checked_add(sinsemilla_cost))
1638            .ok_or_else(|| get_overflow_error("ephemeral cost addition overflow"))
1639    }
1640}
1641
1642#[cfg(test)]
1643#[allow(clippy::identity_op)]
1644mod tests {
1645    use super::*;
1646    use grovedb_costs::storage_cost::removal::StorageRemovedBytes;
1647    use grovedb_costs::storage_cost::StorageCost;
1648    use platform_version::version::fee::storage::FeeStorageVersion;
1649    use platform_version::version::fee::FeeVersion;
1650
1651    /// Helper to get the canonical fee version used across these tests.
1652    fn fee_version() -> &'static FeeVersion {
1653        FeeVersion::first()
1654    }
1655
1656    // ---------------------------------------------------------------
1657    // 1. BaseOp::cost() — spot-check several opcodes
1658    // ---------------------------------------------------------------
1659
1660    #[test]
1661    fn base_op_stop_costs_zero() {
1662        assert_eq!(BaseOp::Stop.cost(), 0);
1663    }
1664
1665    #[test]
1666    fn base_op_add_costs_12() {
1667        assert_eq!(BaseOp::Add.cost(), 12);
1668    }
1669
1670    #[test]
1671    fn base_op_mul_costs_20() {
1672        assert_eq!(BaseOp::Mul.cost(), 20);
1673    }
1674
1675    #[test]
1676    fn base_op_signextend_costs_20() {
1677        assert_eq!(BaseOp::Signextend.cost(), 20);
1678    }
1679
1680    #[test]
1681    fn base_op_addmod_costs_32() {
1682        assert_eq!(BaseOp::Addmod.cost(), 32);
1683    }
1684
1685    #[test]
1686    fn base_op_mulmod_costs_32() {
1687        assert_eq!(BaseOp::Mulmod.cost(), 32);
1688    }
1689
1690    #[test]
1691    fn base_op_byte_costs_12() {
1692        assert_eq!(BaseOp::Byte.cost(), 12);
1693    }
1694
1695    #[test]
1696    fn base_op_sub_costs_12() {
1697        assert_eq!(BaseOp::Sub.cost(), 12);
1698    }
1699
1700    #[test]
1701    fn base_op_div_costs_20() {
1702        assert_eq!(BaseOp::Div.cost(), 20);
1703    }
1704
1705    #[test]
1706    fn base_op_comparison_ops_all_cost_12() {
1707        for op in [
1708            BaseOp::Lt,
1709            BaseOp::Gt,
1710            BaseOp::Slt,
1711            BaseOp::Sgt,
1712            BaseOp::Eq,
1713            BaseOp::Iszero,
1714        ] {
1715            assert_eq!(op.cost(), 12, "comparison op {:?} should cost 12", op);
1716        }
1717    }
1718
1719    #[test]
1720    fn base_op_bitwise_ops_all_cost_12() {
1721        for op in [BaseOp::And, BaseOp::Or, BaseOp::Xor, BaseOp::Not] {
1722            assert_eq!(op.cost(), 12, "bitwise op {:?} should cost 12", op);
1723        }
1724    }
1725
1726    // ---------------------------------------------------------------
1727    // 2. HashFunction — block_size / rounds / block_cost / base_cost
1728    // ---------------------------------------------------------------
1729
1730    #[test]
1731    fn hash_function_block_size_all_64() {
1732        // All four hash functions currently have a 64-byte block size.
1733        assert_eq!(HashFunction::Sha256.block_size(), 64);
1734        assert_eq!(HashFunction::Sha256_2.block_size(), 64);
1735        assert_eq!(HashFunction::Blake3.block_size(), 64);
1736        assert_eq!(HashFunction::Sha256RipeMD160.block_size(), 64);
1737    }
1738
1739    #[test]
1740    fn hash_function_rounds() {
1741        assert_eq!(HashFunction::Sha256.rounds(), 1);
1742        assert_eq!(HashFunction::Sha256_2.rounds(), 2);
1743        assert_eq!(HashFunction::Blake3.rounds(), 1);
1744        assert_eq!(HashFunction::Sha256RipeMD160.rounds(), 1);
1745    }
1746
1747    #[test]
1748    fn hash_function_block_cost_sha256_variants_use_sha256_per_block() {
1749        let fv = fee_version();
1750        let expected = fv.hashing.sha256_per_block;
1751        assert_eq!(HashFunction::Sha256.block_cost(fv), expected);
1752        assert_eq!(HashFunction::Sha256_2.block_cost(fv), expected);
1753        assert_eq!(HashFunction::Sha256RipeMD160.block_cost(fv), expected);
1754    }
1755
1756    #[test]
1757    fn hash_function_block_cost_blake3_uses_blake3_per_block() {
1758        let fv = fee_version();
1759        assert_eq!(
1760            HashFunction::Blake3.block_cost(fv),
1761            fv.hashing.blake3_per_block
1762        );
1763    }
1764
1765    #[test]
1766    fn hash_function_base_cost_sha256() {
1767        let fv = fee_version();
1768        assert_eq!(
1769            HashFunction::Sha256.base_cost(fv),
1770            fv.hashing.single_sha256_base
1771        );
1772    }
1773
1774    #[test]
1775    fn hash_function_base_cost_sha256_2_uses_single_sha256_base() {
1776        let fv = fee_version();
1777        // Sha256_2 intentionally uses single_sha256_base (extra rounds handle the double hash).
1778        assert_eq!(
1779            HashFunction::Sha256_2.base_cost(fv),
1780            fv.hashing.single_sha256_base
1781        );
1782    }
1783
1784    #[test]
1785    fn hash_function_base_cost_blake3() {
1786        let fv = fee_version();
1787        assert_eq!(HashFunction::Blake3.base_cost(fv), fv.hashing.blake3_base);
1788    }
1789
1790    #[test]
1791    fn hash_function_base_cost_sha256_ripe_md160() {
1792        let fv = fee_version();
1793        assert_eq!(
1794            HashFunction::Sha256RipeMD160.base_cost(fv),
1795            fv.hashing.sha256_ripe_md160_base
1796        );
1797    }
1798
1799    // ---------------------------------------------------------------
1800    // 3. FunctionOp::new_with_byte_count — verify blocks/rounds calc
1801    // ---------------------------------------------------------------
1802
1803    #[test]
1804    fn function_op_new_with_byte_count_small_sha256() {
1805        // 32 bytes => blocks = 32/64 + 1 = 1, rounds = 1 + 1 - 1 = 1
1806        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 32);
1807        assert_eq!(op.rounds, 1);
1808        assert_eq!(op.hash, HashFunction::Sha256);
1809    }
1810
1811    #[test]
1812    fn function_op_new_with_byte_count_exact_block_boundary_sha256() {
1813        // 64 bytes => blocks = 64/64 + 1 = 2, rounds = 2 + 1 - 1 = 2
1814        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 64);
1815        assert_eq!(op.rounds, 2);
1816    }
1817
1818    #[test]
1819    fn function_op_new_with_byte_count_large_sha256() {
1820        // 200 bytes => blocks = 200/64 + 1 = 3 + 1 = 4, rounds = 4 + 1 - 1 = 4
1821        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 200);
1822        assert_eq!(op.rounds, 4);
1823    }
1824
1825    #[test]
1826    fn function_op_new_with_byte_count_sha256_2_has_extra_round() {
1827        // 32 bytes => blocks = 32/64 + 1 = 1, rounds = 1 + 2 - 1 = 2
1828        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256_2, 32);
1829        assert_eq!(op.rounds, 2);
1830    }
1831
1832    #[test]
1833    fn function_op_new_with_byte_count_sha256_2_large() {
1834        // 200 bytes => blocks = 200/64 + 1 = 4, rounds = 4 + 2 - 1 = 5
1835        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256_2, 200);
1836        assert_eq!(op.rounds, 5);
1837    }
1838
1839    #[test]
1840    fn function_op_new_with_byte_count_blake3_small() {
1841        // 10 bytes => blocks = 10/64 + 1 = 1, rounds = 1 + 1 - 1 = 1
1842        let op = FunctionOp::new_with_byte_count(HashFunction::Blake3, 10);
1843        assert_eq!(op.rounds, 1);
1844        assert_eq!(op.hash, HashFunction::Blake3);
1845    }
1846
1847    #[test]
1848    fn function_op_new_with_byte_count_blake3_large() {
1849        // 500 bytes => blocks = 500/64 + 1 = 7 + 1 = 8, rounds = 8 + 1 - 1 = 8
1850        let op = FunctionOp::new_with_byte_count(HashFunction::Blake3, 500);
1851        assert_eq!(op.rounds, 8);
1852    }
1853
1854    #[test]
1855    fn function_op_new_with_byte_count_zero_bytes() {
1856        // 0 bytes => blocks = 0/64 + 1 = 1, rounds = 1 + 1 - 1 = 1
1857        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 0);
1858        assert_eq!(op.rounds, 1);
1859    }
1860
1861    #[test]
1862    fn function_op_new_with_byte_count_sha256_ripemd160() {
1863        // 20 bytes => blocks = 20/64 + 1 = 1, rounds = 1 + 1 - 1 = 1
1864        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256RipeMD160, 20);
1865        assert_eq!(op.rounds, 1);
1866        assert_eq!(op.hash, HashFunction::Sha256RipeMD160);
1867    }
1868
1869    // ---------------------------------------------------------------
1870    // 4. FunctionOp::cost — verify rounds * block_cost + base_cost
1871    // ---------------------------------------------------------------
1872
1873    #[test]
1874    fn function_op_cost_sha256_one_round() {
1875        let fv = fee_version();
1876        let op = FunctionOp::new_with_round_count(HashFunction::Sha256, 1);
1877        // cost = base + rounds * block_cost = 100 + 1 * 5000 = 5100
1878        let expected = fv.hashing.single_sha256_base + 1 * fv.hashing.sha256_per_block;
1879        assert_eq!(op.cost(fv), expected);
1880    }
1881
1882    #[test]
1883    fn function_op_cost_sha256_2_two_rounds() {
1884        let fv = fee_version();
1885        let op = FunctionOp::new_with_round_count(HashFunction::Sha256_2, 2);
1886        // cost = base + rounds * block_cost = 100 + 2 * 5000 = 10100
1887        let expected = fv.hashing.single_sha256_base + 2 * fv.hashing.sha256_per_block;
1888        assert_eq!(op.cost(fv), expected);
1889    }
1890
1891    #[test]
1892    fn function_op_cost_blake3_one_round() {
1893        let fv = fee_version();
1894        let op = FunctionOp::new_with_round_count(HashFunction::Blake3, 1);
1895        // cost = blake3_base + 1 * blake3_per_block = 100 + 300 = 400
1896        let expected = fv.hashing.blake3_base + 1 * fv.hashing.blake3_per_block;
1897        assert_eq!(op.cost(fv), expected);
1898    }
1899
1900    #[test]
1901    fn function_op_cost_zero_rounds() {
1902        let fv = fee_version();
1903        let op = FunctionOp::new_with_round_count(HashFunction::Blake3, 0);
1904        // cost = blake3_base + 0 * blake3_per_block = blake3_base
1905        assert_eq!(op.cost(fv), fv.hashing.blake3_base);
1906    }
1907
1908    #[test]
1909    fn function_op_cost_from_byte_count_matches_manual_calc() {
1910        let fv = fee_version();
1911        // 128 bytes of SHA256: blocks = 128/64 + 1 = 3, rounds = 3 + 1 - 1 = 3
1912        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 128);
1913        assert_eq!(op.rounds, 3);
1914        let expected = fv.hashing.single_sha256_base + 3 * fv.hashing.sha256_per_block;
1915        assert_eq!(op.cost(fv), expected);
1916    }
1917
1918    #[test]
1919    fn function_op_cost_sha256_ripemd160() {
1920        let fv = fee_version();
1921        let op = FunctionOp::new_with_round_count(HashFunction::Sha256RipeMD160, 1);
1922        let expected = fv.hashing.sha256_ripe_md160_base + 1 * fv.hashing.sha256_per_block;
1923        assert_eq!(op.cost(fv), expected);
1924    }
1925
1926    #[test]
1927    fn function_op_cost_saturating_mul_does_not_panic_on_large_rounds() {
1928        let fv = fee_version();
1929        let op = FunctionOp::new_with_round_count(HashFunction::Sha256, u32::MAX);
1930        // u32::MAX as u64 * sha256_per_block (5000) fits in u64 without overflow,
1931        // so cost = base + rounds * block_cost, computed via saturating ops.
1932        let expected_block_cost = (u32::MAX as u64).saturating_mul(fv.hashing.sha256_per_block);
1933        let expected = fv
1934            .hashing
1935            .single_sha256_base
1936            .saturating_add(expected_block_cost);
1937        assert_eq!(op.cost(fv), expected);
1938    }
1939
1940    #[test]
1941    fn function_op_cost_saturates_to_max_with_extreme_fee_version() {
1942        // Construct a fee version where block_cost is large enough that
1943        // u32::MAX * block_cost overflows u64, triggering saturation.
1944        let mut fv = fee_version().clone();
1945        fv.hashing.sha256_per_block = u64::MAX;
1946        let op = FunctionOp::new_with_round_count(HashFunction::Sha256, 2);
1947        // 2 * u64::MAX saturates to u64::MAX, then base.saturating_add(u64::MAX) = u64::MAX.
1948        assert_eq!(op.cost(&fv), u64::MAX);
1949    }
1950
1951    // ---------------------------------------------------------------
1952    // 5. operation_cost() — test all 4 match arms
1953    // ---------------------------------------------------------------
1954
1955    #[test]
1956    fn operation_cost_calculated_cost_operation_returns_cost() {
1957        let cost = OperationCost {
1958            seek_count: 3,
1959            storage_cost: StorageCost {
1960                added_bytes: 100,
1961                replaced_bytes: 50,
1962                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1963            },
1964            storage_loaded_bytes: 200,
1965            hash_node_calls: 5,
1966            sinsemilla_hash_calls: 0,
1967        };
1968        let op = CalculatedCostOperation(cost.clone());
1969        let result = op.operation_cost().expect("should return Ok");
1970        assert_eq!(result, cost);
1971    }
1972
1973    #[test]
1974    fn operation_cost_grove_operation_returns_error() {
1975        let grove_op = LowLevelDriveOperation::insert_for_known_path_key_element(
1976            vec![vec![1, 2, 3]],
1977            vec![4, 5, 6],
1978            Element::empty_tree(),
1979        );
1980        let result = grove_op.operation_cost();
1981        assert!(result.is_err());
1982        let err_msg = format!("{:?}", result.unwrap_err());
1983        assert!(
1984            err_msg.contains("grove operations must be executed"),
1985            "unexpected error: {}",
1986            err_msg
1987        );
1988    }
1989
1990    #[test]
1991    fn operation_cost_pre_calculated_fee_result_returns_error() {
1992        let fee = FeeResult {
1993            storage_fee: 100,
1994            processing_fee: 200,
1995            ..Default::default()
1996        };
1997        let op = PreCalculatedFeeResult(fee);
1998        let result = op.operation_cost();
1999        assert!(result.is_err());
2000        let err_msg = format!("{:?}", result.unwrap_err());
2001        assert!(
2002            err_msg.contains("pre calculated fees should not be requested"),
2003            "unexpected error: {}",
2004            err_msg
2005        );
2006    }
2007
2008    #[test]
2009    fn operation_cost_function_operation_returns_error() {
2010        let func_op = FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Blake3, 1));
2011        let result = func_op.operation_cost();
2012        assert!(result.is_err());
2013        let err_msg = format!("{:?}", result.unwrap_err());
2014        assert!(
2015            err_msg.contains("function operations should not be requested"),
2016            "unexpected error: {}",
2017            err_msg
2018        );
2019    }
2020
2021    // ---------------------------------------------------------------
2022    // 6. combine_cost_operations — filter and sum
2023    // ---------------------------------------------------------------
2024
2025    #[test]
2026    fn combine_cost_operations_sums_calculated_costs_only() {
2027        let cost1 = OperationCost {
2028            seek_count: 2,
2029            storage_cost: StorageCost {
2030                added_bytes: 10,
2031                replaced_bytes: 0,
2032                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
2033            },
2034            storage_loaded_bytes: 50,
2035            hash_node_calls: 1,
2036            sinsemilla_hash_calls: 0,
2037        };
2038        let cost2 = OperationCost {
2039            seek_count: 3,
2040            storage_cost: StorageCost {
2041                added_bytes: 20,
2042                replaced_bytes: 5,
2043                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
2044            },
2045            storage_loaded_bytes: 100,
2046            hash_node_calls: 2,
2047            sinsemilla_hash_calls: 1,
2048        };
2049
2050        let operations = vec![
2051            CalculatedCostOperation(cost1.clone()),
2052            // This FunctionOperation should be ignored by combine_cost_operations
2053            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Sha256, 1)),
2054            CalculatedCostOperation(cost2.clone()),
2055            // PreCalculatedFeeResult should also be ignored
2056            PreCalculatedFeeResult(FeeResult::default()),
2057        ];
2058
2059        let combined = LowLevelDriveOperation::combine_cost_operations(&operations);
2060        assert_eq!(combined.seek_count, 2 + 3);
2061        assert_eq!(combined.storage_cost.added_bytes, 10 + 20);
2062        assert_eq!(combined.storage_cost.replaced_bytes, 0 + 5);
2063        assert_eq!(combined.storage_loaded_bytes, 50 + 100);
2064        assert_eq!(combined.hash_node_calls, 1 + 2);
2065        assert_eq!(combined.sinsemilla_hash_calls, 0 + 1);
2066    }
2067
2068    #[test]
2069    fn combine_cost_operations_empty_list_returns_default() {
2070        let combined = LowLevelDriveOperation::combine_cost_operations(&[]);
2071        assert_eq!(combined, OperationCost::default());
2072    }
2073
2074    #[test]
2075    fn combine_cost_operations_no_calculated_costs_returns_default() {
2076        let operations = vec![
2077            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Blake3, 2)),
2078            PreCalculatedFeeResult(FeeResult {
2079                processing_fee: 999,
2080                ..Default::default()
2081            }),
2082        ];
2083        let combined = LowLevelDriveOperation::combine_cost_operations(&operations);
2084        assert_eq!(combined, OperationCost::default());
2085    }
2086
2087    // ---------------------------------------------------------------
2088    // 7. grovedb_operations_batch / _consume / _consume_with_leftovers
2089    // ---------------------------------------------------------------
2090
2091    /// Helper: creates a GroveOperation variant (insert_or_replace).
2092    fn make_grove_op(key_byte: u8) -> LowLevelDriveOperation {
2093        LowLevelDriveOperation::insert_for_known_path_key_element(
2094            vec![vec![0]],
2095            vec![key_byte],
2096            Element::new_item(vec![key_byte]),
2097        )
2098    }
2099
2100    fn make_mixed_ops() -> Vec<LowLevelDriveOperation> {
2101        vec![
2102            make_grove_op(1),
2103            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Sha256, 1)),
2104            make_grove_op(2),
2105            CalculatedCostOperation(OperationCost::default()),
2106            make_grove_op(3),
2107        ]
2108    }
2109
2110    #[test]
2111    fn grovedb_operations_batch_filters_grove_ops_from_ref() {
2112        let ops = make_mixed_ops();
2113        let batch = LowLevelDriveOperation::grovedb_operations_batch(&ops);
2114        assert_eq!(batch.len(), 3);
2115    }
2116
2117    #[test]
2118    fn grovedb_operations_batch_empty_input() {
2119        let batch = LowLevelDriveOperation::grovedb_operations_batch(&[]);
2120        assert!(batch.is_empty());
2121    }
2122
2123    #[test]
2124    fn grovedb_operations_batch_no_grove_ops() {
2125        let ops = vec![
2126            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Blake3, 1)),
2127            CalculatedCostOperation(OperationCost::default()),
2128        ];
2129        let batch = LowLevelDriveOperation::grovedb_operations_batch(&ops);
2130        assert!(batch.is_empty());
2131    }
2132
2133    #[test]
2134    fn grovedb_operations_batch_consume_filters_grove_ops() {
2135        let ops = make_mixed_ops();
2136        let batch = LowLevelDriveOperation::grovedb_operations_batch_consume(ops);
2137        assert_eq!(batch.len(), 3);
2138    }
2139
2140    #[test]
2141    fn grovedb_operations_batch_consume_empty_input() {
2142        let batch = LowLevelDriveOperation::grovedb_operations_batch_consume(vec![]);
2143        assert!(batch.is_empty());
2144    }
2145
2146    #[test]
2147    fn grovedb_operations_batch_consume_with_leftovers_partitions_correctly() {
2148        let ops = make_mixed_ops();
2149        let (batch, leftovers) =
2150            LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(ops);
2151        assert_eq!(batch.len(), 3);
2152        assert_eq!(leftovers.len(), 2);
2153
2154        // Verify leftovers contain the non-grove operations.
2155        for leftover in &leftovers {
2156            assert!(
2157                !matches!(leftover, GroveOperation(_)),
2158                "leftovers should not contain GroveOperation variants"
2159            );
2160        }
2161    }
2162
2163    #[test]
2164    fn grovedb_operations_batch_consume_with_leftovers_all_grove() {
2165        let ops = vec![make_grove_op(10), make_grove_op(20)];
2166        let (batch, leftovers) =
2167            LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(ops);
2168        assert_eq!(batch.len(), 2);
2169        assert!(leftovers.is_empty());
2170    }
2171
2172    #[test]
2173    fn grovedb_operations_batch_consume_with_leftovers_no_grove() {
2174        let ops = vec![
2175            CalculatedCostOperation(OperationCost::default()),
2176            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Sha256, 1)),
2177        ];
2178        let (batch, leftovers) =
2179            LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(ops);
2180        assert!(batch.is_empty());
2181        assert_eq!(leftovers.len(), 2);
2182    }
2183
2184    #[test]
2185    fn grovedb_operations_batch_consume_with_leftovers_empty() {
2186        let (batch, leftovers) =
2187            LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(vec![]);
2188        assert!(batch.is_empty());
2189        assert!(leftovers.is_empty());
2190    }
2191
2192    // ---------------------------------------------------------------
2193    // 8. DriveCost::ephemeral_cost — various scenarios
2194    // ---------------------------------------------------------------
2195
2196    #[test]
2197    fn ephemeral_cost_zero_operation() {
2198        let fv = fee_version();
2199        let cost = OperationCost::default();
2200        let result = cost.ephemeral_cost(fv).expect("should not overflow");
2201        assert_eq!(result, 0);
2202    }
2203
2204    #[test]
2205    fn ephemeral_cost_seek_only() {
2206        let fv = fee_version();
2207        let cost = OperationCost {
2208            seek_count: 5,
2209            storage_cost: StorageCost::default(),
2210            storage_loaded_bytes: 0,
2211            hash_node_calls: 0,
2212            sinsemilla_hash_calls: 0,
2213        };
2214        let result = cost.ephemeral_cost(fv).expect("should not overflow");
2215        let expected = 5u64 * fv.storage.storage_seek_cost;
2216        assert_eq!(result, expected);
2217    }
2218
2219    #[test]
2220    fn ephemeral_cost_storage_added_bytes() {
2221        let fv = fee_version();
2222        let cost = OperationCost {
2223            seek_count: 0,
2224            storage_cost: StorageCost {
2225                added_bytes: 100,
2226                replaced_bytes: 0,
2227                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
2228            },
2229            storage_loaded_bytes: 0,
2230            hash_node_calls: 0,
2231            sinsemilla_hash_calls: 0,
2232        };
2233        let result = cost.ephemeral_cost(fv).expect("should not overflow");
2234        let expected = 100u64 * fv.storage.storage_processing_credit_per_byte;
2235        assert_eq!(result, expected);
2236    }
2237
2238    #[test]
2239    fn ephemeral_cost_storage_replaced_bytes() {
2240        let fv = fee_version();
2241        let cost = OperationCost {
2242            seek_count: 0,
2243            storage_cost: StorageCost {
2244                added_bytes: 0,
2245                replaced_bytes: 50,
2246                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
2247            },
2248            storage_loaded_bytes: 0,
2249            hash_node_calls: 0,
2250            sinsemilla_hash_calls: 0,
2251        };
2252        let result = cost.ephemeral_cost(fv).expect("should not overflow");
2253        let expected = 50u64 * fv.storage.storage_processing_credit_per_byte;
2254        assert_eq!(result, expected);
2255    }
2256
2257    #[test]
2258    fn ephemeral_cost_storage_removed_bytes_basic() {
2259        let fv = fee_version();
2260        let cost = OperationCost {
2261            seek_count: 0,
2262            storage_cost: StorageCost {
2263                added_bytes: 0,
2264                replaced_bytes: 0,
2265                removed_bytes: StorageRemovedBytes::BasicStorageRemoval(75),
2266            },
2267            storage_loaded_bytes: 0,
2268            hash_node_calls: 0,
2269            sinsemilla_hash_calls: 0,
2270        };
2271        let result = cost.ephemeral_cost(fv).expect("should not overflow");
2272        let expected = 75u64 * fv.storage.storage_processing_credit_per_byte;
2273        assert_eq!(result, expected);
2274    }
2275
2276    #[test]
2277    fn ephemeral_cost_loaded_bytes() {
2278        let fv = fee_version();
2279        let cost = OperationCost {
2280            seek_count: 0,
2281            storage_cost: StorageCost::default(),
2282            storage_loaded_bytes: 300,
2283            hash_node_calls: 0,
2284            sinsemilla_hash_calls: 0,
2285        };
2286        let result = cost.ephemeral_cost(fv).expect("should not overflow");
2287        let expected = 300u64 * fv.storage.storage_load_credit_per_byte;
2288        assert_eq!(result, expected);
2289    }
2290
2291    #[test]
2292    fn ephemeral_cost_hash_node_calls() {
2293        let fv = fee_version();
2294        let cost = OperationCost {
2295            seek_count: 0,
2296            storage_cost: StorageCost::default(),
2297            storage_loaded_bytes: 0,
2298            hash_node_calls: 10,
2299            sinsemilla_hash_calls: 0,
2300        };
2301        let result = cost.ephemeral_cost(fv).expect("should not overflow");
2302        let blake3_total = fv.hashing.blake3_base + fv.hashing.blake3_per_block;
2303        let expected = blake3_total * 10;
2304        assert_eq!(result, expected);
2305    }
2306
2307    #[test]
2308    fn ephemeral_cost_sinsemilla_hash_calls() {
2309        let fv = fee_version();
2310        let cost = OperationCost {
2311            seek_count: 0,
2312            storage_cost: StorageCost::default(),
2313            storage_loaded_bytes: 0,
2314            hash_node_calls: 0,
2315            sinsemilla_hash_calls: 3,
2316        };
2317        let result = cost.ephemeral_cost(fv).expect("should not overflow");
2318        let expected = fv.hashing.sinsemilla_base * 3;
2319        assert_eq!(result, expected);
2320    }
2321
2322    #[test]
2323    fn ephemeral_cost_all_components_combined() {
2324        let fv = fee_version();
2325        let cost = OperationCost {
2326            seek_count: 2,
2327            storage_cost: StorageCost {
2328                added_bytes: 10,
2329                replaced_bytes: 20,
2330                removed_bytes: StorageRemovedBytes::BasicStorageRemoval(30),
2331            },
2332            storage_loaded_bytes: 40,
2333            hash_node_calls: 5,
2334            sinsemilla_hash_calls: 1,
2335        };
2336        let result = cost.ephemeral_cost(fv).expect("should not overflow");
2337
2338        let seek_cost = 2u64 * fv.storage.storage_seek_cost;
2339        let processing_per_byte = fv.storage.storage_processing_credit_per_byte;
2340        let added_cost = 10u64 * processing_per_byte;
2341        let replaced_cost = 20u64 * processing_per_byte;
2342        let removed_cost = 30u64 * processing_per_byte;
2343        let loaded_cost = 40u64 * fv.storage.storage_load_credit_per_byte;
2344        let blake3_total = fv.hashing.blake3_base + fv.hashing.blake3_per_block;
2345        let hash_cost = blake3_total * 5;
2346        let sinsemilla_cost = fv.hashing.sinsemilla_base * 1;
2347
2348        let expected = seek_cost
2349            + added_cost
2350            + replaced_cost
2351            + loaded_cost
2352            + removed_cost
2353            + hash_cost
2354            + sinsemilla_cost;
2355        assert_eq!(result, expected);
2356    }
2357
2358    #[test]
2359    fn ephemeral_cost_overflow_seek_cost() {
2360        let fv = &FeeVersion {
2361            storage: FeeStorageVersion {
2362                storage_seek_cost: u64::MAX,
2363                ..fee_version().storage.clone()
2364            },
2365            ..fee_version().clone()
2366        };
2367        let cost = OperationCost {
2368            seek_count: 2, // 2 * u64::MAX overflows
2369            storage_cost: StorageCost::default(),
2370            storage_loaded_bytes: 0,
2371            hash_node_calls: 0,
2372            sinsemilla_hash_calls: 0,
2373        };
2374        let result = cost.ephemeral_cost(fv);
2375        assert!(result.is_err(), "expected overflow error for seek cost");
2376    }
2377
2378    #[test]
2379    fn ephemeral_cost_overflow_storage_written_bytes() {
2380        let fv = &FeeVersion {
2381            storage: FeeStorageVersion {
2382                storage_processing_credit_per_byte: u64::MAX,
2383                ..fee_version().storage.clone()
2384            },
2385            ..fee_version().clone()
2386        };
2387        let cost = OperationCost {
2388            seek_count: 0,
2389            storage_cost: StorageCost {
2390                added_bytes: 2, // 2 * u64::MAX overflows
2391                replaced_bytes: 0,
2392                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
2393            },
2394            storage_loaded_bytes: 0,
2395            hash_node_calls: 0,
2396            sinsemilla_hash_calls: 0,
2397        };
2398        let result = cost.ephemeral_cost(fv);
2399        assert!(
2400            result.is_err(),
2401            "expected overflow error for storage written bytes"
2402        );
2403    }
2404
2405    #[test]
2406    fn ephemeral_cost_overflow_loaded_bytes() {
2407        let fv = &FeeVersion {
2408            storage: FeeStorageVersion {
2409                storage_load_credit_per_byte: u64::MAX,
2410                ..fee_version().storage.clone()
2411            },
2412            ..fee_version().clone()
2413        };
2414        let cost = OperationCost {
2415            seek_count: 0,
2416            storage_cost: StorageCost::default(),
2417            storage_loaded_bytes: 2, // 2 * u64::MAX overflows
2418            hash_node_calls: 0,
2419            sinsemilla_hash_calls: 0,
2420        };
2421        let result = cost.ephemeral_cost(fv);
2422        assert!(
2423            result.is_err(),
2424            "expected overflow error for loaded bytes cost"
2425        );
2426    }
2427
2428    /// Covers the `TreeType::ProvableSumTree` arm of
2429    /// `LowLevelDriveOperationTreeTypeConverter::empty_tree_operation_for_known_path_key`
2430    /// added by the grovedb#661 bump. Drive doesn't currently construct
2431    /// `ProvableSumTree` anywhere else, so without this test the new arm is
2432    /// uncovered.
2433    #[test]
2434    fn empty_tree_operation_for_known_path_key_provable_sum_tree() {
2435        use grovedb::batch::GroveOp;
2436
2437        let op = TreeType::ProvableSumTree
2438            .empty_tree_operation_for_known_path_key(vec![b"root".to_vec()], b"k".to_vec(), None)
2439            .expect("empty_tree_operation_for_known_path_key");
2440
2441        match op {
2442            LowLevelDriveOperation::GroveOperation(grove_op) => match grove_op.op {
2443                GroveOp::InsertOrReplace { element } => assert!(
2444                    matches!(element, Element::ProvableSumTree(..)),
2445                    "expected ProvableSumTree element, got: {:?}",
2446                    element
2447                ),
2448                other => panic!("expected GroveOp::InsertOrReplace, got: {:?}", other),
2449            },
2450            other => panic!("expected GroveOperation, got: {:?}", other),
2451        }
2452    }
2453
2454    /// Table-driven pin of the v14 zero-contribution dispatcher: every
2455    /// accepted parent × inner cell must produce exactly the specified
2456    /// wrapper (or an unwrapped tree), and every rejected parent must
2457    /// error for every inner. This decides consensus-relevant element
2458    /// shapes for v14 continuation inserts, so a regression here (or a
2459    /// demotion-helper change routing a provable parent in) must fail
2460    /// loudly.
2461    #[test]
2462    fn zero_contribution_dispatcher_full_matrix() {
2463        use grovedb::batch::GroveOp;
2464
2465        const ALL_INNERS: [TreeType; 9] = [
2466            TreeType::NormalTree,
2467            TreeType::SumTree,
2468            TreeType::BigSumTree,
2469            TreeType::CountTree,
2470            TreeType::CountSumTree,
2471            TreeType::ProvableCountTree,
2472            TreeType::ProvableCountSumTree,
2473            TreeType::ProvableSumTree,
2474            TreeType::ProvableCountProvableSumTree,
2475        ];
2476
2477        fn is_sum_bearing(tree_type: TreeType) -> bool {
2478            matches!(
2479                tree_type,
2480                TreeType::SumTree
2481                    | TreeType::BigSumTree
2482                    | TreeType::CountSumTree
2483                    | TreeType::ProvableCountSumTree
2484                    | TreeType::ProvableSumTree
2485                    | TreeType::ProvableCountProvableSumTree
2486            )
2487        }
2488
2489        fn element_tree_type(element: &Element) -> TreeType {
2490            match element {
2491                Element::Tree(..) => TreeType::NormalTree,
2492                Element::SumTree(..) => TreeType::SumTree,
2493                Element::BigSumTree(..) => TreeType::BigSumTree,
2494                Element::CountTree(..) => TreeType::CountTree,
2495                Element::CountSumTree(..) => TreeType::CountSumTree,
2496                Element::ProvableCountTree(..) => TreeType::ProvableCountTree,
2497                Element::ProvableCountSumTree(..) => TreeType::ProvableCountSumTree,
2498                Element::ProvableSumTree(..) => TreeType::ProvableSumTree,
2499                Element::ProvableCountProvableSumTree(..) => TreeType::ProvableCountProvableSumTree,
2500                other => panic!("unexpected inner element: {other:?}"),
2501            }
2502        }
2503
2504        #[derive(Debug, PartialEq)]
2505        enum Expected {
2506            NonCounted,
2507            NotSummed,
2508            NotCountedOrSummed,
2509            Unwrapped,
2510        }
2511
2512        let dispatch = |parent: TreeType, inner: TreeType| {
2513            LowLevelDriveOperation::for_known_path_key_empty_tree_contributing_zero_to_parent(
2514                vec![b"root".to_vec()],
2515                b"key".to_vec(),
2516                parent,
2517                inner,
2518                None,
2519            )
2520        };
2521
2522        let assert_cell = |parent: TreeType, inner: TreeType, expected: Expected| {
2523            let op = dispatch(parent, inner).unwrap_or_else(|error| {
2524                panic!("parent {parent:?} inner {inner:?} must be accepted: {error}")
2525            });
2526            let element = match op {
2527                LowLevelDriveOperation::GroveOperation(grove_op) => match grove_op.op {
2528                    GroveOp::InsertOrReplace { element } => element,
2529                    other => panic!("expected InsertOrReplace, got {other:?}"),
2530                },
2531                other => panic!("expected GroveOperation, got {other:?}"),
2532            };
2533            let (wrapper, produced_inner) = match &element {
2534                Element::NonCounted(inner_element) => {
2535                    (Expected::NonCounted, inner_element.as_ref())
2536                }
2537                Element::NotSummed(inner_element) => (Expected::NotSummed, inner_element.as_ref()),
2538                Element::NotCountedOrSummed(inner_element) => {
2539                    (Expected::NotCountedOrSummed, inner_element.as_ref())
2540                }
2541                plain => (Expected::Unwrapped, plain),
2542            };
2543            assert_eq!(
2544                wrapper, expected,
2545                "parent {parent:?} inner {inner:?}: wrong wrapper"
2546            );
2547            assert_eq!(
2548                element_tree_type(produced_inner),
2549                inner,
2550                "parent {parent:?} inner {inner:?}: wrong inner tree type"
2551            );
2552        };
2553
2554        // Count-only parents wrap every inner NonCounted.
2555        for inner in ALL_INNERS {
2556            assert_cell(TreeType::CountTree, inner, Expected::NonCounted);
2557        }
2558        // Count-sum parents: sum-bearing inners get NotCountedOrSummed,
2559        // non-sum inners get NonCounted.
2560        for inner in ALL_INNERS {
2561            let expected = if is_sum_bearing(inner) {
2562                Expected::NotCountedOrSummed
2563            } else {
2564                Expected::NonCounted
2565            };
2566            assert_cell(TreeType::CountSumTree, inner, expected);
2567        }
2568        // Sum-only parents: sum-bearing inners get NotSummed, non-sum
2569        // inners are inserted unwrapped (they contribute 0 naturally).
2570        for parent in [
2571            TreeType::SumTree,
2572            TreeType::BigSumTree,
2573            TreeType::ProvableSumTree,
2574        ] {
2575            for inner in ALL_INNERS {
2576                let expected = if is_sum_bearing(inner) {
2577                    Expected::NotSummed
2578                } else {
2579                    Expected::Unwrapped
2580                };
2581                assert_cell(parent, inner, expected);
2582            }
2583        }
2584        // Provable count-bearing parents can't host zero-contributing
2585        // children (the walkers demote them first); non-aggregating
2586        // parents should use the plain path. Both must error for every
2587        // inner.
2588        for parent in [
2589            TreeType::NormalTree,
2590            TreeType::ProvableCountTree,
2591            TreeType::ProvableCountSumTree,
2592            TreeType::ProvableCountProvableSumTree,
2593        ] {
2594            for inner in ALL_INNERS {
2595                assert!(
2596                    dispatch(parent, inner).is_err(),
2597                    "parent {parent:?} inner {inner:?} must be rejected"
2598                );
2599            }
2600        }
2601
2602        // Ranked (indexed) trees are property-name trees, never value
2603        // trees, and can never be a continuation inside an aggregating
2604        // value tree — rejected in both roles, for every counterpart,
2605        // including the sum-only parents whose non-sum inners are
2606        // otherwise inserted unwrapped.
2607        const INDEXED: [TreeType; 3] = [
2608            TreeType::ProvableCountIndexedTree,
2609            TreeType::ProvableSumIndexedTree,
2610            TreeType::ProvableCountProvableSumIndexedTree,
2611        ];
2612        for indexed in INDEXED {
2613            for inner in ALL_INNERS {
2614                assert!(
2615                    dispatch(indexed, inner).is_err(),
2616                    "indexed parent {indexed:?} inner {inner:?} must be rejected"
2617                );
2618            }
2619            for parent in [
2620                TreeType::CountTree,
2621                TreeType::CountSumTree,
2622                TreeType::SumTree,
2623                TreeType::BigSumTree,
2624                TreeType::ProvableSumTree,
2625            ] {
2626                assert!(
2627                    dispatch(parent, indexed).is_err(),
2628                    "parent {parent:?} indexed inner {indexed:?} must be rejected"
2629                );
2630            }
2631        }
2632    }
2633
2634    #[test]
2635    fn ephemeral_cost_overflow_in_addition_chain() {
2636        // Use values that individually do not overflow but whose sum does.
2637        let fv = fee_version();
2638        let cost = OperationCost {
2639            seek_count: u32::MAX,
2640            storage_cost: StorageCost {
2641                added_bytes: u32::MAX,
2642                replaced_bytes: u32::MAX,
2643                removed_bytes: StorageRemovedBytes::BasicStorageRemoval(u32::MAX),
2644            },
2645            storage_loaded_bytes: u64::MAX,
2646            hash_node_calls: u32::MAX,
2647            sinsemilla_hash_calls: u32::MAX,
2648        };
2649        let result = cost.ephemeral_cost(fv);
2650        assert!(
2651            result.is_err(),
2652            "expected overflow error when summing large components"
2653        );
2654    }
2655}