Skip to content

fix(dpp)!: contract updates may not change an integer property's width or signedness (PV14) - #4925

Merged
QuantumExplorer merged 1 commit into
v4.2-devfrom
claude/integer-width-update-guard
Sep 22, 2026
Merged

QuantumExplorer merged 1 commit into
v4.2-devfrom
claude/integer-width-update-guard

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

With sizedIntegerTypes on (the default since config V1, protocol version 9), an integer property's type (u8 to i64) comes from its minimum and maximum, or from its enum values when it has no bounds (find_integer_type_for_subschema_value). The JSON schema compatibility rules allow every change that moves that type on a contract update:

  • raising maximum ({"minimum": 0, "maximum": 100} is a u8, "maximum": 1000 makes it a u16)
  • lowering minimum below zero, which also changes the signedness
  • removing minimum, maximum or both
  • adding enum values past the width

The contract config also lets an update turn sizedIntegerTypes on for a config V1 contract created with it off. The config check only refuses turning it off. Turning it on changes every bounded integer from i64.

Documents and index keys store an integer at the width of its type. After such an update, documents already stored no longer decode against the updated document type: U8.encode_value_ref_with_size(&Value::U8(7), true) is [7], and the u16 reader returns CorruptedSerialization("error reading u16 from serialized document"). Documents written under serialization v1/v2 can also read back as other values, and their index entries stay under keys of the old width. Every later transition on such a document fails, whoever owns the document, and so does every query that decodes it. Nothing lets the contract owner undo it: lowering maximum back is refused by the same compatibility rules.

validate_byte_array_encoding_stability already refuses the analogous change for byte arrays (a fixed-size array turning length-prefixed). Nothing held the integer type.

What was done?

  • validate_update v1 (selected only by protocol version 14's tables, still unreleased) runs a new validate_integer_encoding_stability. The check sits right after the byte array one and before the schema compatibility check.
    • It walks the flattened properties, nested ones included. When the property is an integer on both sides and its type changed, it refuses with a DocumentTypeUpdateError: document type can not change the integer encoding of property 'score': its values are stored as u8 and would be read as u16.
    • A bound change that keeps the type (a u8 with maximum raised from 100 to 200) is still allowed. A change to a non-integer type is left to the schema compatibility check, which already refuses a type change.
  • Signedness is held together with width. A u8 capped at 100 reads the same as an i8, but a u64 that becomes an i64 (minimum lowered from 0 to -1) misreads anything above i64::MAX. One comparison keeps the rule simple.
  • validate_update v0 (protocol versions 1 to 13) is unchanged, byte for byte, for replay.
  • DocumentPropertyType::stored_encoding() describes how a scalar value is laid out: an integer by its type, and a byte array as fixed raw bytes or length-prefixed.

How Has This Been Tested?

New tests in validate_update/v1 (integer_encoding_update). All go through the public validate_update dispatcher at PlatformVersion::latest() unless stated:

  • should_not_read_a_stored_u8_back_as_the_u16_a_raised_maximum_gives: the parsed types are U8 and U16, and the encoded [7] fails to read back.
  • Refused:
    • should_reject_raising_maximum_past_the_width_of_the_type (u8 to u16)
    • should_reject_lowering_minimum_below_zero (u64 to i64)
    • should_reject_removing_the_bounds (u8 to i64)
    • should_reject_adding_an_enum_value_past_the_width_of_the_type (u8 to u16)
    • should_reject_a_width_change_of_a_nested_integer_property (stats.level)
    • should_reject_turning_sized_integer_types_on (i64 to u8)
  • Accepted:
    • should_accept_a_bound_change_that_keeps_the_type
    • should_accept_raising_maximum_without_sized_integer_types
  • should_still_accept_a_width_change_at_protocol_version_13: the other side of the gate, where v0 is frozen.

Locally:

Not run locally: drive-abci, and an end-to-end process_raw_state_transitions test of a contract update. CI runs the drive-abci suite.

Breaking Changes

Consensus change at protocol version 14: a data contract update that changes the integer type of an existing property of an existing document type is now refused with DocumentTypeUpdateError, where protocol versions up to 13 accepted it. No new error code or variant.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed
  • If I added or changed GroveDB structure, I described it in the area's structure.rs, regenerated grovedb-structure.json, and checked the structure viewer link posted on this pull request

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

PR Hygiene · 84cb144

  • Bots — coderabbitai not yet · thepastaclaw not yet — /skip-bots proceeds without the ones not yet reported
  • Self-review — post /self-reviewed once the bots are done
  • Within your 5 open PRs
  • Build running
  • Approvals — you own every area touched; none needed

When every box is checked the PR Hygiene check passes and this can merge.

@coderabbitai

coderabbitai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 58 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: dashpay/platform/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 2ce42d84-b833-4a82-8988-c5377933c933

📥 Commits

Reviewing files that changed from the base of the PR and between 1d6497e and 84cb144.

📒 Files selected for processing (2)
  • packages/rs-dpp/src/data_contract/document_type/methods/validate_update/v1/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/property/mod.rs

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.

@github-actions github-actions Bot added this to the v4.2.0 milestone Sep 22, 2026
@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Sep 22, 2026
…h or signedness (PV14)

A sized integer property takes its type (u8 ... i64) from its `minimum`,
`maximum` or `enum`, and the schema compatibility rules allow raising
`maximum`, lowering or removing `minimum`, removing the bounds and adding
`enum` values; the contract config allows turning `sizedIntegerTypes` on.
Each can move the type, and documents stored at the old width then no
longer decode against the updated document type (or read back other
values), while their index entries stay under keys of the old width.

validate_update v1 (protocol version 14 only) now refuses any change of
an integer property's type, nested properties included, with a
DocumentTypeUpdateError, as validate_byte_array_encoding_stability
refuses a byte array's fixed-size change. A bound change that keeps the
type is still allowed. validate_update v0 is unchanged for replay.

The layout description moves to DocumentPropertyType::stored_encoding,
and the typed array element check (#4923) calls it instead of its own
copy, so both checks describe a scalar's layout the same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@QuantumExplorer
QuantumExplorer force-pushed the claude/integer-width-update-guard branch from 5006801 to 84cb144 Compare September 22, 2026 22:37
@thepastaclaw

thepastaclaw commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

🕓 Review not started yet because the new head is waiting for the 30-minute push debounce.

  • Request normal review — click when the PR is ready for review.
  • Request priority review — click to move this review to the front of the queue.

Commit 84cb144. Normal review starts when eligible; priority review starts as soon as a slot is available.

@QuantumExplorer QuantumExplorer left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Approved

@QuantumExplorer
QuantumExplorer merged commit a358ff7 into v4.2-dev Sep 22, 2026
12 of 13 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/integer-width-update-guard branch September 22, 2026 22:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-bots Waiting for the review bots to report on this head

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants