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
59 changes: 55 additions & 4 deletions .github/workflows/grovedb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,38 @@ jobs:
echo "needs-tests=${{ steps.filter.outputs.any-code }}" >> "$GITHUB_OUTPUT"
fi

test:
name: Test (${{ matrix.partition }}/3)
# Fast path: run all tests on self-hosted Mac runner (no sharding needed)
test-mac:
name: Test (macOS)
needs: detect-changes
if: needs.detect-changes.outputs.needs-tests == 'true'
runs-on: [self-hosted, macOS, ARM64]
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
with:
components: llvm-tools
- uses: taiki-e/install-action@cargo-llvm-cov
- uses: taiki-e/install-action@cargo-nextest
- name: Run all tests
run: >
cargo llvm-cov nextest
--workspace
--all-features
--lcov --output-path lcov.info
- name: Upload coverage
if: always()
uses: codecov/codecov-action@v5
with:
files: lcov.info
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false

# Fallback: if Mac runner doesn't pick up the job within 15s, run sharded on Ubuntu
test-ubuntu:
name: Test Ubuntu (${{ matrix.partition }}/3)
needs: detect-changes
if: needs.detect-changes.outputs.needs-tests == 'true'
runs-on: ubuntu-latest
Expand All @@ -72,28 +102,49 @@ jobs:
matrix:
partition: [1, 2, 3]
steps:
- name: Wait and check if Mac runner is available
id: check-mac
env:
GH_TOKEN: ${{ github.token }}
run: |
sleep 15
MAC_STATUS=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs \
--jq '.jobs[] | select(.name == "Test (macOS)") | .status' 2>/dev/null || echo "unknown")
echo "mac-status=$MAC_STATUS" >> "$GITHUB_OUTPUT"
if [ "$MAC_STATUS" = "in_progress" ]; then
echo "Mac runner picked up the job — skipping Ubuntu fallback"
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "Mac runner not available (status: $MAC_STATUS) — running on Ubuntu"
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
- uses: actions/checkout@v4
if: steps.check-mac.outputs.skip == 'false'
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
if: steps.check-mac.outputs.skip == 'false'
with:
components: llvm-tools
- uses: Swatinem/rust-cache@v2
if: steps.check-mac.outputs.skip == 'false'
with:
cache-on-failure: "false"
# cargo-llvm-cov uses target/llvm-cov-target/ instead of target/
workspaces: ". -> target/llvm-cov-target"
- uses: taiki-e/install-action@cargo-llvm-cov
if: steps.check-mac.outputs.skip == 'false'
- uses: taiki-e/install-action@cargo-nextest
if: steps.check-mac.outputs.skip == 'false'
- name: Run tests (shard ${{ matrix.partition }}/3)
if: steps.check-mac.outputs.skip == 'false'
run: >
cargo llvm-cov nextest
--workspace
--all-features
--partition count:${{ matrix.partition }}/3
--lcov --output-path lcov.info
- name: Upload coverage
if: always()
if: always() && steps.check-mac.outputs.skip == 'false'
uses: codecov/codecov-action@v5
with:
files: lcov.info
Expand Down
11 changes: 11 additions & 0 deletions docs/book/translations/ar/src/batch-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ pub enum SubelementsDeletionBehavior {
}
```

| Variant | Tree state | Emptiness check | Deletes tree | Storage cleanup |
|---|---|---|---|---|
| `DontCheckWithNoCleanup` | empty | No | Yes | No |
| `DontCheckWithNoCleanup` | non-empty | No | Yes | No |
| `DeleteChildren` | empty | No | Yes | Yes |
| `DeleteChildren` | non-empty | No | Yes | Yes |
| `Error` | empty | Yes | Yes | Yes |
| `Error` | non-empty | Yes | No (returns error) | No |
| `Skip` | empty | Yes | Yes | Yes |
| `Skip` | non-empty | Yes | No (silently skips) | No |

كل عملية تُغلَّف في `QualifiedGroveDbOp` يتضمن المسار:

```rust
Expand Down
154 changes: 141 additions & 13 deletions docs/book/translations/ar/src/dense-tree.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ Navigation:
```

تُلحق القيم تتابعياً: القيمة الأولى تذهب للموقع 0 (الجذر)، ثم
الموقع 1، 2، 3، وهكذا.
الموقع 1، 2، 3، وهكذا. هذا يعني أن الجذر يحتوي دائماً على بيانات، والشجرة تُملأ
بترتيب المستويات — وهو أكثر ترتيب عبور طبيعي لشجرة ثنائية كاملة.

## حساب التجزئة

تجزئة الجذر لا تُخزَّن منفصلة — يُعاد حسابها من الصفر عند الحاجة:
تجزئة الجذر لا تُخزَّن منفصلة — يُعاد حسابها من الصفر عند الحاجة.
الخوارزمية التكرارية تزور فقط المواقع الممتلئة:

```text
hash(position, store):
Expand All @@ -57,6 +59,21 @@ hash(position, store):
- المواقع غير الممتلئة: `[0u8; 32]` (تجزئة صفرية)
- الشجرة الفارغة (count = 0): `[0u8; 32]`

