pub const PLATFORM_V14: PlatformVersion;Expand description
v14 hosts six consensus changes:
- Contract-level ranked aggregates: an index can declare that its groups are rankable by an aggregate, so a query like “top 5 restaurants by average grade” is served from an ordered secondary tree in O(log n + k) with a proof, instead of being rejected.
- The shared-prefix aggregate index fix: a data contract declaring
an aggregating (countable / summable) index that terminates at a
property which is also the prefix of a compound index (e.g. summable
[a]next to[a, b]) registered successfully but rejected every document insert for most flag combinations, because Drive could not legally hang the compound continuation tree under the aggregating per-value tree. The v2 document index walkers (plus the v1 update walker) that fix it gate here as well: tree types derive through a shared continuation-demotion helper (provable count-bearing value trees with compound continuations demote toCountSumTree, since grovedb rejects count-suppressed children under provable count parents by design) and continuation inserts route through the completed zero-contribution wrapper matrix. No state migration is needed: shapes without compound continuations produce bit-identical operations, the broken shapes could never hold documents, and the one previously-insertable shape the demotion changes (a provable count-bearing value tree whose continuations were all sum-bearing — insertable pre-v14 only through an unenforced grovedb batch guard) simply getsCountSumTreevalue trees for values first seen at v14+, which readers treat identically. - The contested vote poll index cross-check: the index named by a
document create transition’s prefunded voting balance keys the vote
poll, its stored info, its end-date entry and its prefunded
specialized balance, while the contested index the contender is
inserted under always comes from the document type. Up to v13 nothing
tied the two together, so a submitter could register and fund a
contest under a vote poll describing a different index than the one
the contest was created on — which halts the chain when that poll
ends — or open a contest for a document that is not a contested
resource at all. State validation also prevents a non-contested create
from occupying a live contested document’s id before the contest winner
is awarded into primary storage. Drive’s contested insert also recreates
an abstain or lock vote tree over the storage an earlier poll’s cleanup
left orphaned (it only removed the trees that received votes), so a
resource can be contested again instead of failing with
CorruptedContractIndexes. - Relative daily withdrawal limit: the flat 2000 Dash per 24 hours that
applied from v8 becomes 15% of the total credits Platform held a day ago
(
SYSTEM_LIMITS_V4.daily_withdrawal_limit_percent, read bydaily_withdrawal_limitv2 throughDPP_METHOD_VERSIONS_V3), never below one maximal withdrawal (max_withdrawal_amount) so every accepted withdrawal eventually fits and cannot block the pooling queue. The base is capped atmax_daily_withdrawal_amount(4000 Dash, Core’s unlock capacity per day under V24 as written); the credit inflows of the active window — every credit mint, recorded per block byrecord_credit_inflows_for_withdrawalsin the credit inflows sum tree — are added after the cap, so the limit counts net outflow and a matching deposit -> withdraw cycle does not consume the capped budget of other users (#4471). Outflow funded by same-window deposits may therefore exceed the cap; this mirrors the net credit-pool rule Core adopts for V24 alongside this change (tracked in #4471), which must land before V24 activates. Both the inflows and the pooled reservations count over the interval after the base snapshot only — an entry the snapshot already reflects is neither added nor subtracted again. The base is the total credits recorded at the latest block at least 24 hours before the current one:DRIVE_ABCI_METHOD_VERSIONS_V10turns onrecord_total_credits_history_for_withdrawals, which checks the total credits every block once fees and epoch rewards are in, writes it under the withdrawals tree keyed by block time whenever it changed (an entry describes the total until the next one) and prunes entries older than the one the limit reads, andDRIVE_VERSION_V9’s identity withdrawal table bumpscalculate_current_withdrawal_limitto 1 to read that lagged value. Until an entry is a day old — the first day after activation — the flat 2000 Dash keeps applying, so the lag cannot be skipped by inflating the total before or at activation. The lag is the guardrail: a sudden jump in the total credits does not raise the limit for a day. Amounts already pooled in the last 24 hours keep counting against the maximum exactly as before. Pre-V24 Core caps unlocks atLimitAmountV22(2000 Dash) per block, with the amount checked only at block level, so any daily total is still minable across blocks; V24’s 4000 Dash per 576-block window matches the capped base and is raised to the same net rule before activation (see above). - Time-range indexes: an index can declare a
timeRangetransform that buckets a required system timestamp ($createdAt/$updatedAt/$transferredAt) into fixed-length, regularly-spaced, optionally overlapping windows declared in seconds (range/step, plus an optionalphase < stepalignment offset). Each grid gets its own index subtree — the level is keyed by the property name qualified with the grid — so several grids may bucket one timestamp side by side. A document is stored once per containing bucket per grid (the v2 insert/delete and v1 update walkers carry the fan-out; the per-document write amplification is capped per index bySystemLimits::max_time_range_overlap_factor), and the v1getDocumentshandler resolves the newIN_TIME_RANGEoperator — a typedTimeRangeSelectionoperand:NEWEST/OLDEST(resolved to a bucket-start equality from committed block time) orBY_START(naming any window, current or historic, by its grid-aligned start), with agridmember naming one grid where several bucket the field — making trending/leaderboard document and count/sum/avg queries provable over the current or any named window.unique: trueis admitted only for non-overlapping windows (range == step) sourced from the immutable$createdAt. - Deterministic token reward math:
DistributionFunction::evaluate(logarithmic, inverted-logarithmic, exponential and polynomial perpetual distributions) computesln/exp/powthrough the pinned pure-Rustlibmcrate instead of the platform C library. musl’slogtakes an FMA path on aarch64 and a non-FMA path on x86_64, so the two disagree by 1 ulp on some inputs; a contract owner could pick parameters whose reward sat within that ulp of an integer, andfloorthen minted different amounts on the two architectures, splitting the app hash both at claim time and at contract registration (validation evaluates the start value). Gated ondistribution_function_evaluate_versionso both architectures switch at the same height; pre-v14 blocks replay on the old math byte-for-byte.log/exphave no architecture dispatch andpow’s only arch-touching call is the correctly-roundedsqrt, so the result is bit-identical on every target Platform builds for. The goal is determinism, not correct rounding: on a boundary tuple the host libm (glibc, macOS) can still be 1 ulp away, so anything predicting rewards with host math may differ from consensus by one unit.
The first two are orthogonal by construction: the ranked upgrade decides the
property-name tree type, the demotion decides the value tree type
one level below it, and a demoted CountSumTree value tree contributes
its (count, sum) to a ranked indexed parent exactly as the provable
variant did — so ranked secondaries keep ranking correctly over
shared-prefix shapes.
Until a contract uses the ranked or time-range grammar, the only v14 behavior changes are the shared-prefix fix, the contested-index cross-check, the index-reorder schema-compatibility fix and the relative daily withdrawal limit; everything else matches v13:
CONTRACT_VERSIONS_V6pointsdocument_type_schemaat the v3 document meta-schema, which hosts the ranked index keywords (rankedCountable/rankedSummable/rankedAverageable), therefersToreference keyword and thetimeRangeindex transform. v13 keeps validating against meta-schema v2, where those keys are rejected as unknown properties, so a pre-v14 contract cannot smuggle them in. It also bumpsvalidate_schema_compatibilityto 1, which strips the top-levelindiceskey before diffing the old and new document type schemas: index immutability is enforced byvalidate_updatev1’s name-keyed comparison, so a contract update that merely reorders theindicesarray validates cleanly instead of hitting the unsupported-keyword hard error (an internal error under v13).DRIVE_VERSION_V9carriesDRIVE_DOCUMENT_METHOD_VERSIONS_V4, adding thedetect_ranked_moderouting slot, plus the grove-method slots for creating the three indexed tree variants and the verify-method slot forverify_ranked_top_k_proof. All are 0 today. The same table bumps the four index walkers to v2 and the document update walker to v1 for the shared-prefix fix; those same walker versions carry the time-range bucket fan-out, so both features gate on one table entry.DRIVE_ABCI_QUERY_VERSIONS_V3bumpsdocument_query_helpers.compute_aggregate_mode_and_check_limit0 → 2, opening two routes on the v1 document-query handler: the ranked path (a grouped aggregate whose singleorder_bynames the selected aggregate —ORDER BY <agg> [ASC|DESC] LIMIT n [OFFSET m]) and the boolean-HAVINGrange path (a grouped aggregate carrying exactly onehavingclause on the selected aggregate —GROUP BY p HAVING <agg> <op> <value> LIMIT n), the latter served as a value-bounded range read of the covering ranked index’s axis secondary. v13 and earlier keep the v1 table and therefore keep rejecting both shapes, so mixed-version networks agree across the upgrade.DRIVE_ABCI_VALIDATION_VERSIONS_V10bumpsdocument_create_transition_structure_validation0 → 1, requiring a contested create transition’s prefunded voting balance to name the same vote poll the document itself resolves to, and rejecting one on a document that resolves to no contested index. It also bumps document create state validation to 2, enforcingrefersTodocument references and rejecting a non-contested create whose id is already present in the contested tree. Document replace state validation 1 enforces the same reference checks. v13 keeps the v9 table and therefore keeps accepting all of these, so replay of pre-upgrade blocks is unchanged.DOCUMENT_VERSIONS_V4bumpsdocument_serialization_versionto default 3: documents are stamped with the contract version their bytes conform to (a varint after the format prefix), enabling therequiredSinceproperty keyword — a contract update may add a new required property annotated with the version that update creates. Documents stamped below a property’srequiredSincekeep the presence-flagged layout they were written with, so the latest contract alone reconstructs every stamp’s layout and no historical contract lookups are ever needed. Reads dispatch on the byte prefix, so formats 0–2 (all pre-v14 documents) deserialize exactly as before with an unstamped (pre-annotation) layout.
- Client-side GroveDB proof envelope floor:
SYSTEM_LIMITS_V4.minimum_grovedb_proof_envelope_versionbecomes 1, so a client verifying with v14 tables rejects the legacy V0 proof envelope before its bytes reach Drive (drive-proof-verifier,wasm-drive-verify, and the nested compacted address proofs). V0’s item binding lets a prover return different item bytes under the same authenticated root; every live network has emitted V1 envelopes since v13 (grove version 3), so no honest response is affected. - Epoch-based perpetual distribution claims stop wrapping:
RewardDistributionType::max_cycle_moment(the cap on how far one claim may redeem, selected byTOKEN_VERSIONS_V3.reward_distribution_max_cycle_moment_version1) computesstart + interval * cyclesinu64with saturating arithmetic and narrows back toEpochIndexonly after capping at the last completed cycle moment (current cycle moment - interval, the previous epoch for an interval of one as before; for wider intervals the same cycles are paid, but the cap now sits on a cycle boundary, the only shape in whichevaluate_interval’s fixed-amount step count and its per-cycle loop agree). Up to v13 the sum was taken inu16: a fixed-amount function allows 32,767 cycles, so any epoch interval of three or more with a nonzero start (or two with a start at epoch two or later) pushed the cap pastu16::MAX. Release builds wrap, the cap landed below the start,evaluate_intervalsaw an empty range and the claim was refused withInvalidTokenClaimNoCurrentRewardson every attempt. The v0 arithmetic is kept, wrapping explicitly, so those refusals replay. - Evonode reward cycles weighted by the epochs they span: the
per-cycle evaluator in
DistributionFunction::evaluate_intervalasks the participation ratio for the epochs a cycle covers (TOKEN_VERSIONS_V3.distribution_function_cycle_epochs_version1:cycle moment - interval + 1 ..= cycle moment). Up to v13 it passed the cycle’s step index as if it were an epoch, which coincides only for an interval of one; for a wider interval it named epochs before the distribution started, outside the epoch window the claim loads, and anEvonodesByParticipationclaim with a function other than a fixed amount failed as an internal error (reachable only once item 8 let the cap stop wrapping). Interval-one distributions are unchanged. - A zero epoch interval is rejected at registration:
RewardDistributionType::validate_structure_intervalv1 (CONTRACT_VERSIONS_V6.token_versions.validate_structure_interval) refuses anEpochBasedDistributionwithinterval: 0with the newInvalidTokenDistributionEpochIntervalTooShortError(code 10828) on contract create and update. Up to v13 the epoch arm enforced nothing, so such a contract registered and every claim on it failed as an internal error, since no cycle can be computed from a zero step. Block and time minimums are unchanged.
-
ShieldFromIdentity(state transition type 21) activates:SHIELD_FROM_IDENTITY_INITIAL_PROTOCOL_VERSION = 14gates it inis_allowed, andDRIVE_ABCI_VALIDATION_VERSIONS_V10is the first table whoseshield_from_identity_state_transitionrow enables basic structure, identity signature, and nonce validation. It moves credits from an identity balance straight into the shielded pool: the funding side is identity-signed likeIdentityCreditTransferToAddresses, the pool side is an outputs-only Orchard bundle likeShield, and the fee is metered plus the shielded compute fee, paid from the identity. -
IdentityTopUpFromShieldedPool(state transition type 22) activates at the same gate (IDENTITY_TOP_UP_FROM_SHIELDED_POOL_INITIAL_PROTOCOL_VERSION = 14,DRIVE_ABCI_VALIDATION_VERSIONS_V10row). It spends shielded notes likeUnshieldand credits an EXISTING identity’s balance instead of a platform address: pool-paid flat fee (compute_shielded_identity_top_up_fee), no platform signature, the target identity and gross amount bound into the Orchard sighash, and no system-credit adjustment (pool and identity balances are both conservation-equation terms).
The wire surface changes only additively: GetDocumentsRequestV1
already carries selects / group_by / order_by / limit /
offset; the ranked response is an additive ResultData.ranked
variant, whose skipped field is likewise additive; and the v1
where-clause operator enum gains IN_TIME_RANGE = 11, which pre-v14
servers reject as an unknown operator rather than misread (the v0 wire
has no time-range operator at all).
Contract-bound authentication keys activate through contract-bounds validation v2,
identity-signature validation v1 and batch advanced-structure v1. Identity creation
validates key bounds (state v1) and identity-update state v1 retains the contract
lookup fees; Drive identity methods v2 index and refresh the bound keys.
Contract group bounds on authentication keys ride the same versions: contract-bounds
validation v2 admits them, batch transform v2 resolves the member contract’s group
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
shielded pool, whose sighash preimage layout predates them.
A transition carrying such a key is inactive before this version (active_version_range),
so earlier protocol versions reject it without charging, as a binary that cannot decode it does.
Authentication keys may carry a budget and an expiry (the version 1 public key format, which
StateTransition::active_version_range admits from 14). Key structure validation v1
(STATE_TRANSITION_METHOD_VERSIONS_V2) and validate_identity_public_keys_limits decide
which keys may carry them; Drive identity methods v2 write the remaining budget when the key
is added; identity-signature validation v1 refuses a key whose budget is spent;
validate_fees_of_event v1 refuses an expired key and a spend the remaining budget does not
cover (only metered processing may overshoot); execute_event v1 deducts what was spent.
Shielded-proof validation v1 refuses a key that carries a budget or an expiry in identity
creation from the shielded pool, whose sighash preimage does not cover the limits.
IdentityKeyLimitsUpdate (state transition type 23, gated by
IDENTITY_KEY_LIMITS_UPDATE_INITIAL_PROTOCOL_VERSION) raises a key’s total budget, and the
remaining budget with it, or moves its expiry later; it only ever loosens limits. Signed by a
MASTER key or by a CRITICAL key without limits (DRIVE_ABCI_VALIDATION_VERSIONS_V10 turns
its gates on; Drive identity methods v2 rewrite the key and raise the remaining budget).