Skip to content

docs(book): add document serialization wire format chapter#3392

Open
QuantumExplorer wants to merge 4 commits intov3.1-devfrom
docs/document-serialization
Open

docs(book): add document serialization wire format chapter#3392
QuantumExplorer wants to merge 4 commits intov3.1-devfrom
docs/document-serialization

Conversation

@QuantumExplorer
Copy link
Member

@QuantumExplorer QuantumExplorer commented Mar 20, 2026

Summary

  • Adds a new chapter to the book under Serialization documenting the binary wire format for platform documents
  • Covers serialization versions (v0, v1, v2), field layout, time fields bitfield encoding, property value encoding by type, and common pitfalls for third-party deserializers
  • Motivated by mnowatch.org incorrectly parsing v2 documents due to undocumented format changes

Test plan

  • mdbook build book succeeds
  • Review chapter content for accuracy

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation
    • Added a comprehensive chapter on document binary serialization: versioned wire formats and fallback rules; precise byte-level layout and field encodings for integers, times, identifiers, strings, byte arrays, booleans, arrays/objects; presence/optional/transient semantics and conditional fields; decoding example and CLI usage; guidance and common pitfalls for third-party deserializers.

Describes the binary wire format for serialized documents, including
the version varint, field layout, time fields bitfield, property
encoding rules, and common pitfalls for third-party deserializers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions bot added this to the v3.1.0 milestone Mar 20, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 20, 2026

📖 Book Preview built successfully.

Download the preview from the workflow artifacts.
To view locally: download the artifact, unzip, and open index.html.

Updated at 2026-03-20T16:59:57.877Z

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 20, 2026

📝 Walkthrough

Walkthrough

Added a new documentation chapter describing the Dash Platform document binary serialization format (v0–v2) and updated the book navigation to include Document Serialization.

Changes

Cohort / File(s) Summary
Serialization Documentation
book/src/SUMMARY.md, book/src/serialization/document-serialization.md
Adds a new chapter defining on-wire document layout and version dispatch (v0–v2); details field encodings (ids, ownerId, optional creatorId, revision varint, time bitfield + values, optional price), per-property serialization rules, type encodings, a worked hex decoding example, CLI decode note, and common deserializer pitfalls. Navigation/SUMMARY updated.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~2 minutes

Poem

🐰 I hop through bytes at break of day,