**لا تُستخدم علامات فصل نطاق بين الأوراق والعقد الداخلية.** بنية الشجرة (`height`
و`count`) مُوثّقة خارجياً في العنصر الأب `Element::DenseAppendOnlyFixedSizeTree`،
الذي يتدفق عبر تسلسل Merk الهرمي. المُحقِّق يعرف دائماً بالضبط أي
المواقع أوراق وأيها عقد داخلية من الارتفاع والعدد، لذا لا يستطيع المهاجم
استبدال أحدها بالآخر دون كسر سلسلة التوثيق الأصلية.

هذا يعني أن تجزئة الجذر تُشفّر التزاماً بكل قيمة مُخزَّنة وموقعها الدقيق
في الشجرة. تغيير أي قيمة (لو كانت قابلة للتعديل) سيتتالى عبر
جميع تجزئات الأسلاف صعوداً إلى الجذر.

**تكلفة التجزئة:** حساب تجزئة الجذر يزور جميع المواقع الممتلئة بالإضافة إلى أي أبناء
غير ممتلئين. لشجرة بها *n* قيمة، أسوأ حالة هي O(*n*) استدعاءات blake3. هذا
مقبول لأن الشجرة مُصمّمة لسعات صغيرة محدودة (ارتفاع أقصى 16،
أقصى 65,535 موقعاً).

## متغير العنصر

```rust
Expand All @@ -67,37 +84,67 @@ Element::DenseAppendOnlyFixedSizeTree(
)
```

| Field | Type | Description |
|---|---|---|
| `count` | `u16` | Number of values inserted so far (max 65,535) |
| `height` | `u8` | Tree height (1..=16), immutable after creation |
| `flags` | `Option<ElementFlags>` | Optional storage flags |

تجزئة الجذر لا تُخزَّن في العنصر — تتدفق كتجزئة Merk الابن
عبر معامل `subtree_root_hash` في `insert_subtree`.

**المُميِّز:** 14 (ElementType)، TreeType = 10

**حجم التكلفة:** `DENSE_TREE_COST_SIZE = 6` بايت (2 عدد + 1 ارتفاع + 1 مُميِّز
+ 2 حِمل إضافي)

## تخطيط التخزين

مثل MmrTree وBulkAppendTree، تُخزّن DenseAppendOnlyFixedSizeTree البيانات في
فضاء اسم **البيانات**. القيم مُفتَّحة بموقعها كـ `u64` بترتيب الطرف الأكبر:

```text
Subtree path: blake3(parent_path || key)

Storage keys:
[0, 0, 0, 0, 0, 0, 0, 0] → value at position 0 (root)
[0, 0, 0, 0, 0, 0, 0, 1] → value at position 1
[0, 0, 0, 0, 0, 0, 0, 2] → value at position 2
...
```

العنصر نفسه (المُخزَّن في Merk الأب) يحمل `count` و`height`.
تجزئة الجذر تتدفق كتجزئة Merk الابن. هذا يعني:
- **قراءة تجزئة الجذر** تتطلب إعادة حساب من التخزين (تجزئة O(n))
- **قراءة قيمة بالموقع هي O(1)** — بحث تخزين واحد
- **الإدراج هو تجزئة O(n)** — كتابة تخزين واحدة + إعادة حساب تجزئة الجذر بالكامل

