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::v2::STATE_TRANSITION_METHOD_VERSIONS_V2;
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::v4::STATE_TRANSITION_VERSIONS_V4;
13use crate::version::dpp_versions::dpp_token_versions::v3::TOKEN_VERSIONS_V3;
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::v2::DRIVE_ABCI_STRUCTURE_VERSIONS_V2;
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::v3::FEE_VERSION3;
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 six consensus changes:
34///
35/// 1. **Contract-level ranked aggregates**: 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. State validation also prevents a non-contested create
69/// from occupying a live contested document's id before the contest winner
70/// is awarded into primary storage. Drive's contested insert also recreates
71/// an abstain or lock vote tree over the storage an earlier poll's cleanup
72/// left orphaned (it only removed the trees that received votes), so a
73/// resource can be contested again instead of failing with
74/// `CorruptedContractIndexes`.
75/// 4. **Relative daily withdrawal limit**: the flat 2000 Dash per 24 hours that
76/// applied from v8 becomes 15% of the total credits Platform held a day ago
77/// (`SYSTEM_LIMITS_V4.daily_withdrawal_limit_percent`, read by
78/// `daily_withdrawal_limit` v2 through `DPP_METHOD_VERSIONS_V3`), never below
79/// one maximal withdrawal (`max_withdrawal_amount`) so every accepted
80/// withdrawal eventually fits and cannot block the pooling queue. The base is
81/// capped at `max_daily_withdrawal_amount` (4000 Dash, Core's unlock capacity
82/// per day under V24 as written); the credit inflows of the active window —
83/// every credit mint, recorded per block by
84/// `record_credit_inflows_for_withdrawals` in the credit inflows sum tree —
85/// are added after the cap, so the limit counts net outflow and a matching
86/// deposit -> withdraw cycle does not consume the capped budget of other
87/// users (#4471). Outflow funded by same-window deposits may therefore
88/// exceed the cap; this mirrors the net credit-pool rule Core adopts for V24
89/// alongside this change (tracked in #4471), which must land before V24
90/// activates. Both the inflows and the pooled reservations count over the
91/// interval after the base snapshot only — an entry the snapshot already
92/// reflects is neither added nor subtracted again. The base is
93/// the total credits recorded at the latest block at least 24 hours before
94/// the current one: `DRIVE_ABCI_METHOD_VERSIONS_V10` turns on
95/// `record_total_credits_history_for_withdrawals`, which checks the total
96/// credits every block once fees and epoch rewards are in, writes it under
97/// the withdrawals tree keyed by block time whenever it changed (an entry
98/// describes the total until the next one) and prunes entries older than the
99/// one the limit reads, and `DRIVE_VERSION_V9`'s identity withdrawal table
100/// bumps `calculate_current_withdrawal_limit` to 1 to read that lagged
101/// value. Until an entry is a day old — the first day after activation — the
102/// flat 2000 Dash keeps applying, so the lag cannot be skipped by inflating
103/// the total before or at activation. The lag is the guardrail: a sudden
104/// jump in the total credits does not raise the limit for a day. Amounts
105/// already pooled in the last 24 hours keep counting against the maximum
106/// exactly as before. Pre-V24 Core caps unlocks at `LimitAmountV22` (2000
107/// Dash) per *block*, with the amount checked only at block level, so any
108/// daily total is still minable across blocks; V24's 4000 Dash per 576-block
109/// window matches the capped base and is raised to the same net rule before
110/// activation (see above).
111/// 5. **Time-range indexes**: an index can declare a `timeRange` transform
112/// that buckets a required system timestamp (`$createdAt` /
113/// `$updatedAt` / `$transferredAt`) into fixed-length, regularly-spaced,
114/// optionally overlapping windows declared in seconds (`range` / `step`,
115/// plus an optional `phase < step` alignment offset). Each grid gets its
116/// own index subtree — the level is keyed by the property name qualified
117/// with the grid — so several grids may bucket one timestamp side by
118/// side. A document is stored once per containing bucket per grid (the
119/// v2 insert/delete and v1 update walkers carry the fan-out; the
120/// per-document write amplification is capped per index by
121/// `SystemLimits::max_time_range_overlap_factor`), and the v1
122/// `getDocuments` handler resolves the new `IN_TIME_RANGE` operator —
123/// a typed `TimeRangeSelection` operand: `NEWEST`/`OLDEST` (resolved to
124/// a bucket-start equality from committed block time) or `BY_START`
125/// (naming any window, current or historic, by its grid-aligned start),
126/// with a `grid` member naming one grid where several bucket the field
127/// — making trending/leaderboard document and count/sum/avg queries
128/// provable over the current or any named window. `unique: true` is
129/// admitted only for non-overlapping windows (`range == step`) sourced
130/// from the immutable `$createdAt`.
131/// 6. **Deterministic token reward math**: `DistributionFunction::evaluate`
132/// (logarithmic, inverted-logarithmic, exponential and polynomial perpetual
133/// distributions) computes `ln`/`exp`/`pow` through the pinned pure-Rust
134/// `libm` crate instead of the platform C library. musl's `log` takes an
135/// FMA path on aarch64 and a non-FMA path on x86_64, so the two disagree by
136/// 1 ulp on some inputs; a contract owner could pick parameters whose reward
137/// sat within that ulp of an integer, and `floor` then minted different
138/// amounts on the two architectures, splitting the app hash both at claim
139/// time and at contract registration (validation evaluates the start
140/// value). Gated on `distribution_function_evaluate_version` so both
141/// architectures switch at the same height; pre-v14 blocks replay on the
142/// old math byte-for-byte. `log`/`exp` have no architecture dispatch and
143/// `pow`'s only arch-touching call is the correctly-rounded `sqrt`, so the
144/// result is bit-identical on every target Platform builds for. The goal
145/// is determinism, not correct rounding: on a boundary tuple the host
146/// libm (glibc, macOS) can still be 1 ulp away, so anything predicting
147/// rewards with host math may differ from consensus by one unit.
148///
149/// The first two are orthogonal by construction: the ranked upgrade decides the
150/// *property-name* tree type, the demotion decides the *value* tree type
151/// one level below it, and a demoted `CountSumTree` value tree contributes
152/// its (count, sum) to a ranked indexed parent exactly as the provable
153/// variant did — so ranked secondaries keep ranking correctly over
154/// shared-prefix shapes.
155///
156/// Until a contract uses the ranked or time-range grammar, the only v14
157/// behavior changes are the shared-prefix fix, the contested-index
158/// cross-check, the index-reorder schema-compatibility fix and the relative
159/// daily withdrawal limit; everything else matches v13:
160///
161/// * `CONTRACT_VERSIONS_V6` points `document_type_schema` at the v3 document
162/// meta-schema, which hosts the ranked index keywords
163/// (`rankedCountable` / `rankedSummable` / `rankedAverageable`), the
164/// `refersTo` reference keyword and the `timeRange` index transform. v13
165/// keeps validating against meta-schema v2, where those keys are rejected
166/// as unknown properties, so a pre-v14 contract cannot smuggle them in.
167/// It also bumps `validate_schema_compatibility` to 1, which strips the
168/// top-level `indices` key before diffing the old and new document type
169/// schemas: index immutability is enforced by `validate_update` v1's
170/// name-keyed comparison, so a contract update that merely reorders the
171/// `indices` array validates cleanly instead of hitting the
172/// unsupported-keyword hard error (an internal error under v13).
173/// * `DRIVE_VERSION_V9` carries `DRIVE_DOCUMENT_METHOD_VERSIONS_V4`, adding
174/// the `detect_ranked_mode` routing slot, plus the grove-method slots for
175/// creating the three indexed tree variants and the verify-method slot for
176/// `verify_ranked_top_k_proof`. All are 0 today. The same table bumps the
177/// four index walkers to v2 and the document update walker to v1 for the
178/// shared-prefix fix; those same walker versions carry the time-range
179/// bucket fan-out, so both features gate on one table entry.
180/// * `DRIVE_ABCI_QUERY_VERSIONS_V3` bumps
181/// `document_query_helpers.compute_aggregate_mode_and_check_limit` 0 → 2,
182/// opening two routes on the v1 document-query handler: the ranked path
183/// (a grouped aggregate whose single `order_by` names the selected
184/// aggregate — `ORDER BY <agg> [ASC|DESC] LIMIT n [OFFSET m]`) and the
185/// boolean-`HAVING` range path (a grouped aggregate carrying exactly one
186/// `having` clause on the selected aggregate — `GROUP BY p HAVING <agg>
187/// <op> <value> LIMIT n`), the latter served as a value-bounded range
188/// read of the covering ranked index's axis secondary. v13 and earlier
189/// keep the v1 table and therefore keep rejecting both shapes, so
190/// mixed-version networks agree across the upgrade.
191/// * `DRIVE_ABCI_VALIDATION_VERSIONS_V10` bumps
192/// `document_create_transition_structure_validation` 0 → 1, requiring a
193/// contested create transition's prefunded voting balance to name the
194/// same vote poll the document itself resolves to, and rejecting one on a
195/// document that resolves to no contested index. It also bumps document
196/// create state validation to 2, enforcing `refersTo` document references
197/// and rejecting a non-contested create whose id is already present in the
198/// contested tree. Document replace state validation 1 enforces the same
199/// reference checks. v13 keeps the v9 table and therefore keeps
200/// accepting all of these, so replay of pre-upgrade blocks is unchanged.
201/// * `DOCUMENT_VERSIONS_V4` bumps `document_serialization_version` to
202/// default 3: documents are stamped with the contract version their bytes
203/// conform to (a varint after the format prefix), enabling the
204/// `requiredSince` property keyword — a contract update may add a new
205/// required property annotated with the version that update creates.
206/// Documents stamped below a property's `requiredSince` keep the
207/// presence-flagged layout they were written with, so the latest contract
208/// alone reconstructs every stamp's layout and no historical contract
209/// lookups are ever needed. Reads dispatch on the byte prefix, so
210/// formats 0–2 (all pre-v14 documents) deserialize exactly as before with
211/// an unstamped (pre-annotation) layout.
212/// 7. **Client-side GroveDB proof envelope floor**:
213/// `SYSTEM_LIMITS_V4.minimum_grovedb_proof_envelope_version` becomes 1, so
214/// a client verifying with v14 tables rejects the legacy V0 proof
215/// envelope before its bytes reach Drive (`drive-proof-verifier`,
216/// `wasm-drive-verify`, and the nested compacted address proofs). V0's
217/// item binding lets a prover return different item bytes under the same
218/// authenticated root; every live network has emitted V1 envelopes since
219/// v13 (grove version 3), so no honest response is affected.
220/// 8. **Epoch-based perpetual distribution claims stop wrapping**:
221/// `RewardDistributionType::max_cycle_moment` (the cap on how far one claim
222/// may redeem, selected by
223/// `TOKEN_VERSIONS_V3.reward_distribution_max_cycle_moment_version` 1)
224/// computes `start + interval * cycles` in `u64` with saturating
225/// arithmetic and narrows back to `EpochIndex` only after capping at the
226/// last completed cycle moment (`current cycle moment - interval`, the
227/// previous epoch for an interval of one as before; for wider intervals the
228/// same cycles are paid, but the cap now sits on a cycle boundary, the only
229/// shape in which `evaluate_interval`'s fixed-amount step count and its
230/// per-cycle loop agree). Up to v13 the sum was taken in `u16`: a
231/// fixed-amount function allows 32,767 cycles, so any epoch interval of
232/// three or more with a nonzero start (or two with a start at epoch two
233/// or later) pushed the cap past `u16::MAX`. Release builds wrap, the cap landed below the
234/// start, `evaluate_interval` saw an empty range and the claim was
235/// refused with `InvalidTokenClaimNoCurrentRewards` on every attempt. The
236/// v0 arithmetic is kept, wrapping explicitly, so those refusals replay.
237/// 9. **Evonode reward cycles weighted by the epochs they span**: the
238/// per-cycle evaluator in `DistributionFunction::evaluate_interval` asks
239/// the participation ratio for the epochs a cycle covers
240/// (`TOKEN_VERSIONS_V3.distribution_function_cycle_epochs_version` 1:
241/// `cycle moment - interval + 1 ..= cycle moment`). Up to v13 it passed the
242/// cycle's step index as if it were an epoch, which coincides only for an
243/// interval of one; for a wider interval it named epochs before the
244/// distribution started, outside the epoch window the claim loads, and an
245/// `EvonodesByParticipation` claim with a function other than a fixed
246/// amount failed as an internal error (reachable only once item 8 let the
247/// cap stop wrapping). Interval-one distributions are unchanged.
248/// 10. **A zero epoch interval is rejected at registration**:
249/// `RewardDistributionType::validate_structure_interval` v1
250/// (`CONTRACT_VERSIONS_V6.token_versions.validate_structure_interval`)
251/// refuses an `EpochBasedDistribution` with `interval: 0` with the new
252/// `InvalidTokenDistributionEpochIntervalTooShortError` (code 10828) on
253/// contract create and update. Up to v13 the epoch arm enforced nothing,
254/// so such a contract registered and every claim on it failed as an
255/// internal error, since no cycle can be computed from a zero step. Block
256/// and time minimums are unchanged.
257///
258/// * `ShieldFromIdentity` (state transition type 21) activates:
259/// `SHIELD_FROM_IDENTITY_INITIAL_PROTOCOL_VERSION = 14` gates it in
260/// `is_allowed`, and `DRIVE_ABCI_VALIDATION_VERSIONS_V10` is the first
261/// table whose `shield_from_identity_state_transition` row enables basic
262/// structure, identity signature, and nonce validation. It moves credits
263/// from an identity balance straight into the shielded pool: the funding
264/// side is identity-signed like `IdentityCreditTransferToAddresses`, the
265/// pool side is an outputs-only Orchard bundle like `Shield`, and the fee
266/// is metered plus the shielded compute fee, paid from the identity.
267///
268/// * `IdentityTopUpFromShieldedPool` (state transition type 22) activates at the
269/// same gate (`IDENTITY_TOP_UP_FROM_SHIELDED_POOL_INITIAL_PROTOCOL_VERSION = 14`,
270/// `DRIVE_ABCI_VALIDATION_VERSIONS_V10` row). It spends shielded notes like
271/// `Unshield` and credits an EXISTING identity's balance instead of a platform
272/// address: pool-paid flat fee (`compute_shielded_identity_top_up_fee`), no
273/// platform signature, the target identity and gross amount bound into the
274/// Orchard sighash, and no system-credit adjustment (pool and identity balances
275/// are both conservation-equation terms).
276///
277/// The wire surface changes only additively: `GetDocumentsRequestV1`
278/// already carries `selects` / `group_by` / `order_by` / `limit` /
279/// `offset`; the ranked response is an additive `ResultData.ranked`
280/// variant, whose `skipped` field is likewise additive; and the v1
281/// where-clause operator enum gains `IN_TIME_RANGE = 11`, which pre-v14
282/// servers reject as an unknown operator rather than misread (the v0 wire
283/// has no time-range operator at all).
284/// Contract-bound authentication keys activate through contract-bounds validation v2,
285/// identity-signature validation v1 and batch advanced-structure v1. Identity creation
286/// validates key bounds (state v1) and identity-update state v1 retains the contract
287/// lookup fees; Drive identity methods v2 index and refresh the bound keys.
288/// Contract group bounds on authentication keys ride the same versions: contract-bounds
289/// validation v2 admits them, batch transform v2 resolves the member contract's group
290/// memberships into the action (only for a group-bound signing key) for advanced-structure v1 to judge, and shielded-proof validation v1 refuses them in identity creation from the
291/// shielded pool, whose sighash preimage layout predates them.
292/// A transition carrying such a key is inactive before this version (`active_version_range`),
293/// so earlier protocol versions reject it without charging, as a binary that cannot decode it does.
294/// Authentication keys may carry a budget and an expiry (the version 1 public key format, which
295/// `StateTransition::active_version_range` admits from 14). Key structure validation v1
296/// (`STATE_TRANSITION_METHOD_VERSIONS_V2`) and `validate_identity_public_keys_limits` decide
297/// which keys may carry them; Drive identity methods v2 write the remaining budget when the key
298/// is added; identity-signature validation v1 refuses a key whose budget is spent;
299/// `validate_fees_of_event` v1 refuses an expired key and a spend the remaining budget does not
300/// cover (only metered processing may overshoot); `execute_event` v1 deducts what was spent.
301/// Shielded-proof validation v1 refuses a key that carries a budget or an expiry in identity
302/// creation from the shielded pool, whose sighash preimage does not cover the limits.
303/// `IdentityKeyLimitsUpdate` (state transition type 23, gated by
304/// `IDENTITY_KEY_LIMITS_UPDATE_INITIAL_PROTOCOL_VERSION`) raises a key's total budget, and the
305/// remaining budget with it, or moves its expiry later; it only ever loosens limits. Signed by a
306/// MASTER key or by a CRITICAL key without limits (`DRIVE_ABCI_VALIDATION_VERSIONS_V10` turns
307/// its gates on; Drive identity methods v2 rewrite the key and raise the remaining budget).
308pub const PLATFORM_V14: PlatformVersion = PlatformVersion {
309 protocol_version: PROTOCOL_VERSION_14,
310 drive: DRIVE_VERSION_V9, // changed: drive document method versions v4 — v2 index walkers (shared-prefix aggregate indexes become insertable) + the detect_ranked_mode slot
311 drive_abci: DriveAbciVersion {
312 structs: DRIVE_ABCI_STRUCTURE_VERSIONS_V2, // changed: saved platform state structure 1 keeps masternodes and validator sets as one aux entry each
313 methods: DRIVE_ABCI_METHOD_VERSIONS_V10, // changed: records the per-block total credits history for the daily withdrawal limit
314 validation_and_processing: DRIVE_ABCI_VALIDATION_VERSIONS_V10, // changed: contested-index cross-check + refersTo document reference validation
315 withdrawal_constants: DRIVE_ABCI_WITHDRAWAL_CONSTANTS_V3, // changed: prune bound for the total credits history
316 query: DRIVE_ABCI_QUERY_VERSIONS_V3, // changed: ranked + boolean-HAVING routing gate; the v1 handler also resolves IN_TIME_RANGE from committed block time
317 checkpoints: DRIVE_ABCI_CHECKPOINT_PARAMETERS_V1,
318 },
319 dpp: DPPVersion {
320 costs: DPP_COSTS_VERSIONS_V1,
321 validation: DPP_VALIDATION_VERSIONS_V5,
322 state_transition_serialization_versions: STATE_TRANSITION_SERIALIZATION_VERSIONS_V3, // changed: the indexOnly delete-by-values kind (documentIndexOnlyDelete) joins the wire
323 state_transition_conversion_versions: STATE_TRANSITION_CONVERSION_VERSIONS_V2,
324 state_transition_method_versions: STATE_TRANSITION_METHOD_VERSIONS_V2, // changed: public keys in creation may carry a budget or an expiry
325 state_transitions: STATE_TRANSITION_VERSIONS_V4,
326 contract_versions: CONTRACT_VERSIONS_V6, // changed: v3 document meta-schema hosts the ranked, refersTo, requiredSince and timeRange keywords; validate_structure_interval v1 rejects a zero epoch interval
327 document_versions: DOCUMENT_VERSIONS_V4, // changed: document serialization format 3 — the contract version stamp that enables `requiredSince` properties
328 identity_versions: IDENTITY_VERSIONS_V1,
329 voting_versions: VOTING_VERSION_V2,
330 token_versions: TOKEN_VERSIONS_V3, // changed: distribution_function_evaluate v1 — deterministic libm for token reward math; reward_distribution_max_cycle_moment v1: the epoch claim cap no longer wraps; distribution_function_cycle_epochs v1: evonode cycles weighted by the epochs they span
331 asset_lock_versions: DPP_ASSET_LOCK_VERSIONS_V1,
332 methods: DPP_METHOD_VERSIONS_V3, // changed: daily_withdrawal_limit v2 — a percentage of the total credits a day ago
333 factory_versions: DPP_FACTORY_VERSIONS_V1,
334 },
335 system_data_contracts: SYSTEM_DATA_CONTRACT_VERSIONS_V3, // changed: DashPay v2 adds profile payment address fields (DIP-33); withdrawals v2 admits the terminal FAILED status
336 // The TTL ephemeral-bytes rate (270 credits/byte to processing) rides
337 // the shared storage table; it is dead below v14 (the `ttl` grammar
338 // does not parse), so no table fork is needed.
339 fee_version: FEE_VERSION3, // changed: contested document contribution reduced to 0.1 DASH
340 system_limits: SYSTEM_LIMITS_V4, // changed: daily withdrawal limit becomes 15% of the total credits a day ago + time-range overlap-factor cap (24) + time-range TTL cap (1 week) and per-write drop cap (32) + GroveDB proof envelope floor (V1)
341 consensus: ConsensusVersions {
342 tenderdash_consensus_version: 1,
343 },
344};
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349 use crate::version::v13::PLATFORM_V13;
350
351 #[test]
352 fn should_halve_only_the_contested_document_fee_at_protocol_14() {
353 for protocol_version in 1..14 {
354 let version = PlatformVersion::get(protocol_version).expect("known protocol version");
355 assert_eq!(
356 version
357 .fee_version
358 .vote_resolution_fund_fees
359 .contested_document_vote_resolution_fund_required_amount,
360 20_000_000_000,
361 "protocol {protocol_version} must preserve the 0.2 DASH contribution"
362 );
363 }
364
365 let mut expected_fees = PLATFORM_V13.fee_version.clone();
366 expected_fees
367 .vote_resolution_fund_fees
368 .contested_document_vote_resolution_fund_required_amount = 10_000_000_000;
369 assert_eq!(PLATFORM_V14.fee_version, expected_fees);
370 }
371
372 /// The ranked / boolean-HAVING routing gate lives in v14's own query
373 /// table, so flipping it touches only v14: a v13 node keeps running
374 /// the v0 helper, which rejects every non-empty HAVING, so a
375 /// mixed-version network agrees until the upgrade vote carries.
376 ///
377 /// v14 selects the v2 helper, which routes the ranked shape
378 /// (`ORDER BY <agg> LIMIT n`) to `dispatch_ranked_v1` and the
379 /// boolean-HAVING range shape (exactly one `having` clause on the
380 /// selected aggregate) to `dispatch_having_v1`. A change that made
381 /// v13 non-zero here would be consensus-breaking for
382 /// already-deployed nodes, which is exactly what the v13 half of
383 /// this assertion guards.
384 #[test]
385 fn ranked_having_routing_gate_is_v14_only() {
386 assert_eq!(
387 PLATFORM_V13
388 .drive_abci
389 .query
390 .document_query_helpers
391 .compute_aggregate_mode_and_check_limit,
392 0
393 );
394 assert_eq!(
395 PLATFORM_V14
396 .drive_abci
397 .query
398 .document_query_helpers
399 .compute_aggregate_mode_and_check_limit,
400 2
401 );
402 }
403
404 /// The ranked index keywords are gated by the meta-schema version, so v14
405 /// must select meta-schema v3 while v13 stays on v2.
406 #[test]
407 fn ranked_index_keywords_are_gated_by_meta_schema_v3() {
408 assert_eq!(
409 PLATFORM_V13
410 .dpp
411 .contract_versions
412 .document_type_versions
413 .schema
414 .document_type_schema,
415 2
416 );
417 assert_eq!(
418 PLATFORM_V14
419 .dpp
420 .contract_versions
421 .document_type_versions
422 .schema
423 .document_type_schema,
424 3
425 );
426 }
427
428 /// The ranked grammar lives in its own document-type parser generation
429 /// rather than behind a version gate inside a shipped one, so v14 must
430 /// select generation 3 while v13 stays on generation 2. Pinned here
431 /// because it is the whole reason generations 0/1/2 can stay byte-identical
432 /// to what consensus already ran: a historical block replayed at v13 is
433 /// parsed by a generation that has never heard of the ranked keywords.
434 /// The grove v4 cleanup gates (batch overwrite inspection + delete-tree
435 /// actual-type cleanup) exist for the indexed trees that ranked indexes
436 /// lay down, so v14 must select grove protocol 4 while v13 stays on 3.
437 /// The gates are cost-neutral — they derive the old element from data the
438 /// merk apply already loads — and the fee-constant tests pin identical
439 /// fees on both sides of the boundary. Platform flows cannot themselves
440 /// overwrite a ranked index (the flags are immutable on contract update
441 /// and new indexes cannot be added to an existing document type), so the
442 /// cleanup behavior itself is exercised by grovedb's own overwrite suites
443 /// at the pinned revision; this test pins that v14 actually activates
444 /// them.
445 #[test]
446 fn grove_v4_cleanup_gates_activate_at_v14() {
447 assert_eq!(PLATFORM_V13.drive.grove_version.protocol_version, 3);
448 assert_eq!(PLATFORM_V14.drive.grove_version.protocol_version, 4);
449 }
450
451 #[test]
452 fn ranked_grammar_gets_its_own_parser_generation() {
453 assert_eq!(
454 PLATFORM_V13
455 .dpp
456 .contract_versions
457 .document_type_versions
458 .class_method_versions
459 .try_from_schema,
460 2
461 );
462 assert_eq!(
463 PLATFORM_V14
464 .dpp
465 .contract_versions
466 .document_type_versions
467 .class_method_versions
468 .try_from_schema,
469 3
470 );
471 }
472
473 /// v14 introduces the slots but activates none of them yet. If a later
474 /// change flips one of these, it must do so deliberately — and update this
475 /// test — rather than by inheriting a default.
476 #[test]
477 fn ranked_feature_slots_exist_but_are_dormant() {
478 assert_eq!(
479 PLATFORM_V14.drive.methods.document.query.detect_ranked_mode,
480 0
481 );
482 assert_eq!(
483 PLATFORM_V14.drive.methods.document.query.detect_having_mode,
484 0
485 );
486 assert_eq!(
487 PLATFORM_V14
488 .drive
489 .methods
490 .verify
491 .document_ranked
492 .verify_ranked_top_k_proof,
493 0
494 );
495 assert_eq!(
496 PLATFORM_V14
497 .drive
498 .methods
499 .verify
500 .document_ranked
501 .verify_having_range_proof,
502 0
503 );
504 let grove = &PLATFORM_V14.drive.grove_methods.batch;
505 assert_eq!(grove.batch_insert_empty_provable_count_indexed_tree, 0);
506 assert_eq!(grove.batch_insert_empty_provable_sum_indexed_tree, 0);
507 assert_eq!(
508 grove.batch_insert_empty_provable_count_provable_sum_indexed_tree,
509 0
510 );
511 }
512
513 /// The contested vote poll index cross-check changes accept/reject
514 /// behavior for document create transitions, so it lives in v14's own
515 /// validation table: a v13 node keeps running structure validation v0,
516 /// which validates only the prefunded amount and ignores the index name.
517 /// A change that made v13 non-zero here would retroactively reject
518 /// transitions already in the chain.
519 #[test]
520 fn contested_index_cross_check_is_v14_only() {
521 assert_eq!(
522 PLATFORM_V13
523 .drive_abci
524 .validation_and_processing
525 .state_transitions
526 .batch_state_transition
527 .document_create_transition_structure_validation,
528 0
529 );
530 assert_eq!(
531 PLATFORM_V14
532 .drive_abci
533 .validation_and_processing
534 .state_transitions
535 .batch_state_transition
536 .document_create_transition_structure_validation,
537 1
538 );
539 assert_eq!(
540 PLATFORM_V13
541 .drive_abci
542 .validation_and_processing
543 .state_transitions
544 .batch_state_transition
545 .document_create_transition_state_validation,
546 1
547 );
548 assert_eq!(
549 PLATFORM_V14
550 .drive_abci
551 .validation_and_processing
552 .state_transitions
553 .batch_state_transition
554 .document_create_transition_state_validation,
555 2
556 );
557 assert_eq!(
558 PLATFORM_V13
559 .drive
560 .methods
561 .document
562 .insert_contested
563 .add_contested_vote_subtree_for_non_identities_operations,
564 0
565 );
566 assert_eq!(
567 PLATFORM_V14
568 .drive
569 .methods
570 .document
571 .insert_contested
572 .add_contested_vote_subtree_for_non_identities_operations,
573 1
574 );
575 }
576}