Mapping fields in tidy rows,
Versions tell me which to play,
A doc decoded — onward it goes,
Tiny paws, big serialization joy!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a new documentation chapter about the Dash Platform document binary serialization wire format.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/document-serialization

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@book/src/serialization/document-serialization.md`:
- Line 11: Fenced code blocks in document-serialization.md are missing language
identifiers (causing markdownlint MD040); update each unnamed triple-backtick
block that contains the ASCII art/hex examples (e.g., the block starting with
"┌──────────────────────┐", the block with "0x01  [32 bytes creatorId]", the
block with "0x01  [8 bytes big-endian u64]", and the block starting "02 ←
serialization version") by adding a language tag such as text (i.e., change ```
to ```text) for all four occurrences so the lint rule is satisfied.
- Around line 180-181: The example shows an invalid two-byte varint for
serialization version 2; update the worked example so the serialization version
is represented by a single byte 0x02 and remove the spurious continuation line
that reads "02 ← (continuation of varint, actual value = 2)". Locate the lines
containing "02 ← serialization version (varint: 2)" and the following
continuation line and delete or replace the continuation line so the example
only shows the single-byte varint representation for version 2.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7fb5ec55-d609-4126-862c-162ceaa2e1f4

📥 Commits

Reviewing files that changed from the base of the PR and between e398cd7 and c338683.

📒 Files selected for processing (2)
  • book/src/SUMMARY.md
  • book/src/serialization/document-serialization.md

Address CodeRabbit review feedback:
- Add `text` language tags to fenced code blocks (MD040)
- Fix incorrect varint continuation line — version 2 is a single-byte
  varint, the second 0x02 was the first byte of $id
- Rewrite worked example with correct byte offsets and add decoded
  tool output for clarity

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Collaborator

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The most critical finding is confirmed: the documentation incorrectly states properties are serialized in BTreeMap/alphabetical order, when the code uses IndexMap sorted by schema-defined position order. This would cause third-party deserializers to misalign fields. Two additional documentation gaps are validated: optional date encoding has an undocumented 0xff prefix, and array types are missing from the type table. The remaining findings are minor but valid.

Reviewed commit: d61d2fb

🔴 1 blocking | 🟡 3 suggestion(s) | 💬 2 nitpick(s)

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `book/src/serialization/document-serialization.md`:
- [BLOCKING] line 129: User properties are serialized in schema position order, not alphabetical/BTreeMap order
  Line 129 states `document_type.properties()` returns a `BTreeMap`, so properties are in alphabetical order. This is incorrect. The trait `DocumentTypeV0Getters::properties()` returns `&IndexMap<String, DocumentProperty>` (see `accessors/v0/mod.rs:43`). Properties are inserted into the IndexMap sorted by their `position` field from the JSON schema (`try_from_schema/mod.rs:195-202`). For the withdrawals schema, position order is: transactionIndex(0), transactionSignHeight(1), amount(2), coreFeePerByte(3), pooling(4), outputScript(5), status(6) — which differs significantly from alphabetical. The same error recurs on line 163 ('in alphabetical/BTreeMap order') and line 247 in Common Pitfalls item 4 ('Property order is alphabetical (BTreeMap order), not declaration order'). Notably, line 32 in the high-level diagram correctly says '(in schema order)', creating an internal contradiction. A third-party decoder following alphabetical-order guidance will misalign every field.
- [SUGGESTION] lines 135-156: Optional date (and potentially other types) encoding has undocumented 0xff value prefix
  The doc says optional fields have a 1-byte presence flag (0x00/0x01) then the value. However, the serializer calls `property_type.encode_value_ref_with_size(value, property.required)` with `required=false` for optional fields. For `DocumentPropertyType::Date`, this prepends an additional `0xff` byte before the 8-byte f64 payload (`property/mod.rs:1008-1019`, confirmed by test at line 3675). So the actual wire format for a present optional date is: `0x01` (presence from serialize.rs) + `0xff` (from encode_value_ref_with_size) + 8 bytes f64 = 10 bytes total. The doc's type table on line 156 only shows '8 bytes big-endian f64' with no mention of the extra prefix byte when the field is optional.
- [SUGGESTION] lines 131-137: Transient fields get a presence byte even when required
  The serialization code at `serialize.rs:232` checks `if !property.required || property.transient { buffer.push(1); }` — meaning a field that is both `required` AND `transient` receives a 1-byte presence indicator, just like optional fields. The documentation only describes the required/optional distinction (lines 133-137) and does not mention the transient exception. While transient fields may be uncommon, a third-party implementer could encounter them.
- [SUGGESTION] lines 143-157: Array type encoding is missing from the type table
  The type encoding table omits `array`. The serializer handles `DocumentPropertyType::Array` by writing a varint element count followed by each element encoded in sequence (`property/mod.rs:1118-1132`). `VariableTypeArray` is rejected during serialization. Both array types are rejected during tree-key deserialization. This is a documentation gap for any implementer who encounters an array-typed property.


### User-defined properties

Properties are serialized **in the order defined by the document type's schema** (`document_type.properties()` returns a `BTreeMap`, so properties are in alphabetical order by field name).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Blocking: User properties are serialized in schema position order, not alphabetical/BTreeMap order

Line 129 states document_type.properties() returns a BTreeMap, so properties are in alphabetical order. This is incorrect. The trait DocumentTypeV0Getters::properties() returns &IndexMap<String, DocumentProperty> (see accessors/v0/mod.rs:43). Properties are inserted into the IndexMap sorted by their position field from the JSON schema (try_from_schema/mod.rs:195-202). For the withdrawals schema, position order is: transactionIndex(0), transactionSignHeight(1), amount(2), coreFeePerByte(3), pooling(4), outputScript(5), status(6) — which differs significantly from alphabetical. The same error recurs on line 163 ('in alphabetical/BTreeMap order') and line 247 in Common Pitfalls item 4 ('Property order is alphabetical (BTreeMap order), not declaration order'). Notably, line 32 in the high-level diagram correctly says '(in schema order)', creating an internal contradiction. A third-party decoder following alphabetical-order guidance will misalign every field.

source: ['codex']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `book/src/serialization/document-serialization.md`:
- [BLOCKING] line 129: User properties are serialized in schema position order, not alphabetical/BTreeMap order
  Line 129 states `document_type.properties()` returns a `BTreeMap`, so properties are in alphabetical order. This is incorrect. The trait `DocumentTypeV0Getters::properties()` returns `&IndexMap<String, DocumentProperty>` (see `accessors/v0/mod.rs:43`). Properties are inserted into the IndexMap sorted by their `position` field from the JSON schema (`try_from_schema/mod.rs:195-202`). For the withdrawals schema, position order is: transactionIndex(0), transactionSignHeight(1), amount(2), coreFeePerByte(3), pooling(4), outputScript(5), status(6) — which differs significantly from alphabetical. The same error recurs on line 163 ('in alphabetical/BTreeMap order') and line 247 in Common Pitfalls item 4 ('Property order is alphabetical (BTreeMap order), not declaration order'). Notably, line 32 in the high-level diagram correctly says '(in schema order)', creating an internal contradiction. A third-party decoder following alphabetical-order guidance will misalign every field.

Comment on lines +135 to +156
**Optional fields**: A 1-byte presence flag is written first:
- `0x01` followed by the encoded value — field is present
- `0x00` — field is absent

#### Value encoding by type

All numeric values use **big-endian** byte order.

| Type | Encoding |
|------|----------|
| `u8` / `i8` | 1 byte |
| `u16` / `i16` | 2 bytes big-endian |
| `u32` / `i32` | 4 bytes big-endian |
| `u64` / `i64` | 8 bytes big-endian |
| `u128` / `i128` | 16 bytes big-endian |
| `f64` | 8 bytes big-endian IEEE 754 |
| `boolean` | 1 byte: `0x01` = true, `0x00` = false |
| `string` | varint length prefix + UTF-8 bytes |
| `byteArray` (fixed size) | raw bytes (no length prefix if min_size == max_size) |
| `byteArray` (variable size) | varint length prefix + raw bytes |
| `identifier` | 32 bytes raw |
| `date` | 8 bytes big-endian f64 |
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: Optional date (and potentially other types) encoding has undocumented 0xff value prefix

The doc says optional fields have a 1-byte presence flag (0x00/0x01) then the value. However, the serializer calls property_type.encode_value_ref_with_size(value, property.required) with required=false for optional fields. For DocumentPropertyType::Date, this prepends an additional 0xff byte before the 8-byte f64 payload (property/mod.rs:1008-1019, confirmed by test at line 3675). So the actual wire format for a present optional date is: 0x01 (presence from serialize.rs) + 0xff (from encode_value_ref_with_size) + 8 bytes f64 = 10 bytes total. The doc's type table on line 156 only shows '8 bytes big-endian f64' with no mention of the extra prefix byte when the field is optional.

source: ['codex']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `book/src/serialization/document-serialization.md`:
- [SUGGESTION] lines 135-156: Optional date (and potentially other types) encoding has undocumented 0xff value prefix
  The doc says optional fields have a 1-byte presence flag (0x00/0x01) then the value. However, the serializer calls `property_type.encode_value_ref_with_size(value, property.required)` with `required=false` for optional fields. For `DocumentPropertyType::Date`, this prepends an additional `0xff` byte before the 8-byte f64 payload (`property/mod.rs:1008-1019`, confirmed by test at line 3675). So the actual wire format for a present optional date is: `0x01` (presence from serialize.rs) + `0xff` (from encode_value_ref_with_size) + 8 bytes f64 = 10 bytes total. The doc's type table on line 156 only shows '8 bytes big-endian f64' with no mention of the extra prefix byte when the field is optional.

Comment on lines +131 to +137
Each property is encoded based on its type and whether it is required:

**Required fields**: The value is written directly with no prefix byte.

**Optional fields**: A 1-byte presence flag is written first:
- `0x01` followed by the encoded value — field is present
- `0x00` — field is absent
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: Transient fields get a presence byte even when required

The serialization code at serialize.rs:232 checks if !property.required || property.transient { buffer.push(1); } — meaning a field that is both required AND transient receives a 1-byte presence indicator, just like optional fields. The documentation only describes the required/optional distinction (lines 133-137) and does not mention the transient exception. While transient fields may be uncommon, a third-party implementer could encounter them.

source: ['claude']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `book/src/serialization/document-serialization.md`:
- [SUGGESTION] lines 131-137: Transient fields get a presence byte even when required
  The serialization code at `serialize.rs:232` checks `if !property.required || property.transient { buffer.push(1); }` — meaning a field that is both `required` AND `transient` receives a 1-byte presence indicator, just like optional fields. The documentation only describes the required/optional distinction (lines 133-137) and does not mention the transient exception. While transient fields may be uncommon, a third-party implementer could encounter them.

Comment on lines +143 to +157
| Type | Encoding |
|------|----------|
| `u8` / `i8` | 1 byte |
| `u16` / `i16` | 2 bytes big-endian |
| `u32` / `i32` | 4 bytes big-endian |
| `u64` / `i64` | 8 bytes big-endian |
| `u128` / `i128` | 16 bytes big-endian |
| `f64` | 8 bytes big-endian IEEE 754 |
| `boolean` | 1 byte: `0x01` = true, `0x00` = false |
| `string` | varint length prefix + UTF-8 bytes |
| `byteArray` (fixed size) | raw bytes (no length prefix if min_size == max_size) |
| `byteArray` (variable size) | varint length prefix + raw bytes |
| `identifier` | 32 bytes raw |
| `date` | 8 bytes big-endian f64 |
| `object` | Nested fields serialized recursively in their schema order |
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: Array type encoding is missing from the type table

The type encoding table omits array. The serializer handles DocumentPropertyType::Array by writing a varint element count followed by each element encoded in sequence (property/mod.rs:1118-1132). VariableTypeArray is rejected during serialization. Both array types are rejected during tree-key deserialization. This is a documentation gap for any implementer who encounters an array-typed property.

source: ['codex']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `book/src/serialization/document-serialization.md`:
- [SUGGESTION] lines 143-157: Array type encoding is missing from the type table
  The type encoding table omits `array`. The serializer handles `DocumentPropertyType::Array` by writing a varint element count followed by each element encoded in sequence (`property/mod.rs:1118-1132`). `VariableTypeArray` is rejected during serialization. Both array types are rejected during tree-key deserialization. This is a documentation gap for any implementer who encounters an array-typed property.

Comment on lines +186 to +187
6de0 157e
c501 ← $revision (varint: 197)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 Nitpick: Worked example should note why $creatorId is absent despite v2

The example uses serialization version 2. The high-level diagram (line 20-21) states v2 includes $creatorId for document types supporting transfers/trading. The hex walkthrough jumps from $ownerId directly to $revision with no mention of $creatorId. The code (serialize.rs:498-500) shows the entire $creatorId section (including the flag byte) is omitted when trade_mode == None && !documents_transferable(). A brief note would help readers following along understand why the field is absent entirely rather than present with a 0x00 flag.

💡 Suggested change
Suggested change
6de0 157e
c501 ← $revision (varint: 197)
Add a comment line in the hex walkthrough, e.g.: '← $creatorId: omitted (withdrawal type is not transferable/tradeable)'

source: ['claude']

| `byteArray` (fixed size) | raw bytes (no length prefix if min_size == max_size) |
| `byteArray` (variable size) | varint length prefix + raw bytes |
| `identifier` | 32 bytes raw |
| `date` | 8 bytes big-endian f64 |
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 Nitpick: User-property date type (f64) vs system timestamp type (u64) could be explicitly contrasted

System time fields ($createdAt, etc.) on line 112 are documented as u64, and user-property date on line 156 is documented as f64. Both are 8 bytes but different types. The information is technically present in both sections, but a reader implementing a deserializer might miss the distinction since both are 'timestamp-like' 8-byte values.

source: ['claude']

…details

- Properties use schema position order (IndexMap), not alphabetical (BTreeMap)
- Document transient fields always get a presence byte even when required
- Add array type encoding to the type table
- Note 0xff prefix for optional date fields
- Clarify user-property date (f64) vs system timestamp (u64) encoding
- Note why $creatorId is absent in the v2 worked example

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
book/src/serialization/document-serialization.md (1)

168-179: ⚠️ Potential issue | 🟠 Major

Property-order guidance is still contradictory and can misalign decoders.

Line 129 correctly says schema-position order, but Line 253 still says alphabetical/BTreeMap order. Please make this consistent and ensure the worked example list (Lines 168-179) matches actual schema position values.

Suggested doc fix
-4. **Property order is alphabetical (BTreeMap order), not declaration order.** If you hard-code field offsets based on the JSON schema declaration order rather than alphabetical order, fields will be read from the wrong positions.
+4. **Property order is schema position order (IndexMap insertion order), not alphabetical order.** If you parse properties using alphabetical/key order instead of each field’s schema `position`, fields will be read from the wrong offsets.

Run this to verify the withdrawal property order from schema position values before finalizing the worked example list:

#!/bin/bash
set -euo pipefail

python - <<'PY'
import json, pathlib

target_fields = {
    "transactionIndex",
    "transactionSignHeight",
    "amount",
    "coreFeePerByte",
    "pooling",
    "outputScript",
    "status",
}

def walk(obj):
    if isinstance(obj, dict):
        yield obj
        for v in obj.values():
            yield from walk(v)
    elif isinstance(obj, list):
        for i in obj:
            yield from walk(i)

for p in pathlib.Path(".").rglob("*.json"):
    try:
        data = json.loads(p.read_text(encoding="utf-8"))
    except Exception:
        continue

    for node in walk(data):
        props = node.get("properties") if isinstance(node, dict) else None
        if isinstance(props, dict) and target_fields.issubset(props.keys()):
            rows = []
            for k in target_fields:
                v = props.get(k, {})
                pos = v.get("position") if isinstance(v, dict) else None
                rows.append((pos, k))
            rows.sort(key=lambda x: (x[0] is None, x[0], x[1]))
            print(f"\nSchema candidate: {p}")
            print("Ordered by position:")
            for pos, k in rows:
                print(f"  {pos}: {k}")
PY

Also applies to: 253-254

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@book/src/serialization/document-serialization.md` around lines 168 - 179, The
worked example and prose for the withdrawals document are inconsistent: replace
any mention that properties are ordered alphabetically/BTreeMap with a clear
statement that properties must follow schema "position" order, then reorder the
example table rows for the withdrawal document (the properties amount,
coreFeePerByte, outputScript, pooling, status, transactionIndex,
transactionSignHeight) to match the schema "position" values; verify the final
ordering by running the provided JSON-walking verification script against the
contract schemas and update the example/table to exactly match the discovered
positions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@book/src/serialization/document-serialization.md`:
- Around line 168-179: The worked example and prose for the withdrawals document
are inconsistent: replace any mention that properties are ordered
alphabetically/BTreeMap with a clear statement that properties must follow
schema "position" order, then reorder the example table rows for the withdrawal
document (the properties amount, coreFeePerByte, outputScript, pooling, status,
transactionIndex, transactionSignHeight) to match the schema "position" values;
verify the final ordering by running the provided JSON-walking verification
script against the contract schemas and update the example/table to exactly
match the discovered positions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 30f8fa40-b068-437d-9c90-b7fa296a9f1c

📥 Commits

Reviewing files that changed from the base of the PR and between c338683 and 1425adf.

📒 Files selected for processing (1)
  • book/src/serialization/document-serialization.md

Copy link
Collaborator

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The prior push correctly fixed the main body text (property ordering, transient fields, date encoding, array type). However, two ordering inconsistencies remain: pitfall #4 still references the old alphabetical/BTreeMap order, and the worked example table claims schema position order but is actually alphabetical. Both would mislead implementers.

Reviewed commit: 1425adf

🔴 2 blocking

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `book/src/serialization/document-serialization.md`:
- [BLOCKING] line 253: Pitfall #4 still says alphabetical/BTreeMap order — contradicts the corrected text at line 129
  Line 129 was correctly updated to say 'Properties are serialized **in schema position order**', but pitfall #4 still reads 'Property order is alphabetical (BTreeMap order), not declaration order.' This directly contradicts the fix. The serializer at `packages/rs-dpp/src/document/v0/serialize.rs:213-216` iterates `document_type.properties()` which returns an `IndexMap` sorted by each property's `position` field (sorted at schema parse time in `try_from_schema/mod.rs:193-200`). Implementers who skip to the pitfalls section will get the wrong ordering rule.
- [BLOCKING] lines 168-178: Worked example property table is in alphabetical order, not schema position order
  Line 168 says 'listed here in schema position order' but the table lists properties alphabetically: amount, coreFeePerByte, outputScript, pooling, status, transactionIndex, transactionSignHeight. The actual positions from `packages/withdrawals-contract/schema/v1/withdrawals-documents.json` are: transactionIndex=0, transactionSignHeight=1, amount=2, coreFeePerByte=3, pooling=4, outputScript=5, status=6. The current table order would cause an implementer to start the property payload with `amount` instead of `transactionIndex`, misaligning every subsequent field.


3. **The time fields bitfield is variable-length data.** The 2-byte bitfield tells you how many time fields follow. If you assume a fixed number of time fields, any document with a different set of time fields (e.g., one that includes `$transferredAt` or block heights) will be misaligned.

4. **Property order is alphabetical (BTreeMap order), not declaration order.** If you hard-code field offsets based on the JSON schema declaration order rather than alphabetical order, fields will be read from the wrong positions.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Blocking: Pitfall #4 still says alphabetical/BTreeMap order — contradicts the corrected text at line 129

Line 129 was correctly updated to say 'Properties are serialized in schema position order', but pitfall #4 still reads 'Property order is alphabetical (BTreeMap order), not declaration order.' This directly contradicts the fix. The serializer at packages/rs-dpp/src/document/v0/serialize.rs:213-216 iterates document_type.properties() which returns an IndexMap sorted by each property's position field (sorted at schema parse time in try_from_schema/mod.rs:193-200). Implementers who skip to the pitfalls section will get the wrong ordering rule.

source: ['claude', 'codex']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `book/src/serialization/document-serialization.md`:
- [BLOCKING] line 253: Pitfall #4 still says alphabetical/BTreeMap order — contradicts the corrected text at line 129
  Line 129 was correctly updated to say 'Properties are serialized **in schema position order**', but pitfall #4 still reads 'Property order is alphabetical (BTreeMap order), not declaration order.' This directly contradicts the fix. The serializer at `packages/rs-dpp/src/document/v0/serialize.rs:213-216` iterates `document_type.properties()` which returns an `IndexMap` sorted by each property's `position` field (sorted at schema parse time in `try_from_schema/mod.rs:193-200`). Implementers who skip to the pitfalls section will get the wrong ordering rule.

Comment on lines +168 to +178
The withdrawals contract defines a `withdrawal` document type with these properties (listed here in schema position order):

| Property | Type | Required |
|----------|------|----------|
| `amount` | integer (i64) | yes |
| `coreFeePerByte` | integer (i64) | yes |
| `outputScript` | byteArray (23–25 bytes, variable) | yes |
| `pooling` | integer (i64) | yes |
| `status` | integer (i64) | yes |
| `transactionIndex` | integer (i64) | no |
| `transactionSignHeight` | integer (i64) | no |
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Blocking: Worked example property table is in alphabetical order, not schema position order

Line 168 says 'listed here in schema position order' but the table lists properties alphabetically: amount, coreFeePerByte, outputScript, pooling, status, transactionIndex, transactionSignHeight. The actual positions from packages/withdrawals-contract/schema/v1/withdrawals-documents.json are: transactionIndex=0, transactionSignHeight=1, amount=2, coreFeePerByte=3, pooling=4, outputScript=5, status=6. The current table order would cause an implementer to start the property payload with amount instead of transactionIndex, misaligning every subsequent field.

💡 Suggested change
Suggested change
The withdrawals contract defines a `withdrawal` document type with these properties (listed here in schema position order):
| Property | Type | Required |
|----------|------|----------|
| `amount` | integer (i64) | yes |
| `coreFeePerByte` | integer (i64) | yes |
| `outputScript` | byteArray (23–25 bytes, variable) | yes |
| `pooling` | integer (i64) | yes |
| `status` | integer (i64) | yes |
| `transactionIndex` | integer (i64) | no |
| `transactionSignHeight` | integer (i64) | no |
The withdrawals contract defines a `withdrawal` document type with these properties (listed here in schema position order):
| Property | Type | Required |
|----------|------|----------|
| `transactionIndex` | integer (i64) | no |
| `transactionSignHeight` | integer (i64) | no |
| `amount` | integer (i64) | yes |
| `coreFeePerByte` | integer (i64) | yes |
| `pooling` | integer (i64) | yes |
| `outputScript` | byteArray (23\u201325 bytes, variable) | yes |
| `status` | integer (i64) | yes |

source: ['claude', 'codex']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `book/src/serialization/document-serialization.md`:
- [BLOCKING] lines 168-178: Worked example property table is in alphabetical order, not schema position order
  Line 168 says 'listed here in schema position order' but the table lists properties alphabetically: amount, coreFeePerByte, outputScript, pooling, status, transactionIndex, transactionSignHeight. The actual positions from `packages/withdrawals-contract/schema/v1/withdrawals-documents.json` are: transactionIndex=0, transactionSignHeight=1, amount=2, coreFeePerByte=3, pooling=4, outputScript=5, status=6. The current table order would cause an implementer to start the property payload with `amount` instead of `transactionIndex`, misaligning every subsequent field.

…ition order

- Reorder worked example property table to match actual schema positions
- Fix pitfall #4 to say schema position order, not alphabetical/BTreeMap

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@book/src/serialization/document-serialization.md`:
- Line 158: Update the table row for `date` to clarify that an optional date
includes the standard presence byte plus the date-specific prefix and 8-byte f64
(i.e., present optional date is 0x01 (presence) + 0xff
(encode_value_ref_with_size prefix) + 8 bytes = 10 bytes), or add a short note
under the table stating the 0xff prefix is in addition to the 0x01 presence flag
described earlier (lines referring to the presence flag logic); reference
`encode_value_ref_with_size` and the presence-flag logic to make the relation
explicit.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e2eb556c-f3c5-4000-a708-ab907fe46a9d

