platform_version/version/v14.rs
1use crate::version::consensus_versions::ConsensusVersions;
2use crate::version::dpp_versions::dpp_asset_lock_versions::v1::DPP_ASSET_LOCK_VERSIONS_V1;
3use crate::version::dpp_versions::dpp_contract_versions::v6::CONTRACT_VERSIONS_V6;
4use crate::version::dpp_versions::dpp_costs_versions::v1::DPP_COSTS_VERSIONS_V1;
5use crate::version::dpp_versions::dpp_document_versions::v4::DOCUMENT_VERSIONS_V4;
6use crate::version::dpp_versions::dpp_factory_versions::v1::DPP_FACTORY_VERSIONS_V1;
7use crate::version::dpp_versions::dpp_identity_versions::v1::IDENTITY_VERSIONS_V1;
8use crate::version::dpp_versions::dpp_method_versions::v3::DPP_METHOD_VERSIONS_V3;
9use crate::version::dpp_versions::dpp_state_transition_conversion_versions::v2::STATE_TRANSITION_CONVERSION_VERSIONS_V2;
10use crate::version::dpp_versions::dpp_state_transition_method_versions::v1::STATE_TRANSITION_METHOD_VERSIONS_V1;
11use crate::version::dpp_versions::dpp_state_transition_serialization_versions::v3::STATE_TRANSITION_SERIALIZATION_VERSIONS_V3;
12use crate::version::dpp_versions::dpp_state_transition_versions::v3::STATE_TRANSITION_VERSIONS_V3;
13use crate::version::dpp_versions::dpp_token_versions::v2::TOKEN_VERSIONS_V2;
14use crate::version::dpp_versions::dpp_validation_versions::v5::DPP_VALIDATION_VERSIONS_V5;
15use crate::version::dpp_versions::dpp_voting_versions::v2::VOTING_VERSION_V2;
16use crate::version::dpp_versions::DPPVersion;
17use crate::version::drive_abci_versions::drive_abci_checkpoint_parameters::v1::DRIVE_ABCI_CHECKPOINT_PARAMETERS_V1;
18use crate::version::drive_abci_versions::drive_abci_method_versions::v10::DRIVE_ABCI_METHOD_VERSIONS_V10;
19use crate::version::drive_abci_versions::drive_abci_query_versions::v3::DRIVE_ABCI_QUERY_VERSIONS_V3;
20use crate::version::drive_abci_versions::drive_abci_structure_versions::v1::DRIVE_ABCI_STRUCTURE_VERSIONS_V1;
21use crate::version::drive_abci_versions::drive_abci_validation_versions::v10::DRIVE_ABCI_VALIDATION_VERSIONS_V10;
22use crate::version::drive_abci_versions::drive_abci_withdrawal_constants::v3::DRIVE_ABCI_WITHDRAWAL_CONSTANTS_V3;
23use crate::version::drive_abci_versions::DriveAbciVersion;
24use crate::version::drive_versions::v9::DRIVE_VERSION_V9;
25use crate::version::fee::v2::FEE_VERSION2;
26use crate::version::protocol_version::PlatformVersion;
27use crate::version::system_data_contract_versions::v3::SYSTEM_DATA_CONTRACT_VERSIONS_V3;
28use crate::version::system_limits::v4::SYSTEM_LIMITS_V4;
29use crate::version::ProtocolVersion;
30
31pub const PROTOCOL_VERSION_14: ProtocolVersion = 14;
32
33/// v14 hosts five consensus changes:
34///
35/// 1. **Contract-level ranked aggregates** (this branch): an index can
36/// declare that its groups are rankable by an aggregate, so a query like
37/// "top 5 restaurants by average grade" is served from an ordered
38/// secondary tree in O(log n + k) with a proof, instead of being rejected.
39/// 2. **The shared-prefix aggregate index fix**: a data contract declaring
40/// an aggregating (countable / summable) index that terminates at a
41/// property which is also the prefix of a compound index (e.g. summable
42/// `[a]` next to `[a, b]`) registered successfully but rejected every
43/// document insert for most flag combinations, because Drive could not
44/// legally hang the compound continuation tree under the aggregating
45/// per-value tree. The v2 document index walkers (plus the v1 update
46/// walker) that fix it gate here as well: tree types derive through a
47/// shared continuation-demotion helper (provable count-bearing value
48/// trees with compound continuations demote to `CountSumTree`, since
49/// grovedb rejects count-suppressed children under provable count
50/// parents by design) and continuation inserts route through the
51/// completed zero-contribution wrapper matrix. No state migration is
52/// needed: shapes without compound continuations produce bit-identical
53/// operations, the broken shapes could never hold documents, and the
54/// one previously-insertable shape the demotion changes (a provable
55/// count-bearing value tree whose continuations were all sum-bearing —
56/// insertable pre-v14 only through an unenforced grovedb batch guard)
57/// simply gets `CountSumTree` value trees for values first seen at
58/// v14+, which readers treat identically.
59/// 3. **The contested vote poll index cross-check**: the index named by a
60/// document create transition's prefunded voting balance keys the vote
61/// poll, its stored info, its end-date entry and its prefunded
62/// specialized balance, while the contested index the contender is
63/// inserted under always comes from the document type. Up to v13 nothing
64/// tied the two together, so a submitter could register and fund a
65/// contest under a vote poll describing a different index than the one
66/// the contest was created on — which halts the chain when that poll
67/// ends — or open a contest for a document that is not a contested
68/// resource at all.
69/// 4. **Relative daily withdrawal limit**: the flat 2000 Dash per 24 hours that
70/// applied from v8 becomes 15% of the total credits Platform held a day ago
71/// (`SYSTEM_LIMITS_V4.daily_withdrawal_limit_percent`, read by
72/// `daily_withdrawal_limit` v2 through `DPP_METHOD_VERSIONS_V3`), never below
73/// one maximal withdrawal (`max_withdrawal_amount`) so every accepted
74/// withdrawal eventually fits and cannot block the pooling queue. The base is
75/// capped at `max_daily_withdrawal_amount` (4000 Dash, Core's unlock capacity
76/// per day under V24 as written); the credit inflows of the active window —
77/// every credit mint, recorded per block by
78/// `record_credit_inflows_for_withdrawals` in the credit inflows sum tree —
79/// are added after the cap, so the limit counts net outflow and a matching
80/// deposit -> withdraw cycle does not consume the capped budget of other
81/// users (#4471). Outflow funded by same-window deposits may therefore
82/// exceed the cap; this mirrors the net credit-pool rule Core adopts for V24
83/// alongside this change (tracked in #4471), which must land before V24
84/// activates. Both the inflows and the pooled reservations count over the
85/// interval after the base snapshot only — an entry the snapshot already
86/// reflects is neither added nor subtracted again. The base is
87/// the total credits recorded at the latest block at least 24 hours before
88/// the current one: `DRIVE_ABCI_METHOD_VERSIONS_V10` turns on
89/// `record_total_credits_history_for_withdrawals`, which checks the total
90/// credits every block once fees and epoch rewards are in, writes it under
91/// the withdrawals tree keyed by block time whenever it changed (an entry
92/// describes the total until the next one) and prunes entries older than the
93/// one the limit reads, and `DRIVE_VERSION_V9`'s identity withdrawal table
94/// bumps `calculate_current_withdrawal_limit` to 1 to read that lagged
95/// value. Until an entry is a day old — the first day after activation — the
96/// flat 2000 Dash keeps applying, so the lag cannot be skipped by inflating
97/// the total before or at activation. The lag is the guardrail: a sudden
98/// jump in the total credits does not raise the limit for a day. Amounts
99/// already pooled in the last 24 hours keep counting against the maximum
100/// exactly as before. Pre-V24 Core caps unlocks at `LimitAmountV22` (2000
101/// Dash) per *block*, with the amount checked only at block level, so any
102/// daily total is still minable across blocks; V24's 4000 Dash per 576-block
103/// window matches the capped base and is raised to the same net rule before
104/// activation (see above).
105/// 5. **Time-range indexes**: an index can declare a `timeRange` transform
106/// that buckets a required system timestamp (`$createdAt` /
107/// `$updatedAt` / `$transferredAt`) into fixed-length, regularly-spaced,
108/// optionally overlapping windows declared in seconds (`range` / `step`,
109/// plus an optional `phase < step` alignment offset). Each grid gets its
110/// own index subtree — the level is keyed by the property name qualified
111/// with the grid — so several grids may bucket one timestamp side by
112/// side. A document is stored once per containing bucket per grid (the
113/// v2 insert/delete and v1 update walkers carry the fan-out; the
114/// per-document write amplification is capped per index by
115/// `SystemLimits::max_time_range_overlap_factor`), and the v1
116/// `getDocuments` handler resolves the new `IN_TIME_RANGE` operator —
117/// bare `"newest"`/`"oldest"` on a single-grid field, or a structured
118/// `[selector, range, step(, phase)]` operand naming one grid — into a
119/// bucket-start equality from committed block time, making "newest
120/// window" trending/leaderboard document and count/sum/avg queries
121/// provable. `unique: true` is admitted only for non-overlapping windows
122/// (`range == step`) sourced from the immutable `$createdAt`.
123///
124/// The first two are orthogonal by construction: the ranked upgrade decides the
125/// *property-name* tree type, the demotion decides the *value* tree type
126/// one level below it, and a demoted `CountSumTree` value tree contributes
127/// its (count, sum) to a ranked indexed parent exactly as the provable
128/// variant did — so ranked secondaries keep ranking correctly over
129/// shared-prefix shapes.
130///
131/// Until a contract uses the ranked or time-range grammar, the only v14
132/// behavior changes are the shared-prefix fix, the contested-index
133/// cross-check, the index-reorder schema-compatibility fix and the relative
134/// daily withdrawal limit; everything else matches v13:
135///
136/// * `CONTRACT_VERSIONS_V6` points `document_type_schema` at the v3 document
137/// meta-schema, which hosts the ranked index keywords
138/// (`rankedCountable` / `rankedSummable` / `rankedAverageable`), the
139/// `refersTo` reference keyword and the `timeRange` index transform. v13
140/// keeps validating against meta-schema v2, where those keys are rejected
141/// as unknown properties, so a pre-v14 contract cannot smuggle them in.
142/// It also bumps `validate_schema_compatibility` to 1, which strips the
143/// top-level `indices` key before diffing the old and new document type
144/// schemas: index immutability is enforced by `validate_update` v1's
145/// name-keyed comparison, so a contract update that merely reorders the
146/// `indices` array validates cleanly instead of hitting the
147/// unsupported-keyword hard error (an internal error under v13).
148/// * `DRIVE_VERSION_V9` carries `DRIVE_DOCUMENT_METHOD_VERSIONS_V4`, adding
149/// the `detect_ranked_mode` routing slot, plus the grove-method slots for
150/// creating the three indexed tree variants and the verify-method slot for
151/// `verify_ranked_top_k_proof`. All are 0 today. The same table bumps the
152/// four index walkers to v2 and the document update walker to v1 for the
153/// shared-prefix fix; those same walker versions carry the time-range
154/// bucket fan-out, so both features gate on one table entry.
155/// * `DRIVE_ABCI_QUERY_VERSIONS_V3` bumps
156/// `document_query_helpers.compute_aggregate_mode_and_check_limit` 0 → 2,
157/// opening two routes on the v1 document-query handler: the ranked path
158/// (a grouped aggregate whose single `order_by` names the selected
159/// aggregate — `ORDER BY <agg> [ASC|DESC] LIMIT n [OFFSET m]`) and the
160/// boolean-`HAVING` range path (a grouped aggregate carrying exactly one
161/// `having` clause on the selected aggregate — `GROUP BY p HAVING <agg>
162/// <op> <value> LIMIT n`), the latter served as a value-bounded range
163/// read of the covering ranked index's axis secondary. v13 and earlier
164/// keep the v1 table and therefore keep rejecting both shapes, so
165/// mixed-version networks agree across the upgrade.
166/// * `DRIVE_ABCI_VALIDATION_VERSIONS_V10` bumps
167/// `document_create_transition_structure_validation` 0 → 1, requiring a
168/// contested create transition's prefunded voting balance to name the
169/// same vote poll the document itself resolves to, and rejecting one on a
170/// document that resolves to no contested index. It also bumps document
171/// create state validation to 2 and document replace state validation to
172/// 1, enforcing `refersTo` document references: a document whose
173/// reference property names an identity or contract that does not exist
174/// is rejected. v13 keeps the v9 table and therefore keeps
175/// accepting all of these, so replay of pre-upgrade blocks is unchanged.
176/// * `DOCUMENT_VERSIONS_V4` bumps `document_serialization_version` to
177/// default 3: documents are stamped with the contract version their bytes
178/// conform to (a varint after the format prefix), enabling the
179/// `requiredSince` property keyword — a contract update may add a new
180/// required property annotated with the version that update creates.
181/// Documents stamped below a property's `requiredSince` keep the
182/// presence-flagged layout they were written with, so the latest contract
183/// alone reconstructs every stamp's layout and no historical contract
184/// lookups are ever needed. Reads dispatch on the byte prefix, so
185/// formats 0–2 (all pre-v14 documents) deserialize exactly as before with
186/// an unstamped (pre-annotation) layout.
187///
188/// The wire surface changes only additively: `GetDocumentsRequestV1`
189/// already carries `selects` / `group_by` / `order_by` / `limit` /
190/// `offset`; the ranked response is an additive `ResultData.ranked`
191/// variant, whose `skipped` field is likewise additive; and the v1
192/// where-clause operator enum gains `IN_TIME_RANGE = 11`, which pre-v14
193/// servers reject as an unknown operator rather than misread (the v0 wire
194/// has no time-range operator at all).
195pub const PLATFORM_V14: PlatformVersion = PlatformVersion {
196 protocol_version: PROTOCOL_VERSION_14,
197 drive: DRIVE_VERSION_V9, // changed: drive document method versions v4 — v2 index walkers (shared-prefix aggregate indexes become insertable) + the detect_ranked_mode slot
198 drive_abci: DriveAbciVersion {
199 structs: DRIVE_ABCI_STRUCTURE_VERSIONS_V1,
200 methods: DRIVE_ABCI_METHOD_VERSIONS_V10, // changed: records the per-block total credits history for the daily withdrawal limit
201 validation_and_processing: DRIVE_ABCI_VALIDATION_VERSIONS_V10, // changed: contested-index cross-check + refersTo document reference validation
202 withdrawal_constants: DRIVE_ABCI_WITHDRAWAL_CONSTANTS_V3, // changed: prune bound for the total credits history
203 query: DRIVE_ABCI_QUERY_VERSIONS_V3, // changed: ranked + boolean-HAVING routing gate; the v1 handler also resolves IN_TIME_RANGE from committed block time
204 checkpoints: DRIVE_ABCI_CHECKPOINT_PARAMETERS_V1,
205 },
206 dpp: DPPVersion {
207 costs: DPP_COSTS_VERSIONS_V1,
208 validation: DPP_VALIDATION_VERSIONS_V5,
209 state_transition_serialization_versions: STATE_TRANSITION_SERIALIZATION_VERSIONS_V3, // changed: the indexOnly delete-by-values kind (documentIndexOnlyDelete) joins the wire
210 state_transition_conversion_versions: STATE_TRANSITION_CONVERSION_VERSIONS_V2,
211 state_transition_method_versions: STATE_TRANSITION_METHOD_VERSIONS_V1,
212 state_transitions: STATE_TRANSITION_VERSIONS_V3,
213 contract_versions: CONTRACT_VERSIONS_V6, // changed: v3 document meta-schema hosts the ranked, refersTo, requiredSince and timeRange keywords
214 document_versions: DOCUMENT_VERSIONS_V4, // changed: document serialization format 3 — the contract version stamp that enables `requiredSince` properties
215 identity_versions: IDENTITY_VERSIONS_V1,
216 voting_versions: VOTING_VERSION_V2,
217 token_versions: TOKEN_VERSIONS_V2,
218 asset_lock_versions: DPP_ASSET_LOCK_VERSIONS_V1,
219 methods: DPP_METHOD_VERSIONS_V3, // changed: daily_withdrawal_limit v2 — a percentage of the total credits a day ago
220 factory_versions: DPP_FACTORY_VERSIONS_V1,
221 },
222 system_data_contracts: SYSTEM_DATA_CONTRACT_VERSIONS_V3, // changed: DashPay v2 adds profile payment address fields (DIP-33)
223 fee_version: FEE_VERSION2,
224 system_limits: SYSTEM_LIMITS_V4, // changed: daily withdrawal limit becomes 15% of the total credits a day ago + time-range overlap-factor cap (24)
225 consensus: ConsensusVersions {
226 tenderdash_consensus_version: 1,
227 },
228};
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233 use crate::version::v13::PLATFORM_V13;
234
235 /// The ranked / boolean-HAVING routing gate lives in v14's own query
236 /// table, so flipping it touches only v14: a v13 node keeps running
237 /// the v0 helper, which rejects every non-empty HAVING, so a
238 /// mixed-version network agrees until the upgrade vote carries.
239 ///
240 /// v14 selects the v2 helper, which routes the ranked shape
241 /// (`ORDER BY <agg> LIMIT n`) to `dispatch_ranked_v1` and the
242 /// boolean-HAVING range shape (exactly one `having` clause on the
243 /// selected aggregate) to `dispatch_having_v1`. A change that made
244 /// v13 non-zero here would be consensus-breaking for
245 /// already-deployed nodes, which is exactly what the v13 half of
246 /// this assertion guards.
247 #[test]
248 fn ranked_having_routing_gate_is_v14_only() {
249 assert_eq!(
250 PLATFORM_V13
251 .drive_abci
252 .query
253 .document_query_helpers
254 .compute_aggregate_mode_and_check_limit,
255 0
256 );
257 assert_eq!(
258 PLATFORM_V14
259 .drive_abci
260 .query
261 .document_query_helpers
262 .compute_aggregate_mode_and_check_limit,
263 2
264 );
265 }
266
267 /// The ranked index keywords are gated by the meta-schema version, so v14
268 /// must select meta-schema v3 while v13 stays on v2.
269 #[test]
270 fn ranked_index_keywords_are_gated_by_meta_schema_v3() {
271 assert_eq!(
272 PLATFORM_V13
273 .dpp
274 .contract_versions
275 .document_type_versions
276 .schema
277 .document_type_schema,
278 2
279 );
280 assert_eq!(
281 PLATFORM_V14
282 .dpp
283 .contract_versions
284 .document_type_versions
285 .schema
286 .document_type_schema,
287 3
288 );
289 }
290
291 /// The ranked grammar lives in its own document-type parser generation
292 /// rather than behind a version gate inside a shipped one, so v14 must
293 /// select generation 3 while v13 stays on generation 2. Pinned here
294 /// because it is the whole reason generations 0/1/2 can stay byte-identical
295 /// to what consensus already ran: a historical block replayed at v13 is
296 /// parsed by a generation that has never heard of the ranked keywords.
297 /// The grove v4 cleanup gates (batch overwrite inspection + delete-tree
298 /// actual-type cleanup) exist for the indexed trees that ranked indexes
299 /// lay down, so v14 must select grove protocol 4 while v13 stays on 3.
300 /// The gates are cost-neutral — they derive the old element from data the
301 /// merk apply already loads — and the fee-constant tests pin identical
302 /// fees on both sides of the boundary. Platform flows cannot themselves
303 /// overwrite a ranked index (the flags are immutable on contract update
304 /// and new indexes cannot be added to an existing document type), so the
305 /// cleanup behavior itself is exercised by grovedb's own overwrite suites
306 /// at the pinned revision; this test pins that v14 actually activates
307 /// them.
308 #[test]
309 fn grove_v4_cleanup_gates_activate_at_v14() {
310 assert_eq!(PLATFORM_V13.drive.grove_version.protocol_version, 3);
311 assert_eq!(PLATFORM_V14.drive.grove_version.protocol_version, 4);
312 }
313
314 #[test]
315 fn ranked_grammar_gets_its_own_parser_generation() {
316 assert_eq!(
317 PLATFORM_V13
318 .dpp
319 .contract_versions
320 .document_type_versions
321 .class_method_versions
322 .try_from_schema,
323 2
324 );
325 assert_eq!(
326 PLATFORM_V14
327 .dpp
328 .contract_versions
329 .document_type_versions
330 .class_method_versions
331 .try_from_schema,
332 3
333 );
334 }
335
336 /// v14 introduces the slots but activates none of them yet. If a later
337 /// change flips one of these, it must do so deliberately — and update this
338 /// test — rather than by inheriting a default.
339 #[test]
340 fn ranked_feature_slots_exist_but_are_dormant() {
341 assert_eq!(
342 PLATFORM_V14.drive.methods.document.query.detect_ranked_mode,
343 0
344 );
345 assert_eq!(
346 PLATFORM_V14.drive.methods.document.query.detect_having_mode,
347 0
348 );
349 assert_eq!(
350 PLATFORM_V14
351 .drive
352 .methods
353 .verify
354 .document_ranked
355 .verify_ranked_top_k_proof,
356 0
357 );
358 assert_eq!(
359 PLATFORM_V14
360 .drive
361 .methods
362 .verify
363 .document_ranked
364 .verify_having_range_proof,
365 0
366 );
367 let grove = &PLATFORM_V14.drive.grove_methods.batch;
368 assert_eq!(grove.batch_insert_empty_provable_count_indexed_tree, 0);
369 assert_eq!(grove.batch_insert_empty_provable_sum_indexed_tree, 0);
370 assert_eq!(
371 grove.batch_insert_empty_provable_count_provable_sum_indexed_tree,
372 0
373 );
374 }
375
376 /// The contested vote poll index cross-check changes accept/reject
377 /// behavior for document create transitions, so it lives in v14's own
378 /// validation table: a v13 node keeps running structure validation v0,
379 /// which validates only the prefunded amount and ignores the index name.
380 /// A change that made v13 non-zero here would retroactively reject
381 /// transitions already in the chain.
382 #[test]
383 fn contested_index_cross_check_is_v14_only() {
384 assert_eq!(
385 PLATFORM_V13
386 .drive_abci
387 .validation_and_processing
388 .state_transitions
389 .batch_state_transition
390 .document_create_transition_structure_validation,
391 0
392 );
393 assert_eq!(
394 PLATFORM_V14
395 .drive_abci
396 .validation_and_processing
397 .state_transitions
398 .batch_state_transition
399 .document_create_transition_structure_validation,
400 1
401 );
402 }
403}