From c00615485cb460342fdfc2c50cd857c09a1f5934 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sun, 27 Sep 2026 18:25:43 +0700 Subject: [PATCH 1/2] docs(platform): say why in-place edits to shipped generations are inert Several shipped generations were edited in place during 4.2 without a comment at the edited lines saying why the edit cannot change consensus, and a few unversioned helpers became load-bearing for shipped generations without saying so. Comments only: - masternode vote balance pre-check v0 (#4904): both outcomes stay out of every block - parse_token_costs (#4828): meta-schemas v0-v2 refuse the optional key - parse_indices TTL agreement loop (#4581): only generation 3 admits timeRange - finalize_block_proposal v0 (#4741): the read only sets a Tenderdash hint - terminal_member_tree_type, property_name_tree_type_and_ranked_axes and its per-level form, equal_underlying_data: shipped generations call them, so a change belongs in a versioned method Co-Authored-By: Claude Opus 5.5 --- .../class_methods/try_from_schema/common/mod.rs | 10 +++++++++- .../engine/finalize_block_proposal/v0/mod.rs | 4 +++- .../masternode_vote/balance/v0/mod.rs | 5 ++++- .../src/drive/document/index_level_tree_types.rs | 8 ++++++++ .../src/drive/document/ranked_index_tree_type.rs | 11 +++++++++++ packages/rs-platform-value/src/eq.rs | 7 +++++++ 6 files changed, 42 insertions(+), 3 deletions(-) diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/common/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/common/mod.rs index 9266e6938f8..3cc99297de5 100644 --- a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/common/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/common/mod.rs @@ -1196,6 +1196,10 @@ fn parse_indices( // storage level), so two indexes sharing a grid on one field share one // level's subtrees — and a level cannot have two lifecycles. Identical // grids must declare identical TTLs (including both declaring none). + // + // Document type generations 0-2 (protocol versions 1-13) run this loop too, but only the + // generation 3 index grammar admits `timeRange`, so every index they parse has + // `time_range: None` and the loop changes nothing for them. for (name_a, index_a) in indices.iter() { let Some(transform_a) = &index_a.time_range else { continue; @@ -1461,7 +1465,11 @@ fn parse_token_costs( .transpose()? .unwrap_or(DocumentActionTokenEffect::TransferTokenToContractOwner); // Whether a transition may skip the token payment and have its signer pay - // the gas in credits instead (the v3 meta-schema admits the flag) + // the gas in credits instead. Only the v3 meta-schema admits the flag. + // Document type generations 0-2 (protocol versions 1-13) also run this + // parser, but their meta-schemas (v0-v2) set `additionalProperties: false` + // on `documentActionTokenCost`, so no contract they accept carries the key + // and this reads `false` for them, as before the flag existed. let optional = action_cost .get_optional_bool("optional")? .unwrap_or_default(); diff --git a/packages/rs-drive-abci/src/execution/engine/finalize_block_proposal/v0/mod.rs b/packages/rs-drive-abci/src/execution/engine/finalize_block_proposal/v0/mod.rs index 2a13cc17780..0c07f6ecbe0 100644 --- a/packages/rs-drive-abci/src/execution/engine/finalize_block_proposal/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/engine/finalize_block_proposal/v0/mod.rs @@ -272,7 +272,9 @@ where // Withdrawal transactions pooled in this block (or left over from a backlog) wait for // the next block to sign them. Ask Tenderdash for that block right away instead of - // after the empty-block interval. + // after the empty-block interval. Added in place to this shipped generation: the read + // is unbilled and only sets a hint for Tenderdash, so it changes no fee, state or app + // hash at any protocol version. let propose_next_block_immediately = self.has_pending_withdrawal_work(Some(transaction), platform_version)?; diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/balance/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/balance/v0/mod.rs index b3e93ea69ce..bdabab16e3d 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/balance/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/balance/v0/mod.rs @@ -65,7 +65,10 @@ impl MasternodeVoteTransitionBalanceValidationV0 for MasternodeVoteTransition { // What executing the vote deducts from the fund. Until 4.2 the vote's minimum fee was // required here instead, a smaller amount, so a fund between the two passed this check - // and the vote failed inside execution. + // and the vote failed inside execution. Edited in place: it cannot change a block at any + // protocol version, because a vote refused here is refused unpaid and a vote that failed + // inside execution was an internal error, and both are left out of every block (see the + // prefunded balance pre-check in the state transition processor). let single_vote_cost = platform_version .fee_version .vote_resolution_fund_fees diff --git a/packages/rs-drive/src/drive/document/index_level_tree_types.rs b/packages/rs-drive/src/drive/document/index_level_tree_types.rs index 2245319577f..09b93c827de 100644 --- a/packages/rs-drive/src/drive/document/index_level_tree_types.rs +++ b/packages/rs-drive/src/drive/document/index_level_tree_types.rs @@ -224,6 +224,14 @@ pub(crate) fn time_range_index_keys<'a>( /// × stored/indexOnly): the insert side creates the tree and the delete /// side emits `EstimatedLayerInformation` describing it, and any drift /// produces dry-run fees that disagree with applied fees. +/// +/// Shipped generations depend on this function: +/// `add_reference_for_index_level_for_contract_operations` v0 and +/// `remove_reference_for_index_level_for_contract_operations` v0, which every +/// protocol version selects, call it for every stored-type index terminal. +/// Changing what it returns for an index shape protocol versions 1-13 can +/// declare changes the trees and fees of those versions; make such a change a +/// new versioned method instead of editing this function. pub(crate) fn terminal_member_tree_type(index_type: &IndexLevelTypeInfo) -> TreeType { let count_provable = matches!( index_type.countable, diff --git a/packages/rs-drive/src/drive/document/ranked_index_tree_type.rs b/packages/rs-drive/src/drive/document/ranked_index_tree_type.rs index a514cc63581..134ade4b098 100644 --- a/packages/rs-drive/src/drive/document/ranked_index_tree_type.rs +++ b/packages/rs-drive/src/drive/document/ranked_index_tree_type.rs @@ -127,6 +127,10 @@ pub(crate) fn ranked_property_name_tree_type( /// Callers pass the `has_index_with_type()` of the level *named after the /// property* — `None` for pure prefix levels, which resolve to /// `(NormalTree, [])`. +/// +/// Shipped generations depend on this function through +/// [`property_name_tree_type_and_ranked_axes_for_level`]: see the note there +/// before changing what it returns. pub(crate) fn property_name_tree_type_and_ranked_axes( index_level_info: Option<&IndexLevelTypeInfo>, ) -> Result<(TreeType, Vec), Error> { @@ -163,6 +167,13 @@ pub(crate) fn property_name_tree_type_and_ranked_axes( /// rs-dpp's structural validation guarantees no index terminates at a /// grouping or propagating level; both fail closed here on a stamped /// terminator rather than pick one of two contradictory layouts. +/// +/// Shipped generations depend on this function: `insert_contract` v0 and +/// `update_contract` v0 (protocol versions 1-13) call it to choose the tree +/// type of every top-level index level they create. Changing what it returns +/// for an index level protocol versions 1-13 can declare changes the trees +/// and fees of those versions; make such a change a new versioned method +/// instead of editing this function. pub(crate) fn property_name_tree_type_and_ranked_axes_for_level( level: &IndexLevel, ) -> Result<(TreeType, Vec), Error> { diff --git a/packages/rs-platform-value/src/eq.rs b/packages/rs-platform-value/src/eq.rs index f32943f1a70..d76c6121f06 100644 --- a/packages/rs-platform-value/src/eq.rs +++ b/packages/rs-platform-value/src/eq.rs @@ -159,6 +159,13 @@ impl Value { /// stored at a narrower width, or an object whose members were /// reordered by schema position, still compares equal. /// * Otherwise falls back to normal `==` (`PartialEq`) behaviour. + /// + /// Shipped generations call this at every protocol version: the document + /// replace transition transformer v0 uses it to decide which fields a + /// replace changed, and `is_equal_ignoring_timestamps` v0 uses it when a + /// client verifies a state transition proof. A change to its result for + /// some pair of values changes their output everywhere; make such a change + /// a new versioned method instead of editing this function. pub fn equal_underlying_data(&self, other: &Value) -> bool { // 1) bytes-like cross-variant equality if let (Ok(a), Ok(b)) = (self.as_bytes_slice(), other.as_bytes_slice()) { From d56f31350aeb1d0577c05eda161f4a9de3605f76 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sun, 27 Sep 2026 23:55:24 +0700 Subject: [PATCH 2/2] docs(platform): name the generations that reach the commented shared code Review follow-up: the shared parser code is reached by document type generations 1-2 (protocol versions 9-13); generation 0 parses inline. remove_reference v0 serves protocol versions 1-13 and v1 serves 14, both calling terminal_member_tree_type. The insert/update contract v0 operations are composed by every later generation, so every protocol version reaches the ranked tree-type helper. Co-Authored-By: Claude Opus 5.5 --- .../class_methods/try_from_schema/common/mod.rs | 10 ++++++---- .../src/drive/document/index_level_tree_types.rs | 7 ++++--- .../src/drive/document/ranked_index_tree_type.rs | 7 ++++--- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/common/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/common/mod.rs index 3cc99297de5..9fa64c4609e 100644 --- a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/common/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/common/mod.rs @@ -1197,9 +1197,10 @@ fn parse_indices( // level's subtrees — and a level cannot have two lifecycles. Identical // grids must declare identical TTLs (including both declaring none). // - // Document type generations 0-2 (protocol versions 1-13) run this loop too, but only the + // Document type generations 1-2 (protocol versions 9-13) run this loop too, but only the // generation 3 index grammar admits `timeRange`, so every index they parse has - // `time_range: None` and the loop changes nothing for them. + // `time_range: None` and the loop changes nothing for them. Generation 0 (protocol + // versions 1-8) parses its indices inline and never reaches this loop. for (name_a, index_a) in indices.iter() { let Some(transform_a) = &index_a.time_range else { continue; @@ -1466,10 +1467,11 @@ fn parse_token_costs( .unwrap_or(DocumentActionTokenEffect::TransferTokenToContractOwner); // Whether a transition may skip the token payment and have its signer pay // the gas in credits instead. Only the v3 meta-schema admits the flag. - // Document type generations 0-2 (protocol versions 1-13) also run this + // Document type generations 1-2 (protocol versions 9-13) also run this // parser, but their meta-schemas (v0-v2) set `additionalProperties: false` // on `documentActionTokenCost`, so no contract they accept carries the key - // and this reads `false` for them, as before the flag existed. + // and this reads `false` for them, as before the flag existed. Generation 0 + // (protocol versions 1-8) never reaches this parser. let optional = action_cost .get_optional_bool("optional")? .unwrap_or_default(); diff --git a/packages/rs-drive/src/drive/document/index_level_tree_types.rs b/packages/rs-drive/src/drive/document/index_level_tree_types.rs index 09b93c827de..dd9593a88d8 100644 --- a/packages/rs-drive/src/drive/document/index_level_tree_types.rs +++ b/packages/rs-drive/src/drive/document/index_level_tree_types.rs @@ -226,9 +226,10 @@ pub(crate) fn time_range_index_keys<'a>( /// produces dry-run fees that disagree with applied fees. /// /// Shipped generations depend on this function: -/// `add_reference_for_index_level_for_contract_operations` v0 and -/// `remove_reference_for_index_level_for_contract_operations` v0, which every -/// protocol version selects, call it for every stored-type index terminal. +/// `add_reference_for_index_level_for_contract_operations` v0 (protocol +/// versions 1-14) and `remove_reference_for_index_level_for_contract_operations` +/// v0 (protocol versions 1-13) call it for every stored-type index terminal; +/// protocol version 14's remove-reference v1 calls it too. /// Changing what it returns for an index shape protocol versions 1-13 can /// declare changes the trees and fees of those versions; make such a change a /// new versioned method instead of editing this function. diff --git a/packages/rs-drive/src/drive/document/ranked_index_tree_type.rs b/packages/rs-drive/src/drive/document/ranked_index_tree_type.rs index 134ade4b098..c7b1f630c47 100644 --- a/packages/rs-drive/src/drive/document/ranked_index_tree_type.rs +++ b/packages/rs-drive/src/drive/document/ranked_index_tree_type.rs @@ -168,9 +168,10 @@ pub(crate) fn property_name_tree_type_and_ranked_axes( /// grouping or propagating level; both fail closed here on a stamped /// terminator rather than pick one of two contradictory layouts. /// -/// Shipped generations depend on this function: `insert_contract` v0 and -/// `update_contract` v0 (protocol versions 1-13) call it to choose the tree -/// type of every top-level index level they create. Changing what it returns +/// Shipped generations depend on this function: the `insert_contract` v0 and +/// `update_contract` v0 operations call it to choose the tree type of every +/// top-level index level they create, and every later generation of both +/// composes those operations, so every protocol version reaches it. Changing what it returns /// for an index level protocol versions 1-13 can declare changes the trees /// and fees of those versions; make such a change a new versioned method /// instead of editing this function.