📥 Commits

Reviewing files that changed from the base of the PR and between 1425adf and 6355f35.

📒 Files selected for processing (1)
  • book/src/serialization/document-serialization.md

| `byteArray` (fixed size) | raw bytes (no length prefix if min_size == max_size) |
| `byteArray` (variable size) | varint length prefix + raw bytes |
| `identifier` | 32 bytes raw |
| `date` | 8 bytes big-endian f64 (when optional: `0xff` prefix + 8 bytes) |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clarify the complete encoding for optional date fields.

The type table states that optional dates use "0xff prefix + 8 bytes," but this omits the initial presence flag described in lines 135–137. Based on the serialization logic, an optional date field that is present should be encoded as:

  • 0x01 (presence flag, per lines 135–137)
  • 0xff (date-type-specific prefix from encode_value_ref_with_size)
  • 8 bytes (the f64 value)

Total: 10 bytes

The current table entry could mislead implementers into expecting 9 bytes.

📝 Suggested clarification
-| `date` | 8 bytes big-endian f64 (when optional: `0xff` prefix + 8 bytes) |
+| `date` | 8 bytes big-endian f64 (when optional: `0x01` presence + `0xff` + 8 bytes) |

Alternatively, add a note below the table:

Note: The 0xff prefix for optional dates is in addition to the standard 0x01 presence byte described above, resulting in 10 bytes total for a present optional date field.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| `date` | 8 bytes big-endian f64 (when optional: `0xff` prefix + 8 bytes) |
| `date` | 8 bytes big-endian f64 (when optional: `0x01` presence + `0xff` + 8 bytes) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@book/src/serialization/document-serialization.md` at line 158, Update the
table row for `date` to clarify that an optional date includes the standard
presence byte plus the date-specific prefix and 8-byte f64 (i.e., present
optional date is 0x01 (presence) + 0xff (encode_value_ref_with_size prefix) + 8
bytes = 10 bytes), or add a short note under the table stating the 0xff prefix
is in addition to the 0x01 presence flag described earlier (lines referring to
the presence flag logic); reference `encode_value_ref_with_size` and the
presence-flag logic to make the relation explicit.

Copy link
Collaborator

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Clean incremental push — the property table in the worked example now correctly uses schema position order (positions 0-6 verified against packages/withdrawals-contract/schema/v1/withdrawals-documents.json) and pitfall #4 accurately describes position-based serialization via IndexMap. Both independent reviewers (Claude + Codex) verified the positions against the withdrawal contract schema and the serialization code in packages/rs-dpp/src/document/v0/serialize.rs. No new issues found.

Reviewed commit: 6355f35

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants