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