fix(drive)!: bound pre-programmed distribution amounts and queue a shared release-time tree once - #4837
Conversation
…pdate A token added through a data contract update never got its perpetual or pre-programmed distribution storage: update_contract v1 only creates the token's balance, identity info, status, contract info and supply entries, while insert_contract v1 also calls add_perpetual_distribution and add_pre_programmed_distributions. A claim on such a token read the missing last-claim path as None and then failed while applying (the last-claim insert for perpetual, the scheduled-reference delete for pre-programmed), so it became an InternalError, was stripped from every proposal, and the distribution was unclaimable. - update_contract v2, selected by protocol version 14 only (DRIVE_CONTRACT_METHOD_VERSIONS_V4 amended in place), creates the storage for tokens absent from the original contract. v1 is unchanged outside its tests, which are now pinned to protocol version 13. - transition_to_version_14 backfills the storage of tokens added by update before the upgrade (Drive::add_missing_token_distribution_storage_to_all_contracts). It is idempotent, runs inside the block transaction, and skips a pre-programmed distribution whose amounts no sum tree can hold instead of failing the upgrade block. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Brings in #4827 (once-per-identity token distribution). Textual conflict in transition_to_version_14: both sides appended a step. Kept both, the token history contract v2 and the once-per-identity root tree first, then the distribution storage backfill. Semantic conflict git merged silently: #4827 created the once-per-identity claims subtree of a token added by update inside update_contract v1, but protocol version 14 selects v2 since this branch, so on the merged tree such a token got no subtree and test_update_contract_v1_adds_token_with_once_per_identity_distribution failed. The block and its test move into v2, inside the same absent-from-the-original-contract gate as the perpetual and pre-programmed storage, and v1 returns to its shipped body with its tests pinned to protocol version 13. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ng to repair The first block of protocol version 14 walked every contract to create the distribution storage of tokens that an update had added before the upgrade. Mainnet has no such token, checked at block 436796: - all 20 data contract updates ever broadcast (12 succeeded) decode to a contract without tokens, and an update that adds a token has to carry it - the 5 tokens on mainnet all belong to contracts still at version 1, so none was ever touched by a contract update or a token config update, and none has a perpetual or pre-programmed distribution So the walk could only ever be a no-op there, and it was the riskiest part of the change: state rewritten on the upgrade block, where an error halts the chain. transition_to_version_14 and the migration module are back to what v4.2-dev has. update_contract v2 is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
v2 was a full copy of v1 with the distribution storage added. It now calls update_contract_operations_v1 and appends the perpetual and pre-programmed storage of the tokens the update adds, the way v1 builds on update_contract_operations_v0. v1 is back to what v4.2-dev has, apart from the visibility of that one function. That includes the once-per-identity claims subtree #4827 put there, which v2 now gets by delegation instead of carrying its own copy, and v1's tests, which run through v2 at the latest protocol version and so need neither pinning nor duplicating. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e_contract v2 #4827 created the claims subtree of a token added by update inside update_contract v1, the generation protocol version 14 selected at the time. The once-per-identity kind is new in protocol version 14 and that version selects v2 now, so the block and its test move there, next to the perpetual and pre-programmed storage and inside the same absent-from-the-original-contract gate. v1 is back to what it was before #4827, apart from the visibility of update_contract_operations_v1. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ared release-time tree once
Two pre-programmed token distribution inputs passed every validation and then
failed inside Drive as internal errors, which are never paid for and are
stripped from every proposal. Both fixes are gated to protocol version 14.
Amounts. A release is stored as a sum tree of its recipients' amounts, so an
amount above i64::MAX, or amounts totalling more, can not be written. The new
TokenPreProgrammedDistribution::validate_amounts (rs-dpp) rejects such a
release with PreProgrammedDistributionAmountOverLimitError (basic, 10277):
- on contract create, in basic_structure v2 (unpaid, caught at check_tx);
- on contract update, in DataContract::validate_update v1, for the tokens the
update adds only (paid).
What protocol version 13 did, verified by test: a single amount over the limit
was an internal error on create AND on update, because the fee estimation of
an update takes the contract insert path. Only a release whose amounts each
fit but total more was accepted by an update, since the estimation does not
sum and the v1 update wrote no distribution storage. A network may hold such
a token, so tokens a contract already has are not judged and the contract
stays updatable.
Shared release time. add_pre_programmed_distributions v0 looked for the
release-time tree all tokens share in state only, so two tokens of one
contract releasing at the same time queued it twice in one batch. That is
refused ("insertion order error") only by a Drive with
batching_consistency_verification on, which rs-drive tests force and shipped
nodes leave off; with it off GroveDB folds the identical inserts and the
contract is stored. v1 also looks among the gathered operations. It stores
the same state (equal root hash, pinned by test) and lowers the processing
fee by the existence read the later tokens no longer make, hence a new
version (DRIVE_TOKEN_METHOD_VERSIONS_V2, selected by DRIVE_VERSION_V9 only)
rather than an edit of v0.
The new BasicError variant goes after #4827's at the enum tail and is pinned
at discriminant 188 in basic_error_tail_discriminants_are_frozen.
Stacked on #4834 (update_contract v2).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configurationConfiguration used: Repository: dashpay/platform/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (27)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
📖 Book Preview built successfully. Download the preview from the workflow artifacts. Updated at 2026-09-19T13:16:21.288Z |
PR HygieneState: waiting-bots · commit
Self-review is an author attestation that you have read the diff: This report does not bypass CI or repository protection rules. |
PR HygieneState: waiting-bots · commit
Self-review is an author attestation that you have read the diff: This report does not bypass CI or repository protection rules. |
|
🕓 Queued for automated review — 6th in line, estimated start in ~35 min (commit b9bc374)
|
Conflict in the data contract update tests: this branch and #4837 both appended a helper and its tests at the end of `token_tests`. Both blocks are kept whole, #4837's first. The protocol version 14 changelog merged cleanly but with two entries numbered 13, since #4837 added 13 and 14. The entry for tokens added by a contract update becomes 15. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## v4.2-dev #4837 +/- ##
============================================
- Coverage 88.02% 78.58% -9.45%
============================================
Files 2977 2981 +4
Lines 388320 436083 +47763
============================================
+ Hits 341836 342682 +846
- Misses 46484 93401 +46917
🚀 New features to boost your workflow:
|
Issue being fixed or feature implemented
Two pre-programmed token distribution inputs pass every validation and then fail inside Drive as internal errors. An
InternalErrorstate transition is never paid for and is stripped from every proposal, so the user just sees the transition vanish.i64::MAXfails inadd_pre_programmed_distributions(ProtocolError::Overflow), and amounts that each fit but total more fail in GroveDB (Overflow("sum is overflowing")). No consensus validation bounded either.add_pre_programmed_distributionsv0 looks for the shared[Tokens, distributions, timed, ms] / timetree in state only, so two tokens releasing at the same time queue the same tree twice in one batch.Testing corrected two assumptions, and the corrections shape the fix:
GroveDBInsertion("insertion order error")) only whenbatching_consistency_verificationis on.setup_drive_with_initial_state_structureforces it on, but the shipped default is off and dashmate does not override it. With it off GroveDB folds the two identical inserts and the contract is stored. The real hazard is a node with the flag on failing a transition every other node executes.apply_contractin fee-estimation mode cannot see the stored contract, so an update's estimation takes the insert path and hits the explicitamount > i64::MAXcheck. A single over-limit amount was therefore an internal error on update at PV13 too. Only a release whose amounts each fit but total over the limit was accepted (estimation does not sum, and the v1 update wrote no distribution storage).What was done?
All behaviour changes are selected by protocol version 14 only. Shipped
vNmodules are untouched.Amount bound
TokenPreProgrammedDistribution::validate_amountsin rs-dpp (pure-data invariant tier), versioned bycontract_versions.token_versions.validate_pre_programmed_distribution_amounts. One rule covers both cases: each release must total at mosti64::MAX.PreProgrammedDistributionAmountOverLimitError, basic code 10277 (next free in the data contract band). Appended after feat(platform)!: once-per-identity token distribution #4827's variant at theBasicErrortail and pinned at discriminant 188 inbasic_error_tail_discriminants_are_frozen. Mapped in wasm-dpp; added to the book's error code table.basic_structurev2 (PV14-only, extended in place). Unpaid, rejected atcheck_tx.DataContract::validate_updatev1 (PV14-only, extended in place), for the tokens the update adds only. Paid. Tokens a contract already has are not judged, so a contract holding a legacy total-overflow token stays updatable. fix(drive)!: create distribution trees for tokens added by contract update #4834 found no such token on mainnet; other networks were not checked.Shared release time
add_pre_programmed_distributionsv1, selected through a newDRIVE_TOKEN_METHOD_VERSIONS_V2that onlyDRIVE_VERSION_V9references. It uses the existingbatch_insert_empty_tree_if_not_exists_check_existing_operationsso the tree is queued once.v14.rs,drive_versions/v9.rsand the new table document both changes.How Has This Been Tested?
Tests were written first and observed red: at unit level for each defect, and end to end with the three fix sites temporarily disabled (both amount cases failed as
InternalError("... distribution amount over i64::Max")), then restored and checksum-verified.validate_amountsunit tests (limit, single amount over, total over, total not fitting au64, releases judged independently);validate_updatev1 tests for added tokens, carried-over tokens, and the PV13 pin; frozen discriminant test.add_pre_programmed_distribution/v1): insert, estimate, and PV14 update of two tokens sharing a release time, with both releases fetched and claimed; sharing the tree with an earlier contract; PV13 pin; same root hash as v0 with the shipped batching config and a lower processing fee; a release totalling overi64::MAXpasses estimation and fails at apply.basic_structurev2 tests through the dispatcher including the PV13 pin; end-to-end create and update throughprocess_raw_state_transitionsfor shared release times and both over-limit shapes (UnpaidConsensusErroron create,PaidConsensusErroron update); two PV13 update pins (total overflow accepted, single amountInternalError).Targeted runs after rebasing onto
dc3c53de8b:cargo test -p platform-version,-p dpp,-p drive,-p drive-abciwith name filters (contract create/update, token, pre-programmed), all passing.cargo clippy --all-targetsclean on the four crates,cargo fmt --checkclean,cargo check -p wasm-dpppasses. Full suites were not run locally.Breaking Changes
Consensus change, gated to protocol version 14:
i64::MAXare rejected with consensus error 10277. On create this input was already an internal error. On update, a total-overflow release was accepted before PV14.Protocol version 13 behaviour is unchanged and pinned by tests.
Checklist:
For repository code-owners and collaborators only
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation