Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 30 additions & 23 deletions grovedb-version/src/version/grovedb_versions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,34 +55,41 @@ pub struct GroveDBApplyBatchVersions {
///
/// - `0` (V1..V3): the caller-declared `TreeType` carried by the op is
/// taken at face value.
/// - `1` (V4+): the stored element is read and its actual type used
/// instead, and a declared/stored mismatch involving an indexed tree is
/// rejected. Closes an indexed type-confusion — a declared type hiding a
/// stored indexed primary skips the per-axis secondary sweep and leaves
/// - `1` (V4+): the ACTUAL stored type is used instead, and a
/// declared/stored mismatch involving an indexed tree is rejected.
/// Closes an indexed type-confusion — a declared type hiding a stored
/// indexed primary skips the per-axis secondary sweep and leaves
/// authenticated stale rows — and a `CommitmentTree` case where the
/// declared type sends the op down the wrong emptiness path, orphaning
/// its non-Merk data. Costs one extra stored-element read per op, which
/// is why it cannot apply to the released versions.
/// its non-Merk data.
///
/// The stored element comes from data the apply already loads (the
/// emptiness pre-scan's own read, or the old value the merk delete
/// surfaces through the old-value observer), so V4 charges exactly the
/// V1..V3 cost per op. The slot still gates the check because it flips
/// an accepted/rejected outcome — a mismatched delete that V1..V3
/// accept is refused on V4+ when an indexed tree is involved.
pub delete_tree_cleanup_type_source: FeatureVersion,
/// Whether a batch overwrite (`InsertOrReplace` / `Replace` / `Patch` of a
/// non-reference element, with tree-override protection off) reads the
/// stored element to detect an indexed tree being overwritten.
/// Whether a batch overwrite (`InsertOrReplace` / `Replace` / `Patch`,
/// with tree-override protection off) classifies the element it
/// displaces to detect an indexed tree being overwritten.
///
/// - `0` (V1..V3): no read. Overwrites keep their released cost shape.
/// - `1` (V4+): the stored element is read and, when it is an indexed
/// tree, the overwrite is classified — the safe subset (empty indexed or
/// non-indexed replacement) schedules the per-axis secondary storage for
/// cleanup, and an ambiguous non-empty indexed replacement is refused.
/// Without the read, overwriting an indexed primary would orphan its
/// secondary namespaces at their derived prefixes.
/// - `0` (V1..V3): no classification. Overwrites keep their released
/// accepted/rejected outcomes.
/// - `1` (V4+): the displaced element is classified — the safe subset
/// (empty indexed or non-indexed replacement, references included)
/// schedules the per-axis secondary storage for cleanup, and an
/// ambiguous non-empty indexed replacement is refused. Without this,
/// overwriting an indexed primary would orphan its secondary
/// namespaces at their derived prefixes.
///
/// Costs one extra stored-element read per overwrite-capable op, which
/// measurably changes tracked costs (+1 seek, +129 loaded bytes on the
/// repo's own cost tests) — cost feeds fees, so like
/// [`Self::delete_tree_cleanup_type_source`] it cannot apply to the
/// released versions. The hole it closes needs an indexed tree to be the
/// element being overwritten, which cannot occur before the version that
/// introduces indexed trees.
/// The old element bytes come from the node the merk walk fetched
/// anyway to rewrite the key, surfaced through the old-value observer —
/// no dedicated stored-element read, so V4 charges exactly the V1..V3
/// cost per overwrite-capable op. Like
/// [`Self::delete_tree_cleanup_type_source`] the slot gates behaviour,
/// not cost: a non-empty indexed replacement that would be accepted
/// blind on V1..V3 is refused on V4+.
pub overwrite_indexed_cleanup_inspection: FeatureVersion,
}

Expand Down
26 changes: 15 additions & 11 deletions grovedb-version/src/version/v4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,22 @@
//! and no longer is — gates land here as they are written. Currently flipped:
//!
//! - `apply_batch.delete_tree_cleanup_type_source: 1` — a batch `DeleteTree`
//! reads the stored element and uses its ACTUAL type to select cleanup
//! namespaces, rejecting a declared/stored mismatch that involves an indexed
//! tree. V1..V3 keep taking the declared type at face value. Costs one extra
//! stored-element read per op, which is why it cannot apply to the released
//! versions.
//! uses the stored element's ACTUAL type to select cleanup namespaces,
//! rejecting a declared/stored mismatch that involves an indexed tree.
//! V1..V3 keep taking the declared type at face value. The stored element
//! comes from data the apply already loads (the emptiness pre-scan's own
//! read, or the old value the merk delete surfaces through the old-value
//! observer), so V4 charges exactly the V1..V3 cost — the gate exists
//! because it flips an accepted/rejected outcome, not because of cost.
//!
//! - `apply_batch.overwrite_indexed_cleanup_inspection: 1` — a batch overwrite
//! of a non-reference element (with tree-override protection off) reads the
//! stored element to detect an indexed tree being overwritten, scheduling
//! its per-axis secondary storage for cleanup or refusing the ambiguous
//! case. Same shape as the gate above: one extra stored-element read per
//! overwrite-capable op, so V1..V3 keep their released cost shape.
//! - `apply_batch.overwrite_indexed_cleanup_inspection: 1` — a batch
//! overwrite (with tree-override protection off, references included)
//! classifies the element it displaces to detect an indexed tree being
//! overwritten, scheduling its per-axis secondary storage for cleanup or
//! refusing the ambiguous case. The old bytes come from the node the merk
//! walk fetched anyway to rewrite the key, so — like the gate above —
//! V1..V3 cost is charged exactly and only the accepted/rejected outcome
//! is gated.
//!
//! - `proof.terminal_non_merk_tree_child_hash: 1` — a V1 proof that reports a
//! `CommitmentTree` / `MmrTree` / `BulkAppendTree` /
Expand Down
Loading
Loading