diff --git a/.github/workflows/grovedb.yml b/.github/workflows/grovedb.yml index e28b236b8..006fcf1ab 100644 --- a/.github/workflows/grovedb.yml +++ b/.github/workflows/grovedb.yml @@ -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 @@ -72,20 +102,41 @@ 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 @@ -93,7 +144,7 @@ jobs: --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 diff --git a/docs/book/translations/ar/src/batch-operations.md b/docs/book/translations/ar/src/batch-operations.md index cdfc91d79..d90a50759 100644 --- a/docs/book/translations/ar/src/batch-operations.md +++ b/docs/book/translations/ar/src/batch-operations.md @@ -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 diff --git a/docs/book/translations/ar/src/dense-tree.md b/docs/book/translations/ar/src/dense-tree.md index 95e0e19ac..122752bd9 100644 --- a/docs/book/translations/ar/src/dense-tree.md +++ b/docs/book/translations/ar/src/dense-tree.md @@ -32,11 +32,13 @@ Navigation: ``` تُلحق القيم تتابعياً: القيمة الأولى تذهب للموقع 0 (الجذر)، ثم -الموقع 1، 2، 3، وهكذا. +الموقع 1، 2، 3، وهكذا. هذا يعني أن الجذر يحتوي دائماً على بيانات، والشجرة تُملأ +بترتيب المستويات — وهو أكثر ترتيب عبور طبيعي لشجرة ثنائية كاملة. ## حساب التجزئة -تجزئة الجذر لا تُخزَّن منفصلة — يُعاد حسابها من الصفر عند الحاجة: +تجزئة الجذر لا تُخزَّن منفصلة — يُعاد حسابها من الصفر عند الحاجة. +الخوارزمية التكرارية تزور فقط المواقع الممتلئة: ```text hash(position, store): @@ -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 @@ -67,17 +84,28 @@ 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` | 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 @@ -85,19 +113,38 @@ Storage keys: ... ``` +العنصر نفسه (المُخزَّن في 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)` @@ -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)>, // proved (position, value) pairs @@ -122,6 +209,8 @@ pub struct DenseTreeProof { } ``` +> **ملاحظة:** `height` و`count` ليسا في بنية البرهان — يحصل عليهما المُحقِّق من العنصر الأب، المُوثّق بواسطة تسلسل Merk الهرمي. + ### مثال تفصيلي شجرة بارتفاع=3، سعة=7، عدد=5، إثبات الموقع 4: @@ -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])]` — الموقع المُثبت @@ -149,15 +238,37 @@ 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:** - العدد الأقصى للمدخلات معروف وقت الإنشاء @@ -165,6 +276,10 @@ pub struct DenseTreeProof { - تريد أبسط نموذج بيانات ممكن بدون نمو غير محدود - إعادة حساب تجزئة الجذر بتعقيد O(n) مقبولة (ارتفاعات شجرة صغيرة) +**متى لا تختارها:** +- تحتاج سعة غير محدودة ← استخدم MmrTree أو BulkAppendTree +- تحتاج توافقية ZK ← استخدم CommitmentTree + ## مثال استخدام ```rust @@ -201,6 +316,10 @@ 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)?; ``` ## ملفات التنفيذ @@ -208,9 +327,18 @@ assert_eq!(value, Some(validator_pubkey.to_vec())); | الملف | المحتويات | |-------|-----------| | `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 اختبار تكامل | --- diff --git a/docs/book/translations/ar/src/mmr-tree.md b/docs/book/translations/ar/src/mmr-tree.md index 0bd5d8a66..96fba5387 100644 --- a/docs/book/translations/ar/src/mmr-tree.md +++ b/docs/book/translations/ar/src/mmr-tree.md @@ -207,6 +207,17 @@ mmr_size = 2 * leaf_count - popcount(leaf_count) حيث `popcount` هو عدد البتات 1 (أي عدد القمم). كل عقدة داخلية تدمج شجرتين فرعيتين، مما يُقلّل عدد العقد بواحد لكل دمج. +الحساب العكسي — عدد الأوراق من mmr_size — يستخدم مواقع القمم: + +```rust +fn mmr_size_to_leaf_count(mmr_size: u64) -> u64 { + // Each peak at height h contains 2^h leaves + get_peaks(mmr_size).iter() + .map(|&peak_pos| 1u64 << pos_height_in_tree(peak_pos)) + .sum() +} +``` + | mmr_size | leaf_count | القمم | |----------|-----------|-------| | 0 | 0 | (فارغ) | @@ -284,12 +295,20 @@ fn blake3_merge(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] { } ``` +> **ملاحظة حول PartialEq:** يُنفّذ `MmrNode` سمة `PartialEq` بمقارنة **حقل التجزئة فقط**، +> وليس القيمة. هذا ضروري للتحقق من البراهين: يُقارن مُحقّق ckb جذراً مُعاد بناؤه (value = None) +> مع الجذر المتوقع. لو قارن PartialEq حقل القيمة، لفشلت براهين MMR ذات الورقة الواحدة دائماً +> لأن الورقة تحتوي `value: Some(...)` بينما إعادة بناء الجذر تُنتج `value: None`. + **صيغة الترميز التسلسلي:** ```text Internal: [0x00] [hash: 32 bytes] = 33 bytes Leaf: [0x01] [hash: 32 bytes] [value_len: 4 BE] [value...] = 37 + len bytes ``` +بايت العلامة يُميّز العقد الداخلية عن الأوراق. عملية إلغاء الترميز تتحقق من +الطول الدقيق — لا يُسمح ببايتات إضافية. + ## بنية التخزين تُخزّن MmrTree عقدها في عمود **البيانات** (نفس عائلة الأعمدة المُستخدمة @@ -302,8 +321,16 @@ Leaf: [0x01] [hash: 32 bytes] [value_len: 4 BE] [value...] = 37 + len byte key = 'm' || position_as_be_u64 (9 bytes: prefix + u64 BE) ``` +إذاً الموقع 42 يُخزَّن بالمفتاح `[0x6D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x2A]`. + +البحث عن الورقة *i* يتطلب حساب موقع MMR أولاً: +`pos = leaf_index_to_pos(i)`، ثم قراءة مفتاح البيانات `m{pos}`. + **ذاكرة مؤقتة للكتابة الفورية:** أثناء الإلحاق، يجب أن تكون العقد المكتوبة حديثاً -قابلة للقراءة فوراً لعمليات الدمج التالية في نفس الدفع. +قابلة للقراءة فوراً لعمليات الدمج التالية في نفس الدفع. لأن تخزين GroveDB +المُعاملاتي يؤجل الكتابات إلى دفعة (لا تكون مرئية للقراءات حتى الإيداع)، يلف +مُحوّل `MmrStore` سياق التخزين بذاكرة مؤقتة `HashMap` في الذاكرة: ```mermaid graph LR @@ -319,6 +346,9 @@ graph LR style store fill:#fef9e7,stroke:#f39c12,stroke-width:2px ``` +هذا يضمن أنه عند إلحاق leaf₃ يُفعّل تتالي دمج (إنشاء عقد داخلية في المواقع 5 و6)، +تكون node₅ متاحة فوراً عند حساب node₆، حتى لو لم تُودع node₅ في RocksDB بعد. + **انتشار تجزئة الجذر إلى جذر حالة GroveDB:** ```text @@ -348,6 +378,8 @@ db.mmr_tree_leaf_count(path, key, tx, version) ### تدفق الإلحاق +عملية الإلحاق هي الأكثر تعقيداً، تُنفّذ 8 خطوات: + ```mermaid graph TD subgraph append["mmr_tree_append(path, key, value, tx)"] @@ -367,6 +399,21 @@ graph TD style A6 fill:#d4e6f1,stroke:#2980b9,stroke-width:2px ``` +الخطوة 4 قد تكتب عقدة واحدة (ورقة فقط) أو 1 + N عقدة (ورقة + N عقد دمج داخلية). +الخطوة 5 تستدعي `mmr.commit()` التي تُفرّغ MemStore الخاص بـ ckb إلى MmrStore. +الخطوة 7 تستدعي `insert_subtree` مع جذر MMR الجديد كتجزئة ابن +(عبر `subtree_root_hash`)، بما أن MmrTree ليس لها Merk ابن. + +### عمليات القراءة + +`mmr_tree_root_hash` تحسب الجذر من بيانات MMR في التخزين. +`mmr_tree_leaf_count` تشتق عدد الأوراق من `mmr_size` في العنصر. +لا حاجة للوصول إلى تخزين البيانات. + +`mmr_tree_get_value` تحسب `pos = leaf_index_to_pos(leaf_index)`، تقرأ +مُدخل تخزين البيانات الوحيد عند `m{pos}`، تُلغي ترميز `MmrNode`، وتُعيد +`node.value`. + ## العمليات الدفعية يمكن تجميع عمليات إلحاق MMR متعددة باستخدام `GroveOp::MmrTreeAppend { value }`. @@ -397,10 +444,42 @@ graph TD style body fill:#d5f5e3,stroke:#27ae60,stroke-width:2px ``` +مثال: دفعة بـ 3 عمليات إلحاق لنفس MMR: +```rust +vec![ + QualifiedGroveDbOp { path: p, key: k, op: MmrTreeAppend { value: v1 } }, + QualifiedGroveDbOp { path: p, key: k, op: MmrTreeAppend { value: v2 } }, + QualifiedGroveDbOp { path: p, key: k, op: MmrTreeAppend { value: v3 } }, +] +``` + +المعالجة المسبقة تُحمّل MMR مرة واحدة، تدفع v1 وv2 وv3 (مُنشئة جميع العقد +الوسيطة)، تحفظ كل شيء في تخزين البيانات، ثم تُصدر `ReplaceNonMerkTreeRoot` +واحدة مع `mmr_root` و`mmr_size` النهائيين. آلية الدفعات القياسية تتولى +الباقي. + ## توليد البراهين براهين MMR هي **براهين V1** — تستخدم متغير `ProofBytes::MMR` في بنية -البراهين المتعددة الطبقات (انظر §9.6). +البراهين المتعددة الطبقات (انظر §9.6). يُثبت البرهان أن قيم أوراق محددة +موجودة في مواقع محددة داخل MMR، وأن تجزئاتها متسقة مع `mmr_root` +المُخزَّن في العنصر الأب. + +### ترميز الاستعلام + +مفاتيح الاستعلام تُرمّز المواقع كـ **بايتات u64 بترتيب الطرف الأكبر (big-endian)**. هذا يحافظ +على ترتيب الفرز المعجمي (لأن ترميز BE رتيب)، مما يسمح لجميع متغيرات +`QueryItem` القياسية بالعمل: + +```text +QueryItem::Key([0,0,0,0,0,0,0,5]) → leaf index 5 +QueryItem::RangeInclusive([..2]..=[..7]) → leaf indices [2, 3, 4, 5, 6, 7] +QueryItem::RangeFrom([..10]..) → leaf indices [10, 11, ..., N-1] +QueryItem::RangeFull → all leaves [0..leaf_count) +``` + +حد أمان قدره **10,000,000 فهرس** يمنع استنفاد الذاكرة من +استعلامات النطاق غير المحدودة. MMR فارغ (صفر أوراق) يُعيد برهاناً فارغاً. ### بنية MmrTreeProof @@ -412,9 +491,73 @@ struct MmrTreeProof { } ``` +تحتوي `proof_items` على المجموعة الدنيا من التجزئات اللازمة لإعادة بناء +المسارات من الأوراق المُثبتة وصولاً إلى جذر MMR. هذه هي العقد الشقيقة +في كل مستوى وتجزئات القمم غير المشاركة. + +### تدفق التوليد + +```mermaid +graph TD + subgraph gen["generate_mmr_layer_proof"] + G1["1. Get subquery items
from PathQuery"] + G2["2. Decode BE u64 keys
→ leaf indices"] + G3["3. Open data storage
at subtree path"] + G4["4. Load all MMR nodes
into read-only MemNodeStore"] + G5["5. Call ckb gen_proof(positions)
→ MerkleProof"] + G6["6. Read each proved leaf's
full value from storage"] + G7["7. Extract proof_items
as [u8; 32] hashes"] + G8["8. Encode MmrTreeProof
→ ProofBytes::MMR(bytes)"] + + G1 --> G2 --> G3 --> G4 --> G5 --> G6 --> G7 --> G8 + end + + style gen fill:#fef9e7,stroke:#f39c12,stroke-width:2px +``` + +الخطوة 4 تستخدم `MemNodeStore` — وهي BTreeMap للقراءة فقط تُحمّل مسبقاً جميع عقد +MMR من تخزين البيانات. مُولّد براهين ckb يحتاج وصولاً عشوائياً، لذا يجب أن تكون جميع +العقد في الذاكرة. + +الخطوة 5 هي حيث تقوم مكتبة ckb بالعمل الثقيل: بمعرفة حجم MMR والمواقع +المراد إثباتها، تُحدّد تجزئات الأشقاء والقمم المطلوبة. + +### مثال عملي + +**إثبات الورقة 2 في MMR من 5 أوراق (mmr_size = 8):** + +```text +MMR structure: +pos: 6 7 + / \ + 2 5 + / \ / \ + 0 1 3 4 + +Leaf index 2 → MMR position 3 + +To verify leaf at position 3: + 1. Hash the claimed value: leaf_hash = blake3(value) + 2. Sibling at position 4: node₅ = blake3(leaf_hash || proof[pos 4]) + 3. Sibling at position 2: node₆ = blake3(proof[pos 2] || node₅) + 4. Peak at position 7: root = bag(node₆, proof[pos 7]) + 5. Compare: root == expected mmr_root ✓ + +proof_items = [hash(pos 4), hash(pos 2), hash(pos 7)] +leaves = [(2, original_value_bytes)] +``` + +حجم البرهان في هذا المثال هو: 3 تجزئات (96 بايت) + قيمة ورقة واحدة + +بيانات وصفية. بشكل عام، إثبات K ورقة من MMR بـ N ورقة يتطلب +O(K * log N) تجزئة شقيقة. + ## التحقق من البراهين -التحقق هو عملية **صافية** — لا يتطلب أي وصول لقاعدة البيانات. +التحقق هو عملية **صافية** — لا يتطلب أي وصول لقاعدة البيانات. يحتاج المُحقّق +فقط إلى بايتات البرهان وتجزئة جذر MMR المتوقعة (التي يستخرجها من +العنصر الأب المُثبت في طبقة Merk أعلاه). + +### خطوات التحقق ```mermaid graph TD @@ -437,8 +580,14 @@ graph TD style FAIL fill:#fadbd8,stroke:#e74c3c,stroke-width:2px ``` +دالة `MerkleProof::verify` من ckb تُعيد بناء الجذر من الأوراق +وعناصر البرهان، ثم تُقارنه (باستخدام `PartialEq`، التي تتحقق من التجزئة فقط) +مع الجذر المتوقع. + ### سلسلة الثقة +السلسلة الكاملة من جذر حالة GroveDB إلى قيمة ورقة مُتحقق منها: + ```text GroveDB state_root (known/trusted) │ @@ -457,6 +606,21 @@ GroveDB state_root (known/trusted) └─ Result: leaf₂ = [verified value bytes] ``` +### خصائص الأمان + +- **التحقق المتقاطع من mmr_size:** يجب أن يتطابق `mmr_size` في البرهان مع + `mmr_size` في العنصر. عدم التطابق يشير إلى أن البرهان وُلّد ضد + حالة مختلفة ويُرفض. +- **حد حجم Bincode:** تستخدم عملية إلغاء الترميز حداً قدره 100 ميغابايت لمنع + ترويسات الطول المُصنّعة من التسبب في تخصيصات ضخمة. +- **حساب الحدود:** كل ورقة مُثبتة تُنقص حد الاستعلام الإجمالي بـ + 1 باستخدام `saturating_sub` لمنع التجاوز السفلي. +- **إرجاع تجزئة الابن:** يُعيد المُحقّق تجزئة جذر MMR المحسوبة كتجزئة + ابن لحساب combine_hash في الطبقة الأب. +- **رفض V0:** محاولة استعلام فرعي في MmrTree مع براهين V0 + تُعيد `Error::NotSupported`. فقط براهين V1 يمكنها النزول إلى أشجار + غير Merk. + ## تتبع التكاليف | العملية | استدعاءات التجزئة | عمليات التخزين | @@ -466,9 +630,39 @@ GroveDB state_root (known/trusted) | الحصول على قيمة | 0 | قراءة عنصر + قراءة بيانات واحدة | | عدد الأوراق | 0 | قراءة عنصر واحد | -**تحليل مُطفأ:** على N إلحاق، إجمالي التجزئات هو ≈ 2N. +صيغة عدد التجزئات `1 + trailing_ones(N)` تُعطي العدد الدقيق لاستدعاءات Blake3: +1 لتجزئة الورقة، بالإضافة إلى تجزئة دمج واحدة لكل مستوى تتالي. + +**تحليل مُطفأ:** على N إلحاق، إجمالي عدد التجزئات هو: + +```text +Σ (1 + trailing_ones(i)) for i = 0..N-1 += N + Σ trailing_ones(i) for i = 0..N-1 += N + (N - popcount(N)) +≈ 2N +``` + التكلفة المُطفأة لكل إلحاق هي تقريباً **استدعاءان لتجزئة Blake3** — -ثابتة ومستقلة عن حجم الشجرة. +ثابتة ومستقلة عن حجم الشجرة. قارن هذا مع أشجار Merk AVL حيث +يتطلب كل إدراج O(log N) تجزئة للمسار بالإضافة إلى تجزئات الدوران المحتملة. + +**تكلفة التخزين:** كل إلحاق يكتب عقدة ورقية واحدة (37 + value_len بايت) بالإضافة +إلى 0 إلى log₂(N) عقدة داخلية (33 بايت لكل منها). الكتابة المُطفأة للتخزين لكل +إلحاق هي تقريباً 33 + 37 + value_len بايت ≈ 70 + value_len بايت. + +## ملفات التنفيذ + +| File | Purpose | +|------|---------| +| `grovedb-mmr/src/node.rs` | `MmrNode` struct, Blake3 merge, serialization | +| `grovedb-mmr/src/grove_mmr.rs` | `GroveMmr` wrapper around ckb MMR | +| `grovedb-mmr/src/util.rs` | `mmr_node_key`, `hash_count_for_push`, `mmr_size_to_leaf_count` | +| `grovedb-mmr/src/proof.rs` | `MmrTreeProof` generation and verification | +| `grovedb-mmr/src/dense_merkle.rs` | Dense Merkle tree roots (used by BulkAppendTree) | +| `grovedb/src/operations/mmr_tree.rs` | GroveDB operations + `MmrStore` adapter + batch preprocessing | +| `grovedb/src/operations/proof/generate.rs` | V1 proof generation: `generate_mmr_layer_proof`, `query_items_to_leaf_indices` | +| `grovedb/src/operations/proof/verify.rs` | V1 proof verification: `verify_mmr_lower_layer` | +| `grovedb/src/tests/mmr_tree_tests.rs` | 28 integration tests | ## مقارنة مع البنى الموثّقة الأخرى diff --git a/docs/book/translations/cs/src/batch-operations.md b/docs/book/translations/cs/src/batch-operations.md index 6f47130f6..1f34a8795 100644 --- a/docs/book/translations/cs/src/batch-operations.md +++ b/docs/book/translations/cs/src/batch-operations.md @@ -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 | + Kazda operace je obalena v `QualifiedGroveDbOp`, ktery zahrnuje cestu: ```rust diff --git a/docs/book/translations/de/src/batch-operations.md b/docs/book/translations/de/src/batch-operations.md index b7d8cbbcb..b28494481 100644 --- a/docs/book/translations/de/src/batch-operations.md +++ b/docs/book/translations/de/src/batch-operations.md @@ -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 | + Jede Operation wird in ein `QualifiedGroveDbOp` verpackt, das den Pfad enthält: ```rust diff --git a/docs/book/translations/es/src/batch-operations.md b/docs/book/translations/es/src/batch-operations.md index d5aeefdd2..d937c566f 100644 --- a/docs/book/translations/es/src/batch-operations.md +++ b/docs/book/translations/es/src/batch-operations.md @@ -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 | + Cada operación se envuelve en un `QualifiedGroveDbOp` que incluye la ruta: ```rust diff --git a/docs/book/translations/fr/src/batch-operations.md b/docs/book/translations/fr/src/batch-operations.md index c1ac59ea5..d361e0280 100644 --- a/docs/book/translations/fr/src/batch-operations.md +++ b/docs/book/translations/fr/src/batch-operations.md @@ -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 | + Chaque opération est enveloppée dans un `QualifiedGroveDbOp` qui inclut le chemin : ```rust diff --git a/docs/book/translations/id/src/batch-operations.md b/docs/book/translations/id/src/batch-operations.md index 835389955..c47a745e1 100644 --- a/docs/book/translations/id/src/batch-operations.md +++ b/docs/book/translations/id/src/batch-operations.md @@ -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 | + Setiap operasi dibungkus dalam `QualifiedGroveDbOp` yang mencakup path: ```rust diff --git a/docs/book/translations/it/src/batch-operations.md b/docs/book/translations/it/src/batch-operations.md index 9ea6e54af..e2d198f14 100644 --- a/docs/book/translations/it/src/batch-operations.md +++ b/docs/book/translations/it/src/batch-operations.md @@ -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 | + Ogni operazione e avvolta in un `QualifiedGroveDbOp` che include il percorso: ```rust diff --git a/docs/book/translations/ja/src/batch-operations.md b/docs/book/translations/ja/src/batch-operations.md index 59d3201cc..78bef11cc 100644 --- a/docs/book/translations/ja/src/batch-operations.md +++ b/docs/book/translations/ja/src/batch-operations.md @@ -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 diff --git a/docs/book/translations/ja/src/bulk-append-tree.md b/docs/book/translations/ja/src/bulk-append-tree.md index e72f08ad0..262756968 100644 --- a/docs/book/translations/ja/src/bulk-append-tree.md +++ b/docs/book/translations/ja/src/bulk-append-tree.md @@ -228,6 +228,8 @@ BulkAppendTree は `grovedb/src/operations/bulk_append_tree.rs` で定義され 7. Commit transaction ``` +`AuxBulkStore` アダプタは GroveDB の `get_aux`/`put_aux`/`delete_aux` 呼び出しをラップし、コスト追跡のために `RefCell` 内に `OperationCost` を蓄積します。追記操作からのハッシュコストは `cost.hash_node_calls` に追加されます。 + ### 読み取り操作 | 操作 | 返すもの | 補助ストレージ? | @@ -297,7 +299,51 @@ pub trait BulkStore { ## 証明生成 -BulkAppendTree 証明は位置に対する**範囲クエリ**をサポートします。証明構造は、ステートレスな検証者が特定のデータがツリー内に存在することを確認するために必要なすべてを含みます。 +BulkAppendTree 証明は位置に対する**範囲クエリ**をサポートします。証明構造は、ステートレスな検証者が特定のデータがツリー内に存在することを確認するために必要なすべてを含みます: + +```rust +pub struct BulkAppendTreeProof { + pub chunk_power: u8, + pub total_count: u64, + pub chunk_blobs: Vec<(u64, Vec)>, // Full chunk blobs + pub chunk_mmr_size: u64, + pub chunk_mmr_proof_items: Vec<[u8; 32]>, // MMR sibling hashes + pub chunk_mmr_leaves: Vec<(u64, [u8; 32])>, // (leaf_idx, dense_root) + pub buffer_entries: Vec>, // ALL buffer entries + pub chunk_mmr_root: [u8; 32], +} +``` + +範囲 `[start, end)` の**生成ステップ**(`chunk_size = 1u32 << chunk_power`): + +```text +1. Determine overlapping chunks + first_chunk = start / chunk_size + last_chunk = min((end-1) / chunk_size, completed_chunks - 1) + +2. Read chunk blobs for overlapping chunks + For each chunk_idx in [first_chunk, last_chunk]: + chunk_blobs.push((chunk_idx, store.get(chunk_key(idx)))) + +3. Compute dense Merkle root for each chunk blob + For each blob: + deserialize → values + dense_root = compute_dense_merkle_root_from_values(values) + chunk_mmr_leaves.push((chunk_idx, dense_root)) + +4. Generate MMR proof for those chunk positions + positions = chunk_indices.map(|idx| leaf_to_pos(idx)) + proof = mmr.gen_proof(positions) + chunk_mmr_proof_items = proof.proof_items().map(|n| n.hash) + +5. Get chunk MMR root + +6. Read ALL buffer entries (bounded by chunk_size) + for i in 0..buffer_count: + buffer_entries.push(store.get(buffer_key(i))) +``` + +**なぜすべてのバッファエントリを含めるのか?** バッファは密なマークル木であり、そのルートハッシュはすべてのエントリにコミットしています。検証者は `dense_tree_root` を検証するために、すべてのエントリからツリーを再構築する必要があります。バッファは `capacity`(最大65,535エントリ)で制限されているため、これは合理的なコストです。 ## 証明検証 @@ -328,6 +374,8 @@ Step 4: State root assert computed == expected_state_root ``` +検証が成功すると、`BulkAppendTreeProofResult` は検証済みのチャンクブロブとバッファエントリから特定の値を抽出する `values_in_range(start, end)` メソッドを提供します。 + ## GroveDB ルートハッシュへの接続 BulkAppendTree は**非 Merk ツリー**です — データ名前空間にデータを格納し、子 Merk サブツリーには格納しません。親 Merk では、エレメントは以下のように格納されます: @@ -342,6 +390,10 @@ Element::BulkAppendTree(total_count, chunk_power, flags) combine_hash(value_hash(element_bytes), state_root) ``` +`state_root` は Merk 子ハッシュとして流れます(`insert_subtree` の `subtree_root_hash` パラメータ経由)。ステートルートの変更は GroveDB Merk 階層を通じてルートハッシュまで伝播します。 + +V1 証明(第9.6節)では、親 Merk 証明がエレメントバイトと子ハッシュバインディングを証明し、`BulkAppendTreeProof` がクエリされたデータが子ハッシュとして使用された `state_root` と整合していることを証明します。 + ## コスト追跡 各操作のハッシュコストは明示的に追跡されます: @@ -352,6 +404,8 @@ combine_hash(value_hash(element_bytes), state_root) | 単一追記(コンパクションあり) | 3 + 2C - 1 + ~2 | チェーン + 密なマークル(C=chunk_size) + MMR push + ステートルート | | チャンクからの `get_value` | 0 | 純粋なデシリアライズ、ハッシュなし | | バッファからの `get_value` | 0 | 直接キールックアップ | +| 証明生成 | チャンク数に依存 | チャンクあたりの密なマークルルート + MMR 証明 | +| 証明検証 | 2C·K - K + B·2 + 1 | Kチャンク、Bバッファエントリ、Cチャンクサイズ | **追記あたりの償却コスト**:chunk_size=1024(chunk_power=10)の場合、~2047ハッシュのコンパクションオーバーヘッドは1024回の追記に償却され、追記あたり~2ハッシュを追加します。追記ごとの3ハッシュと合わせて、償却合計は**追記あたり~5回の blake3 呼び出し**です — 暗号学的に認証された構造としては非常に効率的です。 @@ -364,6 +418,7 @@ combine_hash(value_hash(element_bytes), state_root) | **証明の粒度** | 位置に対する範囲クエリ | 個別リーフ証明 | | **不変スナップショット** | あり(チャンクブロブ) | なし | | **CDN対応** | あり(チャンクブロブはキャッシュ可能) | なし | +| **バッファエントリ** | あり(証明にすべて必要) | N/A | | **最適な用途** | 高スループットログ、一括同期 | イベントログ、個別検索 | | **エレメント discriminant** | 13 | 12 | | **TreeType** | 9 | 8 | @@ -384,6 +439,7 @@ combine_hash(value_hash(element_bytes), state_root) | `grovedb-bulk-append-tree/src/chunk.rs` | チャンクブロブシリアライズ(固定 + 可変フォーマット) | | `grovedb-bulk-append-tree/src/proof.rs` | `BulkAppendTreeProof` の生成と検証 | | `grovedb-bulk-append-tree/src/store.rs` | `BulkStore` トレイト | +| `grovedb-bulk-append-tree/src/error.rs` | `BulkAppendError` 列挙型 | | `grovedb/src/operations/bulk_append_tree.rs` | GroveDB 操作、`AuxBulkStore`、バッチ前処理 | | `grovedb/src/tests/bulk_append_tree_tests.rs` | 27の統合テスト | diff --git a/docs/book/translations/ko/src/batch-operations.md b/docs/book/translations/ko/src/batch-operations.md index 9d5b4ef34..b22aea4a8 100644 --- a/docs/book/translations/ko/src/batch-operations.md +++ b/docs/book/translations/ko/src/batch-operations.md @@ -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 diff --git a/docs/book/translations/pl/src/batch-operations.md b/docs/book/translations/pl/src/batch-operations.md index 00457c87a..3511cc675 100644 --- a/docs/book/translations/pl/src/batch-operations.md +++ b/docs/book/translations/pl/src/batch-operations.md @@ -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 | + Kazda operacja jest opakowana w `QualifiedGroveDbOp` zawierajacy sciezke: ```rust diff --git a/docs/book/translations/pt/src/batch-operations.md b/docs/book/translations/pt/src/batch-operations.md index 13e99604a..ed8ba0ff3 100644 --- a/docs/book/translations/pt/src/batch-operations.md +++ b/docs/book/translations/pt/src/batch-operations.md @@ -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 | + Cada operacao e envolvida em um `QualifiedGroveDbOp` que inclui o caminho: ```rust diff --git a/docs/book/translations/ru/src/batch-operations.md b/docs/book/translations/ru/src/batch-operations.md index 621bc39aa..11eaba1b2 100644 --- a/docs/book/translations/ru/src/batch-operations.md +++ b/docs/book/translations/ru/src/batch-operations.md @@ -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 diff --git a/docs/book/translations/th/src/batch-operations.md b/docs/book/translations/th/src/batch-operations.md index fbec13079..1e4cfd28e 100644 --- a/docs/book/translations/th/src/batch-operations.md +++ b/docs/book/translations/th/src/batch-operations.md @@ -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` ที่รวม path: ```rust diff --git a/docs/book/translations/tr/src/batch-operations.md b/docs/book/translations/tr/src/batch-operations.md index fc0450431..c1c374afc 100644 --- a/docs/book/translations/tr/src/batch-operations.md +++ b/docs/book/translations/tr/src/batch-operations.md @@ -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 | + Her islem, yolu iceren bir `QualifiedGroveDbOp` icine sarilir: ```rust diff --git a/docs/book/translations/vi/src/batch-operations.md b/docs/book/translations/vi/src/batch-operations.md index 8a3cba9ed..f3799a3d2 100644 --- a/docs/book/translations/vi/src/batch-operations.md +++ b/docs/book/translations/vi/src/batch-operations.md @@ -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 | + Mỗi thao tác được bọc trong `QualifiedGroveDbOp` bao gồm đường dẫn: ```rust diff --git a/docs/book/translations/zh/src/batch-operations.md b/docs/book/translations/zh/src/batch-operations.md index 0b826c400..d8f48c67a 100644 --- a/docs/book/translations/zh/src/batch-operations.md +++ b/docs/book/translations/zh/src/batch-operations.md @@ -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