Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3e6f29b
feat(private-document-store): add grovedb-private-document-store crate
QuantumExplorer Aug 3, 2026
26a804b
feat: add PrivateDocumentStore element type, gated to GROVE_V4 (#784)
QuantumExplorer Aug 3, 2026
7df52c3
test: raise PrivateDocumentStore patch coverage above the 90% codecov…
QuantumExplorer Aug 3, 2026
fff8363
fix: address CodeRabbit review on PrivateDocumentStore (PR #787)
QuantumExplorer Aug 3, 2026
b94da45
Merge origin/develop into feat/private-document-store
QuantumExplorer Aug 17, 2026
3f1d03e
fix: address the PrivateDocumentStore review findings (PR #787)
QuantumExplorer Aug 19, 2026
677508a
Merge remote-tracking branch 'origin/develop' into feat/private-docum…
QuantumExplorer Aug 19, 2026
4db7b47
fix: thread the declared chunk power into PrivateDocumentStore estimates
QuantumExplorer Aug 19, 2026
637b3ab
fix: address the second review round on PrivateDocumentStore (PR #787)
QuantumExplorer Aug 19, 2026
a6533cd
fix: bill the uncached MMR root read on the lazy path (PR #787)
QuantumExplorer Aug 19, 2026
1d2304c
fix: address the review-body findings on PrivateDocumentStore (PR #787)
QuantumExplorer Aug 19, 2026
2a90139
fix(costs): correct five hash and seek accounting errors on the store…
QuantumExplorer Aug 19, 2026
f05222d
test(pds): cover the corruption and storage-fault paths
QuantumExplorer Aug 19, 2026
540504b
fix(costs): bill compaction work and MMR merge hashes
QuantumExplorer Aug 19, 2026
b7aa74f
fix(costs): drop the duplicated MMR merge charge, and report bagging
QuantumExplorer Aug 19, 2026
f5fb2a7
feat(version): gate the MMR hash-charge corrections behind V0/V1
QuantumExplorer Aug 19, 2026
0e404d6
fix(costs): gate the CommitmentTree compaction under-charge into V4
QuantumExplorer Aug 19, 2026
6fa3277
fix(bulk-append): drop the dead initializer flagged by -D warnings
QuantumExplorer Aug 19, 2026
2c2e95c
refactor: take grove_version directly instead of paired _with_version…
QuantumExplorer Aug 20, 2026
11768ae
refactor(mmr): take grove_version directly on push, get_root and gen_…
QuantumExplorer Aug 20, 2026
f6e76c9
test(pds): cover the empty-at-creation guard on total_count
QuantumExplorer Aug 20, 2026
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ members = [
"grovedb-bulk-append-tree",
"grovedb-dense-fixed-sized-merkle-tree",
"grovedb-query",
"grovedb-private-document-store",
]

[workspace.dependencies]
Expand Down
51 changes: 51 additions & 0 deletions grovedb-element/src/element/constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,57 @@ impl Element {
Element::DenseAppendOnlyFixedSizeTree(count, height, flags)
}

/// Set element to an empty private document store.
///
/// Returns `InvalidInput` unless `entry_size >= 1` and `chunk_power` is
/// in `1..=16` (the underlying `BulkAppendTree` dense-buffer height
/// range). Unlike `empty_commitment_tree` / `empty_bulk_append_tree`,
/// the constraints are enforced eagerly here: the configuration is
/// committed into the state root, so an unusable config must never be
/// constructible.
pub fn empty_private_document_store(
entry_size: u32,
chunk_power: u8,
) -> Result<Self, ElementError> {
Self::empty_private_document_store_with_flags(entry_size, chunk_power, None)
}

/// Set element to an empty private document store with flags.
///
/// Same validation as [`Element::empty_private_document_store`].
pub fn empty_private_document_store_with_flags(
entry_size: u32,
chunk_power: u8,
flags: Option<ElementFlags>,
) -> Result<Self, ElementError> {
if entry_size == 0 {
return Err(ElementError::InvalidInput(
"private document store entry_size must be non-zero",
));
}
if !(1..=16).contains(&chunk_power) {
return Err(ElementError::InvalidInput(
"private document store chunk_power must be between 1 and 16",
));
}
Ok(Element::PrivateDocumentStore(
0,
entry_size,
chunk_power,
flags,
))
}

/// Set element to a private document store with all fields
pub fn new_private_document_store(
total_count: u64,
entry_size: u32,
chunk_power: u8,
flags: Option<ElementFlags>,
) -> Self {
Element::PrivateDocumentStore(total_count, entry_size, chunk_power, flags)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

/// Set element to an empty provable sum-indexed tree without flags.
pub fn empty_provable_sum_indexed_tree() -> Self {
Element::ProvableSumIndexedTree(None, None, 0, None)
Expand Down
20 changes: 20 additions & 0 deletions grovedb-element/src/element/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ impl Element {
| Element::ProvableSumIndexedTree(..)
| Element::ProvableCountIndexedTree(..)
| Element::ProvableCountProvableSumIndexedTree(..)
| Element::PrivateDocumentStore(..)
)
}

Expand Down Expand Up @@ -414,6 +415,12 @@ impl Element {
matches!(self.underlying(), Element::DenseAppendOnlyFixedSizeTree(..))
}

/// Check if the element is a private document store. Looks through
/// `NonCounted`.
pub fn is_private_document_store(&self) -> bool {
matches!(self.underlying(), Element::PrivateDocumentStore(..))
}

/// Check if the element is a tree type that stores data in the data
/// namespace as non-Merk entries. These tree types have an always-empty
/// Merk (root_key = None) and never contain child subtrees. The data
Expand All @@ -430,6 +437,7 @@ impl Element {
| Element::MmrTree(..)
| Element::BulkAppendTree(..)
| Element::DenseAppendOnlyFixedSizeTree(..)
| Element::PrivateDocumentStore(..)
)
}

Expand All @@ -443,6 +451,7 @@ impl Element {
Element::MmrTree(mmr_size, _) => Some(*mmr_size),
Element::BulkAppendTree(count, ..) => Some(*count),
Element::DenseAppendOnlyFixedSizeTree(count, ..) => Some(*count as u64),
Element::PrivateDocumentStore(count, ..) => Some(*count),
_ => None,
}
}
Expand Down Expand Up @@ -470,6 +479,7 @@ impl Element {
| Element::MmrTree(..)
| Element::BulkAppendTree(..)
| Element::DenseAppendOnlyFixedSizeTree(..)
| Element::PrivateDocumentStore(..)
| Element::ProvableSumIndexedTree(Some(_), ..)
| Element::ProvableSumIndexedTree(_, Some(_), ..)
| Element::ProvableCountIndexedTree(Some(_), ..)
Expand Down Expand Up @@ -663,6 +673,7 @@ impl Element {
| Element::MmrTree(.., flags)
| Element::BulkAppendTree(.., flags)
| Element::DenseAppendOnlyFixedSizeTree(.., flags)
| Element::PrivateDocumentStore(.., flags)
| Element::ProvableSumIndexedTree(.., flags)
| Element::ProvableCountIndexedTree(.., flags)
| Element::ReferenceWithSumItem(.., flags) => flags,
Expand Down Expand Up @@ -695,6 +706,7 @@ impl Element {
| Element::MmrTree(.., flags)
| Element::BulkAppendTree(.., flags)
| Element::DenseAppendOnlyFixedSizeTree(.., flags)
| Element::PrivateDocumentStore(.., flags)
| Element::ProvableSumIndexedTree(.., flags)
| Element::ProvableCountIndexedTree(.., flags)
| Element::ReferenceWithSumItem(.., flags) => flags,
Expand Down Expand Up @@ -727,6 +739,7 @@ impl Element {
| Element::MmrTree(.., flags)
| Element::BulkAppendTree(.., flags)
| Element::DenseAppendOnlyFixedSizeTree(.., flags)
| Element::PrivateDocumentStore(.., flags)
| Element::ProvableSumIndexedTree(.., flags)
| Element::ProvableCountIndexedTree(.., flags)
| Element::ReferenceWithSumItem(.., flags) => flags,
Expand Down Expand Up @@ -758,6 +771,7 @@ impl Element {
| Element::MmrTree(.., flags)
| Element::BulkAppendTree(.., flags)
| Element::DenseAppendOnlyFixedSizeTree(.., flags)
| Element::PrivateDocumentStore(.., flags)
| Element::ProvableSumIndexedTree(.., flags)
| Element::ProvableCountIndexedTree(.., flags)
| Element::ReferenceWithSumItem(.., flags) => *flags = new_flags,
Expand Down Expand Up @@ -1320,6 +1334,12 @@ mod flag_accessor_tests {
check_accessors_round_trip(e);
}

#[test]
fn private_document_store_flag_accessors() {
let e = Element::PrivateDocumentStore(0, 64, 4, flags());
check_accessors_round_trip(e);
}

#[test]
fn dense_append_only_tree_flag_accessors() {
let e = Element::DenseAppendOnlyFixedSizeTree(0, 0, flags());
Expand Down
44 changes: 44 additions & 0 deletions grovedb-element/src/element/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,31 @@ pub enum Element {
Vec<(u8, Option<Vec<u8>>)>,
Option<ElementFlags>,
),
/// Private document store: an append-only store of fixed-size opaque
/// entries, a thin wrapper over a `BulkAppendTree` (the same
/// relationship `CommitmentTree` has to it, minus the Sinsemilla
/// frontier). Entries are write-once — there is no per-entry delete or
/// update; immutability is enforced by the type.
///
/// Fields: `(total_count, entry_size, chunk_power, flags)`
/// - `total_count`: Number of entries appended so far.
/// - `entry_size`: Committed byte length of every entry; appends of any
/// other length are rejected.
/// - `chunk_power`: Log2 of the chunk size (actual size = `1 <<
/// chunk_power`).
/// - `flags`: Optional per-element metadata.
///
/// The state root
/// (`blake3("pds_state" || config_hash || bulk_state_root)`, where
/// `config_hash` commits to `{entry_size, chunk_power}`) flows through
/// the Merk child hash mechanism (`insert_subtree`'s
/// `subtree_root_hash` parameter), so the declared configuration is
/// consensus-visible and a proof can never be reinterpreted under a
/// different config.
///
/// Variant order in this enum determines bincode's variant-index
/// encoding on disk. This variant gets index 24.
PrivateDocumentStore(u64, u32, u8, Option<ElementFlags>),
}

pub fn hex_to_ascii(hex_value: &[u8]) -> String {
Expand Down Expand Up @@ -583,6 +608,18 @@ impl fmt::Display for Element {
.map_or(String::new(), |f| format!(", flags: {:?}", f))
)
}
Element::PrivateDocumentStore(total_count, entry_size, chunk_power, flags) => {
write!(
f,
"PrivateDocumentStore(count: {}, entry_size: {}, chunk_power: {}{})",
total_count,
entry_size,
chunk_power,
flags
.as_ref()
.map_or(String::new(), |f| format!(", flags: {:?}", f))
)
}
Element::NotSummed(inner) => {
write!(f, "NotSummed({})", inner)
}
Expand Down Expand Up @@ -660,6 +697,7 @@ impl Element {
Element::ProvableCountProvableSumIndexedTree(..) => {
ElementType::ProvableCountProvableSumIndexedTree
}
Element::PrivateDocumentStore(..) => ElementType::PrivateDocumentStore,
Element::NonCounted(inner) => match inner.element_type() {
ElementType::Item => ElementType::NonCountedItem,
ElementType::Reference => ElementType::NonCountedReference,
Expand Down Expand Up @@ -692,6 +730,7 @@ impl Element {
ElementType::ProvableCountProvableSumIndexedTree => {
ElementType::NonCountedProvableCountProvableSumIndexedTree
}
ElementType::PrivateDocumentStore => ElementType::NonCountedPrivateDocumentStore,
// Inner is always a base type — nested wrappers are
// forbidden at construction and (de)serialization.
already_non_counted => already_non_counted,
Expand Down Expand Up @@ -885,6 +924,7 @@ mod serde_impl {
Vec<(u8, Option<Vec<u8>>)>,
Option<ElementFlags>,
),
PrivateDocumentStore(u64, u32, u8, Option<ElementFlags>),
}

impl From<ElementShadow> for Element {
Expand Down Expand Up @@ -934,6 +974,9 @@ mod serde_impl {
ElementShadow::ProvableCountProvableSumIndexedTree(pk, c, s, axes, f) => {
Element::ProvableCountProvableSumIndexedTree(pk, c, s, axes, f)
}
ElementShadow::PrivateDocumentStore(c, e, p, f) => {
Element::PrivateDocumentStore(c, e, p, f)
}
}
}
}
Expand Down Expand Up @@ -983,6 +1026,7 @@ mod serde_impl {
let cases = vec![
Element::Item(b"abc".to_vec(), None),
Element::SumTree(Some(b"r".to_vec()), 42, None),
Element::PrivateDocumentStore(9, 64, 4, Some(vec![1])),
Element::new_non_counted(Element::Item(b"x".to_vec(), None)).unwrap(),
Element::new_not_summed(Element::SumTree(None, 100, None)).unwrap(),
Element::new_not_counted_or_summed(Element::CountSumTree(None, 3, 100, None))
Expand Down
15 changes: 15 additions & 0 deletions grovedb-element/src/element/visualize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,21 @@ impl Visualize for Element {
drawer = f.visualize(drawer)?;
}
}
Element::PrivateDocumentStore(total_count, entry_size, chunk_power, flags) => {
drawer.write(
format!(
"private_document_store: count: {total_count} entry_size: {entry_size} \
chunk_power: {chunk_power}",
)
.as_bytes(),
)?;

if let Some(f) = flags
&& !f.is_empty()
{
drawer = f.visualize(drawer)?;
}
}
Element::NonCounted(inner) => {
drawer.write(b"non_counted(")?;
drawer = inner.visualize(drawer)?;
Expand Down
Loading
Loading