## العمليات

### `dense_tree_insert(path, key, value, tx, grove_version)`

تُلحق قيمة بالموقع المتاح التالي. تُرجع `(root_hash, position)`.

```text
Step 1: Read element, extract (count, height)
Step 2: Check capacity: if count >= 2^height - 1 → error
Step 3: Build subtree path, open storage context
Step 4: Write value to position = count
Step 5: Reconstruct DenseFixedSizedMerkleTree from state
Step 6: Call tree.insert(value, store) → (root_hash, position, hash_calls)
Step 7: Update element with new root_hash and count + 1
Step 8: Propagate changes up through Merk hierarchy
Step 9: Commit transaction
```

### `dense_tree_get(path, key, position, tx, grove_version)`

تسترجع القيمة في موقع معين. تُرجع `None` إذا كان الموقع >= العدد.

### `dense_tree_root_hash(path, key, tx, grove_version)`

تُرجع تجزئة الجذر المُخزَّنة في العنصر.
تُرجع تجزئة الجذر المُخزَّنة في العنصر. هذه هي التجزئة المحسوبة أثناء
آخر إدراج — لا حاجة لإعادة الحساب.

### `dense_tree_count(path, key, tx, grove_version)`

Expand All @@ -106,14 +153,54 @@ Storage keys:
## العمليات الدفعية

متغير `GroveOp::DenseTreeInsert` يدعم الإدراج الدفعي عبر خط أنابيب
الدفعة القياسي في GroveDB. **المعالجة المسبقة** تعمل كجميع أنواع الأشجار غير-Merk.
الدفعة القياسي في GroveDB:

```rust
let ops = vec![
QualifiedGroveDbOp::dense_tree_insert_op(
vec![b"parent".to_vec()],
b"my_dense_tree".to_vec(),
b"value_data".to_vec(),
),
];
db.apply_batch(ops, None, None, grove_version)?;
```

**المعالجة المسبقة:** مثل جميع أنواع الأشجار غير-Merk، تُعالَج عمليات `DenseTreeInsert` مسبقاً
قبل تنفيذ جسم الدفعة الرئيسي. طريقة `preprocess_dense_tree_ops`:

1. تُجمِّع جميع عمليات `DenseTreeInsert` حسب `(path, key)`
2. لكل مجموعة، تُنفّذ الإدراجات تتابعياً (قراءة العنصر، إدراج
كل قيمة، تحديث تجزئة الجذر)
3. تُحوِّل كل مجموعة إلى عملية `ReplaceNonMerkTreeRoot` تحمل تجزئة الجذر
النهائية والعدد عبر آلية النشر القياسية

الإدراجات المتعددة لنفس الشجرة الكثيفة ضمن دفعة واحدة مدعومة — تُعالَج
بالترتيب وفحص الاتساق يسمح بالمفاتيح المكررة لهذا النوع من العمليات.

**النشر:** تجزئة الجذر والعدد يتدفقان عبر متغير `NonMerkTreeMeta::DenseTree`
في `ReplaceNonMerkTreeRoot`، متبعين نفس نمط MmrTree وBulkAppendTree.

## البراهين

تدعم DenseAppendOnlyFixedSizeTree **براهين استعلامات فرعية V1** عبر متغير `ProofBytes::DenseTree`.
يمكن إثبات المواقع الفردية ضد تجزئة جذر الشجرة باستخدام براهين تضمين
تحمل قيم الأسلاف وتجزئات الأشجار الفرعية الشقيقة.

### بنية مسار التوثيق

لأن العقد الداخلية تُجزّئ **قيمتها الخاصة** (وليس فقط تجزئات الأبناء)، فإن
مسار التوثيق يختلف عن شجرة ميركل القياسية. للتحقق من ورقة في الموقع
`p`، يحتاج المُحقِّق:

1. **قيمة الورقة** (المدخل المُثبت)
2. **تجزئات قيم الأسلاف** لكل عقدة داخلية على المسار من `p` إلى الجذر (فقط التجزئة من 32 بايت، وليس القيمة الكاملة)
3. **تجزئات الأشجار الفرعية الشقيقة** لكل ابن ليس على المسار

لأن جميع العقد تستخدم `blake3(H(value) || H(left) || H(right))` (بدون علامات نطاق)،
فإن البرهان يحمل فقط تجزئات قيم من 32 بايت للأسلاف — وليس القيم الكاملة. هذا
يُبقي البراهين مدمجة بغض النظر عن حجم القيم الفردية.

```rust
pub struct DenseTreeProof {
pub entries: Vec<(u16, Vec<u8>)>, // proved (position, value) pairs
Expand All @@ -122,6 +209,8 @@ pub struct DenseTreeProof {
}
```

> **ملاحظة:** `height` و`count` ليسا في بنية البرهان — يحصل عليهما المُحقِّق من العنصر الأب، المُوثّق بواسطة تسلسل Merk الهرمي.

### مثال تفصيلي

شجرة بارتفاع=3، سعة=7، عدد=5، إثبات الموقع 4:
Expand All @@ -134,7 +223,7 @@ pub struct DenseTreeProof {
3 4 5 6
```

المسار من 4 إلى الجذر: `4 → 1 → 0`.
المسار من 4 إلى الجذر: `4 → 1 → 0`. المجموعة الموسّعة: `{0, 1, 4}`.

البرهان يحتوي:
- **entries**: `[(4, value[4])]` — الموقع المُثبت
Expand All @@ -149,22 +238,48 @@ pub struct DenseTreeProof {
5. `H(0) = blake3(H(value[0]) || H(1) || H(2))` — الجذر
6. مقارنة `H(0)` مع تجزئة الجذر المتوقعة

### براهين المواقع المتعددة

عند إثبات مواقع متعددة، تدمج المجموعة الموسّعة مسارات التوثيق المتداخلة. الأسلاف
المشتركون يُضمَّنون مرة واحدة فقط، مما يجعل براهين المواقع المتعددة أكثر إحكاماً من
البراهين المستقلة لموقع واحد.

### قيود V0

براهين V0 لا تستطيع النزول داخل الأشجار الكثيفة. إذا طابق استعلام V0
`DenseAppendOnlyFixedSizeTree` مع استعلام فرعي، يُرجع النظام
`Error::NotSupported` موجّهاً المُستدعي لاستخدام `prove_query_v1`.

### ترميز مفاتيح الاستعلام

مواقع الشجرة الكثيفة تُرمَّز كمفاتيح استعلام **u16 بترتيب الطرف الأكبر** (2 بايت)، على عكس
MmrTree وBulkAppendTree اللتين تستخدمان u64. جميع أنواع نطاقات `QueryItem` القياسية
مدعومة.

## مقارنة مع الأشجار الأخرى غير-Merk

| | DenseTree | MmrTree | BulkAppendTree | CommitmentTree |
|---|---|---|---|---|
| **السعة** | ثابتة (`2^h - 1`، حد أقصى 65,535) | غير محدودة | غير محدودة | غير محدودة |
| **نموذج البيانات** | كل موقع يُخزّن قيمة | أوراق فقط | مخزن مؤقت + شرائح | أوراق فقط |
| **تكلفة الإدراج (تجزئة)** | O(n) blake3 | O(1) مُطفأة | O(1) مُطفأة | ~33 Sinsemilla |
| **حجم التكلفة** | 6 بايت | 11 بايت | 12 بايت | 12 بايت |
| **الأفضل لـ** | بنى صغيرة محدودة | سجلات أحداث | سجلات عالية الإنتاجية | التزامات ZK |
| **Element discriminant** | 14 | 12 | 13 | 11 |
| **TreeType** | 10 | 8 | 9 | 7 |
| **Capacity** | Fixed (`2^h - 1`, max 65,535) | Unlimited | Unlimited | Unlimited |
| **Data model** | Every position stores a value | Leaf-only | Dense tree buffer + chunks | Leaf-only |
| **Hash in Element?** | No (flows as child hash) | No (flows as child hash) | No (flows as child hash) | No (flows as child hash) |
| **Insert cost (hashing)** | O(n) blake3 | O(1) amortized | O(1) amortized | ~33 Sinsemilla |
| **Cost size** | 6 bytes | 11 bytes | 12 bytes | 12 bytes |
| **Proof support** | V1 (Dense) | V1 (MMR) | V1 (Bulk) | V1 (CommitmentTree) |
| **Best for** | Small bounded structures | Event logs | High-throughput logs | ZK commitments |

**متى تختار DenseAppendOnlyFixedSizeTree:**
- العدد الأقصى للمدخلات معروف وقت الإنشاء
- تحتاج كل موقع (بما في ذلك العقد الداخلية) لتخزين بيانات
- تريد أبسط نموذج بيانات ممكن بدون نمو غير محدود
- إعادة حساب تجزئة الجذر بتعقيد O(n) مقبولة (ارتفاعات شجرة صغيرة)

**متى لا تختارها:**
- تحتاج سعة غير محدودة ← استخدم MmrTree أو BulkAppendTree
- تحتاج توافقية ZK ← استخدم CommitmentTree

## مثال استخدام

```rust
Expand Down Expand Up @@ -201,16 +316,29 @@ let value = db.dense_tree_get(
grove_version,
)?;
assert_eq!(value, Some(validator_pubkey.to_vec()));

// Query metadata
let count = db.dense_tree_count(&[b"state"], b"validator_slots", None, grove_version)?;
let hash = db.dense_tree_root_hash(&[b"state"], b"validator_slots", None, grove_version)?;
```

## ملفات التنفيذ

| الملف | المحتويات |
|-------|-----------|
| `grovedb-dense-fixed-sized-merkle-tree/src/lib.rs` | سمة `DenseTreeStore`، بنية `DenseFixedSizedMerkleTree`، التجزئة التكرارية |
| `grovedb-dense-fixed-sized-merkle-tree/src/proof.rs` | بنية `DenseTreeProof`، `generate()`، `encode_to_vec()` |
| `grovedb-dense-fixed-sized-merkle-tree/src/verify.rs` | `DenseTreeProof::verify()` — دالة صافية |
| `grovedb/src/operations/dense_tree.rs` | عمليات GroveDB، معالجة الدفعات المسبقة |
| `grovedb-dense-fixed-sized-merkle-tree/src/proof.rs` | بنية `DenseTreeProof`، `generate()`، `encode_to_vec()`، `decode_from_slice()` |
| `grovedb-dense-fixed-sized-merkle-tree/src/verify.rs` | `DenseTreeProof::verify()` — دالة صافية، لا تحتاج تخزين |
| `grovedb-element/src/element/mod.rs` | `Element::DenseAppendOnlyFixedSizeTree` (المُميِّز 14) |
| `grovedb-element/src/element/constructor.rs` | `empty_dense_tree()`، `new_dense_tree()` |
| `merk/src/tree_type/mod.rs` | `TreeType::DenseAppendOnlyFixedSizeTree = 10` |
| `merk/src/tree_type/costs.rs` | `DENSE_TREE_COST_SIZE = 6` |
| `grovedb/src/operations/dense_tree.rs` | عمليات GroveDB، `AuxDenseTreeStore`، معالجة الدفعات المسبقة |
| `grovedb/src/operations/proof/generate.rs` | `generate_dense_tree_layer_proof()`، `query_items_to_positions()` |
| `grovedb/src/operations/proof/verify.rs` | `verify_dense_tree_lower_layer()` |
| `grovedb/src/operations/proof/mod.rs` | متغير `ProofBytes::DenseTree` |
| `grovedb/src/batch/estimated_costs/average_case_costs.rs` | نموذج تكلفة الحالة المتوسطة |
| `grovedb/src/batch/estimated_costs/worst_case_costs.rs` | نموذج تكلفة أسوأ حالة |
| `grovedb/src/tests/dense_tree_tests.rs` | 22 اختبار تكامل |

---
Loading