From 8107ddac7e2d596da0c96bd6879e036e814e467d Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 13 Aug 2026 22:12:15 +0700 Subject: [PATCH 01/25] feat(drive): IN over pinned prefix properties on ranked and having-range queries A compound ranked index's leading properties can now carry at most one IN where clause (2..=10 distinct elements, null legal for the absent-value prefix) alongside equality pins, on both the ranked top-k and having-range surfaces. Each element selects its own prefix branch; the executors walk one axis secondary per branch with the full limit and merge deterministically by (aggregate in walk direction, encoded prefix segment ascending, group key in walk direction). Merged entries carry an in_key discriminator - the encoded segment of their branch - since one group key can legally appear under two prefixes. Proofs stay per-branch: the proved response is a versioned container of grovedb indexed-axis proofs in canonical branch order, and the verifier re-derives the branch set from its own resolution, verifies each branch against its own path, requires one root hash across branches, and re-merges with the shared comparator - the merge itself needs no proof because the merged page is a deterministic function of independently proved branch pages (any union entry preceding a returned entry is preceded within its own branch by fewer than limit entries, so per-branch completeness composes). A single-element IN is normalized to an equality pin and stays byte-identical to ==. OFFSET is rejected together with IN (rank-skip is attested from one secondary's counted commitments; no counted structure spans the union). Wire: RankedEntry gains optional in_key (additive; clients regenerated); no request-side changes. The branch ceiling is a hard rejection like the limit ceiling, since the branch set is echoed in the proof container. Grammar, merge order (including a cross-prefix aggregate tie and a null element mixed with a real one), the container tamper matrix (reorder / drop / duplicate / re-version / pad), the degenerate single-element equivalence, and the wire round trip are all pinned in the drive and abci suites. Co-Authored-By: Claude Fable 5 --- book/src/drive/document-ranked-trees.md | 4 +- .../clients/drive/v0/nodejs/drive_pbjs.js | 34 +- .../platform/v0/nodejs/platform_pbjs.js | 34 +- .../platform/v0/nodejs/platform_protoc.js | 74 +- .../platform/v0/objective-c/Platform.pbobjc.h | 18 +- .../platform/v0/objective-c/Platform.pbobjc.m | 11 + .../platform/v0/python/platform_pb2.py | 1239 +++++++++-------- .../clients/platform/v0/web/platform_pb.d.ts | 8 + .../clients/platform/v0/web/platform_pb.js | 74 +- .../protos/platform/v0/platform.proto | 12 +- .../query/document_query/v1/dispatch/mod.rs | 4 + .../src/query/document_query/v1/tests.rs | 84 +- .../src/proof/document_having.rs | 1 + .../src/proof/document_ranked.rs | 9 + .../v0/tests/batched_group_drain.rs | 2 +- .../v0/tests/ranked_index_e2e_tests.rs | 2 +- .../execute_range.rs | 65 +- .../query/drive_document_having_query/mod.rs | 48 +- .../mode_detection/v0/mod.rs | 16 +- .../drive_document_having_query/tests.rs | 218 ++- .../drive_document_ranked_query/branches.rs | 194 +++ .../execute_top_k.rs | 77 +- .../index_picker.rs | 142 +- .../query/drive_document_ranked_query/mod.rs | 77 +- .../mode_detection/mod.rs | 2 +- .../mode_detection/v0/mod.rs | 138 +- .../query/drive_document_ranked_query/path.rs | 7 +- .../drive_document_ranked_query/tests.rs | 412 +++++- .../verify_having_range_proof/v0/mod.rs | 55 +- .../verify_ranked_top_k_proof/v0/mod.rs | 65 +- packages/rs-sdk/src/mock/requests.rs | 21 +- 31 files changed, 2329 insertions(+), 818 deletions(-) create mode 100644 packages/rs-drive/src/query/drive_document_ranked_query/branches.rs diff --git a/book/src/drive/document-ranked-trees.md b/book/src/drive/document-ranked-trees.md index 6197c622352..85cc2d34c8e 100644 --- a/book/src/drive/document-ranked-trees.md +++ b/book/src/drive/document-ranked-trees.md @@ -91,7 +91,7 @@ One asymmetry is worth knowing when authoring: **the meta-schema demands the lit Two structural rules, both enforced at contract-parse time in rs-dpp: -- **No aggregating index on a compound ranked index's full prefix.** Ranked flags are allowed on compound indexes, with **per-prefix** semantics: a ranked `[identityId, class]` puts the indexed tree at each prefix value's terminal `class` property-name level — one ordered secondary per `identityId`, each ranking only that identity's `class` groups. There is deliberately no global cross-prefix ordering; the query surfaces require every leading property to be pinned by an equality `where` clause. The one shape that stays impossible — and is rejected per document type, where all indexes are visible (`validate_no_ranked_prefix_overlap`) — is a countable/summable index terminating at exactly the compound's leading prefix: its aggregating value trees would demand the `NonCounted` / `NotSummed` shell around the ranked terminal tree, and the storage layer structurally rejects any wrapper around an indexed tree, because the wrapper would neutralise the very aggregates the secondaries order by. (Drive's fail-closed guard behind the parse-time check is `INDEXED_INNER_UNWRAPPABLE`.) Only the exact `n-1` prefix conflicts: an aggregating index at a shorter prefix wraps a plain intermediate tree, and one extending past the ranked terminal lives inside its value trees — both supported. +- **No aggregating index on a compound ranked index's full prefix.** Ranked flags are allowed on compound indexes, with **per-prefix** semantics: a ranked `[identityId, class]` puts the indexed tree at each prefix value's terminal `class` property-name level — one ordered secondary per `identityId`, each ranking only that identity's `class` groups. There is deliberately no global cross-prefix ordering; the query surfaces require every leading property to be pinned by a `where` clause — equalities, plus at most one `IN` that fans out across prefix branches and merges deterministically. The one shape that stays impossible — and is rejected per document type, where all indexes are visible (`validate_no_ranked_prefix_overlap`) — is a countable/summable index terminating at exactly the compound's leading prefix: its aggregating value trees would demand the `NonCounted` / `NotSummed` shell around the ranked terminal tree, and the storage layer structurally rejects any wrapper around an indexed tree, because the wrapper would neutralise the very aggregates the secondaries order by. (Drive's fail-closed guard behind the parse-time check is `INDEXED_INNER_UNWRAPPABLE`.) Only the exact `n-1` prefix conflicts: an aggregating index at a shorter prefix wraps a plain intermediate tree, and one extending past the ranked terminal lives inside its value trees — both supported. - **Non-unique indexes only.** `ranked aggregates are not supported on unique indexes: each group of a unique index contains at most one document, so there is nothing meaningful to rank`. Contested indexes are covered transitively — a contested index is unique by construction, so it hits the same check rather than needing its own. ### Version Gate @@ -341,7 +341,7 @@ Note that the fixture puts each shape on its **own document type**. That's not a | Top / bottom K groups by sum of a property | `rankedSummable: true` on an index with `summable: ""` + `rangeSummable: true` | | Top / bottom K groups by average of a property | `rankedAverageable: true` on an index with `averageable: ""` + `rangeAverageable: true` (or the count+sum longhand) | | Two rankings on one index (e.g. by count *and* by average) | Both keywords. The tree is a PCPSIT carrying both axes in its TLV; you pay one secondary Merk per axis on every write. | -| A ranking filtered by another property (`top 5 restaurants in London`) | A **compound ranked index** with the filter property leading: `[city, restaurantId]` with the ranked flags. Each city gets its own secondary; the query pins the prefix with an equality `where` (`WHERE city == "London" GROUP BY restaurantId ORDER BY DESC LIMIT 5`). Only equality pins — a range or `IN` on the prefix is rejected, and there is no cross-prefix (global) ordering on a compound ranked index. | +| A ranking filtered by another property (`top 5 restaurants in London`) | A **compound ranked index** with the filter property leading: `[city, restaurantId]` with the ranked flags. Each city gets its own secondary; the query pins the prefix with an equality `where` (`WHERE city == "London" GROUP BY restaurantId ORDER BY DESC LIMIT 5`). Equality pins select one prefix; at most one pin may be an `IN` (2..=10 distinct elements, `null` legal for the absent-value prefix), which walks one secondary per element and merges by `(aggregate, encoded prefix, group key)` with per-branch proofs in one container — entries then carry `in_key`. A range operator on the prefix stays rejected, `OFFSET` is rejected together with `IN`, and there is still no global cross-prefix ordering beyond that merge. | | A ranking on a unique or contested index | Not available, and not meaningful: every group holds at most one document. | | Range aggregates without ranking (the 4.0 surface) | Just the `range*` flags. Ranking is strictly additive — adding it never changes what a range query returns. | | Nothing ranking-aware (default) | Don't set any `ranked*` flag. The terminal property-name tree keeps the type its range flags give it. | diff --git a/packages/dapi-grpc/clients/drive/v0/nodejs/drive_pbjs.js b/packages/dapi-grpc/clients/drive/v0/nodejs/drive_pbjs.js index 0d1e656f487..5a97b6905be 100644 --- a/packages/dapi-grpc/clients/drive/v0/nodejs/drive_pbjs.js +++ b/packages/dapi-grpc/clients/drive/v0/nodejs/drive_pbjs.js @@ -26727,6 +26727,7 @@ $root.org = (function() { * @property {number|Long|null} [count] RankedEntry count * @property {number|Long|null} [sum] RankedEntry sum * @property {number|null} [avg] RankedEntry avg + * @property {Uint8Array|null} [inKey] RankedEntry inKey */ /** @@ -26776,6 +26777,14 @@ $root.org = (function() { */ RankedEntry.prototype.avg = 0; + /** + * RankedEntry inKey. + * @member {Uint8Array} inKey + * @memberof org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry + * @instance + */ + RankedEntry.prototype.inKey = $util.newBuffer([]); + // OneOf field names bound to virtual getters and setters var $oneOfFields; @@ -26822,6 +26831,8 @@ $root.org = (function() { writer.uint32(/* id 3, wireType 0 =*/24).sint64(message.sum); if (message.avg != null && Object.hasOwnProperty.call(message, "avg")) writer.uint32(/* id 4, wireType 1 =*/33).double(message.avg); + if (message.inKey != null && Object.hasOwnProperty.call(message, "inKey")) + writer.uint32(/* id 5, wireType 2 =*/42).bytes(message.inKey); return writer; }; @@ -26868,6 +26879,9 @@ $root.org = (function() { case 4: message.avg = reader.double(); break; + case 5: + message.inKey = reader.bytes(); + break; default: reader.skipType(tag & 7); break; @@ -26926,6 +26940,9 @@ $root.org = (function() { if (typeof message.avg !== "number") return "avg: number expected"; } + if (message.inKey != null && message.hasOwnProperty("inKey")) + if (!(message.inKey && typeof message.inKey.length === "number" || $util.isString(message.inKey))) + return "inKey: buffer expected"; return null; }; @@ -26966,6 +26983,11 @@ $root.org = (function() { message.sum = new $util.LongBits(object.sum.low >>> 0, object.sum.high >>> 0).toNumber(); if (object.avg != null) message.avg = Number(object.avg); + if (object.inKey != null) + if (typeof object.inKey === "string") + $util.base64.decode(object.inKey, message.inKey = $util.newBuffer($util.base64.length(object.inKey)), 0); + else if (object.inKey.length >= 0) + message.inKey = object.inKey; return message; }; @@ -26982,7 +27004,7 @@ $root.org = (function() { if (!options) options = {}; var object = {}; - if (options.defaults) + if (options.defaults) { if (options.bytes === String) object.key = ""; else { @@ -26990,6 +27012,14 @@ $root.org = (function() { if (options.bytes !== Array) object.key = $util.newBuffer(object.key); } + if (options.bytes === String) + object.inKey = ""; + else { + object.inKey = []; + if (options.bytes !== Array) + object.inKey = $util.newBuffer(object.inKey); + } + } if (message.key != null && message.hasOwnProperty("key")) object.key = options.bytes === String ? $util.base64.encode(message.key, 0, message.key.length) : options.bytes === Array ? Array.prototype.slice.call(message.key) : message.key; if (message.count != null && message.hasOwnProperty("count")) { @@ -27013,6 +27043,8 @@ $root.org = (function() { if (options.oneofs) object.value = "avg"; } + if (message.inKey != null && message.hasOwnProperty("inKey")) + object.inKey = options.bytes === String ? $util.base64.encode(message.inKey, 0, message.inKey.length) : options.bytes === Array ? Array.prototype.slice.call(message.inKey) : message.inKey; return object; }; diff --git a/packages/dapi-grpc/clients/platform/v0/nodejs/platform_pbjs.js b/packages/dapi-grpc/clients/platform/v0/nodejs/platform_pbjs.js index 978be7d0743..24f1ed1c9cd 100644 --- a/packages/dapi-grpc/clients/platform/v0/nodejs/platform_pbjs.js +++ b/packages/dapi-grpc/clients/platform/v0/nodejs/platform_pbjs.js @@ -26219,6 +26219,7 @@ $root.org = (function() { * @property {number|Long|null} [count] RankedEntry count * @property {number|Long|null} [sum] RankedEntry sum * @property {number|null} [avg] RankedEntry avg + * @property {Uint8Array|null} [inKey] RankedEntry inKey */ /** @@ -26268,6 +26269,14 @@ $root.org = (function() { */ RankedEntry.prototype.avg = 0; + /** + * RankedEntry inKey. + * @member {Uint8Array} inKey + * @memberof org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry + * @instance + */ + RankedEntry.prototype.inKey = $util.newBuffer([]); + // OneOf field names bound to virtual getters and setters var $oneOfFields; @@ -26314,6 +26323,8 @@ $root.org = (function() { writer.uint32(/* id 3, wireType 0 =*/24).sint64(message.sum); if (message.avg != null && Object.hasOwnProperty.call(message, "avg")) writer.uint32(/* id 4, wireType 1 =*/33).double(message.avg); + if (message.inKey != null && Object.hasOwnProperty.call(message, "inKey")) + writer.uint32(/* id 5, wireType 2 =*/42).bytes(message.inKey); return writer; }; @@ -26360,6 +26371,9 @@ $root.org = (function() { case 4: message.avg = reader.double(); break; + case 5: + message.inKey = reader.bytes(); + break; default: reader.skipType(tag & 7); break; @@ -26418,6 +26432,9 @@ $root.org = (function() { if (typeof message.avg !== "number") return "avg: number expected"; } + if (message.inKey != null && message.hasOwnProperty("inKey")) + if (!(message.inKey && typeof message.inKey.length === "number" || $util.isString(message.inKey))) + return "inKey: buffer expected"; return null; }; @@ -26458,6 +26475,11 @@ $root.org = (function() { message.sum = new $util.LongBits(object.sum.low >>> 0, object.sum.high >>> 0).toNumber(); if (object.avg != null) message.avg = Number(object.avg); + if (object.inKey != null) + if (typeof object.inKey === "string") + $util.base64.decode(object.inKey, message.inKey = $util.newBuffer($util.base64.length(object.inKey)), 0); + else if (object.inKey.length >= 0) + message.inKey = object.inKey; return message; }; @@ -26474,7 +26496,7 @@ $root.org = (function() { if (!options) options = {}; var object = {}; - if (options.defaults) + if (options.defaults) { if (options.bytes === String) object.key = ""; else { @@ -26482,6 +26504,14 @@ $root.org = (function() { if (options.bytes !== Array) object.key = $util.newBuffer(object.key); } + if (options.bytes === String) + object.inKey = ""; + else { + object.inKey = []; + if (options.bytes !== Array) + object.inKey = $util.newBuffer(object.inKey); + } + } if (message.key != null && message.hasOwnProperty("key")) object.key = options.bytes === String ? $util.base64.encode(message.key, 0, message.key.length) : options.bytes === Array ? Array.prototype.slice.call(message.key) : message.key; if (message.count != null && message.hasOwnProperty("count")) { @@ -26505,6 +26535,8 @@ $root.org = (function() { if (options.oneofs) object.value = "avg"; } + if (message.inKey != null && message.hasOwnProperty("inKey")) + object.inKey = options.bytes === String ? $util.base64.encode(message.inKey, 0, message.inKey.length) : options.bytes === Array ? Array.prototype.slice.call(message.inKey) : message.inKey; return object; }; diff --git a/packages/dapi-grpc/clients/platform/v0/nodejs/platform_protoc.js b/packages/dapi-grpc/clients/platform/v0/nodejs/platform_protoc.js index 10f2641e792..6bb7e0ddfde 100644 --- a/packages/dapi-grpc/clients/platform/v0/nodejs/platform_protoc.js +++ b/packages/dapi-grpc/clients/platform/v0/nodejs/platform_protoc.js @@ -30659,7 +30659,8 @@ proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.Rank key: msg.getKey_asB64(), count: jspb.Message.getFieldWithDefault(msg, 2, "0"), sum: jspb.Message.getFieldWithDefault(msg, 3, "0"), - avg: jspb.Message.getFloatingPointFieldWithDefault(msg, 4, 0.0) + avg: jspb.Message.getFloatingPointFieldWithDefault(msg, 4, 0.0), + inKey: msg.getInKey_asB64() }; if (includeInstance) { @@ -30712,6 +30713,10 @@ proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.Rank var value = /** @type {number} */ (reader.readDouble()); msg.setAvg(value); break; + case 5: + var value = /** @type {!Uint8Array} */ (reader.readBytes()); + msg.setInKey(value); + break; default: reader.skipField(); break; @@ -30769,6 +30774,13 @@ proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.Rank f ); } + f = /** @type {!(string|Uint8Array)} */ (jspb.Message.getField(message, 5)); + if (f != null) { + writer.writeBytes( + 5, + f + ); + } }; @@ -30922,6 +30934,66 @@ proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.Rank }; +/** + * optional bytes in_key = 5; + * @return {string} + */ +proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry.prototype.getInKey = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 5, "")); +}; + + +/** + * optional bytes in_key = 5; + * This is a type-conversion wrapper around `getInKey()` + * @return {string} + */ +proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry.prototype.getInKey_asB64 = function() { + return /** @type {string} */ (jspb.Message.bytesAsB64( + this.getInKey())); +}; + + +/** + * optional bytes in_key = 5; + * Note that Uint8Array is not supported on all browsers. + * @see http://caniuse.com/Uint8Array + * This is a type-conversion wrapper around `getInKey()` + * @return {!Uint8Array} + */ +proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry.prototype.getInKey_asU8 = function() { + return /** @type {!Uint8Array} */ (jspb.Message.bytesAsU8( + this.getInKey())); +}; + + +/** + * @param {!(string|Uint8Array)} value + * @return {!proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry} returns this + */ +proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry.prototype.setInKey = function(value) { + return jspb.Message.setField(this, 5, value); +}; + + +/** + * Clears the field making it undefined. + * @return {!proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry} returns this + */ +proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry.prototype.clearInKey = function() { + return jspb.Message.setField(this, 5, undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry.prototype.hasInKey = function() { + return jspb.Message.getField(this, 5) != null; +}; + + /** * List of repeated fields within this message type. diff --git a/packages/dapi-grpc/clients/platform/v0/objective-c/Platform.pbobjc.h b/packages/dapi-grpc/clients/platform/v0/objective-c/Platform.pbobjc.h index c37388a2691..cb78d1656b0 100644 --- a/packages/dapi-grpc/clients/platform/v0/objective-c/Platform.pbobjc.h +++ b/packages/dapi-grpc/clients/platform/v0/objective-c/Platform.pbobjc.h @@ -2941,11 +2941,11 @@ typedef GPB_ENUM(GetDocumentsRequest_GetDocumentsRequestV1_Start_OneOfCase) { * - a is the In field AND b is the range field, in that order → existing compound distinct shape; entries carry both `in_key` (= a's value) and `key` (= b's value). * * `select=, group_by=[p], order_by=[]` (protocol v14+) — **ranked mode**: - * - exactly one `group_by` property, exactly one `order_by` clause naming the select's aggregate (`f` for `SUM(f)` / `AVG(f)`, the `$count` sentinel for `COUNT(*)`), a `limit` in `1 ..= 100`, an optional `offset`, and no `having` / `start_at`, on an index declaring the matching `rankedCountable` / `rankedSummable` / `rankedAverageable` axis → ranked executor, answered in `ResultData.ranked`. On a single-property ranked index no `where` is accepted; on a compound ranked index every leading index property must be pinned with an `EQUAL` where clause (one per property, `group_by` names the trailing property), selecting which prefix's ranking is read. + * - exactly one `group_by` property, exactly one `order_by` clause naming the select's aggregate (`f` for `SUM(f)` / `AVG(f)`, the `$count` sentinel for `COUNT(*)`), a `limit` in `1 ..= 100`, an optional `offset`, and no `having` / `start_at`, on an index declaring the matching `rankedCountable` / `rankedSummable` / `rankedAverageable` axis → ranked executor, answered in `ResultData.ranked`. On a single-property ranked index no `where` is accepted; on a compound ranked index every leading index property must be pinned (one clause per property, `group_by` names the trailing property) — `EQUAL` pins one prefix, and **at most one** clause may be `IN` (2..=10 distinct elements, `null` legal), fanning the walk out across one prefix branch per element and merging by `(aggregate, encoded prefix, group key)`; merged entries carry `in_key`. `offset` is rejected together with `IN` (rank-skip is per-secondary). * - `DESC` is the "top n" reading (walk the axis from the largest aggregate down), `ASC` the "bottom n" reading. Worked example: `SELECT AVG(grade) GROUP BY restaurantId ORDER BY grade DESC LIMIT 1 OFFSET 4` is the 5th-best restaurant. * * `select=, group_by=[p], having=[ ]` (protocol v14+) — **having-range mode**: - * - exactly one `group_by` property, exactly one `having` clause whose aggregate is the select's aggregate, an operator describing one contiguous range (`EQUAL`, `GREATER_THAN[_OR_EQUALS]`, `LESS_THAN[_OR_EQUALS]`, `BETWEEN*`; `NOT_EQUAL` / `IN` rejected), a `limit` in `1 ..= 100`, an optional `order_by` naming the same aggregate (walk direction; ascending by default), and no `offset` / `start_at` / `start_after`, on an index declaring the matching ranked axis → having-range executor, answered in `ResultData.ranked`. `where` follows the same rule as ranked mode: none on a single-property ranked index; exactly one `EQUAL` pin per leading index property on a compound ranked index. + * - exactly one `group_by` property, exactly one `having` clause whose aggregate is the select's aggregate, an operator describing one contiguous range (`EQUAL`, `GREATER_THAN[_OR_EQUALS]`, `LESS_THAN[_OR_EQUALS]`, `BETWEEN*`; `NOT_EQUAL` / `IN` rejected), a `limit` in `1 ..= 100`, an optional `order_by` naming the same aggregate (walk direction; ascending by default), and no `offset` / `start_at` / `start_after`, on an index declaring the matching ranked axis → having-range executor, answered in `ResultData.ranked`. `where` follows the same rule as ranked mode: none on a single-property ranked index; exactly one pin per leading index property on a compound ranked index, at most one of them an `IN` (2..=10 distinct elements) that fans the bound out across prefix branches and merges, entries carrying `in_key`. * - no offset or cursor pagination: a page cut at `limit` continues only by tightening the bound past the last *distinct* aggregate value seen; a cut inside a tie (several groups sharing the boundary aggregate) cannot be continued, so size `limit` above the widest expected tie. * * **Rejected shapes** (return `Unsupported`): @@ -3717,6 +3717,7 @@ typedef GPB_ENUM(GetDocumentsResponse_GetDocumentsResponseV1_RankedEntry_FieldNu GetDocumentsResponse_GetDocumentsResponseV1_RankedEntry_FieldNumber_Count = 2, GetDocumentsResponse_GetDocumentsResponseV1_RankedEntry_FieldNumber_Sum = 3, GetDocumentsResponse_GetDocumentsResponseV1_RankedEntry_FieldNumber_Avg = 4, + GetDocumentsResponse_GetDocumentsResponseV1_RankedEntry_FieldNumber_InKey = 5, }; typedef GPB_ENUM(GetDocumentsResponse_GetDocumentsResponseV1_RankedEntry_Value_OneOfCase) { @@ -3801,6 +3802,19 @@ GPB_FINAL @interface GetDocumentsResponse_GetDocumentsResponseV1_RankedEntry : G **/ @property(nonatomic, readwrite) double avg; +/** + * The prefix branch this entry came from, set **only** on an + * `IN`-pinned request (see the supported-shape table): the + * encoded index-key bytes of the `IN` property's pinned value — + * empty bytes for the `null` (absent-value) branch. Absent on + * single-prefix responses. The same group key can legally appear + * under two prefixes, so `(in_key, key)` is the entry's identity + * on a merged page, exactly as on `CountEntry`. + **/ +@property(nonatomic, readwrite, copy, null_resettable) NSData *inKey; +/** Test to see if @c inKey has been set. */ +@property(nonatomic, readwrite) BOOL hasInKey; + @end /** diff --git a/packages/dapi-grpc/clients/platform/v0/objective-c/Platform.pbobjc.m b/packages/dapi-grpc/clients/platform/v0/objective-c/Platform.pbobjc.m index dd6f5cb79a7..1fc071948a7 100644 --- a/packages/dapi-grpc/clients/platform/v0/objective-c/Platform.pbobjc.m +++ b/packages/dapi-grpc/clients/platform/v0/objective-c/Platform.pbobjc.m @@ -7164,10 +7164,12 @@ @implementation GetDocumentsResponse_GetDocumentsResponseV1_RankedEntry @dynamic count; @dynamic sum; @dynamic avg; +@dynamic hasInKey, inKey; typedef struct GetDocumentsResponse_GetDocumentsResponseV1_RankedEntry__storage_ { uint32_t _has_storage_[2]; NSData *key; + NSData *inKey; uint64_t count; int64_t sum; double avg; @@ -7215,6 +7217,15 @@ + (GPBDescriptor *)descriptor { .flags = GPBFieldOptional, .dataType = GPBDataTypeDouble, }, + { + .name = "inKey", + .dataTypeSpecific.clazz = Nil, + .number = GetDocumentsResponse_GetDocumentsResponseV1_RankedEntry_FieldNumber_InKey, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GetDocumentsResponse_GetDocumentsResponseV1_RankedEntry__storage_, inKey), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBytes, + }, }; GPBDescriptor *localDescriptor = [GPBDescriptor allocDescriptorForClass:[GetDocumentsResponse_GetDocumentsResponseV1_RankedEntry class] diff --git a/packages/dapi-grpc/clients/platform/v0/python/platform_pb2.py b/packages/dapi-grpc/clients/platform/v0/python/platform_pb2.py index 004bbbe6f33..3b9126ce2e0 100644 --- a/packages/dapi-grpc/clients/platform/v0/python/platform_pb2.py +++ b/packages/dapi-grpc/clients/platform/v0/python/platform_pb2.py @@ -23,7 +23,7 @@ syntax='proto3', serialized_options=None, create_key=_descriptor._internal_create_key, - serialized_pb=b'\n\x0eplatform.proto\x12\x19org.dash.platform.dapi.v0\x1a\x1egoogle/protobuf/wrappers.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\x81\x01\n\x05Proof\x12\x15\n\rgrovedb_proof\x18\x01 \x01(\x0c\x12\x13\n\x0bquorum_hash\x18\x02 \x01(\x0c\x12\x11\n\tsignature\x18\x03 \x01(\x0c\x12\r\n\x05round\x18\x04 \x01(\r\x12\x15\n\rblock_id_hash\x18\x05 \x01(\x0c\x12\x13\n\x0bquorum_type\x18\x06 \x01(\r\"\x98\x01\n\x10ResponseMetadata\x12\x12\n\x06height\x18\x01 \x01(\x04\x42\x02\x30\x01\x12 \n\x18\x63ore_chain_locked_height\x18\x02 \x01(\r\x12\r\n\x05\x65poch\x18\x03 \x01(\r\x12\x13\n\x07time_ms\x18\x04 \x01(\x04\x42\x02\x30\x01\x12\x18\n\x10protocol_version\x18\x05 \x01(\r\x12\x10\n\x08\x63hain_id\x18\x06 \x01(\t\"L\n\x1dStateTransitionBroadcastError\x12\x0c\n\x04\x63ode\x18\x01 \x01(\r\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\";\n\x1f\x42roadcastStateTransitionRequest\x12\x18\n\x10state_transition\x18\x01 \x01(\x0c\"\"\n BroadcastStateTransitionResponse\"\xa4\x01\n\x12GetIdentityRequest\x12P\n\x02v0\x18\x01 \x01(\x0b\x32\x42.org.dash.platform.dapi.v0.GetIdentityRequest.GetIdentityRequestV0H\x00\x1a\x31\n\x14GetIdentityRequestV0\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xc1\x01\n\x17GetIdentityNonceRequest\x12Z\n\x02v0\x18\x01 \x01(\x0b\x32L.org.dash.platform.dapi.v0.GetIdentityNonceRequest.GetIdentityNonceRequestV0H\x00\x1a?\n\x19GetIdentityNonceRequestV0\x12\x13\n\x0bidentity_id\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xf6\x01\n\x1fGetIdentityContractNonceRequest\x12j\n\x02v0\x18\x01 \x01(\x0b\x32\\.org.dash.platform.dapi.v0.GetIdentityContractNonceRequest.GetIdentityContractNonceRequestV0H\x00\x1a\\\n!GetIdentityContractNonceRequestV0\x12\x13\n\x0bidentity_id\x18\x01 \x01(\x0c\x12\x13\n\x0b\x63ontract_id\x18\x02 \x01(\x0c\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\t\n\x07version\"\xc0\x01\n\x19GetIdentityBalanceRequest\x12^\n\x02v0\x18\x01 \x01(\x0b\x32P.org.dash.platform.dapi.v0.GetIdentityBalanceRequest.GetIdentityBalanceRequestV0H\x00\x1a\x38\n\x1bGetIdentityBalanceRequestV0\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xec\x01\n$GetIdentityBalanceAndRevisionRequest\x12t\n\x02v0\x18\x01 \x01(\x0b\x32\x66.org.dash.platform.dapi.v0.GetIdentityBalanceAndRevisionRequest.GetIdentityBalanceAndRevisionRequestV0H\x00\x1a\x43\n&GetIdentityBalanceAndRevisionRequestV0\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\x9e\x02\n\x13GetIdentityResponse\x12R\n\x02v0\x18\x01 \x01(\x0b\x32\x44.org.dash.platform.dapi.v0.GetIdentityResponse.GetIdentityResponseV0H\x00\x1a\xa7\x01\n\x15GetIdentityResponseV0\x12\x12\n\x08identity\x18\x01 \x01(\x0cH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xbc\x02\n\x18GetIdentityNonceResponse\x12\\\n\x02v0\x18\x01 \x01(\x0b\x32N.org.dash.platform.dapi.v0.GetIdentityNonceResponse.GetIdentityNonceResponseV0H\x00\x1a\xb6\x01\n\x1aGetIdentityNonceResponseV0\x12\x1c\n\x0eidentity_nonce\x18\x01 \x01(\x04\x42\x02\x30\x01H\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xe5\x02\n GetIdentityContractNonceResponse\x12l\n\x02v0\x18\x01 \x01(\x0b\x32^.org.dash.platform.dapi.v0.GetIdentityContractNonceResponse.GetIdentityContractNonceResponseV0H\x00\x1a\xc7\x01\n\"GetIdentityContractNonceResponseV0\x12%\n\x17identity_contract_nonce\x18\x01 \x01(\x04\x42\x02\x30\x01H\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xbd\x02\n\x1aGetIdentityBalanceResponse\x12`\n\x02v0\x18\x01 \x01(\x0b\x32R.org.dash.platform.dapi.v0.GetIdentityBalanceResponse.GetIdentityBalanceResponseV0H\x00\x1a\xb1\x01\n\x1cGetIdentityBalanceResponseV0\x12\x15\n\x07\x62\x61lance\x18\x01 \x01(\x04\x42\x02\x30\x01H\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xb1\x04\n%GetIdentityBalanceAndRevisionResponse\x12v\n\x02v0\x18\x01 \x01(\x0b\x32h.org.dash.platform.dapi.v0.GetIdentityBalanceAndRevisionResponse.GetIdentityBalanceAndRevisionResponseV0H\x00\x1a\x84\x03\n\'GetIdentityBalanceAndRevisionResponseV0\x12\x9b\x01\n\x14\x62\x61lance_and_revision\x18\x01 \x01(\x0b\x32{.org.dash.platform.dapi.v0.GetIdentityBalanceAndRevisionResponse.GetIdentityBalanceAndRevisionResponseV0.BalanceAndRevisionH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a?\n\x12\x42\x61lanceAndRevision\x12\x13\n\x07\x62\x61lance\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\x14\n\x08revision\x18\x02 \x01(\x04\x42\x02\x30\x01\x42\x08\n\x06resultB\t\n\x07version\"\xd1\x01\n\x0eKeyRequestType\x12\x36\n\x08\x61ll_keys\x18\x01 \x01(\x0b\x32\".org.dash.platform.dapi.v0.AllKeysH\x00\x12@\n\rspecific_keys\x18\x02 \x01(\x0b\x32\'.org.dash.platform.dapi.v0.SpecificKeysH\x00\x12:\n\nsearch_key\x18\x03 \x01(\x0b\x32$.org.dash.platform.dapi.v0.SearchKeyH\x00\x42\t\n\x07request\"\t\n\x07\x41llKeys\"\x1f\n\x0cSpecificKeys\x12\x0f\n\x07key_ids\x18\x01 \x03(\r\"\xb6\x01\n\tSearchKey\x12I\n\x0bpurpose_map\x18\x01 \x03(\x0b\x32\x34.org.dash.platform.dapi.v0.SearchKey.PurposeMapEntry\x1a^\n\x0fPurposeMapEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12:\n\x05value\x18\x02 \x01(\x0b\x32+.org.dash.platform.dapi.v0.SecurityLevelMap:\x02\x38\x01\"\xbf\x02\n\x10SecurityLevelMap\x12]\n\x12security_level_map\x18\x01 \x03(\x0b\x32\x41.org.dash.platform.dapi.v0.SecurityLevelMap.SecurityLevelMapEntry\x1aw\n\x15SecurityLevelMapEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12M\n\x05value\x18\x02 \x01(\x0e\x32>.org.dash.platform.dapi.v0.SecurityLevelMap.KeyKindRequestType:\x02\x38\x01\"S\n\x12KeyKindRequestType\x12\x1f\n\x1b\x43URRENT_KEY_OF_KIND_REQUEST\x10\x00\x12\x1c\n\x18\x41LL_KEYS_OF_KIND_REQUEST\x10\x01\"\xda\x02\n\x16GetIdentityKeysRequest\x12X\n\x02v0\x18\x01 \x01(\x0b\x32J.org.dash.platform.dapi.v0.GetIdentityKeysRequest.GetIdentityKeysRequestV0H\x00\x1a\xda\x01\n\x18GetIdentityKeysRequestV0\x12\x13\n\x0bidentity_id\x18\x01 \x01(\x0c\x12?\n\x0crequest_type\x18\x02 \x01(\x0b\x32).org.dash.platform.dapi.v0.KeyRequestType\x12+\n\x05limit\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12,\n\x06offset\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12\r\n\x05prove\x18\x05 \x01(\x08\x42\t\n\x07version\"\x99\x03\n\x17GetIdentityKeysResponse\x12Z\n\x02v0\x18\x01 \x01(\x0b\x32L.org.dash.platform.dapi.v0.GetIdentityKeysResponse.GetIdentityKeysResponseV0H\x00\x1a\x96\x02\n\x19GetIdentityKeysResponseV0\x12\x61\n\x04keys\x18\x01 \x01(\x0b\x32Q.org.dash.platform.dapi.v0.GetIdentityKeysResponse.GetIdentityKeysResponseV0.KeysH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x1a\n\x04Keys\x12\x12\n\nkeys_bytes\x18\x01 \x03(\x0c\x42\x08\n\x06resultB\t\n\x07version\"\xef\x02\n GetIdentitiesContractKeysRequest\x12l\n\x02v0\x18\x01 \x01(\x0b\x32^.org.dash.platform.dapi.v0.GetIdentitiesContractKeysRequest.GetIdentitiesContractKeysRequestV0H\x00\x1a\xd1\x01\n\"GetIdentitiesContractKeysRequestV0\x12\x16\n\x0eidentities_ids\x18\x01 \x03(\x0c\x12\x13\n\x0b\x63ontract_id\x18\x02 \x01(\x0c\x12\x1f\n\x12\x64ocument_type_name\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x37\n\x08purposes\x18\x04 \x03(\x0e\x32%.org.dash.platform.dapi.v0.KeyPurpose\x12\r\n\x05prove\x18\x05 \x01(\x08\x42\x15\n\x13_document_type_nameB\t\n\x07version\"\xdf\x06\n!GetIdentitiesContractKeysResponse\x12n\n\x02v0\x18\x01 \x01(\x0b\x32`.org.dash.platform.dapi.v0.GetIdentitiesContractKeysResponse.GetIdentitiesContractKeysResponseV0H\x00\x1a\xbe\x05\n#GetIdentitiesContractKeysResponseV0\x12\x8a\x01\n\x0fidentities_keys\x18\x01 \x01(\x0b\x32o.org.dash.platform.dapi.v0.GetIdentitiesContractKeysResponse.GetIdentitiesContractKeysResponseV0.IdentitiesKeysH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1aY\n\x0bPurposeKeys\x12\x36\n\x07purpose\x18\x01 \x01(\x0e\x32%.org.dash.platform.dapi.v0.KeyPurpose\x12\x12\n\nkeys_bytes\x18\x02 \x03(\x0c\x1a\x9f\x01\n\x0cIdentityKeys\x12\x13\n\x0bidentity_id\x18\x01 \x01(\x0c\x12z\n\x04keys\x18\x02 \x03(\x0b\x32l.org.dash.platform.dapi.v0.GetIdentitiesContractKeysResponse.GetIdentitiesContractKeysResponseV0.PurposeKeys\x1a\x90\x01\n\x0eIdentitiesKeys\x12~\n\x07\x65ntries\x18\x01 \x03(\x0b\x32m.org.dash.platform.dapi.v0.GetIdentitiesContractKeysResponse.GetIdentitiesContractKeysResponseV0.IdentityKeysB\x08\n\x06resultB\t\n\x07version\"\xa4\x02\n*GetEvonodesProposedEpochBlocksByIdsRequest\x12\x80\x01\n\x02v0\x18\x01 \x01(\x0b\x32r.org.dash.platform.dapi.v0.GetEvonodesProposedEpochBlocksByIdsRequest.GetEvonodesProposedEpochBlocksByIdsRequestV0H\x00\x1ah\n,GetEvonodesProposedEpochBlocksByIdsRequestV0\x12\x12\n\x05\x65poch\x18\x01 \x01(\rH\x00\x88\x01\x01\x12\x0b\n\x03ids\x18\x02 \x03(\x0c\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\x08\n\x06_epochB\t\n\x07version\"\x92\x06\n&GetEvonodesProposedEpochBlocksResponse\x12x\n\x02v0\x18\x01 \x01(\x0b\x32j.org.dash.platform.dapi.v0.GetEvonodesProposedEpochBlocksResponse.GetEvonodesProposedEpochBlocksResponseV0H\x00\x1a\xe2\x04\n(GetEvonodesProposedEpochBlocksResponseV0\x12\xb1\x01\n#evonodes_proposed_block_counts_info\x18\x01 \x01(\x0b\x32\x81\x01.org.dash.platform.dapi.v0.GetEvonodesProposedEpochBlocksResponse.GetEvonodesProposedEpochBlocksResponseV0.EvonodesProposedBlocksH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a?\n\x15\x45vonodeProposedBlocks\x12\x13\n\x0bpro_tx_hash\x18\x01 \x01(\x0c\x12\x11\n\x05\x63ount\x18\x02 \x01(\x04\x42\x02\x30\x01\x1a\xc4\x01\n\x16\x45vonodesProposedBlocks\x12\xa9\x01\n\x1e\x65vonodes_proposed_block_counts\x18\x01 \x03(\x0b\x32\x80\x01.org.dash.platform.dapi.v0.GetEvonodesProposedEpochBlocksResponse.GetEvonodesProposedEpochBlocksResponseV0.EvonodeProposedBlocksB\x08\n\x06resultB\t\n\x07version\"\xf2\x02\n,GetEvonodesProposedEpochBlocksByRangeRequest\x12\x84\x01\n\x02v0\x18\x01 \x01(\x0b\x32v.org.dash.platform.dapi.v0.GetEvonodesProposedEpochBlocksByRangeRequest.GetEvonodesProposedEpochBlocksByRangeRequestV0H\x00\x1a\xaf\x01\n.GetEvonodesProposedEpochBlocksByRangeRequestV0\x12\x12\n\x05\x65poch\x18\x01 \x01(\rH\x01\x88\x01\x01\x12\x12\n\x05limit\x18\x02 \x01(\rH\x02\x88\x01\x01\x12\x15\n\x0bstart_after\x18\x03 \x01(\x0cH\x00\x12\x12\n\x08start_at\x18\x04 \x01(\x0cH\x00\x12\r\n\x05prove\x18\x05 \x01(\x08\x42\x07\n\x05startB\x08\n\x06_epochB\x08\n\x06_limitB\t\n\x07version\"\xcd\x01\n\x1cGetIdentitiesBalancesRequest\x12\x64\n\x02v0\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetIdentitiesBalancesRequest.GetIdentitiesBalancesRequestV0H\x00\x1a<\n\x1eGetIdentitiesBalancesRequestV0\x12\x0b\n\x03ids\x18\x01 \x03(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\x9f\x05\n\x1dGetIdentitiesBalancesResponse\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetIdentitiesBalancesResponse.GetIdentitiesBalancesResponseV0H\x00\x1a\x8a\x04\n\x1fGetIdentitiesBalancesResponseV0\x12\x8a\x01\n\x13identities_balances\x18\x01 \x01(\x0b\x32k.org.dash.platform.dapi.v0.GetIdentitiesBalancesResponse.GetIdentitiesBalancesResponseV0.IdentitiesBalancesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1aL\n\x0fIdentityBalance\x12\x13\n\x0bidentity_id\x18\x01 \x01(\x0c\x12\x18\n\x07\x62\x61lance\x18\x02 \x01(\x04\x42\x02\x30\x01H\x00\x88\x01\x01\x42\n\n\x08_balance\x1a\x8f\x01\n\x12IdentitiesBalances\x12y\n\x07\x65ntries\x18\x01 \x03(\x0b\x32h.org.dash.platform.dapi.v0.GetIdentitiesBalancesResponse.GetIdentitiesBalancesResponseV0.IdentityBalanceB\x08\n\x06resultB\t\n\x07version\"\xb4\x01\n\x16GetDataContractRequest\x12X\n\x02v0\x18\x01 \x01(\x0b\x32J.org.dash.platform.dapi.v0.GetDataContractRequest.GetDataContractRequestV0H\x00\x1a\x35\n\x18GetDataContractRequestV0\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xb3\x02\n\x17GetDataContractResponse\x12Z\n\x02v0\x18\x01 \x01(\x0b\x32L.org.dash.platform.dapi.v0.GetDataContractResponse.GetDataContractResponseV0H\x00\x1a\xb0\x01\n\x19GetDataContractResponseV0\x12\x17\n\rdata_contract\x18\x01 \x01(\x0cH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xb9\x01\n\x17GetDataContractsRequest\x12Z\n\x02v0\x18\x01 \x01(\x0b\x32L.org.dash.platform.dapi.v0.GetDataContractsRequest.GetDataContractsRequestV0H\x00\x1a\x37\n\x19GetDataContractsRequestV0\x12\x0b\n\x03ids\x18\x01 \x03(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xcf\x04\n\x18GetDataContractsResponse\x12\\\n\x02v0\x18\x01 \x01(\x0b\x32N.org.dash.platform.dapi.v0.GetDataContractsResponse.GetDataContractsResponseV0H\x00\x1a[\n\x11\x44\x61taContractEntry\x12\x12\n\nidentifier\x18\x01 \x01(\x0c\x12\x32\n\rdata_contract\x18\x02 \x01(\x0b\x32\x1b.google.protobuf.BytesValue\x1au\n\rDataContracts\x12\x64\n\x15\x64\x61ta_contract_entries\x18\x01 \x03(\x0b\x32\x45.org.dash.platform.dapi.v0.GetDataContractsResponse.DataContractEntry\x1a\xf5\x01\n\x1aGetDataContractsResponseV0\x12[\n\x0e\x64\x61ta_contracts\x18\x01 \x01(\x0b\x32\x41.org.dash.platform.dapi.v0.GetDataContractsResponse.DataContractsH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xc5\x02\n\x1dGetDataContractHistoryRequest\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetDataContractHistoryRequest.GetDataContractHistoryRequestV0H\x00\x1a\xb0\x01\n\x1fGetDataContractHistoryRequestV0\x12\n\n\x02id\x18\x01 \x01(\x0c\x12+\n\x05limit\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12,\n\x06offset\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12\x17\n\x0bstart_at_ms\x18\x04 \x01(\x04\x42\x02\x30\x01\x12\r\n\x05prove\x18\x05 \x01(\x08\x42\t\n\x07version\"\xb2\x05\n\x1eGetDataContractHistoryResponse\x12h\n\x02v0\x18\x01 \x01(\x0b\x32Z.org.dash.platform.dapi.v0.GetDataContractHistoryResponse.GetDataContractHistoryResponseV0H\x00\x1a\x9a\x04\n GetDataContractHistoryResponseV0\x12\x8f\x01\n\x15\x64\x61ta_contract_history\x18\x01 \x01(\x0b\x32n.org.dash.platform.dapi.v0.GetDataContractHistoryResponse.GetDataContractHistoryResponseV0.DataContractHistoryH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a;\n\x18\x44\x61taContractHistoryEntry\x12\x10\n\x04\x64\x61te\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\r\n\x05value\x18\x02 \x01(\x0c\x1a\xaa\x01\n\x13\x44\x61taContractHistory\x12\x92\x01\n\x15\x64\x61ta_contract_entries\x18\x01 \x03(\x0b\x32s.org.dash.platform.dapi.v0.GetDataContractHistoryResponse.GetDataContractHistoryResponseV0.DataContractHistoryEntryB\x08\n\x06resultB\t\n\x07version\"\xde\x15\n\x13GetDocumentsRequest\x12R\n\x02v0\x18\x01 \x01(\x0b\x32\x44.org.dash.platform.dapi.v0.GetDocumentsRequest.GetDocumentsRequestV0H\x00\x12R\n\x02v1\x18\x02 \x01(\x0b\x32\x44.org.dash.platform.dapi.v0.GetDocumentsRequest.GetDocumentsRequestV1H\x00\x1a\xfe\x02\n\x12\x44ocumentFieldValue\x12\x14\n\nbool_value\x18\x01 \x01(\x08H\x00\x12\x19\n\x0bint64_value\x18\x02 \x01(\x12\x42\x02\x30\x01H\x00\x12\x1a\n\x0cuint64_value\x18\x03 \x01(\x04\x42\x02\x30\x01H\x00\x12\x16\n\x0c\x64ouble_value\x18\x04 \x01(\x01H\x00\x12\x0e\n\x04text\x18\x05 \x01(\tH\x00\x12\x15\n\x0b\x62ytes_value\x18\x06 \x01(\x0cH\x00\x12[\n\x04list\x18\x07 \x01(\x0b\x32K.org.dash.platform.dapi.v0.GetDocumentsRequest.DocumentFieldValue.ValueListH\x00\x12\x14\n\nnull_value\x18\x08 \x01(\x08H\x00\x1a^\n\tValueList\x12Q\n\x06values\x18\x01 \x03(\x0b\x32\x41.org.dash.platform.dapi.v0.GetDocumentsRequest.DocumentFieldValueB\t\n\x07variant\x1a\xbe\x01\n\x0bWhereClause\x12\r\n\x05\x66ield\x18\x01 \x01(\t\x12N\n\x08operator\x18\x02 \x01(\x0e\x32<.org.dash.platform.dapi.v0.GetDocumentsRequest.WhereOperator\x12P\n\x05value\x18\x03 \x01(\x0b\x32\x41.org.dash.platform.dapi.v0.GetDocumentsRequest.DocumentFieldValue\x1a\xa4\x01\n\x0fHavingAggregate\x12Y\n\x08\x66unction\x18\x01 \x01(\x0e\x32G.org.dash.platform.dapi.v0.GetDocumentsRequest.HavingAggregate.Function\x12\r\n\x05\x66ield\x18\x02 \x01(\t\"\'\n\x08\x46unction\x12\t\n\x05\x43OUNT\x10\x00\x12\x07\n\x03SUM\x10\x01\x12\x07\n\x03\x41VG\x10\x02\x1a\xf9\x03\n\x0cHavingClause\x12Q\n\taggregate\x18\x01 \x01(\x0b\x32>.org.dash.platform.dapi.v0.GetDocumentsRequest.HavingAggregate\x12V\n\x08operator\x18\x02 \x01(\x0e\x32\x44.org.dash.platform.dapi.v0.GetDocumentsRequest.HavingClause.Operator\x12R\n\x05value\x18\x03 \x01(\x0b\x32\x41.org.dash.platform.dapi.v0.GetDocumentsRequest.DocumentFieldValueH\x00\"\xe0\x01\n\x08Operator\x12\t\n\x05\x45QUAL\x10\x00\x12\r\n\tNOT_EQUAL\x10\x01\x12\x10\n\x0cGREATER_THAN\x10\x02\x12\x1a\n\x16GREATER_THAN_OR_EQUALS\x10\x03\x12\r\n\tLESS_THAN\x10\x04\x12\x17\n\x13LESS_THAN_OR_EQUALS\x10\x05\x12\x0b\n\x07\x42\x45TWEEN\x10\x06\x12\x1a\n\x16\x42\x45TWEEN_EXCLUDE_BOUNDS\x10\x07\x12\x18\n\x14\x42\x45TWEEN_EXCLUDE_LEFT\x10\x08\x12\x19\n\x15\x42\x45TWEEN_EXCLUDE_RIGHT\x10\t\x12\x06\n\x02IN\x10\nB\x07\n\x05right\x1a\x90\x01\n\x0bOrderClause\x12\x0f\n\x05\x66ield\x18\x01 \x01(\tH\x00\x12S\n\taggregate\x18\x03 \x01(\x0b\x32>.org.dash.platform.dapi.v0.GetDocumentsRequest.HavingAggregateH\x00\x12\x11\n\tascending\x18\x02 \x01(\x08\x42\x08\n\x06target\x1a\xbb\x01\n\x15GetDocumentsRequestV0\x12\x18\n\x10\x64\x61ta_contract_id\x18\x01 \x01(\x0c\x12\x15\n\rdocument_type\x18\x02 \x01(\t\x12\r\n\x05where\x18\x03 \x01(\x0c\x12\x10\n\x08order_by\x18\x04 \x01(\x0c\x12\r\n\x05limit\x18\x05 \x01(\r\x12\x15\n\x0bstart_after\x18\x06 \x01(\x0cH\x00\x12\x12\n\x08start_at\x18\x07 \x01(\x0cH\x00\x12\r\n\x05prove\x18\x08 \x01(\x08\x42\x07\n\x05start\x1a\xf3\x05\n\x15GetDocumentsRequestV1\x12\x18\n\x10\x64\x61ta_contract_id\x18\x01 \x01(\x0c\x12\x15\n\rdocument_type\x18\x02 \x01(\t\x12Q\n\rwhere_clauses\x18\x03 \x03(\x0b\x32:.org.dash.platform.dapi.v0.GetDocumentsRequest.WhereClause\x12L\n\x08order_by\x18\x04 \x03(\x0b\x32:.org.dash.platform.dapi.v0.GetDocumentsRequest.OrderClause\x12\x12\n\x05limit\x18\x05 \x01(\rH\x01\x88\x01\x01\x12\x15\n\x0bstart_after\x18\x06 \x01(\x0cH\x00\x12\x12\n\x08start_at\x18\x07 \x01(\x0cH\x00\x12\r\n\x05prove\x18\x08 \x01(\x08\x12\\\n\x07selects\x18\t \x03(\x0b\x32K.org.dash.platform.dapi.v0.GetDocumentsRequest.GetDocumentsRequestV1.Select\x12\x10\n\x08group_by\x18\n \x03(\t\x12K\n\x06having\x18\x0b \x03(\x0b\x32;.org.dash.platform.dapi.v0.GetDocumentsRequest.HavingClause\x12\x13\n\x06offset\x18\x0c \x01(\rH\x02\x88\x01\x01\x1a\xc9\x01\n\x06Select\x12\x66\n\x08\x66unction\x18\x01 \x01(\x0e\x32T.org.dash.platform.dapi.v0.GetDocumentsRequest.GetDocumentsRequestV1.Select.Function\x12\r\n\x05\x66ield\x18\x02 \x01(\t\"H\n\x08\x46unction\x12\r\n\tDOCUMENTS\x10\x00\x12\t\n\x05\x43OUNT\x10\x01\x12\x07\n\x03SUM\x10\x02\x12\x07\n\x03\x41VG\x10\x03\x12\x07\n\x03MIN\x10\x04\x12\x07\n\x03MAX\x10\x05\x42\x07\n\x05startB\x08\n\x06_limitB\t\n\x07_offset\"\xe7\x01\n\rWhereOperator\x12\t\n\x05\x45QUAL\x10\x00\x12\x10\n\x0cGREATER_THAN\x10\x01\x12\x1a\n\x16GREATER_THAN_OR_EQUALS\x10\x02\x12\r\n\tLESS_THAN\x10\x03\x12\x17\n\x13LESS_THAN_OR_EQUALS\x10\x04\x12\x0b\n\x07\x42\x45TWEEN\x10\x05\x12\x1a\n\x16\x42\x45TWEEN_EXCLUDE_BOUNDS\x10\x06\x12\x18\n\x14\x42\x45TWEEN_EXCLUDE_LEFT\x10\x07\x12\x19\n\x15\x42\x45TWEEN_EXCLUDE_RIGHT\x10\x08\x12\x06\n\x02IN\x10\t\x12\x0f\n\x0bSTARTS_WITH\x10\nB\t\n\x07version\"\xe7\x15\n\x14GetDocumentsResponse\x12T\n\x02v0\x18\x01 \x01(\x0b\x32\x46.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV0H\x00\x12T\n\x02v1\x18\x02 \x01(\x0b\x32\x46.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1H\x00\x1a\x9b\x02\n\x16GetDocumentsResponseV0\x12\x65\n\tdocuments\x18\x01 \x01(\x0b\x32P.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV0.DocumentsH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x1e\n\tDocuments\x12\x11\n\tdocuments\x18\x01 \x03(\x0c\x42\x08\n\x06result\x1a\xf9\x11\n\x16GetDocumentsResponseV1\x12\x61\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32Q.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.ResultDataH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x1e\n\tDocuments\x12\x11\n\tdocuments\x18\x01 \x03(\x0c\x1aL\n\nCountEntry\x12\x13\n\x06in_key\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x12\x0b\n\x03key\x18\x02 \x01(\x0c\x12\x11\n\x05\x63ount\x18\x03 \x01(\x04\x42\x02\x30\x01\x42\t\n\x07_in_key\x1ar\n\x0c\x43ountEntries\x12\x62\n\x07\x65ntries\x18\x01 \x03(\x0b\x32Q.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.CountEntry\x1a\xa0\x01\n\x0c\x43ountResults\x12\x1d\n\x0f\x61ggregate_count\x18\x01 \x01(\x04\x42\x02\x30\x01H\x00\x12\x66\n\x07\x65ntries\x18\x02 \x01(\x0b\x32S.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.CountEntriesH\x00\x42\t\n\x07variant\x1aH\n\x08SumEntry\x12\x13\n\x06in_key\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x12\x0b\n\x03key\x18\x02 \x01(\x0c\x12\x0f\n\x03sum\x18\x03 \x01(\x12\x42\x02\x30\x01\x42\t\n\x07_in_key\x1an\n\nSumEntries\x12`\n\x07\x65ntries\x18\x01 \x03(\x0b\x32O.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.SumEntry\x1a\x9a\x01\n\nSumResults\x12\x1b\n\raggregate_sum\x18\x01 \x01(\x12\x42\x02\x30\x01H\x00\x12\x64\n\x07\x65ntries\x18\x02 \x01(\x0b\x32Q.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.SumEntriesH\x00\x42\t\n\x07variant\x1a_\n\x0c\x41verageEntry\x12\x13\n\x06in_key\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x12\x0b\n\x03key\x18\x02 \x01(\x0c\x12\x11\n\x05\x63ount\x18\x03 \x01(\x04\x42\x02\x30\x01\x12\x0f\n\x03sum\x18\x04 \x01(\x12\x42\x02\x30\x01\x42\t\n\x07_in_key\x1av\n\x0e\x41verageEntries\x12\x64\n\x07\x65ntries\x18\x01 \x03(\x0b\x32S.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.AverageEntry\x1a\x36\n\x10\x41verageAggregate\x12\x11\n\x05\x63ount\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\x0f\n\x03sum\x18\x02 \x01(\x12\x42\x02\x30\x01\x1a\xfb\x01\n\x0e\x41verageResults\x12t\n\x11\x61ggregate_average\x18\x01 \x01(\x0b\x32W.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.AverageAggregateH\x00\x12h\n\x07\x65ntries\x18\x02 \x01(\x0b\x32U.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.AverageEntriesH\x00\x42\t\n\x07variant\x1aZ\n\x0bRankedEntry\x12\x0b\n\x03key\x18\x01 \x01(\x0c\x12\x13\n\x05\x63ount\x18\x02 \x01(\x04\x42\x02\x30\x01H\x00\x12\x11\n\x03sum\x18\x03 \x01(\x12\x42\x02\x30\x01H\x00\x12\r\n\x03\x61vg\x18\x04 \x01(\x01H\x00\x42\x07\n\x05value\x1a\x9a\x01\n\rRankedEntries\x12\x63\n\x07\x65ntries\x18\x01 \x03(\x0b\x32R.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry\x12\x18\n\x07skipped\x18\x02 \x01(\x04\x42\x02\x30\x01H\x00\x88\x01\x01\x42\n\n\x08_skipped\x1a\x9b\x04\n\nResultData\x12\x65\n\tdocuments\x18\x01 \x01(\x0b\x32P.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.DocumentsH\x00\x12\x65\n\x06\x63ounts\x18\x02 \x01(\x0b\x32S.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.CountResultsH\x00\x12\x61\n\x04sums\x18\x03 \x01(\x0b\x32Q.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.SumResultsH\x00\x12i\n\x08\x61verages\x18\x04 \x01(\x0b\x32U.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.AverageResultsH\x00\x12\x66\n\x06ranked\x18\x05 \x01(\x0b\x32T.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntriesH\x00\x42\t\n\x07variantB\x08\n\x06resultB\t\n\x07version\"\xf4\x02\n\x19GetDocumentHistoryRequest\x12^\n\x02v0\x18\x01 \x01(\x0b\x32P.org.dash.platform.dapi.v0.GetDocumentHistoryRequest.GetDocumentHistoryRequestV0H\x00\x1a\xeb\x01\n\x1bGetDocumentHistoryRequestV0\x12\x18\n\x10\x64\x61ta_contract_id\x18\x01 \x01(\x0c\x12\x1a\n\x12\x64ocument_type_name\x18\x02 \x01(\t\x12\x13\n\x0b\x64ocument_id\x18\x03 \x01(\x0c\x12+\n\x05limit\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12,\n\x06offset\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12\x17\n\x0bstart_at_ms\x18\x06 \x01(\x04\x42\x02\x30\x01\x12\r\n\x05prove\x18\x07 \x01(\x08\x42\t\n\x07version\"\xf7\x04\n\x1aGetDocumentHistoryResponse\x12`\n\x02v0\x18\x01 \x01(\x0b\x32R.org.dash.platform.dapi.v0.GetDocumentHistoryResponse.GetDocumentHistoryResponseV0H\x00\x1a\xeb\x03\n\x1cGetDocumentHistoryResponseV0\x12~\n\x10\x64ocument_history\x18\x01 \x01(\x0b\x32\x62.org.dash.platform.dapi.v0.GetDocumentHistoryResponse.GetDocumentHistoryResponseV0.DocumentHistoryH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x37\n\x14\x44ocumentHistoryEntry\x12\x10\n\x04\x64\x61te\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\r\n\x05value\x18\x02 \x01(\x0c\x1a\x95\x01\n\x0f\x44ocumentHistory\x12\x81\x01\n\x10\x64ocument_entries\x18\x01 \x03(\x0b\x32g.org.dash.platform.dapi.v0.GetDocumentHistoryResponse.GetDocumentHistoryResponseV0.DocumentHistoryEntryB\x08\n\x06resultB\t\n\x07version\"\xed\x01\n!GetIdentityByPublicKeyHashRequest\x12n\n\x02v0\x18\x01 \x01(\x0b\x32`.org.dash.platform.dapi.v0.GetIdentityByPublicKeyHashRequest.GetIdentityByPublicKeyHashRequestV0H\x00\x1aM\n#GetIdentityByPublicKeyHashRequestV0\x12\x17\n\x0fpublic_key_hash\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xda\x02\n\"GetIdentityByPublicKeyHashResponse\x12p\n\x02v0\x18\x01 \x01(\x0b\x32\x62.org.dash.platform.dapi.v0.GetIdentityByPublicKeyHashResponse.GetIdentityByPublicKeyHashResponseV0H\x00\x1a\xb6\x01\n$GetIdentityByPublicKeyHashResponseV0\x12\x12\n\x08identity\x18\x01 \x01(\x0cH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xbd\x02\n*GetIdentityByNonUniquePublicKeyHashRequest\x12\x80\x01\n\x02v0\x18\x01 \x01(\x0b\x32r.org.dash.platform.dapi.v0.GetIdentityByNonUniquePublicKeyHashRequest.GetIdentityByNonUniquePublicKeyHashRequestV0H\x00\x1a\x80\x01\n,GetIdentityByNonUniquePublicKeyHashRequestV0\x12\x17\n\x0fpublic_key_hash\x18\x01 \x01(\x0c\x12\x18\n\x0bstart_after\x18\x02 \x01(\x0cH\x00\x88\x01\x01\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\x0e\n\x0c_start_afterB\t\n\x07version\"\xd6\x06\n+GetIdentityByNonUniquePublicKeyHashResponse\x12\x82\x01\n\x02v0\x18\x01 \x01(\x0b\x32t.org.dash.platform.dapi.v0.GetIdentityByNonUniquePublicKeyHashResponse.GetIdentityByNonUniquePublicKeyHashResponseV0H\x00\x1a\x96\x05\n-GetIdentityByNonUniquePublicKeyHashResponseV0\x12\x9a\x01\n\x08identity\x18\x01 \x01(\x0b\x32\x85\x01.org.dash.platform.dapi.v0.GetIdentityByNonUniquePublicKeyHashResponse.GetIdentityByNonUniquePublicKeyHashResponseV0.IdentityResponseH\x00\x12\x9d\x01\n\x05proof\x18\x02 \x01(\x0b\x32\x8b\x01.org.dash.platform.dapi.v0.GetIdentityByNonUniquePublicKeyHashResponse.GetIdentityByNonUniquePublicKeyHashResponseV0.IdentityProvedResponseH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x36\n\x10IdentityResponse\x12\x15\n\x08identity\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x42\x0b\n\t_identity\x1a\xa6\x01\n\x16IdentityProvedResponse\x12P\n&grovedb_identity_public_key_hash_proof\x18\x01 \x01(\x0b\x32 .org.dash.platform.dapi.v0.Proof\x12!\n\x14identity_proof_bytes\x18\x02 \x01(\x0cH\x00\x88\x01\x01\x42\x17\n\x15_identity_proof_bytesB\x08\n\x06resultB\t\n\x07version\"\xfb\x01\n#WaitForStateTransitionResultRequest\x12r\n\x02v0\x18\x01 \x01(\x0b\x32\x64.org.dash.platform.dapi.v0.WaitForStateTransitionResultRequest.WaitForStateTransitionResultRequestV0H\x00\x1aU\n%WaitForStateTransitionResultRequestV0\x12\x1d\n\x15state_transition_hash\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\x99\x03\n$WaitForStateTransitionResultResponse\x12t\n\x02v0\x18\x01 \x01(\x0b\x32\x66.org.dash.platform.dapi.v0.WaitForStateTransitionResultResponse.WaitForStateTransitionResultResponseV0H\x00\x1a\xef\x01\n&WaitForStateTransitionResultResponseV0\x12I\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x38.org.dash.platform.dapi.v0.StateTransitionBroadcastErrorH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xc4\x01\n\x19GetConsensusParamsRequest\x12^\n\x02v0\x18\x01 \x01(\x0b\x32P.org.dash.platform.dapi.v0.GetConsensusParamsRequest.GetConsensusParamsRequestV0H\x00\x1a<\n\x1bGetConsensusParamsRequestV0\x12\x0e\n\x06height\x18\x01 \x01(\x05\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\x9c\x04\n\x1aGetConsensusParamsResponse\x12`\n\x02v0\x18\x01 \x01(\x0b\x32R.org.dash.platform.dapi.v0.GetConsensusParamsResponse.GetConsensusParamsResponseV0H\x00\x1aP\n\x14\x43onsensusParamsBlock\x12\x11\n\tmax_bytes\x18\x01 \x01(\t\x12\x0f\n\x07max_gas\x18\x02 \x01(\t\x12\x14\n\x0ctime_iota_ms\x18\x03 \x01(\t\x1a\x62\n\x17\x43onsensusParamsEvidence\x12\x1a\n\x12max_age_num_blocks\x18\x01 \x01(\t\x12\x18\n\x10max_age_duration\x18\x02 \x01(\t\x12\x11\n\tmax_bytes\x18\x03 \x01(\t\x1a\xda\x01\n\x1cGetConsensusParamsResponseV0\x12Y\n\x05\x62lock\x18\x01 \x01(\x0b\x32J.org.dash.platform.dapi.v0.GetConsensusParamsResponse.ConsensusParamsBlock\x12_\n\x08\x65vidence\x18\x02 \x01(\x0b\x32M.org.dash.platform.dapi.v0.GetConsensusParamsResponse.ConsensusParamsEvidenceB\t\n\x07version\"\xe4\x01\n%GetProtocolVersionUpgradeStateRequest\x12v\n\x02v0\x18\x01 \x01(\x0b\x32h.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeStateRequest.GetProtocolVersionUpgradeStateRequestV0H\x00\x1a\x38\n\'GetProtocolVersionUpgradeStateRequestV0\x12\r\n\x05prove\x18\x01 \x01(\x08\x42\t\n\x07version\"\xb5\x05\n&GetProtocolVersionUpgradeStateResponse\x12x\n\x02v0\x18\x01 \x01(\x0b\x32j.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeStateResponse.GetProtocolVersionUpgradeStateResponseV0H\x00\x1a\x85\x04\n(GetProtocolVersionUpgradeStateResponseV0\x12\x87\x01\n\x08versions\x18\x01 \x01(\x0b\x32s.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeStateResponse.GetProtocolVersionUpgradeStateResponseV0.VersionsH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x96\x01\n\x08Versions\x12\x89\x01\n\x08versions\x18\x01 \x03(\x0b\x32w.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeStateResponse.GetProtocolVersionUpgradeStateResponseV0.VersionEntry\x1a:\n\x0cVersionEntry\x12\x16\n\x0eversion_number\x18\x01 \x01(\r\x12\x12\n\nvote_count\x18\x02 \x01(\rB\x08\n\x06resultB\t\n\x07version\"\xa3\x02\n*GetProtocolVersionUpgradeVoteStatusRequest\x12\x80\x01\n\x02v0\x18\x01 \x01(\x0b\x32r.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeVoteStatusRequest.GetProtocolVersionUpgradeVoteStatusRequestV0H\x00\x1ag\n,GetProtocolVersionUpgradeVoteStatusRequestV0\x12\x19\n\x11start_pro_tx_hash\x18\x01 \x01(\x0c\x12\r\n\x05\x63ount\x18\x02 \x01(\r\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\t\n\x07version\"\xef\x05\n+GetProtocolVersionUpgradeVoteStatusResponse\x12\x82\x01\n\x02v0\x18\x01 \x01(\x0b\x32t.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeVoteStatusResponse.GetProtocolVersionUpgradeVoteStatusResponseV0H\x00\x1a\xaf\x04\n-GetProtocolVersionUpgradeVoteStatusResponseV0\x12\x98\x01\n\x08versions\x18\x01 \x01(\x0b\x32\x83\x01.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeVoteStatusResponse.GetProtocolVersionUpgradeVoteStatusResponseV0.VersionSignalsH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\xaf\x01\n\x0eVersionSignals\x12\x9c\x01\n\x0fversion_signals\x18\x01 \x03(\x0b\x32\x82\x01.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeVoteStatusResponse.GetProtocolVersionUpgradeVoteStatusResponseV0.VersionSignal\x1a\x35\n\rVersionSignal\x12\x13\n\x0bpro_tx_hash\x18\x01 \x01(\x0c\x12\x0f\n\x07version\x18\x02 \x01(\rB\x08\n\x06resultB\t\n\x07version\"\xf5\x01\n\x14GetEpochsInfoRequest\x12T\n\x02v0\x18\x01 \x01(\x0b\x32\x46.org.dash.platform.dapi.v0.GetEpochsInfoRequest.GetEpochsInfoRequestV0H\x00\x1a|\n\x16GetEpochsInfoRequestV0\x12\x31\n\x0bstart_epoch\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12\r\n\x05\x63ount\x18\x02 \x01(\r\x12\x11\n\tascending\x18\x03 \x01(\x08\x12\r\n\x05prove\x18\x04 \x01(\x08\x42\t\n\x07version\"\x99\x05\n\x15GetEpochsInfoResponse\x12V\n\x02v0\x18\x01 \x01(\x0b\x32H.org.dash.platform.dapi.v0.GetEpochsInfoResponse.GetEpochsInfoResponseV0H\x00\x1a\x9c\x04\n\x17GetEpochsInfoResponseV0\x12\x65\n\x06\x65pochs\x18\x01 \x01(\x0b\x32S.org.dash.platform.dapi.v0.GetEpochsInfoResponse.GetEpochsInfoResponseV0.EpochInfosH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1au\n\nEpochInfos\x12g\n\x0b\x65poch_infos\x18\x01 \x03(\x0b\x32R.org.dash.platform.dapi.v0.GetEpochsInfoResponse.GetEpochsInfoResponseV0.EpochInfo\x1a\xa6\x01\n\tEpochInfo\x12\x0e\n\x06number\x18\x01 \x01(\r\x12\x1e\n\x12\x66irst_block_height\x18\x02 \x01(\x04\x42\x02\x30\x01\x12\x1f\n\x17\x66irst_core_block_height\x18\x03 \x01(\r\x12\x16\n\nstart_time\x18\x04 \x01(\x04\x42\x02\x30\x01\x12\x16\n\x0e\x66\x65\x65_multiplier\x18\x05 \x01(\x01\x12\x18\n\x10protocol_version\x18\x06 \x01(\rB\x08\n\x06resultB\t\n\x07version\"\xbf\x02\n\x1dGetFinalizedEpochInfosRequest\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetFinalizedEpochInfosRequest.GetFinalizedEpochInfosRequestV0H\x00\x1a\xaa\x01\n\x1fGetFinalizedEpochInfosRequestV0\x12\x19\n\x11start_epoch_index\x18\x01 \x01(\r\x12\"\n\x1astart_epoch_index_included\x18\x02 \x01(\x08\x12\x17\n\x0f\x65nd_epoch_index\x18\x03 \x01(\r\x12 \n\x18\x65nd_epoch_index_included\x18\x04 \x01(\x08\x12\r\n\x05prove\x18\x05 \x01(\x08\x42\t\n\x07version\"\xbd\t\n\x1eGetFinalizedEpochInfosResponse\x12h\n\x02v0\x18\x01 \x01(\x0b\x32Z.org.dash.platform.dapi.v0.GetFinalizedEpochInfosResponse.GetFinalizedEpochInfosResponseV0H\x00\x1a\xa5\x08\n GetFinalizedEpochInfosResponseV0\x12\x80\x01\n\x06\x65pochs\x18\x01 \x01(\x0b\x32n.org.dash.platform.dapi.v0.GetFinalizedEpochInfosResponse.GetFinalizedEpochInfosResponseV0.FinalizedEpochInfosH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\xa4\x01\n\x13\x46inalizedEpochInfos\x12\x8c\x01\n\x15\x66inalized_epoch_infos\x18\x01 \x03(\x0b\x32m.org.dash.platform.dapi.v0.GetFinalizedEpochInfosResponse.GetFinalizedEpochInfosResponseV0.FinalizedEpochInfo\x1a\x9f\x04\n\x12\x46inalizedEpochInfo\x12\x0e\n\x06number\x18\x01 \x01(\r\x12\x1e\n\x12\x66irst_block_height\x18\x02 \x01(\x04\x42\x02\x30\x01\x12\x1f\n\x17\x66irst_core_block_height\x18\x03 \x01(\r\x12\x1c\n\x10\x66irst_block_time\x18\x04 \x01(\x04\x42\x02\x30\x01\x12\x16\n\x0e\x66\x65\x65_multiplier\x18\x05 \x01(\x01\x12\x18\n\x10protocol_version\x18\x06 \x01(\r\x12!\n\x15total_blocks_in_epoch\x18\x07 \x01(\x04\x42\x02\x30\x01\x12*\n\"next_epoch_start_core_block_height\x18\x08 \x01(\r\x12!\n\x15total_processing_fees\x18\t \x01(\x04\x42\x02\x30\x01\x12*\n\x1etotal_distributed_storage_fees\x18\n \x01(\x04\x42\x02\x30\x01\x12&\n\x1atotal_created_storage_fees\x18\x0b \x01(\x04\x42\x02\x30\x01\x12\x1e\n\x12\x63ore_block_rewards\x18\x0c \x01(\x04\x42\x02\x30\x01\x12\x81\x01\n\x0f\x62lock_proposers\x18\r \x03(\x0b\x32h.org.dash.platform.dapi.v0.GetFinalizedEpochInfosResponse.GetFinalizedEpochInfosResponseV0.BlockProposer\x1a\x39\n\rBlockProposer\x12\x13\n\x0bproposer_id\x18\x01 \x01(\x0c\x12\x13\n\x0b\x62lock_count\x18\x02 \x01(\rB\x08\n\x06resultB\t\n\x07version\"\xde\x04\n\x1cGetContestedResourcesRequest\x12\x64\n\x02v0\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetContestedResourcesRequest.GetContestedResourcesRequestV0H\x00\x1a\xcc\x03\n\x1eGetContestedResourcesRequestV0\x12\x13\n\x0b\x63ontract_id\x18\x01 \x01(\x0c\x12\x1a\n\x12\x64ocument_type_name\x18\x02 \x01(\t\x12\x12\n\nindex_name\x18\x03 \x01(\t\x12\x1a\n\x12start_index_values\x18\x04 \x03(\x0c\x12\x18\n\x10\x65nd_index_values\x18\x05 \x03(\x0c\x12\x89\x01\n\x13start_at_value_info\x18\x06 \x01(\x0b\x32g.org.dash.platform.dapi.v0.GetContestedResourcesRequest.GetContestedResourcesRequestV0.StartAtValueInfoH\x00\x88\x01\x01\x12\x12\n\x05\x63ount\x18\x07 \x01(\rH\x01\x88\x01\x01\x12\x17\n\x0forder_ascending\x18\x08 \x01(\x08\x12\r\n\x05prove\x18\t \x01(\x08\x1a\x45\n\x10StartAtValueInfo\x12\x13\n\x0bstart_value\x18\x01 \x01(\x0c\x12\x1c\n\x14start_value_included\x18\x02 \x01(\x08\x42\x16\n\x14_start_at_value_infoB\x08\n\x06_countB\t\n\x07version\"\x88\x04\n\x1dGetContestedResourcesResponse\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetContestedResourcesResponse.GetContestedResourcesResponseV0H\x00\x1a\xf3\x02\n\x1fGetContestedResourcesResponseV0\x12\x95\x01\n\x19\x63ontested_resource_values\x18\x01 \x01(\x0b\x32p.org.dash.platform.dapi.v0.GetContestedResourcesResponse.GetContestedResourcesResponseV0.ContestedResourceValuesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a<\n\x17\x43ontestedResourceValues\x12!\n\x19\x63ontested_resource_values\x18\x01 \x03(\x0c\x42\x08\n\x06resultB\t\n\x07version\"\xd2\x05\n\x1cGetVotePollsByEndDateRequest\x12\x64\n\x02v0\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetVotePollsByEndDateRequest.GetVotePollsByEndDateRequestV0H\x00\x1a\xc0\x04\n\x1eGetVotePollsByEndDateRequestV0\x12\x84\x01\n\x0fstart_time_info\x18\x01 \x01(\x0b\x32\x66.org.dash.platform.dapi.v0.GetVotePollsByEndDateRequest.GetVotePollsByEndDateRequestV0.StartAtTimeInfoH\x00\x88\x01\x01\x12\x80\x01\n\rend_time_info\x18\x02 \x01(\x0b\x32\x64.org.dash.platform.dapi.v0.GetVotePollsByEndDateRequest.GetVotePollsByEndDateRequestV0.EndAtTimeInfoH\x01\x88\x01\x01\x12\x12\n\x05limit\x18\x03 \x01(\rH\x02\x88\x01\x01\x12\x13\n\x06offset\x18\x04 \x01(\rH\x03\x88\x01\x01\x12\x11\n\tascending\x18\x05 \x01(\x08\x12\r\n\x05prove\x18\x06 \x01(\x08\x1aI\n\x0fStartAtTimeInfo\x12\x19\n\rstart_time_ms\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\x1b\n\x13start_time_included\x18\x02 \x01(\x08\x1a\x43\n\rEndAtTimeInfo\x12\x17\n\x0b\x65nd_time_ms\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\x19\n\x11\x65nd_time_included\x18\x02 \x01(\x08\x42\x12\n\x10_start_time_infoB\x10\n\x0e_end_time_infoB\x08\n\x06_limitB\t\n\x07_offsetB\t\n\x07version\"\x83\x06\n\x1dGetVotePollsByEndDateResponse\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetVotePollsByEndDateResponse.GetVotePollsByEndDateResponseV0H\x00\x1a\xee\x04\n\x1fGetVotePollsByEndDateResponseV0\x12\x9c\x01\n\x18vote_polls_by_timestamps\x18\x01 \x01(\x0b\x32x.org.dash.platform.dapi.v0.GetVotePollsByEndDateResponse.GetVotePollsByEndDateResponseV0.SerializedVotePollsByTimestampsH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1aV\n\x1eSerializedVotePollsByTimestamp\x12\x15\n\ttimestamp\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\x1d\n\x15serialized_vote_polls\x18\x02 \x03(\x0c\x1a\xd7\x01\n\x1fSerializedVotePollsByTimestamps\x12\x99\x01\n\x18vote_polls_by_timestamps\x18\x01 \x03(\x0b\x32w.org.dash.platform.dapi.v0.GetVotePollsByEndDateResponse.GetVotePollsByEndDateResponseV0.SerializedVotePollsByTimestamp\x12\x18\n\x10\x66inished_results\x18\x02 \x01(\x08\x42\x08\n\x06resultB\t\n\x07version\"\xff\x06\n$GetContestedResourceVoteStateRequest\x12t\n\x02v0\x18\x01 \x01(\x0b\x32\x66.org.dash.platform.dapi.v0.GetContestedResourceVoteStateRequest.GetContestedResourceVoteStateRequestV0H\x00\x1a\xd5\x05\n&GetContestedResourceVoteStateRequestV0\x12\x13\n\x0b\x63ontract_id\x18\x01 \x01(\x0c\x12\x1a\n\x12\x64ocument_type_name\x18\x02 \x01(\t\x12\x12\n\nindex_name\x18\x03 \x01(\t\x12\x14\n\x0cindex_values\x18\x04 \x03(\x0c\x12\x86\x01\n\x0bresult_type\x18\x05 \x01(\x0e\x32q.org.dash.platform.dapi.v0.GetContestedResourceVoteStateRequest.GetContestedResourceVoteStateRequestV0.ResultType\x12\x36\n.allow_include_locked_and_abstaining_vote_tally\x18\x06 \x01(\x08\x12\xa3\x01\n\x18start_at_identifier_info\x18\x07 \x01(\x0b\x32|.org.dash.platform.dapi.v0.GetContestedResourceVoteStateRequest.GetContestedResourceVoteStateRequestV0.StartAtIdentifierInfoH\x00\x88\x01\x01\x12\x12\n\x05\x63ount\x18\x08 \x01(\rH\x01\x88\x01\x01\x12\r\n\x05prove\x18\t \x01(\x08\x1aT\n\x15StartAtIdentifierInfo\x12\x18\n\x10start_identifier\x18\x01 \x01(\x0c\x12!\n\x19start_identifier_included\x18\x02 \x01(\x08\"I\n\nResultType\x12\r\n\tDOCUMENTS\x10\x00\x12\x0e\n\nVOTE_TALLY\x10\x01\x12\x1c\n\x18\x44OCUMENTS_AND_VOTE_TALLY\x10\x02\x42\x1b\n\x19_start_at_identifier_infoB\x08\n\x06_countB\t\n\x07version\"\x94\x0c\n%GetContestedResourceVoteStateResponse\x12v\n\x02v0\x18\x01 \x01(\x0b\x32h.org.dash.platform.dapi.v0.GetContestedResourceVoteStateResponse.GetContestedResourceVoteStateResponseV0H\x00\x1a\xe7\n\n\'GetContestedResourceVoteStateResponseV0\x12\xae\x01\n\x1d\x63ontested_resource_contenders\x18\x01 \x01(\x0b\x32\x84\x01.org.dash.platform.dapi.v0.GetContestedResourceVoteStateResponse.GetContestedResourceVoteStateResponseV0.ContestedResourceContendersH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\xda\x03\n\x10\x46inishedVoteInfo\x12\xad\x01\n\x15\x66inished_vote_outcome\x18\x01 \x01(\x0e\x32\x8d\x01.org.dash.platform.dapi.v0.GetContestedResourceVoteStateResponse.GetContestedResourceVoteStateResponseV0.FinishedVoteInfo.FinishedVoteOutcome\x12\x1f\n\x12won_by_identity_id\x18\x02 \x01(\x0cH\x00\x88\x01\x01\x12$\n\x18\x66inished_at_block_height\x18\x03 \x01(\x04\x42\x02\x30\x01\x12%\n\x1d\x66inished_at_core_block_height\x18\x04 \x01(\r\x12%\n\x19\x66inished_at_block_time_ms\x18\x05 \x01(\x04\x42\x02\x30\x01\x12\x19\n\x11\x66inished_at_epoch\x18\x06 \x01(\r\"O\n\x13\x46inishedVoteOutcome\x12\x14\n\x10TOWARDS_IDENTITY\x10\x00\x12\n\n\x06LOCKED\x10\x01\x12\x16\n\x12NO_PREVIOUS_WINNER\x10\x02\x42\x15\n\x13_won_by_identity_id\x1a\xc4\x03\n\x1b\x43ontestedResourceContenders\x12\x86\x01\n\ncontenders\x18\x01 \x03(\x0b\x32r.org.dash.platform.dapi.v0.GetContestedResourceVoteStateResponse.GetContestedResourceVoteStateResponseV0.Contender\x12\x1f\n\x12\x61\x62stain_vote_tally\x18\x02 \x01(\rH\x00\x88\x01\x01\x12\x1c\n\x0flock_vote_tally\x18\x03 \x01(\rH\x01\x88\x01\x01\x12\x9a\x01\n\x12\x66inished_vote_info\x18\x04 \x01(\x0b\x32y.org.dash.platform.dapi.v0.GetContestedResourceVoteStateResponse.GetContestedResourceVoteStateResponseV0.FinishedVoteInfoH\x02\x88\x01\x01\x42\x15\n\x13_abstain_vote_tallyB\x12\n\x10_lock_vote_tallyB\x15\n\x13_finished_vote_info\x1ak\n\tContender\x12\x12\n\nidentifier\x18\x01 \x01(\x0c\x12\x17\n\nvote_count\x18\x02 \x01(\rH\x00\x88\x01\x01\x12\x15\n\x08\x64ocument\x18\x03 \x01(\x0cH\x01\x88\x01\x01\x42\r\n\x0b_vote_countB\x0b\n\t_documentB\x08\n\x06resultB\t\n\x07version\"\xd5\x05\n,GetContestedResourceVotersForIdentityRequest\x12\x84\x01\n\x02v0\x18\x01 \x01(\x0b\x32v.org.dash.platform.dapi.v0.GetContestedResourceVotersForIdentityRequest.GetContestedResourceVotersForIdentityRequestV0H\x00\x1a\x92\x04\n.GetContestedResourceVotersForIdentityRequestV0\x12\x13\n\x0b\x63ontract_id\x18\x01 \x01(\x0c\x12\x1a\n\x12\x64ocument_type_name\x18\x02 \x01(\t\x12\x12\n\nindex_name\x18\x03 \x01(\t\x12\x14\n\x0cindex_values\x18\x04 \x03(\x0c\x12\x15\n\rcontestant_id\x18\x05 \x01(\x0c\x12\xb4\x01\n\x18start_at_identifier_info\x18\x06 \x01(\x0b\x32\x8c\x01.org.dash.platform.dapi.v0.GetContestedResourceVotersForIdentityRequest.GetContestedResourceVotersForIdentityRequestV0.StartAtIdentifierInfoH\x00\x88\x01\x01\x12\x12\n\x05\x63ount\x18\x07 \x01(\rH\x01\x88\x01\x01\x12\x17\n\x0forder_ascending\x18\x08 \x01(\x08\x12\r\n\x05prove\x18\t \x01(\x08\x1aT\n\x15StartAtIdentifierInfo\x12\x18\n\x10start_identifier\x18\x01 \x01(\x0c\x12!\n\x19start_identifier_included\x18\x02 \x01(\x08\x42\x1b\n\x19_start_at_identifier_infoB\x08\n\x06_countB\t\n\x07version\"\xf1\x04\n-GetContestedResourceVotersForIdentityResponse\x12\x86\x01\n\x02v0\x18\x01 \x01(\x0b\x32x.org.dash.platform.dapi.v0.GetContestedResourceVotersForIdentityResponse.GetContestedResourceVotersForIdentityResponseV0H\x00\x1a\xab\x03\n/GetContestedResourceVotersForIdentityResponseV0\x12\xb6\x01\n\x19\x63ontested_resource_voters\x18\x01 \x01(\x0b\x32\x90\x01.org.dash.platform.dapi.v0.GetContestedResourceVotersForIdentityResponse.GetContestedResourceVotersForIdentityResponseV0.ContestedResourceVotersH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x43\n\x17\x43ontestedResourceVoters\x12\x0e\n\x06voters\x18\x01 \x03(\x0c\x12\x18\n\x10\x66inished_results\x18\x02 \x01(\x08\x42\x08\n\x06resultB\t\n\x07version\"\xad\x05\n(GetContestedResourceIdentityVotesRequest\x12|\n\x02v0\x18\x01 \x01(\x0b\x32n.org.dash.platform.dapi.v0.GetContestedResourceIdentityVotesRequest.GetContestedResourceIdentityVotesRequestV0H\x00\x1a\xf7\x03\n*GetContestedResourceIdentityVotesRequestV0\x12\x13\n\x0bidentity_id\x18\x01 \x01(\x0c\x12+\n\x05limit\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12,\n\x06offset\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12\x17\n\x0forder_ascending\x18\x04 \x01(\x08\x12\xae\x01\n\x1astart_at_vote_poll_id_info\x18\x05 \x01(\x0b\x32\x84\x01.org.dash.platform.dapi.v0.GetContestedResourceIdentityVotesRequest.GetContestedResourceIdentityVotesRequestV0.StartAtVotePollIdInfoH\x00\x88\x01\x01\x12\r\n\x05prove\x18\x06 \x01(\x08\x1a\x61\n\x15StartAtVotePollIdInfo\x12 \n\x18start_at_poll_identifier\x18\x01 \x01(\x0c\x12&\n\x1estart_poll_identifier_included\x18\x02 \x01(\x08\x42\x1d\n\x1b_start_at_vote_poll_id_infoB\t\n\x07version\"\xc8\n\n)GetContestedResourceIdentityVotesResponse\x12~\n\x02v0\x18\x01 \x01(\x0b\x32p.org.dash.platform.dapi.v0.GetContestedResourceIdentityVotesResponse.GetContestedResourceIdentityVotesResponseV0H\x00\x1a\x8f\t\n+GetContestedResourceIdentityVotesResponseV0\x12\xa1\x01\n\x05votes\x18\x01 \x01(\x0b\x32\x8f\x01.org.dash.platform.dapi.v0.GetContestedResourceIdentityVotesResponse.GetContestedResourceIdentityVotesResponseV0.ContestedResourceIdentityVotesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\xf7\x01\n\x1e\x43ontestedResourceIdentityVotes\x12\xba\x01\n!contested_resource_identity_votes\x18\x01 \x03(\x0b\x32\x8e\x01.org.dash.platform.dapi.v0.GetContestedResourceIdentityVotesResponse.GetContestedResourceIdentityVotesResponseV0.ContestedResourceIdentityVote\x12\x18\n\x10\x66inished_results\x18\x02 \x01(\x08\x1a\xad\x02\n\x12ResourceVoteChoice\x12\xad\x01\n\x10vote_choice_type\x18\x01 \x01(\x0e\x32\x92\x01.org.dash.platform.dapi.v0.GetContestedResourceIdentityVotesResponse.GetContestedResourceIdentityVotesResponseV0.ResourceVoteChoice.VoteChoiceType\x12\x18\n\x0bidentity_id\x18\x02 \x01(\x0cH\x00\x88\x01\x01\"=\n\x0eVoteChoiceType\x12\x14\n\x10TOWARDS_IDENTITY\x10\x00\x12\x0b\n\x07\x41\x42STAIN\x10\x01\x12\x08\n\x04LOCK\x10\x02\x42\x0e\n\x0c_identity_id\x1a\x95\x02\n\x1d\x43ontestedResourceIdentityVote\x12\x13\n\x0b\x63ontract_id\x18\x01 \x01(\x0c\x12\x1a\n\x12\x64ocument_type_name\x18\x02 \x01(\t\x12\'\n\x1fserialized_index_storage_values\x18\x03 \x03(\x0c\x12\x99\x01\n\x0bvote_choice\x18\x04 \x01(\x0b\x32\x83\x01.org.dash.platform.dapi.v0.GetContestedResourceIdentityVotesResponse.GetContestedResourceIdentityVotesResponseV0.ResourceVoteChoiceB\x08\n\x06resultB\t\n\x07version\"\xf0\x01\n%GetPrefundedSpecializedBalanceRequest\x12v\n\x02v0\x18\x01 \x01(\x0b\x32h.org.dash.platform.dapi.v0.GetPrefundedSpecializedBalanceRequest.GetPrefundedSpecializedBalanceRequestV0H\x00\x1a\x44\n\'GetPrefundedSpecializedBalanceRequestV0\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xed\x02\n&GetPrefundedSpecializedBalanceResponse\x12x\n\x02v0\x18\x01 \x01(\x0b\x32j.org.dash.platform.dapi.v0.GetPrefundedSpecializedBalanceResponse.GetPrefundedSpecializedBalanceResponseV0H\x00\x1a\xbd\x01\n(GetPrefundedSpecializedBalanceResponseV0\x12\x15\n\x07\x62\x61lance\x18\x01 \x01(\x04\x42\x02\x30\x01H\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xd0\x01\n GetTotalCreditsInPlatformRequest\x12l\n\x02v0\x18\x01 \x01(\x0b\x32^.org.dash.platform.dapi.v0.GetTotalCreditsInPlatformRequest.GetTotalCreditsInPlatformRequestV0H\x00\x1a\x33\n\"GetTotalCreditsInPlatformRequestV0\x12\r\n\x05prove\x18\x01 \x01(\x08\x42\t\n\x07version\"\xd9\x02\n!GetTotalCreditsInPlatformResponse\x12n\n\x02v0\x18\x01 \x01(\x0b\x32`.org.dash.platform.dapi.v0.GetTotalCreditsInPlatformResponse.GetTotalCreditsInPlatformResponseV0H\x00\x1a\xb8\x01\n#GetTotalCreditsInPlatformResponseV0\x12\x15\n\x07\x63redits\x18\x01 \x01(\x04\x42\x02\x30\x01H\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xc4\x01\n\x16GetPathElementsRequest\x12X\n\x02v0\x18\x01 \x01(\x0b\x32J.org.dash.platform.dapi.v0.GetPathElementsRequest.GetPathElementsRequestV0H\x00\x1a\x45\n\x18GetPathElementsRequestV0\x12\x0c\n\x04path\x18\x01 \x03(\x0c\x12\x0c\n\x04keys\x18\x02 \x03(\x0c\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\t\n\x07version\"\xa3\x03\n\x17GetPathElementsResponse\x12Z\n\x02v0\x18\x01 \x01(\x0b\x32L.org.dash.platform.dapi.v0.GetPathElementsResponse.GetPathElementsResponseV0H\x00\x1a\xa0\x02\n\x19GetPathElementsResponseV0\x12i\n\x08\x65lements\x18\x01 \x01(\x0b\x32U.org.dash.platform.dapi.v0.GetPathElementsResponse.GetPathElementsResponseV0.ElementsH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x1c\n\x08\x45lements\x12\x10\n\x08\x65lements\x18\x01 \x03(\x0c\x42\x08\n\x06resultB\t\n\x07version\"\x81\x01\n\x10GetStatusRequest\x12L\n\x02v0\x18\x01 \x01(\x0b\x32>.org.dash.platform.dapi.v0.GetStatusRequest.GetStatusRequestV0H\x00\x1a\x14\n\x12GetStatusRequestV0B\t\n\x07version\"\xe4\x10\n\x11GetStatusResponse\x12N\n\x02v0\x18\x01 \x01(\x0b\x32@.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0H\x00\x1a\xf3\x0f\n\x13GetStatusResponseV0\x12Y\n\x07version\x18\x01 \x01(\x0b\x32H.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0.Version\x12S\n\x04node\x18\x02 \x01(\x0b\x32\x45.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0.Node\x12U\n\x05\x63hain\x18\x03 \x01(\x0b\x32\x46.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0.Chain\x12Y\n\x07network\x18\x04 \x01(\x0b\x32H.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0.Network\x12^\n\nstate_sync\x18\x05 \x01(\x0b\x32J.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0.StateSync\x12S\n\x04time\x18\x06 \x01(\x0b\x32\x45.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0.Time\x1a\x82\x05\n\x07Version\x12\x63\n\x08software\x18\x01 \x01(\x0b\x32Q.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0.Version.Software\x12\x63\n\x08protocol\x18\x02 \x01(\x0b\x32Q.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0.Version.Protocol\x1a^\n\x08Software\x12\x0c\n\x04\x64\x61pi\x18\x01 \x01(\t\x12\x12\n\x05\x64rive\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x17\n\ntenderdash\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x08\n\x06_driveB\r\n\x0b_tenderdash\x1a\xcc\x02\n\x08Protocol\x12p\n\ntenderdash\x18\x01 \x01(\x0b\x32\\.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0.Version.Protocol.Tenderdash\x12\x66\n\x05\x64rive\x18\x02 \x01(\x0b\x32W.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0.Version.Protocol.Drive\x1a(\n\nTenderdash\x12\x0b\n\x03p2p\x18\x01 \x01(\r\x12\r\n\x05\x62lock\x18\x02 \x01(\r\x1a<\n\x05\x44rive\x12\x0e\n\x06latest\x18\x03 \x01(\r\x12\x0f\n\x07\x63urrent\x18\x04 \x01(\r\x12\x12\n\nnext_epoch\x18\x05 \x01(\r\x1a\x7f\n\x04Time\x12\x11\n\x05local\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\x16\n\x05\x62lock\x18\x02 \x01(\x04\x42\x02\x30\x01H\x00\x88\x01\x01\x12\x18\n\x07genesis\x18\x03 \x01(\x04\x42\x02\x30\x01H\x01\x88\x01\x01\x12\x12\n\x05\x65poch\x18\x04 \x01(\rH\x02\x88\x01\x01\x42\x08\n\x06_blockB\n\n\x08_genesisB\x08\n\x06_epoch\x1a<\n\x04Node\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\x18\n\x0bpro_tx_hash\x18\x02 \x01(\x0cH\x00\x88\x01\x01\x42\x0e\n\x0c_pro_tx_hash\x1a\xb3\x02\n\x05\x43hain\x12\x13\n\x0b\x63\x61tching_up\x18\x01 \x01(\x08\x12\x19\n\x11latest_block_hash\x18\x02 \x01(\x0c\x12\x17\n\x0flatest_app_hash\x18\x03 \x01(\x0c\x12\x1f\n\x13latest_block_height\x18\x04 \x01(\x04\x42\x02\x30\x01\x12\x1b\n\x13\x65\x61rliest_block_hash\x18\x05 \x01(\x0c\x12\x19\n\x11\x65\x61rliest_app_hash\x18\x06 \x01(\x0c\x12!\n\x15\x65\x61rliest_block_height\x18\x07 \x01(\x04\x42\x02\x30\x01\x12!\n\x15max_peer_block_height\x18\t \x01(\x04\x42\x02\x30\x01\x12%\n\x18\x63ore_chain_locked_height\x18\n \x01(\rH\x00\x88\x01\x01\x42\x1b\n\x19_core_chain_locked_height\x1a\x43\n\x07Network\x12\x10\n\x08\x63hain_id\x18\x01 \x01(\t\x12\x13\n\x0bpeers_count\x18\x02 \x01(\r\x12\x11\n\tlistening\x18\x03 \x01(\x08\x1a\x85\x02\n\tStateSync\x12\x1d\n\x11total_synced_time\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\x1a\n\x0eremaining_time\x18\x02 \x01(\x04\x42\x02\x30\x01\x12\x17\n\x0ftotal_snapshots\x18\x03 \x01(\r\x12\"\n\x16\x63hunk_process_avg_time\x18\x04 \x01(\x04\x42\x02\x30\x01\x12\x1b\n\x0fsnapshot_height\x18\x05 \x01(\x04\x42\x02\x30\x01\x12!\n\x15snapshot_chunks_count\x18\x06 \x01(\x04\x42\x02\x30\x01\x12\x1d\n\x11\x62\x61\x63kfilled_blocks\x18\x07 \x01(\x04\x42\x02\x30\x01\x12!\n\x15\x62\x61\x63kfill_blocks_total\x18\x08 \x01(\x04\x42\x02\x30\x01\x42\t\n\x07version\"\xb1\x01\n\x1cGetCurrentQuorumsInfoRequest\x12\x64\n\x02v0\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetCurrentQuorumsInfoRequest.GetCurrentQuorumsInfoRequestV0H\x00\x1a \n\x1eGetCurrentQuorumsInfoRequestV0B\t\n\x07version\"\xa1\x05\n\x1dGetCurrentQuorumsInfoResponse\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetCurrentQuorumsInfoResponse.GetCurrentQuorumsInfoResponseV0H\x00\x1a\x46\n\x0bValidatorV0\x12\x13\n\x0bpro_tx_hash\x18\x01 \x01(\x0c\x12\x0f\n\x07node_ip\x18\x02 \x01(\t\x12\x11\n\tis_banned\x18\x03 \x01(\x08\x1a\xaf\x01\n\x0eValidatorSetV0\x12\x13\n\x0bquorum_hash\x18\x01 \x01(\x0c\x12\x13\n\x0b\x63ore_height\x18\x02 \x01(\r\x12U\n\x07members\x18\x03 \x03(\x0b\x32\x44.org.dash.platform.dapi.v0.GetCurrentQuorumsInfoResponse.ValidatorV0\x12\x1c\n\x14threshold_public_key\x18\x04 \x01(\x0c\x1a\x92\x02\n\x1fGetCurrentQuorumsInfoResponseV0\x12\x15\n\rquorum_hashes\x18\x01 \x03(\x0c\x12\x1b\n\x13\x63urrent_quorum_hash\x18\x02 \x01(\x0c\x12_\n\x0evalidator_sets\x18\x03 \x03(\x0b\x32G.org.dash.platform.dapi.v0.GetCurrentQuorumsInfoResponse.ValidatorSetV0\x12\x1b\n\x13last_block_proposer\x18\x04 \x01(\x0c\x12=\n\x08metadata\x18\x05 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\t\n\x07version\"\xf4\x01\n\x1fGetIdentityTokenBalancesRequest\x12j\n\x02v0\x18\x01 \x01(\x0b\x32\\.org.dash.platform.dapi.v0.GetIdentityTokenBalancesRequest.GetIdentityTokenBalancesRequestV0H\x00\x1aZ\n!GetIdentityTokenBalancesRequestV0\x12\x13\n\x0bidentity_id\x18\x01 \x01(\x0c\x12\x11\n\ttoken_ids\x18\x02 \x03(\x0c\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\t\n\x07version\"\xad\x05\n GetIdentityTokenBalancesResponse\x12l\n\x02v0\x18\x01 \x01(\x0b\x32^.org.dash.platform.dapi.v0.GetIdentityTokenBalancesResponse.GetIdentityTokenBalancesResponseV0H\x00\x1a\x8f\x04\n\"GetIdentityTokenBalancesResponseV0\x12\x86\x01\n\x0etoken_balances\x18\x01 \x01(\x0b\x32l.org.dash.platform.dapi.v0.GetIdentityTokenBalancesResponse.GetIdentityTokenBalancesResponseV0.TokenBalancesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1aG\n\x11TokenBalanceEntry\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12\x14\n\x07\x62\x61lance\x18\x02 \x01(\x04H\x00\x88\x01\x01\x42\n\n\x08_balance\x1a\x9a\x01\n\rTokenBalances\x12\x88\x01\n\x0etoken_balances\x18\x01 \x03(\x0b\x32p.org.dash.platform.dapi.v0.GetIdentityTokenBalancesResponse.GetIdentityTokenBalancesResponseV0.TokenBalanceEntryB\x08\n\x06resultB\t\n\x07version\"\xfc\x01\n!GetIdentitiesTokenBalancesRequest\x12n\n\x02v0\x18\x01 \x01(\x0b\x32`.org.dash.platform.dapi.v0.GetIdentitiesTokenBalancesRequest.GetIdentitiesTokenBalancesRequestV0H\x00\x1a\\\n#GetIdentitiesTokenBalancesRequestV0\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12\x14\n\x0cidentity_ids\x18\x02 \x03(\x0c\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\t\n\x07version\"\xf2\x05\n\"GetIdentitiesTokenBalancesResponse\x12p\n\x02v0\x18\x01 \x01(\x0b\x32\x62.org.dash.platform.dapi.v0.GetIdentitiesTokenBalancesResponse.GetIdentitiesTokenBalancesResponseV0H\x00\x1a\xce\x04\n$GetIdentitiesTokenBalancesResponseV0\x12\x9b\x01\n\x17identity_token_balances\x18\x01 \x01(\x0b\x32x.org.dash.platform.dapi.v0.GetIdentitiesTokenBalancesResponse.GetIdentitiesTokenBalancesResponseV0.IdentityTokenBalancesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1aR\n\x19IdentityTokenBalanceEntry\x12\x13\n\x0bidentity_id\x18\x01 \x01(\x0c\x12\x14\n\x07\x62\x61lance\x18\x02 \x01(\x04H\x00\x88\x01\x01\x42\n\n\x08_balance\x1a\xb7\x01\n\x15IdentityTokenBalances\x12\x9d\x01\n\x17identity_token_balances\x18\x01 \x03(\x0b\x32|.org.dash.platform.dapi.v0.GetIdentitiesTokenBalancesResponse.GetIdentitiesTokenBalancesResponseV0.IdentityTokenBalanceEntryB\x08\n\x06resultB\t\n\x07version\"\xe8\x01\n\x1cGetIdentityTokenInfosRequest\x12\x64\n\x02v0\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetIdentityTokenInfosRequest.GetIdentityTokenInfosRequestV0H\x00\x1aW\n\x1eGetIdentityTokenInfosRequestV0\x12\x13\n\x0bidentity_id\x18\x01 \x01(\x0c\x12\x11\n\ttoken_ids\x18\x02 \x03(\x0c\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\t\n\x07version\"\x98\x06\n\x1dGetIdentityTokenInfosResponse\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetIdentityTokenInfosResponse.GetIdentityTokenInfosResponseV0H\x00\x1a\x83\x05\n\x1fGetIdentityTokenInfosResponseV0\x12z\n\x0btoken_infos\x18\x01 \x01(\x0b\x32\x63.org.dash.platform.dapi.v0.GetIdentityTokenInfosResponse.GetIdentityTokenInfosResponseV0.TokenInfosH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a(\n\x16TokenIdentityInfoEntry\x12\x0e\n\x06\x66rozen\x18\x01 \x01(\x08\x1a\xb0\x01\n\x0eTokenInfoEntry\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12\x82\x01\n\x04info\x18\x02 \x01(\x0b\x32o.org.dash.platform.dapi.v0.GetIdentityTokenInfosResponse.GetIdentityTokenInfosResponseV0.TokenIdentityInfoEntryH\x00\x88\x01\x01\x42\x07\n\x05_info\x1a\x8a\x01\n\nTokenInfos\x12|\n\x0btoken_infos\x18\x01 \x03(\x0b\x32g.org.dash.platform.dapi.v0.GetIdentityTokenInfosResponse.GetIdentityTokenInfosResponseV0.TokenInfoEntryB\x08\n\x06resultB\t\n\x07version\"\xf0\x01\n\x1eGetIdentitiesTokenInfosRequest\x12h\n\x02v0\x18\x01 \x01(\x0b\x32Z.org.dash.platform.dapi.v0.GetIdentitiesTokenInfosRequest.GetIdentitiesTokenInfosRequestV0H\x00\x1aY\n GetIdentitiesTokenInfosRequestV0\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12\x14\n\x0cidentity_ids\x18\x02 \x03(\x0c\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\t\n\x07version\"\xca\x06\n\x1fGetIdentitiesTokenInfosResponse\x12j\n\x02v0\x18\x01 \x01(\x0b\x32\\.org.dash.platform.dapi.v0.GetIdentitiesTokenInfosResponse.GetIdentitiesTokenInfosResponseV0H\x00\x1a\xaf\x05\n!GetIdentitiesTokenInfosResponseV0\x12\x8f\x01\n\x14identity_token_infos\x18\x01 \x01(\x0b\x32o.org.dash.platform.dapi.v0.GetIdentitiesTokenInfosResponse.GetIdentitiesTokenInfosResponseV0.IdentityTokenInfosH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a(\n\x16TokenIdentityInfoEntry\x12\x0e\n\x06\x66rozen\x18\x01 \x01(\x08\x1a\xb7\x01\n\x0eTokenInfoEntry\x12\x13\n\x0bidentity_id\x18\x01 \x01(\x0c\x12\x86\x01\n\x04info\x18\x02 \x01(\x0b\x32s.org.dash.platform.dapi.v0.GetIdentitiesTokenInfosResponse.GetIdentitiesTokenInfosResponseV0.TokenIdentityInfoEntryH\x00\x88\x01\x01\x42\x07\n\x05_info\x1a\x97\x01\n\x12IdentityTokenInfos\x12\x80\x01\n\x0btoken_infos\x18\x01 \x03(\x0b\x32k.org.dash.platform.dapi.v0.GetIdentitiesTokenInfosResponse.GetIdentitiesTokenInfosResponseV0.TokenInfoEntryB\x08\n\x06resultB\t\n\x07version\"\xbf\x01\n\x17GetTokenStatusesRequest\x12Z\n\x02v0\x18\x01 \x01(\x0b\x32L.org.dash.platform.dapi.v0.GetTokenStatusesRequest.GetTokenStatusesRequestV0H\x00\x1a=\n\x19GetTokenStatusesRequestV0\x12\x11\n\ttoken_ids\x18\x01 \x03(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xe7\x04\n\x18GetTokenStatusesResponse\x12\\\n\x02v0\x18\x01 \x01(\x0b\x32N.org.dash.platform.dapi.v0.GetTokenStatusesResponse.GetTokenStatusesResponseV0H\x00\x1a\xe1\x03\n\x1aGetTokenStatusesResponseV0\x12v\n\x0etoken_statuses\x18\x01 \x01(\x0b\x32\\.org.dash.platform.dapi.v0.GetTokenStatusesResponse.GetTokenStatusesResponseV0.TokenStatusesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x44\n\x10TokenStatusEntry\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12\x13\n\x06paused\x18\x02 \x01(\x08H\x00\x88\x01\x01\x42\t\n\x07_paused\x1a\x88\x01\n\rTokenStatuses\x12w\n\x0etoken_statuses\x18\x01 \x03(\x0b\x32_.org.dash.platform.dapi.v0.GetTokenStatusesResponse.GetTokenStatusesResponseV0.TokenStatusEntryB\x08\n\x06resultB\t\n\x07version\"\xef\x01\n#GetTokenDirectPurchasePricesRequest\x12r\n\x02v0\x18\x01 \x01(\x0b\x32\x64.org.dash.platform.dapi.v0.GetTokenDirectPurchasePricesRequest.GetTokenDirectPurchasePricesRequestV0H\x00\x1aI\n%GetTokenDirectPurchasePricesRequestV0\x12\x11\n\ttoken_ids\x18\x01 \x03(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\x8b\t\n$GetTokenDirectPurchasePricesResponse\x12t\n\x02v0\x18\x01 \x01(\x0b\x32\x66.org.dash.platform.dapi.v0.GetTokenDirectPurchasePricesResponse.GetTokenDirectPurchasePricesResponseV0H\x00\x1a\xe1\x07\n&GetTokenDirectPurchasePricesResponseV0\x12\xa9\x01\n\x1ctoken_direct_purchase_prices\x18\x01 \x01(\x0b\x32\x80\x01.org.dash.platform.dapi.v0.GetTokenDirectPurchasePricesResponse.GetTokenDirectPurchasePricesResponseV0.TokenDirectPurchasePricesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x33\n\x10PriceForQuantity\x12\x10\n\x08quantity\x18\x01 \x01(\x04\x12\r\n\x05price\x18\x02 \x01(\x04\x1a\xa7\x01\n\x0fPricingSchedule\x12\x93\x01\n\x12price_for_quantity\x18\x01 \x03(\x0b\x32w.org.dash.platform.dapi.v0.GetTokenDirectPurchasePricesResponse.GetTokenDirectPurchasePricesResponseV0.PriceForQuantity\x1a\xe4\x01\n\x1dTokenDirectPurchasePriceEntry\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12\x15\n\x0b\x66ixed_price\x18\x02 \x01(\x04H\x00\x12\x90\x01\n\x0evariable_price\x18\x03 \x01(\x0b\x32v.org.dash.platform.dapi.v0.GetTokenDirectPurchasePricesResponse.GetTokenDirectPurchasePricesResponseV0.PricingScheduleH\x00\x42\x07\n\x05price\x1a\xc8\x01\n\x19TokenDirectPurchasePrices\x12\xaa\x01\n\x1btoken_direct_purchase_price\x18\x01 \x03(\x0b\x32\x84\x01.org.dash.platform.dapi.v0.GetTokenDirectPurchasePricesResponse.GetTokenDirectPurchasePricesResponseV0.TokenDirectPurchasePriceEntryB\x08\n\x06resultB\t\n\x07version\"\xce\x01\n\x1bGetTokenContractInfoRequest\x12\x62\n\x02v0\x18\x01 \x01(\x0b\x32T.org.dash.platform.dapi.v0.GetTokenContractInfoRequest.GetTokenContractInfoRequestV0H\x00\x1a@\n\x1dGetTokenContractInfoRequestV0\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xfb\x03\n\x1cGetTokenContractInfoResponse\x12\x64\n\x02v0\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetTokenContractInfoResponse.GetTokenContractInfoResponseV0H\x00\x1a\xe9\x02\n\x1eGetTokenContractInfoResponseV0\x12|\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32l.org.dash.platform.dapi.v0.GetTokenContractInfoResponse.GetTokenContractInfoResponseV0.TokenContractInfoDataH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1aM\n\x15TokenContractInfoData\x12\x13\n\x0b\x63ontract_id\x18\x01 \x01(\x0c\x12\x1f\n\x17token_contract_position\x18\x02 \x01(\rB\x08\n\x06resultB\t\n\x07version\"\xef\x04\n)GetTokenPreProgrammedDistributionsRequest\x12~\n\x02v0\x18\x01 \x01(\x0b\x32p.org.dash.platform.dapi.v0.GetTokenPreProgrammedDistributionsRequest.GetTokenPreProgrammedDistributionsRequestV0H\x00\x1a\xb6\x03\n+GetTokenPreProgrammedDistributionsRequestV0\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12\x98\x01\n\rstart_at_info\x18\x02 \x01(\x0b\x32|.org.dash.platform.dapi.v0.GetTokenPreProgrammedDistributionsRequest.GetTokenPreProgrammedDistributionsRequestV0.StartAtInfoH\x00\x88\x01\x01\x12\x12\n\x05limit\x18\x03 \x01(\rH\x01\x88\x01\x01\x12\r\n\x05prove\x18\x04 \x01(\x08\x1a\x9a\x01\n\x0bStartAtInfo\x12\x15\n\rstart_time_ms\x18\x01 \x01(\x04\x12\x1c\n\x0fstart_recipient\x18\x02 \x01(\x0cH\x00\x88\x01\x01\x12%\n\x18start_recipient_included\x18\x03 \x01(\x08H\x01\x88\x01\x01\x42\x12\n\x10_start_recipientB\x1b\n\x19_start_recipient_includedB\x10\n\x0e_start_at_infoB\x08\n\x06_limitB\t\n\x07version\"\xec\x07\n*GetTokenPreProgrammedDistributionsResponse\x12\x80\x01\n\x02v0\x18\x01 \x01(\x0b\x32r.org.dash.platform.dapi.v0.GetTokenPreProgrammedDistributionsResponse.GetTokenPreProgrammedDistributionsResponseV0H\x00\x1a\xaf\x06\n,GetTokenPreProgrammedDistributionsResponseV0\x12\xa5\x01\n\x13token_distributions\x18\x01 \x01(\x0b\x32\x85\x01.org.dash.platform.dapi.v0.GetTokenPreProgrammedDistributionsResponse.GetTokenPreProgrammedDistributionsResponseV0.TokenDistributionsH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a>\n\x16TokenDistributionEntry\x12\x14\n\x0crecipient_id\x18\x01 \x01(\x0c\x12\x0e\n\x06\x61mount\x18\x02 \x01(\x04\x1a\xd4\x01\n\x1bTokenTimedDistributionEntry\x12\x11\n\ttimestamp\x18\x01 \x01(\x04\x12\xa1\x01\n\rdistributions\x18\x02 \x03(\x0b\x32\x89\x01.org.dash.platform.dapi.v0.GetTokenPreProgrammedDistributionsResponse.GetTokenPreProgrammedDistributionsResponseV0.TokenDistributionEntry\x1a\xc3\x01\n\x12TokenDistributions\x12\xac\x01\n\x13token_distributions\x18\x01 \x03(\x0b\x32\x8e\x01.org.dash.platform.dapi.v0.GetTokenPreProgrammedDistributionsResponse.GetTokenPreProgrammedDistributionsResponseV0.TokenTimedDistributionEntryB\x08\n\x06resultB\t\n\x07version\"\x82\x04\n-GetTokenPerpetualDistributionLastClaimRequest\x12\x86\x01\n\x02v0\x18\x01 \x01(\x0b\x32x.org.dash.platform.dapi.v0.GetTokenPerpetualDistributionLastClaimRequest.GetTokenPerpetualDistributionLastClaimRequestV0H\x00\x1aI\n\x11\x43ontractTokenInfo\x12\x13\n\x0b\x63ontract_id\x18\x01 \x01(\x0c\x12\x1f\n\x17token_contract_position\x18\x02 \x01(\r\x1a\xf1\x01\n/GetTokenPerpetualDistributionLastClaimRequestV0\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12v\n\rcontract_info\x18\x02 \x01(\x0b\x32Z.org.dash.platform.dapi.v0.GetTokenPerpetualDistributionLastClaimRequest.ContractTokenInfoH\x00\x88\x01\x01\x12\x13\n\x0bidentity_id\x18\x04 \x01(\x0c\x12\r\n\x05prove\x18\x05 \x01(\x08\x42\x10\n\x0e_contract_infoB\t\n\x07version\"\x93\x05\n.GetTokenPerpetualDistributionLastClaimResponse\x12\x88\x01\n\x02v0\x18\x01 \x01(\x0b\x32z.org.dash.platform.dapi.v0.GetTokenPerpetualDistributionLastClaimResponse.GetTokenPerpetualDistributionLastClaimResponseV0H\x00\x1a\xca\x03\n0GetTokenPerpetualDistributionLastClaimResponseV0\x12\x9f\x01\n\nlast_claim\x18\x01 \x01(\x0b\x32\x88\x01.org.dash.platform.dapi.v0.GetTokenPerpetualDistributionLastClaimResponse.GetTokenPerpetualDistributionLastClaimResponseV0.LastClaimInfoH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1ax\n\rLastClaimInfo\x12\x1a\n\x0ctimestamp_ms\x18\x01 \x01(\x04\x42\x02\x30\x01H\x00\x12\x1a\n\x0c\x62lock_height\x18\x02 \x01(\x04\x42\x02\x30\x01H\x00\x12\x0f\n\x05\x65poch\x18\x03 \x01(\rH\x00\x12\x13\n\traw_bytes\x18\x04 \x01(\x0cH\x00\x42\t\n\x07paid_atB\x08\n\x06resultB\t\n\x07version\"\xca\x01\n\x1aGetTokenTotalSupplyRequest\x12`\n\x02v0\x18\x01 \x01(\x0b\x32R.org.dash.platform.dapi.v0.GetTokenTotalSupplyRequest.GetTokenTotalSupplyRequestV0H\x00\x1a?\n\x1cGetTokenTotalSupplyRequestV0\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xaf\x04\n\x1bGetTokenTotalSupplyResponse\x12\x62\n\x02v0\x18\x01 \x01(\x0b\x32T.org.dash.platform.dapi.v0.GetTokenTotalSupplyResponse.GetTokenTotalSupplyResponseV0H\x00\x1a\xa0\x03\n\x1dGetTokenTotalSupplyResponseV0\x12\x88\x01\n\x12token_total_supply\x18\x01 \x01(\x0b\x32j.org.dash.platform.dapi.v0.GetTokenTotalSupplyResponse.GetTokenTotalSupplyResponseV0.TokenTotalSupplyEntryH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1ax\n\x15TokenTotalSupplyEntry\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12\x30\n(total_aggregated_amount_in_user_accounts\x18\x02 \x01(\x04\x12\x1b\n\x13total_system_amount\x18\x03 \x01(\x04\x42\x08\n\x06resultB\t\n\x07version\"\xd2\x01\n\x13GetGroupInfoRequest\x12R\n\x02v0\x18\x01 \x01(\x0b\x32\x44.org.dash.platform.dapi.v0.GetGroupInfoRequest.GetGroupInfoRequestV0H\x00\x1a\\\n\x15GetGroupInfoRequestV0\x12\x13\n\x0b\x63ontract_id\x18\x01 \x01(\x0c\x12\x1f\n\x17group_contract_position\x18\x02 \x01(\r\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\t\n\x07version\"\xd4\x05\n\x14GetGroupInfoResponse\x12T\n\x02v0\x18\x01 \x01(\x0b\x32\x46.org.dash.platform.dapi.v0.GetGroupInfoResponse.GetGroupInfoResponseV0H\x00\x1a\xda\x04\n\x16GetGroupInfoResponseV0\x12\x66\n\ngroup_info\x18\x01 \x01(\x0b\x32P.org.dash.platform.dapi.v0.GetGroupInfoResponse.GetGroupInfoResponseV0.GroupInfoH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x04 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x34\n\x10GroupMemberEntry\x12\x11\n\tmember_id\x18\x01 \x01(\x0c\x12\r\n\x05power\x18\x02 \x01(\r\x1a\x98\x01\n\x0eGroupInfoEntry\x12h\n\x07members\x18\x01 \x03(\x0b\x32W.org.dash.platform.dapi.v0.GetGroupInfoResponse.GetGroupInfoResponseV0.GroupMemberEntry\x12\x1c\n\x14group_required_power\x18\x02 \x01(\r\x1a\x8a\x01\n\tGroupInfo\x12n\n\ngroup_info\x18\x01 \x01(\x0b\x32U.org.dash.platform.dapi.v0.GetGroupInfoResponse.GetGroupInfoResponseV0.GroupInfoEntryH\x00\x88\x01\x01\x42\r\n\x0b_group_infoB\x08\n\x06resultB\t\n\x07version\"\xed\x03\n\x14GetGroupInfosRequest\x12T\n\x02v0\x18\x01 \x01(\x0b\x32\x46.org.dash.platform.dapi.v0.GetGroupInfosRequest.GetGroupInfosRequestV0H\x00\x1au\n\x1cStartAtGroupContractPosition\x12%\n\x1dstart_group_contract_position\x18\x01 \x01(\r\x12.\n&start_group_contract_position_included\x18\x02 \x01(\x08\x1a\xfc\x01\n\x16GetGroupInfosRequestV0\x12\x13\n\x0b\x63ontract_id\x18\x01 \x01(\x0c\x12{\n start_at_group_contract_position\x18\x02 \x01(\x0b\x32L.org.dash.platform.dapi.v0.GetGroupInfosRequest.StartAtGroupContractPositionH\x00\x88\x01\x01\x12\x12\n\x05\x63ount\x18\x03 \x01(\rH\x01\x88\x01\x01\x12\r\n\x05prove\x18\x04 \x01(\x08\x42#\n!_start_at_group_contract_positionB\x08\n\x06_countB\t\n\x07version\"\xff\x05\n\x15GetGroupInfosResponse\x12V\n\x02v0\x18\x01 \x01(\x0b\x32H.org.dash.platform.dapi.v0.GetGroupInfosResponse.GetGroupInfosResponseV0H\x00\x1a\x82\x05\n\x17GetGroupInfosResponseV0\x12j\n\x0bgroup_infos\x18\x01 \x01(\x0b\x32S.org.dash.platform.dapi.v0.GetGroupInfosResponse.GetGroupInfosResponseV0.GroupInfosH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x04 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x34\n\x10GroupMemberEntry\x12\x11\n\tmember_id\x18\x01 \x01(\x0c\x12\r\n\x05power\x18\x02 \x01(\r\x1a\xc3\x01\n\x16GroupPositionInfoEntry\x12\x1f\n\x17group_contract_position\x18\x01 \x01(\r\x12j\n\x07members\x18\x02 \x03(\x0b\x32Y.org.dash.platform.dapi.v0.GetGroupInfosResponse.GetGroupInfosResponseV0.GroupMemberEntry\x12\x1c\n\x14group_required_power\x18\x03 \x01(\r\x1a\x82\x01\n\nGroupInfos\x12t\n\x0bgroup_infos\x18\x01 \x03(\x0b\x32_.org.dash.platform.dapi.v0.GetGroupInfosResponse.GetGroupInfosResponseV0.GroupPositionInfoEntryB\x08\n\x06resultB\t\n\x07version\"\xbe\x04\n\x16GetGroupActionsRequest\x12X\n\x02v0\x18\x01 \x01(\x0b\x32J.org.dash.platform.dapi.v0.GetGroupActionsRequest.GetGroupActionsRequestV0H\x00\x1aL\n\x0fStartAtActionId\x12\x17\n\x0fstart_action_id\x18\x01 \x01(\x0c\x12 \n\x18start_action_id_included\x18\x02 \x01(\x08\x1a\xc8\x02\n\x18GetGroupActionsRequestV0\x12\x13\n\x0b\x63ontract_id\x18\x01 \x01(\x0c\x12\x1f\n\x17group_contract_position\x18\x02 \x01(\r\x12N\n\x06status\x18\x03 \x01(\x0e\x32>.org.dash.platform.dapi.v0.GetGroupActionsRequest.ActionStatus\x12\x62\n\x12start_at_action_id\x18\x04 \x01(\x0b\x32\x41.org.dash.platform.dapi.v0.GetGroupActionsRequest.StartAtActionIdH\x00\x88\x01\x01\x12\x12\n\x05\x63ount\x18\x05 \x01(\rH\x01\x88\x01\x01\x12\r\n\x05prove\x18\x06 \x01(\x08\x42\x15\n\x13_start_at_action_idB\x08\n\x06_count\"&\n\x0c\x41\x63tionStatus\x12\n\n\x06\x41\x43TIVE\x10\x00\x12\n\n\x06\x43LOSED\x10\x01\x42\t\n\x07version\"\xd6\x1e\n\x17GetGroupActionsResponse\x12Z\n\x02v0\x18\x01 \x01(\x0b\x32L.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0H\x00\x1a\xd3\x1d\n\x19GetGroupActionsResponseV0\x12r\n\rgroup_actions\x18\x01 \x01(\x0b\x32Y.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.GroupActionsH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a[\n\tMintEvent\x12\x0e\n\x06\x61mount\x18\x01 \x01(\x04\x12\x14\n\x0crecipient_id\x18\x02 \x01(\x0c\x12\x18\n\x0bpublic_note\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_public_note\x1a[\n\tBurnEvent\x12\x0e\n\x06\x61mount\x18\x01 \x01(\x04\x12\x14\n\x0c\x62urn_from_id\x18\x02 \x01(\x0c\x12\x18\n\x0bpublic_note\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_public_note\x1aJ\n\x0b\x46reezeEvent\x12\x11\n\tfrozen_id\x18\x01 \x01(\x0c\x12\x18\n\x0bpublic_note\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_public_note\x1aL\n\rUnfreezeEvent\x12\x11\n\tfrozen_id\x18\x01 \x01(\x0c\x12\x18\n\x0bpublic_note\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_public_note\x1a\x66\n\x17\x44\x65stroyFrozenFundsEvent\x12\x11\n\tfrozen_id\x18\x01 \x01(\x0c\x12\x0e\n\x06\x61mount\x18\x02 \x01(\x04\x12\x18\n\x0bpublic_note\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_public_note\x1a\x64\n\x13SharedEncryptedNote\x12\x18\n\x10sender_key_index\x18\x01 \x01(\r\x12\x1b\n\x13recipient_key_index\x18\x02 \x01(\r\x12\x16\n\x0e\x65ncrypted_data\x18\x03 \x01(\x0c\x1a{\n\x15PersonalEncryptedNote\x12!\n\x19root_encryption_key_index\x18\x01 \x01(\r\x12\'\n\x1f\x64\x65rivation_encryption_key_index\x18\x02 \x01(\r\x12\x16\n\x0e\x65ncrypted_data\x18\x03 \x01(\x0c\x1a\xe9\x01\n\x14\x45mergencyActionEvent\x12\x81\x01\n\x0b\x61\x63tion_type\x18\x01 \x01(\x0e\x32l.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.EmergencyActionEvent.ActionType\x12\x18\n\x0bpublic_note\x18\x02 \x01(\tH\x00\x88\x01\x01\"#\n\nActionType\x12\t\n\x05PAUSE\x10\x00\x12\n\n\x06RESUME\x10\x01\x42\x0e\n\x0c_public_note\x1a\x64\n\x16TokenConfigUpdateEvent\x12 \n\x18token_config_update_item\x18\x01 \x01(\x0c\x12\x18\n\x0bpublic_note\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_public_note\x1a\xe6\x03\n\x1eUpdateDirectPurchasePriceEvent\x12\x15\n\x0b\x66ixed_price\x18\x01 \x01(\x04H\x00\x12\x95\x01\n\x0evariable_price\x18\x02 \x01(\x0b\x32{.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.UpdateDirectPurchasePriceEvent.PricingScheduleH\x00\x12\x18\n\x0bpublic_note\x18\x03 \x01(\tH\x01\x88\x01\x01\x1a\x33\n\x10PriceForQuantity\x12\x10\n\x08quantity\x18\x01 \x01(\x04\x12\r\n\x05price\x18\x02 \x01(\x04\x1a\xac\x01\n\x0fPricingSchedule\x12\x98\x01\n\x12price_for_quantity\x18\x01 \x03(\x0b\x32|.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.UpdateDirectPurchasePriceEvent.PriceForQuantityB\x07\n\x05priceB\x0e\n\x0c_public_note\x1a\xfc\x02\n\x10GroupActionEvent\x12n\n\x0btoken_event\x18\x01 \x01(\x0b\x32W.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.TokenEventH\x00\x12t\n\x0e\x64ocument_event\x18\x02 \x01(\x0b\x32Z.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.DocumentEventH\x00\x12t\n\x0e\x63ontract_event\x18\x03 \x01(\x0b\x32Z.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.ContractEventH\x00\x42\x0c\n\nevent_type\x1a\x8b\x01\n\rDocumentEvent\x12r\n\x06\x63reate\x18\x01 \x01(\x0b\x32`.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.DocumentCreateEventH\x00\x42\x06\n\x04type\x1a/\n\x13\x44ocumentCreateEvent\x12\x18\n\x10\x63reated_document\x18\x01 \x01(\x0c\x1a/\n\x13\x43ontractUpdateEvent\x12\x18\n\x10updated_contract\x18\x01 \x01(\x0c\x1a\x8b\x01\n\rContractEvent\x12r\n\x06update\x18\x01 \x01(\x0b\x32`.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.ContractUpdateEventH\x00\x42\x06\n\x04type\x1a\xd1\x07\n\nTokenEvent\x12\x66\n\x04mint\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.MintEventH\x00\x12\x66\n\x04\x62urn\x18\x02 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.BurnEventH\x00\x12j\n\x06\x66reeze\x18\x03 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.FreezeEventH\x00\x12n\n\x08unfreeze\x18\x04 \x01(\x0b\x32Z.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.UnfreezeEventH\x00\x12\x84\x01\n\x14\x64\x65stroy_frozen_funds\x18\x05 \x01(\x0b\x32\x64.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.DestroyFrozenFundsEventH\x00\x12}\n\x10\x65mergency_action\x18\x06 \x01(\x0b\x32\x61.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.EmergencyActionEventH\x00\x12\x82\x01\n\x13token_config_update\x18\x07 \x01(\x0b\x32\x63.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.TokenConfigUpdateEventH\x00\x12\x83\x01\n\x0cupdate_price\x18\x08 \x01(\x0b\x32k.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.UpdateDirectPurchasePriceEventH\x00\x42\x06\n\x04type\x1a\x93\x01\n\x10GroupActionEntry\x12\x11\n\taction_id\x18\x01 \x01(\x0c\x12l\n\x05\x65vent\x18\x02 \x01(\x0b\x32].org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.GroupActionEvent\x1a\x84\x01\n\x0cGroupActions\x12t\n\rgroup_actions\x18\x01 \x03(\x0b\x32].org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.GroupActionEntryB\x08\n\x06resultB\t\n\x07version\"\x88\x03\n\x1cGetGroupActionSignersRequest\x12\x64\n\x02v0\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetGroupActionSignersRequest.GetGroupActionSignersRequestV0H\x00\x1a\xce\x01\n\x1eGetGroupActionSignersRequestV0\x12\x13\n\x0b\x63ontract_id\x18\x01 \x01(\x0c\x12\x1f\n\x17group_contract_position\x18\x02 \x01(\r\x12T\n\x06status\x18\x03 \x01(\x0e\x32\x44.org.dash.platform.dapi.v0.GetGroupActionSignersRequest.ActionStatus\x12\x11\n\taction_id\x18\x04 \x01(\x0c\x12\r\n\x05prove\x18\x05 \x01(\x08\"&\n\x0c\x41\x63tionStatus\x12\n\n\x06\x41\x43TIVE\x10\x00\x12\n\n\x06\x43LOSED\x10\x01\x42\t\n\x07version\"\x8b\x05\n\x1dGetGroupActionSignersResponse\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetGroupActionSignersResponse.GetGroupActionSignersResponseV0H\x00\x1a\xf6\x03\n\x1fGetGroupActionSignersResponseV0\x12\x8b\x01\n\x14group_action_signers\x18\x01 \x01(\x0b\x32k.org.dash.platform.dapi.v0.GetGroupActionSignersResponse.GetGroupActionSignersResponseV0.GroupActionSignersH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x35\n\x11GroupActionSigner\x12\x11\n\tsigner_id\x18\x01 \x01(\x0c\x12\r\n\x05power\x18\x02 \x01(\r\x1a\x91\x01\n\x12GroupActionSigners\x12{\n\x07signers\x18\x01 \x03(\x0b\x32j.org.dash.platform.dapi.v0.GetGroupActionSignersResponse.GetGroupActionSignersResponseV0.GroupActionSignerB\x08\n\x06resultB\t\n\x07version\"\xb5\x01\n\x15GetAddressInfoRequest\x12V\n\x02v0\x18\x01 \x01(\x0b\x32H.org.dash.platform.dapi.v0.GetAddressInfoRequest.GetAddressInfoRequestV0H\x00\x1a\x39\n\x17GetAddressInfoRequestV0\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\x85\x01\n\x10\x41\x64\x64ressInfoEntry\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0c\x12J\n\x11\x62\x61lance_and_nonce\x18\x02 \x01(\x0b\x32*.org.dash.platform.dapi.v0.BalanceAndNonceH\x00\x88\x01\x01\x42\x14\n\x12_balance_and_nonce\"1\n\x0f\x42\x61lanceAndNonce\x12\x0f\n\x07\x62\x61lance\x18\x01 \x01(\x04\x12\r\n\x05nonce\x18\x02 \x01(\r\"_\n\x12\x41\x64\x64ressInfoEntries\x12I\n\x14\x61\x64\x64ress_info_entries\x18\x01 \x03(\x0b\x32+.org.dash.platform.dapi.v0.AddressInfoEntry\"m\n\x14\x41\x64\x64ressBalanceChange\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0c\x12\x19\n\x0bset_balance\x18\x02 \x01(\x04\x42\x02\x30\x01H\x00\x12\x1c\n\x0e\x61\x64\x64_to_balance\x18\x03 \x01(\x04\x42\x02\x30\x01H\x00\x42\x0b\n\toperation\"x\n\x1a\x42lockAddressBalanceChanges\x12\x18\n\x0c\x62lock_height\x18\x01 \x01(\x04\x42\x02\x30\x01\x12@\n\x07\x63hanges\x18\x02 \x03(\x0b\x32/.org.dash.platform.dapi.v0.AddressBalanceChange\"k\n\x1b\x41\x64\x64ressBalanceUpdateEntries\x12L\n\rblock_changes\x18\x01 \x03(\x0b\x32\x35.org.dash.platform.dapi.v0.BlockAddressBalanceChanges\"\xe1\x02\n\x16GetAddressInfoResponse\x12X\n\x02v0\x18\x01 \x01(\x0b\x32J.org.dash.platform.dapi.v0.GetAddressInfoResponse.GetAddressInfoResponseV0H\x00\x1a\xe1\x01\n\x18GetAddressInfoResponseV0\x12I\n\x12\x61\x64\x64ress_info_entry\x18\x01 \x01(\x0b\x32+.org.dash.platform.dapi.v0.AddressInfoEntryH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xc3\x01\n\x18GetAddressesInfosRequest\x12\\\n\x02v0\x18\x01 \x01(\x0b\x32N.org.dash.platform.dapi.v0.GetAddressesInfosRequest.GetAddressesInfosRequestV0H\x00\x1a>\n\x1aGetAddressesInfosRequestV0\x12\x11\n\taddresses\x18\x01 \x03(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xf1\x02\n\x19GetAddressesInfosResponse\x12^\n\x02v0\x18\x01 \x01(\x0b\x32P.org.dash.platform.dapi.v0.GetAddressesInfosResponse.GetAddressesInfosResponseV0H\x00\x1a\xe8\x01\n\x1bGetAddressesInfosResponseV0\x12M\n\x14\x61\x64\x64ress_info_entries\x18\x01 \x01(\x0b\x32-.org.dash.platform.dapi.v0.AddressInfoEntriesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xb5\x01\n\x1dGetAddressesTrunkStateRequest\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetAddressesTrunkStateRequest.GetAddressesTrunkStateRequestV0H\x00\x1a!\n\x1fGetAddressesTrunkStateRequestV0B\t\n\x07version\"\xaa\x02\n\x1eGetAddressesTrunkStateResponse\x12h\n\x02v0\x18\x01 \x01(\x0b\x32Z.org.dash.platform.dapi.v0.GetAddressesTrunkStateResponse.GetAddressesTrunkStateResponseV0H\x00\x1a\x92\x01\n GetAddressesTrunkStateResponseV0\x12/\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.Proof\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\t\n\x07version\"\xf0\x01\n\x1eGetAddressesBranchStateRequest\x12h\n\x02v0\x18\x01 \x01(\x0b\x32Z.org.dash.platform.dapi.v0.GetAddressesBranchStateRequest.GetAddressesBranchStateRequestV0H\x00\x1aY\n GetAddressesBranchStateRequestV0\x12\x0b\n\x03key\x18\x01 \x01(\x0c\x12\r\n\x05\x64\x65pth\x18\x02 \x01(\r\x12\x19\n\x11\x63heckpoint_height\x18\x03 \x01(\x04\x42\t\n\x07version\"\xd1\x01\n\x1fGetAddressesBranchStateResponse\x12j\n\x02v0\x18\x01 \x01(\x0b\x32\\.org.dash.platform.dapi.v0.GetAddressesBranchStateResponse.GetAddressesBranchStateResponseV0H\x00\x1a\x37\n!GetAddressesBranchStateResponseV0\x12\x12\n\nmerk_proof\x18\x02 \x01(\x0c\x42\t\n\x07version\"\x9e\x02\n%GetRecentAddressBalanceChangesRequest\x12v\n\x02v0\x18\x01 \x01(\x0b\x32h.org.dash.platform.dapi.v0.GetRecentAddressBalanceChangesRequest.GetRecentAddressBalanceChangesRequestV0H\x00\x1ar\n\'GetRecentAddressBalanceChangesRequestV0\x12\x18\n\x0cstart_height\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\r\n\x05prove\x18\x02 \x01(\x08\x12\x1e\n\x16start_height_exclusive\x18\x03 \x01(\x08\x42\t\n\x07version\"\xb8\x03\n&GetRecentAddressBalanceChangesResponse\x12x\n\x02v0\x18\x01 \x01(\x0b\x32j.org.dash.platform.dapi.v0.GetRecentAddressBalanceChangesResponse.GetRecentAddressBalanceChangesResponseV0H\x00\x1a\x88\x02\n(GetRecentAddressBalanceChangesResponseV0\x12`\n\x1e\x61\x64\x64ress_balance_update_entries\x18\x01 \x01(\x0b\x32\x36.org.dash.platform.dapi.v0.AddressBalanceUpdateEntriesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"G\n\x16\x42lockHeightCreditEntry\x12\x18\n\x0c\x62lock_height\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\x13\n\x07\x63redits\x18\x02 \x01(\x04\x42\x02\x30\x01\"\xb0\x01\n\x1d\x43ompactedAddressBalanceChange\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0c\x12\x19\n\x0bset_credits\x18\x02 \x01(\x04\x42\x02\x30\x01H\x00\x12V\n\x19\x61\x64\x64_to_credits_operations\x18\x03 \x01(\x0b\x32\x31.org.dash.platform.dapi.v0.AddToCreditsOperationsH\x00\x42\x0b\n\toperation\"\\\n\x16\x41\x64\x64ToCreditsOperations\x12\x42\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x31.org.dash.platform.dapi.v0.BlockHeightCreditEntry\"\xae\x01\n#CompactedBlockAddressBalanceChanges\x12\x1e\n\x12start_block_height\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\x1c\n\x10\x65nd_block_height\x18\x02 \x01(\x04\x42\x02\x30\x01\x12I\n\x07\x63hanges\x18\x03 \x03(\x0b\x32\x38.org.dash.platform.dapi.v0.CompactedAddressBalanceChange\"\x87\x01\n$CompactedAddressBalanceUpdateEntries\x12_\n\x17\x63ompacted_block_changes\x18\x01 \x03(\x0b\x32>.org.dash.platform.dapi.v0.CompactedBlockAddressBalanceChanges\"\xa9\x02\n.GetRecentCompactedAddressBalanceChangesRequest\x12\x88\x01\n\x02v0\x18\x01 \x01(\x0b\x32z.org.dash.platform.dapi.v0.GetRecentCompactedAddressBalanceChangesRequest.GetRecentCompactedAddressBalanceChangesRequestV0H\x00\x1a\x61\n0GetRecentCompactedAddressBalanceChangesRequestV0\x12\x1e\n\x12start_block_height\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xf0\x03\n/GetRecentCompactedAddressBalanceChangesResponse\x12\x8a\x01\n\x02v0\x18\x01 \x01(\x0b\x32|.org.dash.platform.dapi.v0.GetRecentCompactedAddressBalanceChangesResponse.GetRecentCompactedAddressBalanceChangesResponseV0H\x00\x1a\xa4\x02\n1GetRecentCompactedAddressBalanceChangesResponseV0\x12s\n(compacted_address_balance_update_entries\x18\x01 \x01(\x0b\x32?.org.dash.platform.dapi.v0.CompactedAddressBalanceUpdateEntriesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xf4\x01\n GetShieldedEncryptedNotesRequest\x12l\n\x02v0\x18\x01 \x01(\x0b\x32^.org.dash.platform.dapi.v0.GetShieldedEncryptedNotesRequest.GetShieldedEncryptedNotesRequestV0H\x00\x1aW\n\"GetShieldedEncryptedNotesRequestV0\x12\x13\n\x0bstart_index\x18\x01 \x01(\x04\x12\r\n\x05\x63ount\x18\x02 \x01(\r\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\t\n\x07version\"\xbc\x05\n!GetShieldedEncryptedNotesResponse\x12n\n\x02v0\x18\x01 \x01(\x0b\x32`.org.dash.platform.dapi.v0.GetShieldedEncryptedNotesResponse.GetShieldedEncryptedNotesResponseV0H\x00\x1a\x9b\x04\n#GetShieldedEncryptedNotesResponseV0\x12\x8a\x01\n\x0f\x65ncrypted_notes\x18\x01 \x01(\x0b\x32o.org.dash.platform.dapi.v0.GetShieldedEncryptedNotesResponse.GetShieldedEncryptedNotesResponseV0.EncryptedNotesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1aW\n\rEncryptedNote\x12\x11\n\tnullifier\x18\x01 \x01(\x0c\x12\x0b\n\x03\x63mx\x18\x02 \x01(\x0c\x12\x16\n\x0e\x65ncrypted_note\x18\x03 \x01(\x0c\x12\x0e\n\x06\x63v_net\x18\x04 \x01(\x0c\x1a\x91\x01\n\x0e\x45ncryptedNotes\x12\x7f\n\x07\x65ntries\x18\x01 \x03(\x0b\x32n.org.dash.platform.dapi.v0.GetShieldedEncryptedNotesResponse.GetShieldedEncryptedNotesResponseV0.EncryptedNoteB\x08\n\x06resultB\t\n\x07version\"\xb4\x01\n\x19GetShieldedAnchorsRequest\x12^\n\x02v0\x18\x01 \x01(\x0b\x32P.org.dash.platform.dapi.v0.GetShieldedAnchorsRequest.GetShieldedAnchorsRequestV0H\x00\x1a,\n\x1bGetShieldedAnchorsRequestV0\x12\r\n\x05prove\x18\x01 \x01(\x08\x42\t\n\x07version\"\xb1\x03\n\x1aGetShieldedAnchorsResponse\x12`\n\x02v0\x18\x01 \x01(\x0b\x32R.org.dash.platform.dapi.v0.GetShieldedAnchorsResponse.GetShieldedAnchorsResponseV0H\x00\x1a\xa5\x02\n\x1cGetShieldedAnchorsResponseV0\x12m\n\x07\x61nchors\x18\x01 \x01(\x0b\x32Z.org.dash.platform.dapi.v0.GetShieldedAnchorsResponse.GetShieldedAnchorsResponseV0.AnchorsH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x1a\n\x07\x41nchors\x12\x0f\n\x07\x61nchors\x18\x01 \x03(\x0c\x42\x08\n\x06resultB\t\n\x07version\"\xd8\x01\n\"GetMostRecentShieldedAnchorRequest\x12p\n\x02v0\x18\x01 \x01(\x0b\x32\x62.org.dash.platform.dapi.v0.GetMostRecentShieldedAnchorRequest.GetMostRecentShieldedAnchorRequestV0H\x00\x1a\x35\n$GetMostRecentShieldedAnchorRequestV0\x12\r\n\x05prove\x18\x01 \x01(\x08\x42\t\n\x07version\"\xdc\x02\n#GetMostRecentShieldedAnchorResponse\x12r\n\x02v0\x18\x01 \x01(\x0b\x32\x64.org.dash.platform.dapi.v0.GetMostRecentShieldedAnchorResponse.GetMostRecentShieldedAnchorResponseV0H\x00\x1a\xb5\x01\n%GetMostRecentShieldedAnchorResponseV0\x12\x10\n\x06\x61nchor\x18\x01 \x01(\x0cH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xbc\x01\n\x1bGetShieldedPoolStateRequest\x12\x62\n\x02v0\x18\x01 \x01(\x0b\x32T.org.dash.platform.dapi.v0.GetShieldedPoolStateRequest.GetShieldedPoolStateRequestV0H\x00\x1a.\n\x1dGetShieldedPoolStateRequestV0\x12\r\n\x05prove\x18\x01 \x01(\x08\x42\t\n\x07version\"\xcb\x02\n\x1cGetShieldedPoolStateResponse\x12\x64\n\x02v0\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetShieldedPoolStateResponse.GetShieldedPoolStateResponseV0H\x00\x1a\xb9\x01\n\x1eGetShieldedPoolStateResponseV0\x12\x1b\n\rtotal_balance\x18\x01 \x01(\x04\x42\x02\x30\x01H\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xc0\x01\n\x1cGetShieldedNotesCountRequest\x12\x64\n\x02v0\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetShieldedNotesCountRequest.GetShieldedNotesCountRequestV0H\x00\x1a/\n\x1eGetShieldedNotesCountRequestV0\x12\r\n\x05prove\x18\x01 \x01(\x08\x42\t\n\x07version\"\xd3\x02\n\x1dGetShieldedNotesCountResponse\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetShieldedNotesCountResponse.GetShieldedNotesCountResponseV0H\x00\x1a\xbe\x01\n\x1fGetShieldedNotesCountResponseV0\x12\x1f\n\x11total_notes_count\x18\x01 \x01(\x04\x42\x02\x30\x01H\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xd4\x01\n\x1cGetShieldedNullifiersRequest\x12\x64\n\x02v0\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetShieldedNullifiersRequest.GetShieldedNullifiersRequestV0H\x00\x1a\x43\n\x1eGetShieldedNullifiersRequestV0\x12\x12\n\nnullifiers\x18\x01 \x03(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\x86\x05\n\x1dGetShieldedNullifiersResponse\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetShieldedNullifiersResponse.GetShieldedNullifiersResponseV0H\x00\x1a\xf1\x03\n\x1fGetShieldedNullifiersResponseV0\x12\x88\x01\n\x12nullifier_statuses\x18\x01 \x01(\x0b\x32j.org.dash.platform.dapi.v0.GetShieldedNullifiersResponse.GetShieldedNullifiersResponseV0.NullifierStatusesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x36\n\x0fNullifierStatus\x12\x11\n\tnullifier\x18\x01 \x01(\x0c\x12\x10\n\x08is_spent\x18\x02 \x01(\x08\x1a\x8e\x01\n\x11NullifierStatuses\x12y\n\x07\x65ntries\x18\x01 \x03(\x0b\x32h.org.dash.platform.dapi.v0.GetShieldedNullifiersResponse.GetShieldedNullifiersResponseV0.NullifierStatusB\x08\n\x06resultB\t\n\x07version*Z\n\nKeyPurpose\x12\x12\n\x0e\x41UTHENTICATION\x10\x00\x12\x0e\n\nENCRYPTION\x10\x01\x12\x0e\n\nDECRYPTION\x10\x02\x12\x0c\n\x08TRANSFER\x10\x03\x12\n\n\x06VOTING\x10\x05\x32\xce\x44\n\x08Platform\x12\x93\x01\n\x18\x62roadcastStateTransition\x12:.org.dash.platform.dapi.v0.BroadcastStateTransitionRequest\x1a;.org.dash.platform.dapi.v0.BroadcastStateTransitionResponse\x12l\n\x0bgetIdentity\x12-.org.dash.platform.dapi.v0.GetIdentityRequest\x1a..org.dash.platform.dapi.v0.GetIdentityResponse\x12x\n\x0fgetIdentityKeys\x12\x31.org.dash.platform.dapi.v0.GetIdentityKeysRequest\x1a\x32.org.dash.platform.dapi.v0.GetIdentityKeysResponse\x12\x96\x01\n\x19getIdentitiesContractKeys\x12;.org.dash.platform.dapi.v0.GetIdentitiesContractKeysRequest\x1a<.org.dash.platform.dapi.v0.GetIdentitiesContractKeysResponse\x12{\n\x10getIdentityNonce\x12\x32.org.dash.platform.dapi.v0.GetIdentityNonceRequest\x1a\x33.org.dash.platform.dapi.v0.GetIdentityNonceResponse\x12\x93\x01\n\x18getIdentityContractNonce\x12:.org.dash.platform.dapi.v0.GetIdentityContractNonceRequest\x1a;.org.dash.platform.dapi.v0.GetIdentityContractNonceResponse\x12\x81\x01\n\x12getIdentityBalance\x12\x34.org.dash.platform.dapi.v0.GetIdentityBalanceRequest\x1a\x35.org.dash.platform.dapi.v0.GetIdentityBalanceResponse\x12\x8a\x01\n\x15getIdentitiesBalances\x12\x37.org.dash.platform.dapi.v0.GetIdentitiesBalancesRequest\x1a\x38.org.dash.platform.dapi.v0.GetIdentitiesBalancesResponse\x12\xa2\x01\n\x1dgetIdentityBalanceAndRevision\x12?.org.dash.platform.dapi.v0.GetIdentityBalanceAndRevisionRequest\x1a@.org.dash.platform.dapi.v0.GetIdentityBalanceAndRevisionResponse\x12\xaf\x01\n#getEvonodesProposedEpochBlocksByIds\x12\x45.org.dash.platform.dapi.v0.GetEvonodesProposedEpochBlocksByIdsRequest\x1a\x41.org.dash.platform.dapi.v0.GetEvonodesProposedEpochBlocksResponse\x12\xb3\x01\n%getEvonodesProposedEpochBlocksByRange\x12G.org.dash.platform.dapi.v0.GetEvonodesProposedEpochBlocksByRangeRequest\x1a\x41.org.dash.platform.dapi.v0.GetEvonodesProposedEpochBlocksResponse\x12x\n\x0fgetDataContract\x12\x31.org.dash.platform.dapi.v0.GetDataContractRequest\x1a\x32.org.dash.platform.dapi.v0.GetDataContractResponse\x12\x8d\x01\n\x16getDataContractHistory\x12\x38.org.dash.platform.dapi.v0.GetDataContractHistoryRequest\x1a\x39.org.dash.platform.dapi.v0.GetDataContractHistoryResponse\x12{\n\x10getDataContracts\x12\x32.org.dash.platform.dapi.v0.GetDataContractsRequest\x1a\x33.org.dash.platform.dapi.v0.GetDataContractsResponse\x12\x81\x01\n\x12getDocumentHistory\x12\x34.org.dash.platform.dapi.v0.GetDocumentHistoryRequest\x1a\x35.org.dash.platform.dapi.v0.GetDocumentHistoryResponse\x12o\n\x0cgetDocuments\x12..org.dash.platform.dapi.v0.GetDocumentsRequest\x1a/.org.dash.platform.dapi.v0.GetDocumentsResponse\x12\x99\x01\n\x1agetIdentityByPublicKeyHash\x12<.org.dash.platform.dapi.v0.GetIdentityByPublicKeyHashRequest\x1a=.org.dash.platform.dapi.v0.GetIdentityByPublicKeyHashResponse\x12\xb4\x01\n#getIdentityByNonUniquePublicKeyHash\x12\x45.org.dash.platform.dapi.v0.GetIdentityByNonUniquePublicKeyHashRequest\x1a\x46.org.dash.platform.dapi.v0.GetIdentityByNonUniquePublicKeyHashResponse\x12\x9f\x01\n\x1cwaitForStateTransitionResult\x12>.org.dash.platform.dapi.v0.WaitForStateTransitionResultRequest\x1a?.org.dash.platform.dapi.v0.WaitForStateTransitionResultResponse\x12\x81\x01\n\x12getConsensusParams\x12\x34.org.dash.platform.dapi.v0.GetConsensusParamsRequest\x1a\x35.org.dash.platform.dapi.v0.GetConsensusParamsResponse\x12\xa5\x01\n\x1egetProtocolVersionUpgradeState\x12@.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeStateRequest\x1a\x41.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeStateResponse\x12\xb4\x01\n#getProtocolVersionUpgradeVoteStatus\x12\x45.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeVoteStatusRequest\x1a\x46.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeVoteStatusResponse\x12r\n\rgetEpochsInfo\x12/.org.dash.platform.dapi.v0.GetEpochsInfoRequest\x1a\x30.org.dash.platform.dapi.v0.GetEpochsInfoResponse\x12\x8d\x01\n\x16getFinalizedEpochInfos\x12\x38.org.dash.platform.dapi.v0.GetFinalizedEpochInfosRequest\x1a\x39.org.dash.platform.dapi.v0.GetFinalizedEpochInfosResponse\x12\x8a\x01\n\x15getContestedResources\x12\x37.org.dash.platform.dapi.v0.GetContestedResourcesRequest\x1a\x38.org.dash.platform.dapi.v0.GetContestedResourcesResponse\x12\xa2\x01\n\x1dgetContestedResourceVoteState\x12?.org.dash.platform.dapi.v0.GetContestedResourceVoteStateRequest\x1a@.org.dash.platform.dapi.v0.GetContestedResourceVoteStateResponse\x12\xba\x01\n%getContestedResourceVotersForIdentity\x12G.org.dash.platform.dapi.v0.GetContestedResourceVotersForIdentityRequest\x1aH.org.dash.platform.dapi.v0.GetContestedResourceVotersForIdentityResponse\x12\xae\x01\n!getContestedResourceIdentityVotes\x12\x43.org.dash.platform.dapi.v0.GetContestedResourceIdentityVotesRequest\x1a\x44.org.dash.platform.dapi.v0.GetContestedResourceIdentityVotesResponse\x12\x8a\x01\n\x15getVotePollsByEndDate\x12\x37.org.dash.platform.dapi.v0.GetVotePollsByEndDateRequest\x1a\x38.org.dash.platform.dapi.v0.GetVotePollsByEndDateResponse\x12\xa5\x01\n\x1egetPrefundedSpecializedBalance\x12@.org.dash.platform.dapi.v0.GetPrefundedSpecializedBalanceRequest\x1a\x41.org.dash.platform.dapi.v0.GetPrefundedSpecializedBalanceResponse\x12\x96\x01\n\x19getTotalCreditsInPlatform\x12;.org.dash.platform.dapi.v0.GetTotalCreditsInPlatformRequest\x1a<.org.dash.platform.dapi.v0.GetTotalCreditsInPlatformResponse\x12x\n\x0fgetPathElements\x12\x31.org.dash.platform.dapi.v0.GetPathElementsRequest\x1a\x32.org.dash.platform.dapi.v0.GetPathElementsResponse\x12\x66\n\tgetStatus\x12+.org.dash.platform.dapi.v0.GetStatusRequest\x1a,.org.dash.platform.dapi.v0.GetStatusResponse\x12\x8a\x01\n\x15getCurrentQuorumsInfo\x12\x37.org.dash.platform.dapi.v0.GetCurrentQuorumsInfoRequest\x1a\x38.org.dash.platform.dapi.v0.GetCurrentQuorumsInfoResponse\x12\x93\x01\n\x18getIdentityTokenBalances\x12:.org.dash.platform.dapi.v0.GetIdentityTokenBalancesRequest\x1a;.org.dash.platform.dapi.v0.GetIdentityTokenBalancesResponse\x12\x99\x01\n\x1agetIdentitiesTokenBalances\x12<.org.dash.platform.dapi.v0.GetIdentitiesTokenBalancesRequest\x1a=.org.dash.platform.dapi.v0.GetIdentitiesTokenBalancesResponse\x12\x8a\x01\n\x15getIdentityTokenInfos\x12\x37.org.dash.platform.dapi.v0.GetIdentityTokenInfosRequest\x1a\x38.org.dash.platform.dapi.v0.GetIdentityTokenInfosResponse\x12\x90\x01\n\x17getIdentitiesTokenInfos\x12\x39.org.dash.platform.dapi.v0.GetIdentitiesTokenInfosRequest\x1a:.org.dash.platform.dapi.v0.GetIdentitiesTokenInfosResponse\x12{\n\x10getTokenStatuses\x12\x32.org.dash.platform.dapi.v0.GetTokenStatusesRequest\x1a\x33.org.dash.platform.dapi.v0.GetTokenStatusesResponse\x12\x9f\x01\n\x1cgetTokenDirectPurchasePrices\x12>.org.dash.platform.dapi.v0.GetTokenDirectPurchasePricesRequest\x1a?.org.dash.platform.dapi.v0.GetTokenDirectPurchasePricesResponse\x12\x87\x01\n\x14getTokenContractInfo\x12\x36.org.dash.platform.dapi.v0.GetTokenContractInfoRequest\x1a\x37.org.dash.platform.dapi.v0.GetTokenContractInfoResponse\x12\xb1\x01\n\"getTokenPreProgrammedDistributions\x12\x44.org.dash.platform.dapi.v0.GetTokenPreProgrammedDistributionsRequest\x1a\x45.org.dash.platform.dapi.v0.GetTokenPreProgrammedDistributionsResponse\x12\xbd\x01\n&getTokenPerpetualDistributionLastClaim\x12H.org.dash.platform.dapi.v0.GetTokenPerpetualDistributionLastClaimRequest\x1aI.org.dash.platform.dapi.v0.GetTokenPerpetualDistributionLastClaimResponse\x12\x84\x01\n\x13getTokenTotalSupply\x12\x35.org.dash.platform.dapi.v0.GetTokenTotalSupplyRequest\x1a\x36.org.dash.platform.dapi.v0.GetTokenTotalSupplyResponse\x12o\n\x0cgetGroupInfo\x12..org.dash.platform.dapi.v0.GetGroupInfoRequest\x1a/.org.dash.platform.dapi.v0.GetGroupInfoResponse\x12r\n\rgetGroupInfos\x12/.org.dash.platform.dapi.v0.GetGroupInfosRequest\x1a\x30.org.dash.platform.dapi.v0.GetGroupInfosResponse\x12x\n\x0fgetGroupActions\x12\x31.org.dash.platform.dapi.v0.GetGroupActionsRequest\x1a\x32.org.dash.platform.dapi.v0.GetGroupActionsResponse\x12\x8a\x01\n\x15getGroupActionSigners\x12\x37.org.dash.platform.dapi.v0.GetGroupActionSignersRequest\x1a\x38.org.dash.platform.dapi.v0.GetGroupActionSignersResponse\x12u\n\x0egetAddressInfo\x12\x30.org.dash.platform.dapi.v0.GetAddressInfoRequest\x1a\x31.org.dash.platform.dapi.v0.GetAddressInfoResponse\x12~\n\x11getAddressesInfos\x12\x33.org.dash.platform.dapi.v0.GetAddressesInfosRequest\x1a\x34.org.dash.platform.dapi.v0.GetAddressesInfosResponse\x12\x8d\x01\n\x16getAddressesTrunkState\x12\x38.org.dash.platform.dapi.v0.GetAddressesTrunkStateRequest\x1a\x39.org.dash.platform.dapi.v0.GetAddressesTrunkStateResponse\x12\x90\x01\n\x17getAddressesBranchState\x12\x39.org.dash.platform.dapi.v0.GetAddressesBranchStateRequest\x1a:.org.dash.platform.dapi.v0.GetAddressesBranchStateResponse\x12\xa5\x01\n\x1egetRecentAddressBalanceChanges\x12@.org.dash.platform.dapi.v0.GetRecentAddressBalanceChangesRequest\x1a\x41.org.dash.platform.dapi.v0.GetRecentAddressBalanceChangesResponse\x12\xc0\x01\n\'getRecentCompactedAddressBalanceChanges\x12I.org.dash.platform.dapi.v0.GetRecentCompactedAddressBalanceChangesRequest\x1aJ.org.dash.platform.dapi.v0.GetRecentCompactedAddressBalanceChangesResponse\x12\x96\x01\n\x19getShieldedEncryptedNotes\x12;.org.dash.platform.dapi.v0.GetShieldedEncryptedNotesRequest\x1a<.org.dash.platform.dapi.v0.GetShieldedEncryptedNotesResponse\x12\x81\x01\n\x12getShieldedAnchors\x12\x34.org.dash.platform.dapi.v0.GetShieldedAnchorsRequest\x1a\x35.org.dash.platform.dapi.v0.GetShieldedAnchorsResponse\x12\x9c\x01\n\x1bgetMostRecentShieldedAnchor\x12=.org.dash.platform.dapi.v0.GetMostRecentShieldedAnchorRequest\x1a>.org.dash.platform.dapi.v0.GetMostRecentShieldedAnchorResponse\x12\x87\x01\n\x14getShieldedPoolState\x12\x36.org.dash.platform.dapi.v0.GetShieldedPoolStateRequest\x1a\x37.org.dash.platform.dapi.v0.GetShieldedPoolStateResponse\x12\x8a\x01\n\x15getShieldedNotesCount\x12\x37.org.dash.platform.dapi.v0.GetShieldedNotesCountRequest\x1a\x38.org.dash.platform.dapi.v0.GetShieldedNotesCountResponse\x12\x8a\x01\n\x15getShieldedNullifiers\x12\x37.org.dash.platform.dapi.v0.GetShieldedNullifiersRequest\x1a\x38.org.dash.platform.dapi.v0.GetShieldedNullifiersResponseb\x06proto3' + serialized_pb=b'\n\x0eplatform.proto\x12\x19org.dash.platform.dapi.v0\x1a\x1egoogle/protobuf/wrappers.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\x81\x01\n\x05Proof\x12\x15\n\rgrovedb_proof\x18\x01 \x01(\x0c\x12\x13\n\x0bquorum_hash\x18\x02 \x01(\x0c\x12\x11\n\tsignature\x18\x03 \x01(\x0c\x12\r\n\x05round\x18\x04 \x01(\r\x12\x15\n\rblock_id_hash\x18\x05 \x01(\x0c\x12\x13\n\x0bquorum_type\x18\x06 \x01(\r\"\x98\x01\n\x10ResponseMetadata\x12\x12\n\x06height\x18\x01 \x01(\x04\x42\x02\x30\x01\x12 \n\x18\x63ore_chain_locked_height\x18\x02 \x01(\r\x12\r\n\x05\x65poch\x18\x03 \x01(\r\x12\x13\n\x07time_ms\x18\x04 \x01(\x04\x42\x02\x30\x01\x12\x18\n\x10protocol_version\x18\x05 \x01(\r\x12\x10\n\x08\x63hain_id\x18\x06 \x01(\t\"L\n\x1dStateTransitionBroadcastError\x12\x0c\n\x04\x63ode\x18\x01 \x01(\r\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\";\n\x1f\x42roadcastStateTransitionRequest\x12\x18\n\x10state_transition\x18\x01 \x01(\x0c\"\"\n BroadcastStateTransitionResponse\"\xa4\x01\n\x12GetIdentityRequest\x12P\n\x02v0\x18\x01 \x01(\x0b\x32\x42.org.dash.platform.dapi.v0.GetIdentityRequest.GetIdentityRequestV0H\x00\x1a\x31\n\x14GetIdentityRequestV0\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xc1\x01\n\x17GetIdentityNonceRequest\x12Z\n\x02v0\x18\x01 \x01(\x0b\x32L.org.dash.platform.dapi.v0.GetIdentityNonceRequest.GetIdentityNonceRequestV0H\x00\x1a?\n\x19GetIdentityNonceRequestV0\x12\x13\n\x0bidentity_id\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xf6\x01\n\x1fGetIdentityContractNonceRequest\x12j\n\x02v0\x18\x01 \x01(\x0b\x32\\.org.dash.platform.dapi.v0.GetIdentityContractNonceRequest.GetIdentityContractNonceRequestV0H\x00\x1a\\\n!GetIdentityContractNonceRequestV0\x12\x13\n\x0bidentity_id\x18\x01 \x01(\x0c\x12\x13\n\x0b\x63ontract_id\x18\x02 \x01(\x0c\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\t\n\x07version\"\xc0\x01\n\x19GetIdentityBalanceRequest\x12^\n\x02v0\x18\x01 \x01(\x0b\x32P.org.dash.platform.dapi.v0.GetIdentityBalanceRequest.GetIdentityBalanceRequestV0H\x00\x1a\x38\n\x1bGetIdentityBalanceRequestV0\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xec\x01\n$GetIdentityBalanceAndRevisionRequest\x12t\n\x02v0\x18\x01 \x01(\x0b\x32\x66.org.dash.platform.dapi.v0.GetIdentityBalanceAndRevisionRequest.GetIdentityBalanceAndRevisionRequestV0H\x00\x1a\x43\n&GetIdentityBalanceAndRevisionRequestV0\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\x9e\x02\n\x13GetIdentityResponse\x12R\n\x02v0\x18\x01 \x01(\x0b\x32\x44.org.dash.platform.dapi.v0.GetIdentityResponse.GetIdentityResponseV0H\x00\x1a\xa7\x01\n\x15GetIdentityResponseV0\x12\x12\n\x08identity\x18\x01 \x01(\x0cH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xbc\x02\n\x18GetIdentityNonceResponse\x12\\\n\x02v0\x18\x01 \x01(\x0b\x32N.org.dash.platform.dapi.v0.GetIdentityNonceResponse.GetIdentityNonceResponseV0H\x00\x1a\xb6\x01\n\x1aGetIdentityNonceResponseV0\x12\x1c\n\x0eidentity_nonce\x18\x01 \x01(\x04\x42\x02\x30\x01H\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xe5\x02\n GetIdentityContractNonceResponse\x12l\n\x02v0\x18\x01 \x01(\x0b\x32^.org.dash.platform.dapi.v0.GetIdentityContractNonceResponse.GetIdentityContractNonceResponseV0H\x00\x1a\xc7\x01\n\"GetIdentityContractNonceResponseV0\x12%\n\x17identity_contract_nonce\x18\x01 \x01(\x04\x42\x02\x30\x01H\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xbd\x02\n\x1aGetIdentityBalanceResponse\x12`\n\x02v0\x18\x01 \x01(\x0b\x32R.org.dash.platform.dapi.v0.GetIdentityBalanceResponse.GetIdentityBalanceResponseV0H\x00\x1a\xb1\x01\n\x1cGetIdentityBalanceResponseV0\x12\x15\n\x07\x62\x61lance\x18\x01 \x01(\x04\x42\x02\x30\x01H\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xb1\x04\n%GetIdentityBalanceAndRevisionResponse\x12v\n\x02v0\x18\x01 \x01(\x0b\x32h.org.dash.platform.dapi.v0.GetIdentityBalanceAndRevisionResponse.GetIdentityBalanceAndRevisionResponseV0H\x00\x1a\x84\x03\n\'GetIdentityBalanceAndRevisionResponseV0\x12\x9b\x01\n\x14\x62\x61lance_and_revision\x18\x01 \x01(\x0b\x32{.org.dash.platform.dapi.v0.GetIdentityBalanceAndRevisionResponse.GetIdentityBalanceAndRevisionResponseV0.BalanceAndRevisionH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a?\n\x12\x42\x61lanceAndRevision\x12\x13\n\x07\x62\x61lance\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\x14\n\x08revision\x18\x02 \x01(\x04\x42\x02\x30\x01\x42\x08\n\x06resultB\t\n\x07version\"\xd1\x01\n\x0eKeyRequestType\x12\x36\n\x08\x61ll_keys\x18\x01 \x01(\x0b\x32\".org.dash.platform.dapi.v0.AllKeysH\x00\x12@\n\rspecific_keys\x18\x02 \x01(\x0b\x32\'.org.dash.platform.dapi.v0.SpecificKeysH\x00\x12:\n\nsearch_key\x18\x03 \x01(\x0b\x32$.org.dash.platform.dapi.v0.SearchKeyH\x00\x42\t\n\x07request\"\t\n\x07\x41llKeys\"\x1f\n\x0cSpecificKeys\x12\x0f\n\x07key_ids\x18\x01 \x03(\r\"\xb6\x01\n\tSearchKey\x12I\n\x0bpurpose_map\x18\x01 \x03(\x0b\x32\x34.org.dash.platform.dapi.v0.SearchKey.PurposeMapEntry\x1a^\n\x0fPurposeMapEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12:\n\x05value\x18\x02 \x01(\x0b\x32+.org.dash.platform.dapi.v0.SecurityLevelMap:\x02\x38\x01\"\xbf\x02\n\x10SecurityLevelMap\x12]\n\x12security_level_map\x18\x01 \x03(\x0b\x32\x41.org.dash.platform.dapi.v0.SecurityLevelMap.SecurityLevelMapEntry\x1aw\n\x15SecurityLevelMapEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12M\n\x05value\x18\x02 \x01(\x0e\x32>.org.dash.platform.dapi.v0.SecurityLevelMap.KeyKindRequestType:\x02\x38\x01\"S\n\x12KeyKindRequestType\x12\x1f\n\x1b\x43URRENT_KEY_OF_KIND_REQUEST\x10\x00\x12\x1c\n\x18\x41LL_KEYS_OF_KIND_REQUEST\x10\x01\"\xda\x02\n\x16GetIdentityKeysRequest\x12X\n\x02v0\x18\x01 \x01(\x0b\x32J.org.dash.platform.dapi.v0.GetIdentityKeysRequest.GetIdentityKeysRequestV0H\x00\x1a\xda\x01\n\x18GetIdentityKeysRequestV0\x12\x13\n\x0bidentity_id\x18\x01 \x01(\x0c\x12?\n\x0crequest_type\x18\x02 \x01(\x0b\x32).org.dash.platform.dapi.v0.KeyRequestType\x12+\n\x05limit\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12,\n\x06offset\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12\r\n\x05prove\x18\x05 \x01(\x08\x42\t\n\x07version\"\x99\x03\n\x17GetIdentityKeysResponse\x12Z\n\x02v0\x18\x01 \x01(\x0b\x32L.org.dash.platform.dapi.v0.GetIdentityKeysResponse.GetIdentityKeysResponseV0H\x00\x1a\x96\x02\n\x19GetIdentityKeysResponseV0\x12\x61\n\x04keys\x18\x01 \x01(\x0b\x32Q.org.dash.platform.dapi.v0.GetIdentityKeysResponse.GetIdentityKeysResponseV0.KeysH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x1a\n\x04Keys\x12\x12\n\nkeys_bytes\x18\x01 \x03(\x0c\x42\x08\n\x06resultB\t\n\x07version\"\xef\x02\n GetIdentitiesContractKeysRequest\x12l\n\x02v0\x18\x01 \x01(\x0b\x32^.org.dash.platform.dapi.v0.GetIdentitiesContractKeysRequest.GetIdentitiesContractKeysRequestV0H\x00\x1a\xd1\x01\n\"GetIdentitiesContractKeysRequestV0\x12\x16\n\x0eidentities_ids\x18\x01 \x03(\x0c\x12\x13\n\x0b\x63ontract_id\x18\x02 \x01(\x0c\x12\x1f\n\x12\x64ocument_type_name\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x37\n\x08purposes\x18\x04 \x03(\x0e\x32%.org.dash.platform.dapi.v0.KeyPurpose\x12\r\n\x05prove\x18\x05 \x01(\x08\x42\x15\n\x13_document_type_nameB\t\n\x07version\"\xdf\x06\n!GetIdentitiesContractKeysResponse\x12n\n\x02v0\x18\x01 \x01(\x0b\x32`.org.dash.platform.dapi.v0.GetIdentitiesContractKeysResponse.GetIdentitiesContractKeysResponseV0H\x00\x1a\xbe\x05\n#GetIdentitiesContractKeysResponseV0\x12\x8a\x01\n\x0fidentities_keys\x18\x01 \x01(\x0b\x32o.org.dash.platform.dapi.v0.GetIdentitiesContractKeysResponse.GetIdentitiesContractKeysResponseV0.IdentitiesKeysH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1aY\n\x0bPurposeKeys\x12\x36\n\x07purpose\x18\x01 \x01(\x0e\x32%.org.dash.platform.dapi.v0.KeyPurpose\x12\x12\n\nkeys_bytes\x18\x02 \x03(\x0c\x1a\x9f\x01\n\x0cIdentityKeys\x12\x13\n\x0bidentity_id\x18\x01 \x01(\x0c\x12z\n\x04keys\x18\x02 \x03(\x0b\x32l.org.dash.platform.dapi.v0.GetIdentitiesContractKeysResponse.GetIdentitiesContractKeysResponseV0.PurposeKeys\x1a\x90\x01\n\x0eIdentitiesKeys\x12~\n\x07\x65ntries\x18\x01 \x03(\x0b\x32m.org.dash.platform.dapi.v0.GetIdentitiesContractKeysResponse.GetIdentitiesContractKeysResponseV0.IdentityKeysB\x08\n\x06resultB\t\n\x07version\"\xa4\x02\n*GetEvonodesProposedEpochBlocksByIdsRequest\x12\x80\x01\n\x02v0\x18\x01 \x01(\x0b\x32r.org.dash.platform.dapi.v0.GetEvonodesProposedEpochBlocksByIdsRequest.GetEvonodesProposedEpochBlocksByIdsRequestV0H\x00\x1ah\n,GetEvonodesProposedEpochBlocksByIdsRequestV0\x12\x12\n\x05\x65poch\x18\x01 \x01(\rH\x00\x88\x01\x01\x12\x0b\n\x03ids\x18\x02 \x03(\x0c\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\x08\n\x06_epochB\t\n\x07version\"\x92\x06\n&GetEvonodesProposedEpochBlocksResponse\x12x\n\x02v0\x18\x01 \x01(\x0b\x32j.org.dash.platform.dapi.v0.GetEvonodesProposedEpochBlocksResponse.GetEvonodesProposedEpochBlocksResponseV0H\x00\x1a\xe2\x04\n(GetEvonodesProposedEpochBlocksResponseV0\x12\xb1\x01\n#evonodes_proposed_block_counts_info\x18\x01 \x01(\x0b\x32\x81\x01.org.dash.platform.dapi.v0.GetEvonodesProposedEpochBlocksResponse.GetEvonodesProposedEpochBlocksResponseV0.EvonodesProposedBlocksH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a?\n\x15\x45vonodeProposedBlocks\x12\x13\n\x0bpro_tx_hash\x18\x01 \x01(\x0c\x12\x11\n\x05\x63ount\x18\x02 \x01(\x04\x42\x02\x30\x01\x1a\xc4\x01\n\x16\x45vonodesProposedBlocks\x12\xa9\x01\n\x1e\x65vonodes_proposed_block_counts\x18\x01 \x03(\x0b\x32\x80\x01.org.dash.platform.dapi.v0.GetEvonodesProposedEpochBlocksResponse.GetEvonodesProposedEpochBlocksResponseV0.EvonodeProposedBlocksB\x08\n\x06resultB\t\n\x07version\"\xf2\x02\n,GetEvonodesProposedEpochBlocksByRangeRequest\x12\x84\x01\n\x02v0\x18\x01 \x01(\x0b\x32v.org.dash.platform.dapi.v0.GetEvonodesProposedEpochBlocksByRangeRequest.GetEvonodesProposedEpochBlocksByRangeRequestV0H\x00\x1a\xaf\x01\n.GetEvonodesProposedEpochBlocksByRangeRequestV0\x12\x12\n\x05\x65poch\x18\x01 \x01(\rH\x01\x88\x01\x01\x12\x12\n\x05limit\x18\x02 \x01(\rH\x02\x88\x01\x01\x12\x15\n\x0bstart_after\x18\x03 \x01(\x0cH\x00\x12\x12\n\x08start_at\x18\x04 \x01(\x0cH\x00\x12\r\n\x05prove\x18\x05 \x01(\x08\x42\x07\n\x05startB\x08\n\x06_epochB\x08\n\x06_limitB\t\n\x07version\"\xcd\x01\n\x1cGetIdentitiesBalancesRequest\x12\x64\n\x02v0\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetIdentitiesBalancesRequest.GetIdentitiesBalancesRequestV0H\x00\x1a<\n\x1eGetIdentitiesBalancesRequestV0\x12\x0b\n\x03ids\x18\x01 \x03(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\x9f\x05\n\x1dGetIdentitiesBalancesResponse\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetIdentitiesBalancesResponse.GetIdentitiesBalancesResponseV0H\x00\x1a\x8a\x04\n\x1fGetIdentitiesBalancesResponseV0\x12\x8a\x01\n\x13identities_balances\x18\x01 \x01(\x0b\x32k.org.dash.platform.dapi.v0.GetIdentitiesBalancesResponse.GetIdentitiesBalancesResponseV0.IdentitiesBalancesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1aL\n\x0fIdentityBalance\x12\x13\n\x0bidentity_id\x18\x01 \x01(\x0c\x12\x18\n\x07\x62\x61lance\x18\x02 \x01(\x04\x42\x02\x30\x01H\x00\x88\x01\x01\x42\n\n\x08_balance\x1a\x8f\x01\n\x12IdentitiesBalances\x12y\n\x07\x65ntries\x18\x01 \x03(\x0b\x32h.org.dash.platform.dapi.v0.GetIdentitiesBalancesResponse.GetIdentitiesBalancesResponseV0.IdentityBalanceB\x08\n\x06resultB\t\n\x07version\"\xb4\x01\n\x16GetDataContractRequest\x12X\n\x02v0\x18\x01 \x01(\x0b\x32J.org.dash.platform.dapi.v0.GetDataContractRequest.GetDataContractRequestV0H\x00\x1a\x35\n\x18GetDataContractRequestV0\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xb3\x02\n\x17GetDataContractResponse\x12Z\n\x02v0\x18\x01 \x01(\x0b\x32L.org.dash.platform.dapi.v0.GetDataContractResponse.GetDataContractResponseV0H\x00\x1a\xb0\x01\n\x19GetDataContractResponseV0\x12\x17\n\rdata_contract\x18\x01 \x01(\x0cH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xb9\x01\n\x17GetDataContractsRequest\x12Z\n\x02v0\x18\x01 \x01(\x0b\x32L.org.dash.platform.dapi.v0.GetDataContractsRequest.GetDataContractsRequestV0H\x00\x1a\x37\n\x19GetDataContractsRequestV0\x12\x0b\n\x03ids\x18\x01 \x03(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xcf\x04\n\x18GetDataContractsResponse\x12\\\n\x02v0\x18\x01 \x01(\x0b\x32N.org.dash.platform.dapi.v0.GetDataContractsResponse.GetDataContractsResponseV0H\x00\x1a[\n\x11\x44\x61taContractEntry\x12\x12\n\nidentifier\x18\x01 \x01(\x0c\x12\x32\n\rdata_contract\x18\x02 \x01(\x0b\x32\x1b.google.protobuf.BytesValue\x1au\n\rDataContracts\x12\x64\n\x15\x64\x61ta_contract_entries\x18\x01 \x03(\x0b\x32\x45.org.dash.platform.dapi.v0.GetDataContractsResponse.DataContractEntry\x1a\xf5\x01\n\x1aGetDataContractsResponseV0\x12[\n\x0e\x64\x61ta_contracts\x18\x01 \x01(\x0b\x32\x41.org.dash.platform.dapi.v0.GetDataContractsResponse.DataContractsH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xc5\x02\n\x1dGetDataContractHistoryRequest\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetDataContractHistoryRequest.GetDataContractHistoryRequestV0H\x00\x1a\xb0\x01\n\x1fGetDataContractHistoryRequestV0\x12\n\n\x02id\x18\x01 \x01(\x0c\x12+\n\x05limit\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12,\n\x06offset\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12\x17\n\x0bstart_at_ms\x18\x04 \x01(\x04\x42\x02\x30\x01\x12\r\n\x05prove\x18\x05 \x01(\x08\x42\t\n\x07version\"\xb2\x05\n\x1eGetDataContractHistoryResponse\x12h\n\x02v0\x18\x01 \x01(\x0b\x32Z.org.dash.platform.dapi.v0.GetDataContractHistoryResponse.GetDataContractHistoryResponseV0H\x00\x1a\x9a\x04\n GetDataContractHistoryResponseV0\x12\x8f\x01\n\x15\x64\x61ta_contract_history\x18\x01 \x01(\x0b\x32n.org.dash.platform.dapi.v0.GetDataContractHistoryResponse.GetDataContractHistoryResponseV0.DataContractHistoryH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a;\n\x18\x44\x61taContractHistoryEntry\x12\x10\n\x04\x64\x61te\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\r\n\x05value\x18\x02 \x01(\x0c\x1a\xaa\x01\n\x13\x44\x61taContractHistory\x12\x92\x01\n\x15\x64\x61ta_contract_entries\x18\x01 \x03(\x0b\x32s.org.dash.platform.dapi.v0.GetDataContractHistoryResponse.GetDataContractHistoryResponseV0.DataContractHistoryEntryB\x08\n\x06resultB\t\n\x07version\"\xde\x15\n\x13GetDocumentsRequest\x12R\n\x02v0\x18\x01 \x01(\x0b\x32\x44.org.dash.platform.dapi.v0.GetDocumentsRequest.GetDocumentsRequestV0H\x00\x12R\n\x02v1\x18\x02 \x01(\x0b\x32\x44.org.dash.platform.dapi.v0.GetDocumentsRequest.GetDocumentsRequestV1H\x00\x1a\xfe\x02\n\x12\x44ocumentFieldValue\x12\x14\n\nbool_value\x18\x01 \x01(\x08H\x00\x12\x19\n\x0bint64_value\x18\x02 \x01(\x12\x42\x02\x30\x01H\x00\x12\x1a\n\x0cuint64_value\x18\x03 \x01(\x04\x42\x02\x30\x01H\x00\x12\x16\n\x0c\x64ouble_value\x18\x04 \x01(\x01H\x00\x12\x0e\n\x04text\x18\x05 \x01(\tH\x00\x12\x15\n\x0b\x62ytes_value\x18\x06 \x01(\x0cH\x00\x12[\n\x04list\x18\x07 \x01(\x0b\x32K.org.dash.platform.dapi.v0.GetDocumentsRequest.DocumentFieldValue.ValueListH\x00\x12\x14\n\nnull_value\x18\x08 \x01(\x08H\x00\x1a^\n\tValueList\x12Q\n\x06values\x18\x01 \x03(\x0b\x32\x41.org.dash.platform.dapi.v0.GetDocumentsRequest.DocumentFieldValueB\t\n\x07variant\x1a\xbe\x01\n\x0bWhereClause\x12\r\n\x05\x66ield\x18\x01 \x01(\t\x12N\n\x08operator\x18\x02 \x01(\x0e\x32<.org.dash.platform.dapi.v0.GetDocumentsRequest.WhereOperator\x12P\n\x05value\x18\x03 \x01(\x0b\x32\x41.org.dash.platform.dapi.v0.GetDocumentsRequest.DocumentFieldValue\x1a\xa4\x01\n\x0fHavingAggregate\x12Y\n\x08\x66unction\x18\x01 \x01(\x0e\x32G.org.dash.platform.dapi.v0.GetDocumentsRequest.HavingAggregate.Function\x12\r\n\x05\x66ield\x18\x02 \x01(\t\"\'\n\x08\x46unction\x12\t\n\x05\x43OUNT\x10\x00\x12\x07\n\x03SUM\x10\x01\x12\x07\n\x03\x41VG\x10\x02\x1a\xf9\x03\n\x0cHavingClause\x12Q\n\taggregate\x18\x01 \x01(\x0b\x32>.org.dash.platform.dapi.v0.GetDocumentsRequest.HavingAggregate\x12V\n\x08operator\x18\x02 \x01(\x0e\x32\x44.org.dash.platform.dapi.v0.GetDocumentsRequest.HavingClause.Operator\x12R\n\x05value\x18\x03 \x01(\x0b\x32\x41.org.dash.platform.dapi.v0.GetDocumentsRequest.DocumentFieldValueH\x00\"\xe0\x01\n\x08Operator\x12\t\n\x05\x45QUAL\x10\x00\x12\r\n\tNOT_EQUAL\x10\x01\x12\x10\n\x0cGREATER_THAN\x10\x02\x12\x1a\n\x16GREATER_THAN_OR_EQUALS\x10\x03\x12\r\n\tLESS_THAN\x10\x04\x12\x17\n\x13LESS_THAN_OR_EQUALS\x10\x05\x12\x0b\n\x07\x42\x45TWEEN\x10\x06\x12\x1a\n\x16\x42\x45TWEEN_EXCLUDE_BOUNDS\x10\x07\x12\x18\n\x14\x42\x45TWEEN_EXCLUDE_LEFT\x10\x08\x12\x19\n\x15\x42\x45TWEEN_EXCLUDE_RIGHT\x10\t\x12\x06\n\x02IN\x10\nB\x07\n\x05right\x1a\x90\x01\n\x0bOrderClause\x12\x0f\n\x05\x66ield\x18\x01 \x01(\tH\x00\x12S\n\taggregate\x18\x03 \x01(\x0b\x32>.org.dash.platform.dapi.v0.GetDocumentsRequest.HavingAggregateH\x00\x12\x11\n\tascending\x18\x02 \x01(\x08\x42\x08\n\x06target\x1a\xbb\x01\n\x15GetDocumentsRequestV0\x12\x18\n\x10\x64\x61ta_contract_id\x18\x01 \x01(\x0c\x12\x15\n\rdocument_type\x18\x02 \x01(\t\x12\r\n\x05where\x18\x03 \x01(\x0c\x12\x10\n\x08order_by\x18\x04 \x01(\x0c\x12\r\n\x05limit\x18\x05 \x01(\r\x12\x15\n\x0bstart_after\x18\x06 \x01(\x0cH\x00\x12\x12\n\x08start_at\x18\x07 \x01(\x0cH\x00\x12\r\n\x05prove\x18\x08 \x01(\x08\x42\x07\n\x05start\x1a\xf3\x05\n\x15GetDocumentsRequestV1\x12\x18\n\x10\x64\x61ta_contract_id\x18\x01 \x01(\x0c\x12\x15\n\rdocument_type\x18\x02 \x01(\t\x12Q\n\rwhere_clauses\x18\x03 \x03(\x0b\x32:.org.dash.platform.dapi.v0.GetDocumentsRequest.WhereClause\x12L\n\x08order_by\x18\x04 \x03(\x0b\x32:.org.dash.platform.dapi.v0.GetDocumentsRequest.OrderClause\x12\x12\n\x05limit\x18\x05 \x01(\rH\x01\x88\x01\x01\x12\x15\n\x0bstart_after\x18\x06 \x01(\x0cH\x00\x12\x12\n\x08start_at\x18\x07 \x01(\x0cH\x00\x12\r\n\x05prove\x18\x08 \x01(\x08\x12\\\n\x07selects\x18\t \x03(\x0b\x32K.org.dash.platform.dapi.v0.GetDocumentsRequest.GetDocumentsRequestV1.Select\x12\x10\n\x08group_by\x18\n \x03(\t\x12K\n\x06having\x18\x0b \x03(\x0b\x32;.org.dash.platform.dapi.v0.GetDocumentsRequest.HavingClause\x12\x13\n\x06offset\x18\x0c \x01(\rH\x02\x88\x01\x01\x1a\xc9\x01\n\x06Select\x12\x66\n\x08\x66unction\x18\x01 \x01(\x0e\x32T.org.dash.platform.dapi.v0.GetDocumentsRequest.GetDocumentsRequestV1.Select.Function\x12\r\n\x05\x66ield\x18\x02 \x01(\t\"H\n\x08\x46unction\x12\r\n\tDOCUMENTS\x10\x00\x12\t\n\x05\x43OUNT\x10\x01\x12\x07\n\x03SUM\x10\x02\x12\x07\n\x03\x41VG\x10\x03\x12\x07\n\x03MIN\x10\x04\x12\x07\n\x03MAX\x10\x05\x42\x07\n\x05startB\x08\n\x06_limitB\t\n\x07_offset\"\xe7\x01\n\rWhereOperator\x12\t\n\x05\x45QUAL\x10\x00\x12\x10\n\x0cGREATER_THAN\x10\x01\x12\x1a\n\x16GREATER_THAN_OR_EQUALS\x10\x02\x12\r\n\tLESS_THAN\x10\x03\x12\x17\n\x13LESS_THAN_OR_EQUALS\x10\x04\x12\x0b\n\x07\x42\x45TWEEN\x10\x05\x12\x1a\n\x16\x42\x45TWEEN_EXCLUDE_BOUNDS\x10\x06\x12\x18\n\x14\x42\x45TWEEN_EXCLUDE_LEFT\x10\x07\x12\x19\n\x15\x42\x45TWEEN_EXCLUDE_RIGHT\x10\x08\x12\x06\n\x02IN\x10\t\x12\x0f\n\x0bSTARTS_WITH\x10\nB\t\n\x07version\"\x87\x16\n\x14GetDocumentsResponse\x12T\n\x02v0\x18\x01 \x01(\x0b\x32\x46.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV0H\x00\x12T\n\x02v1\x18\x02 \x01(\x0b\x32\x46.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1H\x00\x1a\x9b\x02\n\x16GetDocumentsResponseV0\x12\x65\n\tdocuments\x18\x01 \x01(\x0b\x32P.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV0.DocumentsH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x1e\n\tDocuments\x12\x11\n\tdocuments\x18\x01 \x03(\x0c\x42\x08\n\x06result\x1a\x99\x12\n\x16GetDocumentsResponseV1\x12\x61\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32Q.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.ResultDataH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x1e\n\tDocuments\x12\x11\n\tdocuments\x18\x01 \x03(\x0c\x1aL\n\nCountEntry\x12\x13\n\x06in_key\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x12\x0b\n\x03key\x18\x02 \x01(\x0c\x12\x11\n\x05\x63ount\x18\x03 \x01(\x04\x42\x02\x30\x01\x42\t\n\x07_in_key\x1ar\n\x0c\x43ountEntries\x12\x62\n\x07\x65ntries\x18\x01 \x03(\x0b\x32Q.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.CountEntry\x1a\xa0\x01\n\x0c\x43ountResults\x12\x1d\n\x0f\x61ggregate_count\x18\x01 \x01(\x04\x42\x02\x30\x01H\x00\x12\x66\n\x07\x65ntries\x18\x02 \x01(\x0b\x32S.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.CountEntriesH\x00\x42\t\n\x07variant\x1aH\n\x08SumEntry\x12\x13\n\x06in_key\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x12\x0b\n\x03key\x18\x02 \x01(\x0c\x12\x0f\n\x03sum\x18\x03 \x01(\x12\x42\x02\x30\x01\x42\t\n\x07_in_key\x1an\n\nSumEntries\x12`\n\x07\x65ntries\x18\x01 \x03(\x0b\x32O.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.SumEntry\x1a\x9a\x01\n\nSumResults\x12\x1b\n\raggregate_sum\x18\x01 \x01(\x12\x42\x02\x30\x01H\x00\x12\x64\n\x07\x65ntries\x18\x02 \x01(\x0b\x32Q.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.SumEntriesH\x00\x42\t\n\x07variant\x1a_\n\x0c\x41verageEntry\x12\x13\n\x06in_key\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x12\x0b\n\x03key\x18\x02 \x01(\x0c\x12\x11\n\x05\x63ount\x18\x03 \x01(\x04\x42\x02\x30\x01\x12\x0f\n\x03sum\x18\x04 \x01(\x12\x42\x02\x30\x01\x42\t\n\x07_in_key\x1av\n\x0e\x41verageEntries\x12\x64\n\x07\x65ntries\x18\x01 \x03(\x0b\x32S.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.AverageEntry\x1a\x36\n\x10\x41verageAggregate\x12\x11\n\x05\x63ount\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\x0f\n\x03sum\x18\x02 \x01(\x12\x42\x02\x30\x01\x1a\xfb\x01\n\x0e\x41verageResults\x12t\n\x11\x61ggregate_average\x18\x01 \x01(\x0b\x32W.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.AverageAggregateH\x00\x12h\n\x07\x65ntries\x18\x02 \x01(\x0b\x32U.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.AverageEntriesH\x00\x42\t\n\x07variant\x1az\n\x0bRankedEntry\x12\x0b\n\x03key\x18\x01 \x01(\x0c\x12\x13\n\x05\x63ount\x18\x02 \x01(\x04\x42\x02\x30\x01H\x00\x12\x11\n\x03sum\x18\x03 \x01(\x12\x42\x02\x30\x01H\x00\x12\r\n\x03\x61vg\x18\x04 \x01(\x01H\x00\x12\x13\n\x06in_key\x18\x05 \x01(\x0cH\x01\x88\x01\x01\x42\x07\n\x05valueB\t\n\x07_in_key\x1a\x9a\x01\n\rRankedEntries\x12\x63\n\x07\x65ntries\x18\x01 \x03(\x0b\x32R.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry\x12\x18\n\x07skipped\x18\x02 \x01(\x04\x42\x02\x30\x01H\x00\x88\x01\x01\x42\n\n\x08_skipped\x1a\x9b\x04\n\nResultData\x12\x65\n\tdocuments\x18\x01 \x01(\x0b\x32P.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.DocumentsH\x00\x12\x65\n\x06\x63ounts\x18\x02 \x01(\x0b\x32S.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.CountResultsH\x00\x12\x61\n\x04sums\x18\x03 \x01(\x0b\x32Q.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.SumResultsH\x00\x12i\n\x08\x61verages\x18\x04 \x01(\x0b\x32U.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.AverageResultsH\x00\x12\x66\n\x06ranked\x18\x05 \x01(\x0b\x32T.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntriesH\x00\x42\t\n\x07variantB\x08\n\x06resultB\t\n\x07version\"\xf4\x02\n\x19GetDocumentHistoryRequest\x12^\n\x02v0\x18\x01 \x01(\x0b\x32P.org.dash.platform.dapi.v0.GetDocumentHistoryRequest.GetDocumentHistoryRequestV0H\x00\x1a\xeb\x01\n\x1bGetDocumentHistoryRequestV0\x12\x18\n\x10\x64\x61ta_contract_id\x18\x01 \x01(\x0c\x12\x1a\n\x12\x64ocument_type_name\x18\x02 \x01(\t\x12\x13\n\x0b\x64ocument_id\x18\x03 \x01(\x0c\x12+\n\x05limit\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12,\n\x06offset\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12\x17\n\x0bstart_at_ms\x18\x06 \x01(\x04\x42\x02\x30\x01\x12\r\n\x05prove\x18\x07 \x01(\x08\x42\t\n\x07version\"\xf7\x04\n\x1aGetDocumentHistoryResponse\x12`\n\x02v0\x18\x01 \x01(\x0b\x32R.org.dash.platform.dapi.v0.GetDocumentHistoryResponse.GetDocumentHistoryResponseV0H\x00\x1a\xeb\x03\n\x1cGetDocumentHistoryResponseV0\x12~\n\x10\x64ocument_history\x18\x01 \x01(\x0b\x32\x62.org.dash.platform.dapi.v0.GetDocumentHistoryResponse.GetDocumentHistoryResponseV0.DocumentHistoryH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x37\n\x14\x44ocumentHistoryEntry\x12\x10\n\x04\x64\x61te\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\r\n\x05value\x18\x02 \x01(\x0c\x1a\x95\x01\n\x0f\x44ocumentHistory\x12\x81\x01\n\x10\x64ocument_entries\x18\x01 \x03(\x0b\x32g.org.dash.platform.dapi.v0.GetDocumentHistoryResponse.GetDocumentHistoryResponseV0.DocumentHistoryEntryB\x08\n\x06resultB\t\n\x07version\"\xed\x01\n!GetIdentityByPublicKeyHashRequest\x12n\n\x02v0\x18\x01 \x01(\x0b\x32`.org.dash.platform.dapi.v0.GetIdentityByPublicKeyHashRequest.GetIdentityByPublicKeyHashRequestV0H\x00\x1aM\n#GetIdentityByPublicKeyHashRequestV0\x12\x17\n\x0fpublic_key_hash\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xda\x02\n\"GetIdentityByPublicKeyHashResponse\x12p\n\x02v0\x18\x01 \x01(\x0b\x32\x62.org.dash.platform.dapi.v0.GetIdentityByPublicKeyHashResponse.GetIdentityByPublicKeyHashResponseV0H\x00\x1a\xb6\x01\n$GetIdentityByPublicKeyHashResponseV0\x12\x12\n\x08identity\x18\x01 \x01(\x0cH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xbd\x02\n*GetIdentityByNonUniquePublicKeyHashRequest\x12\x80\x01\n\x02v0\x18\x01 \x01(\x0b\x32r.org.dash.platform.dapi.v0.GetIdentityByNonUniquePublicKeyHashRequest.GetIdentityByNonUniquePublicKeyHashRequestV0H\x00\x1a\x80\x01\n,GetIdentityByNonUniquePublicKeyHashRequestV0\x12\x17\n\x0fpublic_key_hash\x18\x01 \x01(\x0c\x12\x18\n\x0bstart_after\x18\x02 \x01(\x0cH\x00\x88\x01\x01\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\x0e\n\x0c_start_afterB\t\n\x07version\"\xd6\x06\n+GetIdentityByNonUniquePublicKeyHashResponse\x12\x82\x01\n\x02v0\x18\x01 \x01(\x0b\x32t.org.dash.platform.dapi.v0.GetIdentityByNonUniquePublicKeyHashResponse.GetIdentityByNonUniquePublicKeyHashResponseV0H\x00\x1a\x96\x05\n-GetIdentityByNonUniquePublicKeyHashResponseV0\x12\x9a\x01\n\x08identity\x18\x01 \x01(\x0b\x32\x85\x01.org.dash.platform.dapi.v0.GetIdentityByNonUniquePublicKeyHashResponse.GetIdentityByNonUniquePublicKeyHashResponseV0.IdentityResponseH\x00\x12\x9d\x01\n\x05proof\x18\x02 \x01(\x0b\x32\x8b\x01.org.dash.platform.dapi.v0.GetIdentityByNonUniquePublicKeyHashResponse.GetIdentityByNonUniquePublicKeyHashResponseV0.IdentityProvedResponseH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x36\n\x10IdentityResponse\x12\x15\n\x08identity\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x42\x0b\n\t_identity\x1a\xa6\x01\n\x16IdentityProvedResponse\x12P\n&grovedb_identity_public_key_hash_proof\x18\x01 \x01(\x0b\x32 .org.dash.platform.dapi.v0.Proof\x12!\n\x14identity_proof_bytes\x18\x02 \x01(\x0cH\x00\x88\x01\x01\x42\x17\n\x15_identity_proof_bytesB\x08\n\x06resultB\t\n\x07version\"\xfb\x01\n#WaitForStateTransitionResultRequest\x12r\n\x02v0\x18\x01 \x01(\x0b\x32\x64.org.dash.platform.dapi.v0.WaitForStateTransitionResultRequest.WaitForStateTransitionResultRequestV0H\x00\x1aU\n%WaitForStateTransitionResultRequestV0\x12\x1d\n\x15state_transition_hash\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\x99\x03\n$WaitForStateTransitionResultResponse\x12t\n\x02v0\x18\x01 \x01(\x0b\x32\x66.org.dash.platform.dapi.v0.WaitForStateTransitionResultResponse.WaitForStateTransitionResultResponseV0H\x00\x1a\xef\x01\n&WaitForStateTransitionResultResponseV0\x12I\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x38.org.dash.platform.dapi.v0.StateTransitionBroadcastErrorH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xc4\x01\n\x19GetConsensusParamsRequest\x12^\n\x02v0\x18\x01 \x01(\x0b\x32P.org.dash.platform.dapi.v0.GetConsensusParamsRequest.GetConsensusParamsRequestV0H\x00\x1a<\n\x1bGetConsensusParamsRequestV0\x12\x0e\n\x06height\x18\x01 \x01(\x05\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\x9c\x04\n\x1aGetConsensusParamsResponse\x12`\n\x02v0\x18\x01 \x01(\x0b\x32R.org.dash.platform.dapi.v0.GetConsensusParamsResponse.GetConsensusParamsResponseV0H\x00\x1aP\n\x14\x43onsensusParamsBlock\x12\x11\n\tmax_bytes\x18\x01 \x01(\t\x12\x0f\n\x07max_gas\x18\x02 \x01(\t\x12\x14\n\x0ctime_iota_ms\x18\x03 \x01(\t\x1a\x62\n\x17\x43onsensusParamsEvidence\x12\x1a\n\x12max_age_num_blocks\x18\x01 \x01(\t\x12\x18\n\x10max_age_duration\x18\x02 \x01(\t\x12\x11\n\tmax_bytes\x18\x03 \x01(\t\x1a\xda\x01\n\x1cGetConsensusParamsResponseV0\x12Y\n\x05\x62lock\x18\x01 \x01(\x0b\x32J.org.dash.platform.dapi.v0.GetConsensusParamsResponse.ConsensusParamsBlock\x12_\n\x08\x65vidence\x18\x02 \x01(\x0b\x32M.org.dash.platform.dapi.v0.GetConsensusParamsResponse.ConsensusParamsEvidenceB\t\n\x07version\"\xe4\x01\n%GetProtocolVersionUpgradeStateRequest\x12v\n\x02v0\x18\x01 \x01(\x0b\x32h.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeStateRequest.GetProtocolVersionUpgradeStateRequestV0H\x00\x1a\x38\n\'GetProtocolVersionUpgradeStateRequestV0\x12\r\n\x05prove\x18\x01 \x01(\x08\x42\t\n\x07version\"\xb5\x05\n&GetProtocolVersionUpgradeStateResponse\x12x\n\x02v0\x18\x01 \x01(\x0b\x32j.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeStateResponse.GetProtocolVersionUpgradeStateResponseV0H\x00\x1a\x85\x04\n(GetProtocolVersionUpgradeStateResponseV0\x12\x87\x01\n\x08versions\x18\x01 \x01(\x0b\x32s.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeStateResponse.GetProtocolVersionUpgradeStateResponseV0.VersionsH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x96\x01\n\x08Versions\x12\x89\x01\n\x08versions\x18\x01 \x03(\x0b\x32w.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeStateResponse.GetProtocolVersionUpgradeStateResponseV0.VersionEntry\x1a:\n\x0cVersionEntry\x12\x16\n\x0eversion_number\x18\x01 \x01(\r\x12\x12\n\nvote_count\x18\x02 \x01(\rB\x08\n\x06resultB\t\n\x07version\"\xa3\x02\n*GetProtocolVersionUpgradeVoteStatusRequest\x12\x80\x01\n\x02v0\x18\x01 \x01(\x0b\x32r.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeVoteStatusRequest.GetProtocolVersionUpgradeVoteStatusRequestV0H\x00\x1ag\n,GetProtocolVersionUpgradeVoteStatusRequestV0\x12\x19\n\x11start_pro_tx_hash\x18\x01 \x01(\x0c\x12\r\n\x05\x63ount\x18\x02 \x01(\r\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\t\n\x07version\"\xef\x05\n+GetProtocolVersionUpgradeVoteStatusResponse\x12\x82\x01\n\x02v0\x18\x01 \x01(\x0b\x32t.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeVoteStatusResponse.GetProtocolVersionUpgradeVoteStatusResponseV0H\x00\x1a\xaf\x04\n-GetProtocolVersionUpgradeVoteStatusResponseV0\x12\x98\x01\n\x08versions\x18\x01 \x01(\x0b\x32\x83\x01.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeVoteStatusResponse.GetProtocolVersionUpgradeVoteStatusResponseV0.VersionSignalsH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\xaf\x01\n\x0eVersionSignals\x12\x9c\x01\n\x0fversion_signals\x18\x01 \x03(\x0b\x32\x82\x01.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeVoteStatusResponse.GetProtocolVersionUpgradeVoteStatusResponseV0.VersionSignal\x1a\x35\n\rVersionSignal\x12\x13\n\x0bpro_tx_hash\x18\x01 \x01(\x0c\x12\x0f\n\x07version\x18\x02 \x01(\rB\x08\n\x06resultB\t\n\x07version\"\xf5\x01\n\x14GetEpochsInfoRequest\x12T\n\x02v0\x18\x01 \x01(\x0b\x32\x46.org.dash.platform.dapi.v0.GetEpochsInfoRequest.GetEpochsInfoRequestV0H\x00\x1a|\n\x16GetEpochsInfoRequestV0\x12\x31\n\x0bstart_epoch\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12\r\n\x05\x63ount\x18\x02 \x01(\r\x12\x11\n\tascending\x18\x03 \x01(\x08\x12\r\n\x05prove\x18\x04 \x01(\x08\x42\t\n\x07version\"\x99\x05\n\x15GetEpochsInfoResponse\x12V\n\x02v0\x18\x01 \x01(\x0b\x32H.org.dash.platform.dapi.v0.GetEpochsInfoResponse.GetEpochsInfoResponseV0H\x00\x1a\x9c\x04\n\x17GetEpochsInfoResponseV0\x12\x65\n\x06\x65pochs\x18\x01 \x01(\x0b\x32S.org.dash.platform.dapi.v0.GetEpochsInfoResponse.GetEpochsInfoResponseV0.EpochInfosH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1au\n\nEpochInfos\x12g\n\x0b\x65poch_infos\x18\x01 \x03(\x0b\x32R.org.dash.platform.dapi.v0.GetEpochsInfoResponse.GetEpochsInfoResponseV0.EpochInfo\x1a\xa6\x01\n\tEpochInfo\x12\x0e\n\x06number\x18\x01 \x01(\r\x12\x1e\n\x12\x66irst_block_height\x18\x02 \x01(\x04\x42\x02\x30\x01\x12\x1f\n\x17\x66irst_core_block_height\x18\x03 \x01(\r\x12\x16\n\nstart_time\x18\x04 \x01(\x04\x42\x02\x30\x01\x12\x16\n\x0e\x66\x65\x65_multiplier\x18\x05 \x01(\x01\x12\x18\n\x10protocol_version\x18\x06 \x01(\rB\x08\n\x06resultB\t\n\x07version\"\xbf\x02\n\x1dGetFinalizedEpochInfosRequest\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetFinalizedEpochInfosRequest.GetFinalizedEpochInfosRequestV0H\x00\x1a\xaa\x01\n\x1fGetFinalizedEpochInfosRequestV0\x12\x19\n\x11start_epoch_index\x18\x01 \x01(\r\x12\"\n\x1astart_epoch_index_included\x18\x02 \x01(\x08\x12\x17\n\x0f\x65nd_epoch_index\x18\x03 \x01(\r\x12 \n\x18\x65nd_epoch_index_included\x18\x04 \x01(\x08\x12\r\n\x05prove\x18\x05 \x01(\x08\x42\t\n\x07version\"\xbd\t\n\x1eGetFinalizedEpochInfosResponse\x12h\n\x02v0\x18\x01 \x01(\x0b\x32Z.org.dash.platform.dapi.v0.GetFinalizedEpochInfosResponse.GetFinalizedEpochInfosResponseV0H\x00\x1a\xa5\x08\n GetFinalizedEpochInfosResponseV0\x12\x80\x01\n\x06\x65pochs\x18\x01 \x01(\x0b\x32n.org.dash.platform.dapi.v0.GetFinalizedEpochInfosResponse.GetFinalizedEpochInfosResponseV0.FinalizedEpochInfosH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\xa4\x01\n\x13\x46inalizedEpochInfos\x12\x8c\x01\n\x15\x66inalized_epoch_infos\x18\x01 \x03(\x0b\x32m.org.dash.platform.dapi.v0.GetFinalizedEpochInfosResponse.GetFinalizedEpochInfosResponseV0.FinalizedEpochInfo\x1a\x9f\x04\n\x12\x46inalizedEpochInfo\x12\x0e\n\x06number\x18\x01 \x01(\r\x12\x1e\n\x12\x66irst_block_height\x18\x02 \x01(\x04\x42\x02\x30\x01\x12\x1f\n\x17\x66irst_core_block_height\x18\x03 \x01(\r\x12\x1c\n\x10\x66irst_block_time\x18\x04 \x01(\x04\x42\x02\x30\x01\x12\x16\n\x0e\x66\x65\x65_multiplier\x18\x05 \x01(\x01\x12\x18\n\x10protocol_version\x18\x06 \x01(\r\x12!\n\x15total_blocks_in_epoch\x18\x07 \x01(\x04\x42\x02\x30\x01\x12*\n\"next_epoch_start_core_block_height\x18\x08 \x01(\r\x12!\n\x15total_processing_fees\x18\t \x01(\x04\x42\x02\x30\x01\x12*\n\x1etotal_distributed_storage_fees\x18\n \x01(\x04\x42\x02\x30\x01\x12&\n\x1atotal_created_storage_fees\x18\x0b \x01(\x04\x42\x02\x30\x01\x12\x1e\n\x12\x63ore_block_rewards\x18\x0c \x01(\x04\x42\x02\x30\x01\x12\x81\x01\n\x0f\x62lock_proposers\x18\r \x03(\x0b\x32h.org.dash.platform.dapi.v0.GetFinalizedEpochInfosResponse.GetFinalizedEpochInfosResponseV0.BlockProposer\x1a\x39\n\rBlockProposer\x12\x13\n\x0bproposer_id\x18\x01 \x01(\x0c\x12\x13\n\x0b\x62lock_count\x18\x02 \x01(\rB\x08\n\x06resultB\t\n\x07version\"\xde\x04\n\x1cGetContestedResourcesRequest\x12\x64\n\x02v0\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetContestedResourcesRequest.GetContestedResourcesRequestV0H\x00\x1a\xcc\x03\n\x1eGetContestedResourcesRequestV0\x12\x13\n\x0b\x63ontract_id\x18\x01 \x01(\x0c\x12\x1a\n\x12\x64ocument_type_name\x18\x02 \x01(\t\x12\x12\n\nindex_name\x18\x03 \x01(\t\x12\x1a\n\x12start_index_values\x18\x04 \x03(\x0c\x12\x18\n\x10\x65nd_index_values\x18\x05 \x03(\x0c\x12\x89\x01\n\x13start_at_value_info\x18\x06 \x01(\x0b\x32g.org.dash.platform.dapi.v0.GetContestedResourcesRequest.GetContestedResourcesRequestV0.StartAtValueInfoH\x00\x88\x01\x01\x12\x12\n\x05\x63ount\x18\x07 \x01(\rH\x01\x88\x01\x01\x12\x17\n\x0forder_ascending\x18\x08 \x01(\x08\x12\r\n\x05prove\x18\t \x01(\x08\x1a\x45\n\x10StartAtValueInfo\x12\x13\n\x0bstart_value\x18\x01 \x01(\x0c\x12\x1c\n\x14start_value_included\x18\x02 \x01(\x08\x42\x16\n\x14_start_at_value_infoB\x08\n\x06_countB\t\n\x07version\"\x88\x04\n\x1dGetContestedResourcesResponse\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetContestedResourcesResponse.GetContestedResourcesResponseV0H\x00\x1a\xf3\x02\n\x1fGetContestedResourcesResponseV0\x12\x95\x01\n\x19\x63ontested_resource_values\x18\x01 \x01(\x0b\x32p.org.dash.platform.dapi.v0.GetContestedResourcesResponse.GetContestedResourcesResponseV0.ContestedResourceValuesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a<\n\x17\x43ontestedResourceValues\x12!\n\x19\x63ontested_resource_values\x18\x01 \x03(\x0c\x42\x08\n\x06resultB\t\n\x07version\"\xd2\x05\n\x1cGetVotePollsByEndDateRequest\x12\x64\n\x02v0\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetVotePollsByEndDateRequest.GetVotePollsByEndDateRequestV0H\x00\x1a\xc0\x04\n\x1eGetVotePollsByEndDateRequestV0\x12\x84\x01\n\x0fstart_time_info\x18\x01 \x01(\x0b\x32\x66.org.dash.platform.dapi.v0.GetVotePollsByEndDateRequest.GetVotePollsByEndDateRequestV0.StartAtTimeInfoH\x00\x88\x01\x01\x12\x80\x01\n\rend_time_info\x18\x02 \x01(\x0b\x32\x64.org.dash.platform.dapi.v0.GetVotePollsByEndDateRequest.GetVotePollsByEndDateRequestV0.EndAtTimeInfoH\x01\x88\x01\x01\x12\x12\n\x05limit\x18\x03 \x01(\rH\x02\x88\x01\x01\x12\x13\n\x06offset\x18\x04 \x01(\rH\x03\x88\x01\x01\x12\x11\n\tascending\x18\x05 \x01(\x08\x12\r\n\x05prove\x18\x06 \x01(\x08\x1aI\n\x0fStartAtTimeInfo\x12\x19\n\rstart_time_ms\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\x1b\n\x13start_time_included\x18\x02 \x01(\x08\x1a\x43\n\rEndAtTimeInfo\x12\x17\n\x0b\x65nd_time_ms\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\x19\n\x11\x65nd_time_included\x18\x02 \x01(\x08\x42\x12\n\x10_start_time_infoB\x10\n\x0e_end_time_infoB\x08\n\x06_limitB\t\n\x07_offsetB\t\n\x07version\"\x83\x06\n\x1dGetVotePollsByEndDateResponse\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetVotePollsByEndDateResponse.GetVotePollsByEndDateResponseV0H\x00\x1a\xee\x04\n\x1fGetVotePollsByEndDateResponseV0\x12\x9c\x01\n\x18vote_polls_by_timestamps\x18\x01 \x01(\x0b\x32x.org.dash.platform.dapi.v0.GetVotePollsByEndDateResponse.GetVotePollsByEndDateResponseV0.SerializedVotePollsByTimestampsH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1aV\n\x1eSerializedVotePollsByTimestamp\x12\x15\n\ttimestamp\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\x1d\n\x15serialized_vote_polls\x18\x02 \x03(\x0c\x1a\xd7\x01\n\x1fSerializedVotePollsByTimestamps\x12\x99\x01\n\x18vote_polls_by_timestamps\x18\x01 \x03(\x0b\x32w.org.dash.platform.dapi.v0.GetVotePollsByEndDateResponse.GetVotePollsByEndDateResponseV0.SerializedVotePollsByTimestamp\x12\x18\n\x10\x66inished_results\x18\x02 \x01(\x08\x42\x08\n\x06resultB\t\n\x07version\"\xff\x06\n$GetContestedResourceVoteStateRequest\x12t\n\x02v0\x18\x01 \x01(\x0b\x32\x66.org.dash.platform.dapi.v0.GetContestedResourceVoteStateRequest.GetContestedResourceVoteStateRequestV0H\x00\x1a\xd5\x05\n&GetContestedResourceVoteStateRequestV0\x12\x13\n\x0b\x63ontract_id\x18\x01 \x01(\x0c\x12\x1a\n\x12\x64ocument_type_name\x18\x02 \x01(\t\x12\x12\n\nindex_name\x18\x03 \x01(\t\x12\x14\n\x0cindex_values\x18\x04 \x03(\x0c\x12\x86\x01\n\x0bresult_type\x18\x05 \x01(\x0e\x32q.org.dash.platform.dapi.v0.GetContestedResourceVoteStateRequest.GetContestedResourceVoteStateRequestV0.ResultType\x12\x36\n.allow_include_locked_and_abstaining_vote_tally\x18\x06 \x01(\x08\x12\xa3\x01\n\x18start_at_identifier_info\x18\x07 \x01(\x0b\x32|.org.dash.platform.dapi.v0.GetContestedResourceVoteStateRequest.GetContestedResourceVoteStateRequestV0.StartAtIdentifierInfoH\x00\x88\x01\x01\x12\x12\n\x05\x63ount\x18\x08 \x01(\rH\x01\x88\x01\x01\x12\r\n\x05prove\x18\t \x01(\x08\x1aT\n\x15StartAtIdentifierInfo\x12\x18\n\x10start_identifier\x18\x01 \x01(\x0c\x12!\n\x19start_identifier_included\x18\x02 \x01(\x08\"I\n\nResultType\x12\r\n\tDOCUMENTS\x10\x00\x12\x0e\n\nVOTE_TALLY\x10\x01\x12\x1c\n\x18\x44OCUMENTS_AND_VOTE_TALLY\x10\x02\x42\x1b\n\x19_start_at_identifier_infoB\x08\n\x06_countB\t\n\x07version\"\x94\x0c\n%GetContestedResourceVoteStateResponse\x12v\n\x02v0\x18\x01 \x01(\x0b\x32h.org.dash.platform.dapi.v0.GetContestedResourceVoteStateResponse.GetContestedResourceVoteStateResponseV0H\x00\x1a\xe7\n\n\'GetContestedResourceVoteStateResponseV0\x12\xae\x01\n\x1d\x63ontested_resource_contenders\x18\x01 \x01(\x0b\x32\x84\x01.org.dash.platform.dapi.v0.GetContestedResourceVoteStateResponse.GetContestedResourceVoteStateResponseV0.ContestedResourceContendersH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\xda\x03\n\x10\x46inishedVoteInfo\x12\xad\x01\n\x15\x66inished_vote_outcome\x18\x01 \x01(\x0e\x32\x8d\x01.org.dash.platform.dapi.v0.GetContestedResourceVoteStateResponse.GetContestedResourceVoteStateResponseV0.FinishedVoteInfo.FinishedVoteOutcome\x12\x1f\n\x12won_by_identity_id\x18\x02 \x01(\x0cH\x00\x88\x01\x01\x12$\n\x18\x66inished_at_block_height\x18\x03 \x01(\x04\x42\x02\x30\x01\x12%\n\x1d\x66inished_at_core_block_height\x18\x04 \x01(\r\x12%\n\x19\x66inished_at_block_time_ms\x18\x05 \x01(\x04\x42\x02\x30\x01\x12\x19\n\x11\x66inished_at_epoch\x18\x06 \x01(\r\"O\n\x13\x46inishedVoteOutcome\x12\x14\n\x10TOWARDS_IDENTITY\x10\x00\x12\n\n\x06LOCKED\x10\x01\x12\x16\n\x12NO_PREVIOUS_WINNER\x10\x02\x42\x15\n\x13_won_by_identity_id\x1a\xc4\x03\n\x1b\x43ontestedResourceContenders\x12\x86\x01\n\ncontenders\x18\x01 \x03(\x0b\x32r.org.dash.platform.dapi.v0.GetContestedResourceVoteStateResponse.GetContestedResourceVoteStateResponseV0.Contender\x12\x1f\n\x12\x61\x62stain_vote_tally\x18\x02 \x01(\rH\x00\x88\x01\x01\x12\x1c\n\x0flock_vote_tally\x18\x03 \x01(\rH\x01\x88\x01\x01\x12\x9a\x01\n\x12\x66inished_vote_info\x18\x04 \x01(\x0b\x32y.org.dash.platform.dapi.v0.GetContestedResourceVoteStateResponse.GetContestedResourceVoteStateResponseV0.FinishedVoteInfoH\x02\x88\x01\x01\x42\x15\n\x13_abstain_vote_tallyB\x12\n\x10_lock_vote_tallyB\x15\n\x13_finished_vote_info\x1ak\n\tContender\x12\x12\n\nidentifier\x18\x01 \x01(\x0c\x12\x17\n\nvote_count\x18\x02 \x01(\rH\x00\x88\x01\x01\x12\x15\n\x08\x64ocument\x18\x03 \x01(\x0cH\x01\x88\x01\x01\x42\r\n\x0b_vote_countB\x0b\n\t_documentB\x08\n\x06resultB\t\n\x07version\"\xd5\x05\n,GetContestedResourceVotersForIdentityRequest\x12\x84\x01\n\x02v0\x18\x01 \x01(\x0b\x32v.org.dash.platform.dapi.v0.GetContestedResourceVotersForIdentityRequest.GetContestedResourceVotersForIdentityRequestV0H\x00\x1a\x92\x04\n.GetContestedResourceVotersForIdentityRequestV0\x12\x13\n\x0b\x63ontract_id\x18\x01 \x01(\x0c\x12\x1a\n\x12\x64ocument_type_name\x18\x02 \x01(\t\x12\x12\n\nindex_name\x18\x03 \x01(\t\x12\x14\n\x0cindex_values\x18\x04 \x03(\x0c\x12\x15\n\rcontestant_id\x18\x05 \x01(\x0c\x12\xb4\x01\n\x18start_at_identifier_info\x18\x06 \x01(\x0b\x32\x8c\x01.org.dash.platform.dapi.v0.GetContestedResourceVotersForIdentityRequest.GetContestedResourceVotersForIdentityRequestV0.StartAtIdentifierInfoH\x00\x88\x01\x01\x12\x12\n\x05\x63ount\x18\x07 \x01(\rH\x01\x88\x01\x01\x12\x17\n\x0forder_ascending\x18\x08 \x01(\x08\x12\r\n\x05prove\x18\t \x01(\x08\x1aT\n\x15StartAtIdentifierInfo\x12\x18\n\x10start_identifier\x18\x01 \x01(\x0c\x12!\n\x19start_identifier_included\x18\x02 \x01(\x08\x42\x1b\n\x19_start_at_identifier_infoB\x08\n\x06_countB\t\n\x07version\"\xf1\x04\n-GetContestedResourceVotersForIdentityResponse\x12\x86\x01\n\x02v0\x18\x01 \x01(\x0b\x32x.org.dash.platform.dapi.v0.GetContestedResourceVotersForIdentityResponse.GetContestedResourceVotersForIdentityResponseV0H\x00\x1a\xab\x03\n/GetContestedResourceVotersForIdentityResponseV0\x12\xb6\x01\n\x19\x63ontested_resource_voters\x18\x01 \x01(\x0b\x32\x90\x01.org.dash.platform.dapi.v0.GetContestedResourceVotersForIdentityResponse.GetContestedResourceVotersForIdentityResponseV0.ContestedResourceVotersH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x43\n\x17\x43ontestedResourceVoters\x12\x0e\n\x06voters\x18\x01 \x03(\x0c\x12\x18\n\x10\x66inished_results\x18\x02 \x01(\x08\x42\x08\n\x06resultB\t\n\x07version\"\xad\x05\n(GetContestedResourceIdentityVotesRequest\x12|\n\x02v0\x18\x01 \x01(\x0b\x32n.org.dash.platform.dapi.v0.GetContestedResourceIdentityVotesRequest.GetContestedResourceIdentityVotesRequestV0H\x00\x1a\xf7\x03\n*GetContestedResourceIdentityVotesRequestV0\x12\x13\n\x0bidentity_id\x18\x01 \x01(\x0c\x12+\n\x05limit\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12,\n\x06offset\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12\x17\n\x0forder_ascending\x18\x04 \x01(\x08\x12\xae\x01\n\x1astart_at_vote_poll_id_info\x18\x05 \x01(\x0b\x32\x84\x01.org.dash.platform.dapi.v0.GetContestedResourceIdentityVotesRequest.GetContestedResourceIdentityVotesRequestV0.StartAtVotePollIdInfoH\x00\x88\x01\x01\x12\r\n\x05prove\x18\x06 \x01(\x08\x1a\x61\n\x15StartAtVotePollIdInfo\x12 \n\x18start_at_poll_identifier\x18\x01 \x01(\x0c\x12&\n\x1estart_poll_identifier_included\x18\x02 \x01(\x08\x42\x1d\n\x1b_start_at_vote_poll_id_infoB\t\n\x07version\"\xc8\n\n)GetContestedResourceIdentityVotesResponse\x12~\n\x02v0\x18\x01 \x01(\x0b\x32p.org.dash.platform.dapi.v0.GetContestedResourceIdentityVotesResponse.GetContestedResourceIdentityVotesResponseV0H\x00\x1a\x8f\t\n+GetContestedResourceIdentityVotesResponseV0\x12\xa1\x01\n\x05votes\x18\x01 \x01(\x0b\x32\x8f\x01.org.dash.platform.dapi.v0.GetContestedResourceIdentityVotesResponse.GetContestedResourceIdentityVotesResponseV0.ContestedResourceIdentityVotesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\xf7\x01\n\x1e\x43ontestedResourceIdentityVotes\x12\xba\x01\n!contested_resource_identity_votes\x18\x01 \x03(\x0b\x32\x8e\x01.org.dash.platform.dapi.v0.GetContestedResourceIdentityVotesResponse.GetContestedResourceIdentityVotesResponseV0.ContestedResourceIdentityVote\x12\x18\n\x10\x66inished_results\x18\x02 \x01(\x08\x1a\xad\x02\n\x12ResourceVoteChoice\x12\xad\x01\n\x10vote_choice_type\x18\x01 \x01(\x0e\x32\x92\x01.org.dash.platform.dapi.v0.GetContestedResourceIdentityVotesResponse.GetContestedResourceIdentityVotesResponseV0.ResourceVoteChoice.VoteChoiceType\x12\x18\n\x0bidentity_id\x18\x02 \x01(\x0cH\x00\x88\x01\x01\"=\n\x0eVoteChoiceType\x12\x14\n\x10TOWARDS_IDENTITY\x10\x00\x12\x0b\n\x07\x41\x42STAIN\x10\x01\x12\x08\n\x04LOCK\x10\x02\x42\x0e\n\x0c_identity_id\x1a\x95\x02\n\x1d\x43ontestedResourceIdentityVote\x12\x13\n\x0b\x63ontract_id\x18\x01 \x01(\x0c\x12\x1a\n\x12\x64ocument_type_name\x18\x02 \x01(\t\x12\'\n\x1fserialized_index_storage_values\x18\x03 \x03(\x0c\x12\x99\x01\n\x0bvote_choice\x18\x04 \x01(\x0b\x32\x83\x01.org.dash.platform.dapi.v0.GetContestedResourceIdentityVotesResponse.GetContestedResourceIdentityVotesResponseV0.ResourceVoteChoiceB\x08\n\x06resultB\t\n\x07version\"\xf0\x01\n%GetPrefundedSpecializedBalanceRequest\x12v\n\x02v0\x18\x01 \x01(\x0b\x32h.org.dash.platform.dapi.v0.GetPrefundedSpecializedBalanceRequest.GetPrefundedSpecializedBalanceRequestV0H\x00\x1a\x44\n\'GetPrefundedSpecializedBalanceRequestV0\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xed\x02\n&GetPrefundedSpecializedBalanceResponse\x12x\n\x02v0\x18\x01 \x01(\x0b\x32j.org.dash.platform.dapi.v0.GetPrefundedSpecializedBalanceResponse.GetPrefundedSpecializedBalanceResponseV0H\x00\x1a\xbd\x01\n(GetPrefundedSpecializedBalanceResponseV0\x12\x15\n\x07\x62\x61lance\x18\x01 \x01(\x04\x42\x02\x30\x01H\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xd0\x01\n GetTotalCreditsInPlatformRequest\x12l\n\x02v0\x18\x01 \x01(\x0b\x32^.org.dash.platform.dapi.v0.GetTotalCreditsInPlatformRequest.GetTotalCreditsInPlatformRequestV0H\x00\x1a\x33\n\"GetTotalCreditsInPlatformRequestV0\x12\r\n\x05prove\x18\x01 \x01(\x08\x42\t\n\x07version\"\xd9\x02\n!GetTotalCreditsInPlatformResponse\x12n\n\x02v0\x18\x01 \x01(\x0b\x32`.org.dash.platform.dapi.v0.GetTotalCreditsInPlatformResponse.GetTotalCreditsInPlatformResponseV0H\x00\x1a\xb8\x01\n#GetTotalCreditsInPlatformResponseV0\x12\x15\n\x07\x63redits\x18\x01 \x01(\x04\x42\x02\x30\x01H\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xc4\x01\n\x16GetPathElementsRequest\x12X\n\x02v0\x18\x01 \x01(\x0b\x32J.org.dash.platform.dapi.v0.GetPathElementsRequest.GetPathElementsRequestV0H\x00\x1a\x45\n\x18GetPathElementsRequestV0\x12\x0c\n\x04path\x18\x01 \x03(\x0c\x12\x0c\n\x04keys\x18\x02 \x03(\x0c\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\t\n\x07version\"\xa3\x03\n\x17GetPathElementsResponse\x12Z\n\x02v0\x18\x01 \x01(\x0b\x32L.org.dash.platform.dapi.v0.GetPathElementsResponse.GetPathElementsResponseV0H\x00\x1a\xa0\x02\n\x19GetPathElementsResponseV0\x12i\n\x08\x65lements\x18\x01 \x01(\x0b\x32U.org.dash.platform.dapi.v0.GetPathElementsResponse.GetPathElementsResponseV0.ElementsH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x1c\n\x08\x45lements\x12\x10\n\x08\x65lements\x18\x01 \x03(\x0c\x42\x08\n\x06resultB\t\n\x07version\"\x81\x01\n\x10GetStatusRequest\x12L\n\x02v0\x18\x01 \x01(\x0b\x32>.org.dash.platform.dapi.v0.GetStatusRequest.GetStatusRequestV0H\x00\x1a\x14\n\x12GetStatusRequestV0B\t\n\x07version\"\xe4\x10\n\x11GetStatusResponse\x12N\n\x02v0\x18\x01 \x01(\x0b\x32@.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0H\x00\x1a\xf3\x0f\n\x13GetStatusResponseV0\x12Y\n\x07version\x18\x01 \x01(\x0b\x32H.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0.Version\x12S\n\x04node\x18\x02 \x01(\x0b\x32\x45.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0.Node\x12U\n\x05\x63hain\x18\x03 \x01(\x0b\x32\x46.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0.Chain\x12Y\n\x07network\x18\x04 \x01(\x0b\x32H.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0.Network\x12^\n\nstate_sync\x18\x05 \x01(\x0b\x32J.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0.StateSync\x12S\n\x04time\x18\x06 \x01(\x0b\x32\x45.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0.Time\x1a\x82\x05\n\x07Version\x12\x63\n\x08software\x18\x01 \x01(\x0b\x32Q.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0.Version.Software\x12\x63\n\x08protocol\x18\x02 \x01(\x0b\x32Q.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0.Version.Protocol\x1a^\n\x08Software\x12\x0c\n\x04\x64\x61pi\x18\x01 \x01(\t\x12\x12\n\x05\x64rive\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x17\n\ntenderdash\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x08\n\x06_driveB\r\n\x0b_tenderdash\x1a\xcc\x02\n\x08Protocol\x12p\n\ntenderdash\x18\x01 \x01(\x0b\x32\\.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0.Version.Protocol.Tenderdash\x12\x66\n\x05\x64rive\x18\x02 \x01(\x0b\x32W.org.dash.platform.dapi.v0.GetStatusResponse.GetStatusResponseV0.Version.Protocol.Drive\x1a(\n\nTenderdash\x12\x0b\n\x03p2p\x18\x01 \x01(\r\x12\r\n\x05\x62lock\x18\x02 \x01(\r\x1a<\n\x05\x44rive\x12\x0e\n\x06latest\x18\x03 \x01(\r\x12\x0f\n\x07\x63urrent\x18\x04 \x01(\r\x12\x12\n\nnext_epoch\x18\x05 \x01(\r\x1a\x7f\n\x04Time\x12\x11\n\x05local\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\x16\n\x05\x62lock\x18\x02 \x01(\x04\x42\x02\x30\x01H\x00\x88\x01\x01\x12\x18\n\x07genesis\x18\x03 \x01(\x04\x42\x02\x30\x01H\x01\x88\x01\x01\x12\x12\n\x05\x65poch\x18\x04 \x01(\rH\x02\x88\x01\x01\x42\x08\n\x06_blockB\n\n\x08_genesisB\x08\n\x06_epoch\x1a<\n\x04Node\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\x18\n\x0bpro_tx_hash\x18\x02 \x01(\x0cH\x00\x88\x01\x01\x42\x0e\n\x0c_pro_tx_hash\x1a\xb3\x02\n\x05\x43hain\x12\x13\n\x0b\x63\x61tching_up\x18\x01 \x01(\x08\x12\x19\n\x11latest_block_hash\x18\x02 \x01(\x0c\x12\x17\n\x0flatest_app_hash\x18\x03 \x01(\x0c\x12\x1f\n\x13latest_block_height\x18\x04 \x01(\x04\x42\x02\x30\x01\x12\x1b\n\x13\x65\x61rliest_block_hash\x18\x05 \x01(\x0c\x12\x19\n\x11\x65\x61rliest_app_hash\x18\x06 \x01(\x0c\x12!\n\x15\x65\x61rliest_block_height\x18\x07 \x01(\x04\x42\x02\x30\x01\x12!\n\x15max_peer_block_height\x18\t \x01(\x04\x42\x02\x30\x01\x12%\n\x18\x63ore_chain_locked_height\x18\n \x01(\rH\x00\x88\x01\x01\x42\x1b\n\x19_core_chain_locked_height\x1a\x43\n\x07Network\x12\x10\n\x08\x63hain_id\x18\x01 \x01(\t\x12\x13\n\x0bpeers_count\x18\x02 \x01(\r\x12\x11\n\tlistening\x18\x03 \x01(\x08\x1a\x85\x02\n\tStateSync\x12\x1d\n\x11total_synced_time\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\x1a\n\x0eremaining_time\x18\x02 \x01(\x04\x42\x02\x30\x01\x12\x17\n\x0ftotal_snapshots\x18\x03 \x01(\r\x12\"\n\x16\x63hunk_process_avg_time\x18\x04 \x01(\x04\x42\x02\x30\x01\x12\x1b\n\x0fsnapshot_height\x18\x05 \x01(\x04\x42\x02\x30\x01\x12!\n\x15snapshot_chunks_count\x18\x06 \x01(\x04\x42\x02\x30\x01\x12\x1d\n\x11\x62\x61\x63kfilled_blocks\x18\x07 \x01(\x04\x42\x02\x30\x01\x12!\n\x15\x62\x61\x63kfill_blocks_total\x18\x08 \x01(\x04\x42\x02\x30\x01\x42\t\n\x07version\"\xb1\x01\n\x1cGetCurrentQuorumsInfoRequest\x12\x64\n\x02v0\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetCurrentQuorumsInfoRequest.GetCurrentQuorumsInfoRequestV0H\x00\x1a \n\x1eGetCurrentQuorumsInfoRequestV0B\t\n\x07version\"\xa1\x05\n\x1dGetCurrentQuorumsInfoResponse\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetCurrentQuorumsInfoResponse.GetCurrentQuorumsInfoResponseV0H\x00\x1a\x46\n\x0bValidatorV0\x12\x13\n\x0bpro_tx_hash\x18\x01 \x01(\x0c\x12\x0f\n\x07node_ip\x18\x02 \x01(\t\x12\x11\n\tis_banned\x18\x03 \x01(\x08\x1a\xaf\x01\n\x0eValidatorSetV0\x12\x13\n\x0bquorum_hash\x18\x01 \x01(\x0c\x12\x13\n\x0b\x63ore_height\x18\x02 \x01(\r\x12U\n\x07members\x18\x03 \x03(\x0b\x32\x44.org.dash.platform.dapi.v0.GetCurrentQuorumsInfoResponse.ValidatorV0\x12\x1c\n\x14threshold_public_key\x18\x04 \x01(\x0c\x1a\x92\x02\n\x1fGetCurrentQuorumsInfoResponseV0\x12\x15\n\rquorum_hashes\x18\x01 \x03(\x0c\x12\x1b\n\x13\x63urrent_quorum_hash\x18\x02 \x01(\x0c\x12_\n\x0evalidator_sets\x18\x03 \x03(\x0b\x32G.org.dash.platform.dapi.v0.GetCurrentQuorumsInfoResponse.ValidatorSetV0\x12\x1b\n\x13last_block_proposer\x18\x04 \x01(\x0c\x12=\n\x08metadata\x18\x05 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\t\n\x07version\"\xf4\x01\n\x1fGetIdentityTokenBalancesRequest\x12j\n\x02v0\x18\x01 \x01(\x0b\x32\\.org.dash.platform.dapi.v0.GetIdentityTokenBalancesRequest.GetIdentityTokenBalancesRequestV0H\x00\x1aZ\n!GetIdentityTokenBalancesRequestV0\x12\x13\n\x0bidentity_id\x18\x01 \x01(\x0c\x12\x11\n\ttoken_ids\x18\x02 \x03(\x0c\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\t\n\x07version\"\xad\x05\n GetIdentityTokenBalancesResponse\x12l\n\x02v0\x18\x01 \x01(\x0b\x32^.org.dash.platform.dapi.v0.GetIdentityTokenBalancesResponse.GetIdentityTokenBalancesResponseV0H\x00\x1a\x8f\x04\n\"GetIdentityTokenBalancesResponseV0\x12\x86\x01\n\x0etoken_balances\x18\x01 \x01(\x0b\x32l.org.dash.platform.dapi.v0.GetIdentityTokenBalancesResponse.GetIdentityTokenBalancesResponseV0.TokenBalancesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1aG\n\x11TokenBalanceEntry\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12\x14\n\x07\x62\x61lance\x18\x02 \x01(\x04H\x00\x88\x01\x01\x42\n\n\x08_balance\x1a\x9a\x01\n\rTokenBalances\x12\x88\x01\n\x0etoken_balances\x18\x01 \x03(\x0b\x32p.org.dash.platform.dapi.v0.GetIdentityTokenBalancesResponse.GetIdentityTokenBalancesResponseV0.TokenBalanceEntryB\x08\n\x06resultB\t\n\x07version\"\xfc\x01\n!GetIdentitiesTokenBalancesRequest\x12n\n\x02v0\x18\x01 \x01(\x0b\x32`.org.dash.platform.dapi.v0.GetIdentitiesTokenBalancesRequest.GetIdentitiesTokenBalancesRequestV0H\x00\x1a\\\n#GetIdentitiesTokenBalancesRequestV0\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12\x14\n\x0cidentity_ids\x18\x02 \x03(\x0c\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\t\n\x07version\"\xf2\x05\n\"GetIdentitiesTokenBalancesResponse\x12p\n\x02v0\x18\x01 \x01(\x0b\x32\x62.org.dash.platform.dapi.v0.GetIdentitiesTokenBalancesResponse.GetIdentitiesTokenBalancesResponseV0H\x00\x1a\xce\x04\n$GetIdentitiesTokenBalancesResponseV0\x12\x9b\x01\n\x17identity_token_balances\x18\x01 \x01(\x0b\x32x.org.dash.platform.dapi.v0.GetIdentitiesTokenBalancesResponse.GetIdentitiesTokenBalancesResponseV0.IdentityTokenBalancesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1aR\n\x19IdentityTokenBalanceEntry\x12\x13\n\x0bidentity_id\x18\x01 \x01(\x0c\x12\x14\n\x07\x62\x61lance\x18\x02 \x01(\x04H\x00\x88\x01\x01\x42\n\n\x08_balance\x1a\xb7\x01\n\x15IdentityTokenBalances\x12\x9d\x01\n\x17identity_token_balances\x18\x01 \x03(\x0b\x32|.org.dash.platform.dapi.v0.GetIdentitiesTokenBalancesResponse.GetIdentitiesTokenBalancesResponseV0.IdentityTokenBalanceEntryB\x08\n\x06resultB\t\n\x07version\"\xe8\x01\n\x1cGetIdentityTokenInfosRequest\x12\x64\n\x02v0\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetIdentityTokenInfosRequest.GetIdentityTokenInfosRequestV0H\x00\x1aW\n\x1eGetIdentityTokenInfosRequestV0\x12\x13\n\x0bidentity_id\x18\x01 \x01(\x0c\x12\x11\n\ttoken_ids\x18\x02 \x03(\x0c\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\t\n\x07version\"\x98\x06\n\x1dGetIdentityTokenInfosResponse\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetIdentityTokenInfosResponse.GetIdentityTokenInfosResponseV0H\x00\x1a\x83\x05\n\x1fGetIdentityTokenInfosResponseV0\x12z\n\x0btoken_infos\x18\x01 \x01(\x0b\x32\x63.org.dash.platform.dapi.v0.GetIdentityTokenInfosResponse.GetIdentityTokenInfosResponseV0.TokenInfosH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a(\n\x16TokenIdentityInfoEntry\x12\x0e\n\x06\x66rozen\x18\x01 \x01(\x08\x1a\xb0\x01\n\x0eTokenInfoEntry\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12\x82\x01\n\x04info\x18\x02 \x01(\x0b\x32o.org.dash.platform.dapi.v0.GetIdentityTokenInfosResponse.GetIdentityTokenInfosResponseV0.TokenIdentityInfoEntryH\x00\x88\x01\x01\x42\x07\n\x05_info\x1a\x8a\x01\n\nTokenInfos\x12|\n\x0btoken_infos\x18\x01 \x03(\x0b\x32g.org.dash.platform.dapi.v0.GetIdentityTokenInfosResponse.GetIdentityTokenInfosResponseV0.TokenInfoEntryB\x08\n\x06resultB\t\n\x07version\"\xf0\x01\n\x1eGetIdentitiesTokenInfosRequest\x12h\n\x02v0\x18\x01 \x01(\x0b\x32Z.org.dash.platform.dapi.v0.GetIdentitiesTokenInfosRequest.GetIdentitiesTokenInfosRequestV0H\x00\x1aY\n GetIdentitiesTokenInfosRequestV0\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12\x14\n\x0cidentity_ids\x18\x02 \x03(\x0c\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\t\n\x07version\"\xca\x06\n\x1fGetIdentitiesTokenInfosResponse\x12j\n\x02v0\x18\x01 \x01(\x0b\x32\\.org.dash.platform.dapi.v0.GetIdentitiesTokenInfosResponse.GetIdentitiesTokenInfosResponseV0H\x00\x1a\xaf\x05\n!GetIdentitiesTokenInfosResponseV0\x12\x8f\x01\n\x14identity_token_infos\x18\x01 \x01(\x0b\x32o.org.dash.platform.dapi.v0.GetIdentitiesTokenInfosResponse.GetIdentitiesTokenInfosResponseV0.IdentityTokenInfosH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a(\n\x16TokenIdentityInfoEntry\x12\x0e\n\x06\x66rozen\x18\x01 \x01(\x08\x1a\xb7\x01\n\x0eTokenInfoEntry\x12\x13\n\x0bidentity_id\x18\x01 \x01(\x0c\x12\x86\x01\n\x04info\x18\x02 \x01(\x0b\x32s.org.dash.platform.dapi.v0.GetIdentitiesTokenInfosResponse.GetIdentitiesTokenInfosResponseV0.TokenIdentityInfoEntryH\x00\x88\x01\x01\x42\x07\n\x05_info\x1a\x97\x01\n\x12IdentityTokenInfos\x12\x80\x01\n\x0btoken_infos\x18\x01 \x03(\x0b\x32k.org.dash.platform.dapi.v0.GetIdentitiesTokenInfosResponse.GetIdentitiesTokenInfosResponseV0.TokenInfoEntryB\x08\n\x06resultB\t\n\x07version\"\xbf\x01\n\x17GetTokenStatusesRequest\x12Z\n\x02v0\x18\x01 \x01(\x0b\x32L.org.dash.platform.dapi.v0.GetTokenStatusesRequest.GetTokenStatusesRequestV0H\x00\x1a=\n\x19GetTokenStatusesRequestV0\x12\x11\n\ttoken_ids\x18\x01 \x03(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xe7\x04\n\x18GetTokenStatusesResponse\x12\\\n\x02v0\x18\x01 \x01(\x0b\x32N.org.dash.platform.dapi.v0.GetTokenStatusesResponse.GetTokenStatusesResponseV0H\x00\x1a\xe1\x03\n\x1aGetTokenStatusesResponseV0\x12v\n\x0etoken_statuses\x18\x01 \x01(\x0b\x32\\.org.dash.platform.dapi.v0.GetTokenStatusesResponse.GetTokenStatusesResponseV0.TokenStatusesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x44\n\x10TokenStatusEntry\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12\x13\n\x06paused\x18\x02 \x01(\x08H\x00\x88\x01\x01\x42\t\n\x07_paused\x1a\x88\x01\n\rTokenStatuses\x12w\n\x0etoken_statuses\x18\x01 \x03(\x0b\x32_.org.dash.platform.dapi.v0.GetTokenStatusesResponse.GetTokenStatusesResponseV0.TokenStatusEntryB\x08\n\x06resultB\t\n\x07version\"\xef\x01\n#GetTokenDirectPurchasePricesRequest\x12r\n\x02v0\x18\x01 \x01(\x0b\x32\x64.org.dash.platform.dapi.v0.GetTokenDirectPurchasePricesRequest.GetTokenDirectPurchasePricesRequestV0H\x00\x1aI\n%GetTokenDirectPurchasePricesRequestV0\x12\x11\n\ttoken_ids\x18\x01 \x03(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\x8b\t\n$GetTokenDirectPurchasePricesResponse\x12t\n\x02v0\x18\x01 \x01(\x0b\x32\x66.org.dash.platform.dapi.v0.GetTokenDirectPurchasePricesResponse.GetTokenDirectPurchasePricesResponseV0H\x00\x1a\xe1\x07\n&GetTokenDirectPurchasePricesResponseV0\x12\xa9\x01\n\x1ctoken_direct_purchase_prices\x18\x01 \x01(\x0b\x32\x80\x01.org.dash.platform.dapi.v0.GetTokenDirectPurchasePricesResponse.GetTokenDirectPurchasePricesResponseV0.TokenDirectPurchasePricesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x33\n\x10PriceForQuantity\x12\x10\n\x08quantity\x18\x01 \x01(\x04\x12\r\n\x05price\x18\x02 \x01(\x04\x1a\xa7\x01\n\x0fPricingSchedule\x12\x93\x01\n\x12price_for_quantity\x18\x01 \x03(\x0b\x32w.org.dash.platform.dapi.v0.GetTokenDirectPurchasePricesResponse.GetTokenDirectPurchasePricesResponseV0.PriceForQuantity\x1a\xe4\x01\n\x1dTokenDirectPurchasePriceEntry\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12\x15\n\x0b\x66ixed_price\x18\x02 \x01(\x04H\x00\x12\x90\x01\n\x0evariable_price\x18\x03 \x01(\x0b\x32v.org.dash.platform.dapi.v0.GetTokenDirectPurchasePricesResponse.GetTokenDirectPurchasePricesResponseV0.PricingScheduleH\x00\x42\x07\n\x05price\x1a\xc8\x01\n\x19TokenDirectPurchasePrices\x12\xaa\x01\n\x1btoken_direct_purchase_price\x18\x01 \x03(\x0b\x32\x84\x01.org.dash.platform.dapi.v0.GetTokenDirectPurchasePricesResponse.GetTokenDirectPurchasePricesResponseV0.TokenDirectPurchasePriceEntryB\x08\n\x06resultB\t\n\x07version\"\xce\x01\n\x1bGetTokenContractInfoRequest\x12\x62\n\x02v0\x18\x01 \x01(\x0b\x32T.org.dash.platform.dapi.v0.GetTokenContractInfoRequest.GetTokenContractInfoRequestV0H\x00\x1a@\n\x1dGetTokenContractInfoRequestV0\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xfb\x03\n\x1cGetTokenContractInfoResponse\x12\x64\n\x02v0\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetTokenContractInfoResponse.GetTokenContractInfoResponseV0H\x00\x1a\xe9\x02\n\x1eGetTokenContractInfoResponseV0\x12|\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32l.org.dash.platform.dapi.v0.GetTokenContractInfoResponse.GetTokenContractInfoResponseV0.TokenContractInfoDataH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1aM\n\x15TokenContractInfoData\x12\x13\n\x0b\x63ontract_id\x18\x01 \x01(\x0c\x12\x1f\n\x17token_contract_position\x18\x02 \x01(\rB\x08\n\x06resultB\t\n\x07version\"\xef\x04\n)GetTokenPreProgrammedDistributionsRequest\x12~\n\x02v0\x18\x01 \x01(\x0b\x32p.org.dash.platform.dapi.v0.GetTokenPreProgrammedDistributionsRequest.GetTokenPreProgrammedDistributionsRequestV0H\x00\x1a\xb6\x03\n+GetTokenPreProgrammedDistributionsRequestV0\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12\x98\x01\n\rstart_at_info\x18\x02 \x01(\x0b\x32|.org.dash.platform.dapi.v0.GetTokenPreProgrammedDistributionsRequest.GetTokenPreProgrammedDistributionsRequestV0.StartAtInfoH\x00\x88\x01\x01\x12\x12\n\x05limit\x18\x03 \x01(\rH\x01\x88\x01\x01\x12\r\n\x05prove\x18\x04 \x01(\x08\x1a\x9a\x01\n\x0bStartAtInfo\x12\x15\n\rstart_time_ms\x18\x01 \x01(\x04\x12\x1c\n\x0fstart_recipient\x18\x02 \x01(\x0cH\x00\x88\x01\x01\x12%\n\x18start_recipient_included\x18\x03 \x01(\x08H\x01\x88\x01\x01\x42\x12\n\x10_start_recipientB\x1b\n\x19_start_recipient_includedB\x10\n\x0e_start_at_infoB\x08\n\x06_limitB\t\n\x07version\"\xec\x07\n*GetTokenPreProgrammedDistributionsResponse\x12\x80\x01\n\x02v0\x18\x01 \x01(\x0b\x32r.org.dash.platform.dapi.v0.GetTokenPreProgrammedDistributionsResponse.GetTokenPreProgrammedDistributionsResponseV0H\x00\x1a\xaf\x06\n,GetTokenPreProgrammedDistributionsResponseV0\x12\xa5\x01\n\x13token_distributions\x18\x01 \x01(\x0b\x32\x85\x01.org.dash.platform.dapi.v0.GetTokenPreProgrammedDistributionsResponse.GetTokenPreProgrammedDistributionsResponseV0.TokenDistributionsH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a>\n\x16TokenDistributionEntry\x12\x14\n\x0crecipient_id\x18\x01 \x01(\x0c\x12\x0e\n\x06\x61mount\x18\x02 \x01(\x04\x1a\xd4\x01\n\x1bTokenTimedDistributionEntry\x12\x11\n\ttimestamp\x18\x01 \x01(\x04\x12\xa1\x01\n\rdistributions\x18\x02 \x03(\x0b\x32\x89\x01.org.dash.platform.dapi.v0.GetTokenPreProgrammedDistributionsResponse.GetTokenPreProgrammedDistributionsResponseV0.TokenDistributionEntry\x1a\xc3\x01\n\x12TokenDistributions\x12\xac\x01\n\x13token_distributions\x18\x01 \x03(\x0b\x32\x8e\x01.org.dash.platform.dapi.v0.GetTokenPreProgrammedDistributionsResponse.GetTokenPreProgrammedDistributionsResponseV0.TokenTimedDistributionEntryB\x08\n\x06resultB\t\n\x07version\"\x82\x04\n-GetTokenPerpetualDistributionLastClaimRequest\x12\x86\x01\n\x02v0\x18\x01 \x01(\x0b\x32x.org.dash.platform.dapi.v0.GetTokenPerpetualDistributionLastClaimRequest.GetTokenPerpetualDistributionLastClaimRequestV0H\x00\x1aI\n\x11\x43ontractTokenInfo\x12\x13\n\x0b\x63ontract_id\x18\x01 \x01(\x0c\x12\x1f\n\x17token_contract_position\x18\x02 \x01(\r\x1a\xf1\x01\n/GetTokenPerpetualDistributionLastClaimRequestV0\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12v\n\rcontract_info\x18\x02 \x01(\x0b\x32Z.org.dash.platform.dapi.v0.GetTokenPerpetualDistributionLastClaimRequest.ContractTokenInfoH\x00\x88\x01\x01\x12\x13\n\x0bidentity_id\x18\x04 \x01(\x0c\x12\r\n\x05prove\x18\x05 \x01(\x08\x42\x10\n\x0e_contract_infoB\t\n\x07version\"\x93\x05\n.GetTokenPerpetualDistributionLastClaimResponse\x12\x88\x01\n\x02v0\x18\x01 \x01(\x0b\x32z.org.dash.platform.dapi.v0.GetTokenPerpetualDistributionLastClaimResponse.GetTokenPerpetualDistributionLastClaimResponseV0H\x00\x1a\xca\x03\n0GetTokenPerpetualDistributionLastClaimResponseV0\x12\x9f\x01\n\nlast_claim\x18\x01 \x01(\x0b\x32\x88\x01.org.dash.platform.dapi.v0.GetTokenPerpetualDistributionLastClaimResponse.GetTokenPerpetualDistributionLastClaimResponseV0.LastClaimInfoH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1ax\n\rLastClaimInfo\x12\x1a\n\x0ctimestamp_ms\x18\x01 \x01(\x04\x42\x02\x30\x01H\x00\x12\x1a\n\x0c\x62lock_height\x18\x02 \x01(\x04\x42\x02\x30\x01H\x00\x12\x0f\n\x05\x65poch\x18\x03 \x01(\rH\x00\x12\x13\n\traw_bytes\x18\x04 \x01(\x0cH\x00\x42\t\n\x07paid_atB\x08\n\x06resultB\t\n\x07version\"\xca\x01\n\x1aGetTokenTotalSupplyRequest\x12`\n\x02v0\x18\x01 \x01(\x0b\x32R.org.dash.platform.dapi.v0.GetTokenTotalSupplyRequest.GetTokenTotalSupplyRequestV0H\x00\x1a?\n\x1cGetTokenTotalSupplyRequestV0\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xaf\x04\n\x1bGetTokenTotalSupplyResponse\x12\x62\n\x02v0\x18\x01 \x01(\x0b\x32T.org.dash.platform.dapi.v0.GetTokenTotalSupplyResponse.GetTokenTotalSupplyResponseV0H\x00\x1a\xa0\x03\n\x1dGetTokenTotalSupplyResponseV0\x12\x88\x01\n\x12token_total_supply\x18\x01 \x01(\x0b\x32j.org.dash.platform.dapi.v0.GetTokenTotalSupplyResponse.GetTokenTotalSupplyResponseV0.TokenTotalSupplyEntryH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1ax\n\x15TokenTotalSupplyEntry\x12\x10\n\x08token_id\x18\x01 \x01(\x0c\x12\x30\n(total_aggregated_amount_in_user_accounts\x18\x02 \x01(\x04\x12\x1b\n\x13total_system_amount\x18\x03 \x01(\x04\x42\x08\n\x06resultB\t\n\x07version\"\xd2\x01\n\x13GetGroupInfoRequest\x12R\n\x02v0\x18\x01 \x01(\x0b\x32\x44.org.dash.platform.dapi.v0.GetGroupInfoRequest.GetGroupInfoRequestV0H\x00\x1a\\\n\x15GetGroupInfoRequestV0\x12\x13\n\x0b\x63ontract_id\x18\x01 \x01(\x0c\x12\x1f\n\x17group_contract_position\x18\x02 \x01(\r\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\t\n\x07version\"\xd4\x05\n\x14GetGroupInfoResponse\x12T\n\x02v0\x18\x01 \x01(\x0b\x32\x46.org.dash.platform.dapi.v0.GetGroupInfoResponse.GetGroupInfoResponseV0H\x00\x1a\xda\x04\n\x16GetGroupInfoResponseV0\x12\x66\n\ngroup_info\x18\x01 \x01(\x0b\x32P.org.dash.platform.dapi.v0.GetGroupInfoResponse.GetGroupInfoResponseV0.GroupInfoH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x04 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x34\n\x10GroupMemberEntry\x12\x11\n\tmember_id\x18\x01 \x01(\x0c\x12\r\n\x05power\x18\x02 \x01(\r\x1a\x98\x01\n\x0eGroupInfoEntry\x12h\n\x07members\x18\x01 \x03(\x0b\x32W.org.dash.platform.dapi.v0.GetGroupInfoResponse.GetGroupInfoResponseV0.GroupMemberEntry\x12\x1c\n\x14group_required_power\x18\x02 \x01(\r\x1a\x8a\x01\n\tGroupInfo\x12n\n\ngroup_info\x18\x01 \x01(\x0b\x32U.org.dash.platform.dapi.v0.GetGroupInfoResponse.GetGroupInfoResponseV0.GroupInfoEntryH\x00\x88\x01\x01\x42\r\n\x0b_group_infoB\x08\n\x06resultB\t\n\x07version\"\xed\x03\n\x14GetGroupInfosRequest\x12T\n\x02v0\x18\x01 \x01(\x0b\x32\x46.org.dash.platform.dapi.v0.GetGroupInfosRequest.GetGroupInfosRequestV0H\x00\x1au\n\x1cStartAtGroupContractPosition\x12%\n\x1dstart_group_contract_position\x18\x01 \x01(\r\x12.\n&start_group_contract_position_included\x18\x02 \x01(\x08\x1a\xfc\x01\n\x16GetGroupInfosRequestV0\x12\x13\n\x0b\x63ontract_id\x18\x01 \x01(\x0c\x12{\n start_at_group_contract_position\x18\x02 \x01(\x0b\x32L.org.dash.platform.dapi.v0.GetGroupInfosRequest.StartAtGroupContractPositionH\x00\x88\x01\x01\x12\x12\n\x05\x63ount\x18\x03 \x01(\rH\x01\x88\x01\x01\x12\r\n\x05prove\x18\x04 \x01(\x08\x42#\n!_start_at_group_contract_positionB\x08\n\x06_countB\t\n\x07version\"\xff\x05\n\x15GetGroupInfosResponse\x12V\n\x02v0\x18\x01 \x01(\x0b\x32H.org.dash.platform.dapi.v0.GetGroupInfosResponse.GetGroupInfosResponseV0H\x00\x1a\x82\x05\n\x17GetGroupInfosResponseV0\x12j\n\x0bgroup_infos\x18\x01 \x01(\x0b\x32S.org.dash.platform.dapi.v0.GetGroupInfosResponse.GetGroupInfosResponseV0.GroupInfosH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x04 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x34\n\x10GroupMemberEntry\x12\x11\n\tmember_id\x18\x01 \x01(\x0c\x12\r\n\x05power\x18\x02 \x01(\r\x1a\xc3\x01\n\x16GroupPositionInfoEntry\x12\x1f\n\x17group_contract_position\x18\x01 \x01(\r\x12j\n\x07members\x18\x02 \x03(\x0b\x32Y.org.dash.platform.dapi.v0.GetGroupInfosResponse.GetGroupInfosResponseV0.GroupMemberEntry\x12\x1c\n\x14group_required_power\x18\x03 \x01(\r\x1a\x82\x01\n\nGroupInfos\x12t\n\x0bgroup_infos\x18\x01 \x03(\x0b\x32_.org.dash.platform.dapi.v0.GetGroupInfosResponse.GetGroupInfosResponseV0.GroupPositionInfoEntryB\x08\n\x06resultB\t\n\x07version\"\xbe\x04\n\x16GetGroupActionsRequest\x12X\n\x02v0\x18\x01 \x01(\x0b\x32J.org.dash.platform.dapi.v0.GetGroupActionsRequest.GetGroupActionsRequestV0H\x00\x1aL\n\x0fStartAtActionId\x12\x17\n\x0fstart_action_id\x18\x01 \x01(\x0c\x12 \n\x18start_action_id_included\x18\x02 \x01(\x08\x1a\xc8\x02\n\x18GetGroupActionsRequestV0\x12\x13\n\x0b\x63ontract_id\x18\x01 \x01(\x0c\x12\x1f\n\x17group_contract_position\x18\x02 \x01(\r\x12N\n\x06status\x18\x03 \x01(\x0e\x32>.org.dash.platform.dapi.v0.GetGroupActionsRequest.ActionStatus\x12\x62\n\x12start_at_action_id\x18\x04 \x01(\x0b\x32\x41.org.dash.platform.dapi.v0.GetGroupActionsRequest.StartAtActionIdH\x00\x88\x01\x01\x12\x12\n\x05\x63ount\x18\x05 \x01(\rH\x01\x88\x01\x01\x12\r\n\x05prove\x18\x06 \x01(\x08\x42\x15\n\x13_start_at_action_idB\x08\n\x06_count\"&\n\x0c\x41\x63tionStatus\x12\n\n\x06\x41\x43TIVE\x10\x00\x12\n\n\x06\x43LOSED\x10\x01\x42\t\n\x07version\"\xd6\x1e\n\x17GetGroupActionsResponse\x12Z\n\x02v0\x18\x01 \x01(\x0b\x32L.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0H\x00\x1a\xd3\x1d\n\x19GetGroupActionsResponseV0\x12r\n\rgroup_actions\x18\x01 \x01(\x0b\x32Y.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.GroupActionsH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a[\n\tMintEvent\x12\x0e\n\x06\x61mount\x18\x01 \x01(\x04\x12\x14\n\x0crecipient_id\x18\x02 \x01(\x0c\x12\x18\n\x0bpublic_note\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_public_note\x1a[\n\tBurnEvent\x12\x0e\n\x06\x61mount\x18\x01 \x01(\x04\x12\x14\n\x0c\x62urn_from_id\x18\x02 \x01(\x0c\x12\x18\n\x0bpublic_note\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_public_note\x1aJ\n\x0b\x46reezeEvent\x12\x11\n\tfrozen_id\x18\x01 \x01(\x0c\x12\x18\n\x0bpublic_note\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_public_note\x1aL\n\rUnfreezeEvent\x12\x11\n\tfrozen_id\x18\x01 \x01(\x0c\x12\x18\n\x0bpublic_note\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_public_note\x1a\x66\n\x17\x44\x65stroyFrozenFundsEvent\x12\x11\n\tfrozen_id\x18\x01 \x01(\x0c\x12\x0e\n\x06\x61mount\x18\x02 \x01(\x04\x12\x18\n\x0bpublic_note\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_public_note\x1a\x64\n\x13SharedEncryptedNote\x12\x18\n\x10sender_key_index\x18\x01 \x01(\r\x12\x1b\n\x13recipient_key_index\x18\x02 \x01(\r\x12\x16\n\x0e\x65ncrypted_data\x18\x03 \x01(\x0c\x1a{\n\x15PersonalEncryptedNote\x12!\n\x19root_encryption_key_index\x18\x01 \x01(\r\x12\'\n\x1f\x64\x65rivation_encryption_key_index\x18\x02 \x01(\r\x12\x16\n\x0e\x65ncrypted_data\x18\x03 \x01(\x0c\x1a\xe9\x01\n\x14\x45mergencyActionEvent\x12\x81\x01\n\x0b\x61\x63tion_type\x18\x01 \x01(\x0e\x32l.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.EmergencyActionEvent.ActionType\x12\x18\n\x0bpublic_note\x18\x02 \x01(\tH\x00\x88\x01\x01\"#\n\nActionType\x12\t\n\x05PAUSE\x10\x00\x12\n\n\x06RESUME\x10\x01\x42\x0e\n\x0c_public_note\x1a\x64\n\x16TokenConfigUpdateEvent\x12 \n\x18token_config_update_item\x18\x01 \x01(\x0c\x12\x18\n\x0bpublic_note\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0e\n\x0c_public_note\x1a\xe6\x03\n\x1eUpdateDirectPurchasePriceEvent\x12\x15\n\x0b\x66ixed_price\x18\x01 \x01(\x04H\x00\x12\x95\x01\n\x0evariable_price\x18\x02 \x01(\x0b\x32{.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.UpdateDirectPurchasePriceEvent.PricingScheduleH\x00\x12\x18\n\x0bpublic_note\x18\x03 \x01(\tH\x01\x88\x01\x01\x1a\x33\n\x10PriceForQuantity\x12\x10\n\x08quantity\x18\x01 \x01(\x04\x12\r\n\x05price\x18\x02 \x01(\x04\x1a\xac\x01\n\x0fPricingSchedule\x12\x98\x01\n\x12price_for_quantity\x18\x01 \x03(\x0b\x32|.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.UpdateDirectPurchasePriceEvent.PriceForQuantityB\x07\n\x05priceB\x0e\n\x0c_public_note\x1a\xfc\x02\n\x10GroupActionEvent\x12n\n\x0btoken_event\x18\x01 \x01(\x0b\x32W.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.TokenEventH\x00\x12t\n\x0e\x64ocument_event\x18\x02 \x01(\x0b\x32Z.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.DocumentEventH\x00\x12t\n\x0e\x63ontract_event\x18\x03 \x01(\x0b\x32Z.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.ContractEventH\x00\x42\x0c\n\nevent_type\x1a\x8b\x01\n\rDocumentEvent\x12r\n\x06\x63reate\x18\x01 \x01(\x0b\x32`.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.DocumentCreateEventH\x00\x42\x06\n\x04type\x1a/\n\x13\x44ocumentCreateEvent\x12\x18\n\x10\x63reated_document\x18\x01 \x01(\x0c\x1a/\n\x13\x43ontractUpdateEvent\x12\x18\n\x10updated_contract\x18\x01 \x01(\x0c\x1a\x8b\x01\n\rContractEvent\x12r\n\x06update\x18\x01 \x01(\x0b\x32`.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.ContractUpdateEventH\x00\x42\x06\n\x04type\x1a\xd1\x07\n\nTokenEvent\x12\x66\n\x04mint\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.MintEventH\x00\x12\x66\n\x04\x62urn\x18\x02 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.BurnEventH\x00\x12j\n\x06\x66reeze\x18\x03 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.FreezeEventH\x00\x12n\n\x08unfreeze\x18\x04 \x01(\x0b\x32Z.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.UnfreezeEventH\x00\x12\x84\x01\n\x14\x64\x65stroy_frozen_funds\x18\x05 \x01(\x0b\x32\x64.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.DestroyFrozenFundsEventH\x00\x12}\n\x10\x65mergency_action\x18\x06 \x01(\x0b\x32\x61.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.EmergencyActionEventH\x00\x12\x82\x01\n\x13token_config_update\x18\x07 \x01(\x0b\x32\x63.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.TokenConfigUpdateEventH\x00\x12\x83\x01\n\x0cupdate_price\x18\x08 \x01(\x0b\x32k.org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.UpdateDirectPurchasePriceEventH\x00\x42\x06\n\x04type\x1a\x93\x01\n\x10GroupActionEntry\x12\x11\n\taction_id\x18\x01 \x01(\x0c\x12l\n\x05\x65vent\x18\x02 \x01(\x0b\x32].org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.GroupActionEvent\x1a\x84\x01\n\x0cGroupActions\x12t\n\rgroup_actions\x18\x01 \x03(\x0b\x32].org.dash.platform.dapi.v0.GetGroupActionsResponse.GetGroupActionsResponseV0.GroupActionEntryB\x08\n\x06resultB\t\n\x07version\"\x88\x03\n\x1cGetGroupActionSignersRequest\x12\x64\n\x02v0\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetGroupActionSignersRequest.GetGroupActionSignersRequestV0H\x00\x1a\xce\x01\n\x1eGetGroupActionSignersRequestV0\x12\x13\n\x0b\x63ontract_id\x18\x01 \x01(\x0c\x12\x1f\n\x17group_contract_position\x18\x02 \x01(\r\x12T\n\x06status\x18\x03 \x01(\x0e\x32\x44.org.dash.platform.dapi.v0.GetGroupActionSignersRequest.ActionStatus\x12\x11\n\taction_id\x18\x04 \x01(\x0c\x12\r\n\x05prove\x18\x05 \x01(\x08\"&\n\x0c\x41\x63tionStatus\x12\n\n\x06\x41\x43TIVE\x10\x00\x12\n\n\x06\x43LOSED\x10\x01\x42\t\n\x07version\"\x8b\x05\n\x1dGetGroupActionSignersResponse\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetGroupActionSignersResponse.GetGroupActionSignersResponseV0H\x00\x1a\xf6\x03\n\x1fGetGroupActionSignersResponseV0\x12\x8b\x01\n\x14group_action_signers\x18\x01 \x01(\x0b\x32k.org.dash.platform.dapi.v0.GetGroupActionSignersResponse.GetGroupActionSignersResponseV0.GroupActionSignersH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x35\n\x11GroupActionSigner\x12\x11\n\tsigner_id\x18\x01 \x01(\x0c\x12\r\n\x05power\x18\x02 \x01(\r\x1a\x91\x01\n\x12GroupActionSigners\x12{\n\x07signers\x18\x01 \x03(\x0b\x32j.org.dash.platform.dapi.v0.GetGroupActionSignersResponse.GetGroupActionSignersResponseV0.GroupActionSignerB\x08\n\x06resultB\t\n\x07version\"\xb5\x01\n\x15GetAddressInfoRequest\x12V\n\x02v0\x18\x01 \x01(\x0b\x32H.org.dash.platform.dapi.v0.GetAddressInfoRequest.GetAddressInfoRequestV0H\x00\x1a\x39\n\x17GetAddressInfoRequestV0\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\x85\x01\n\x10\x41\x64\x64ressInfoEntry\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0c\x12J\n\x11\x62\x61lance_and_nonce\x18\x02 \x01(\x0b\x32*.org.dash.platform.dapi.v0.BalanceAndNonceH\x00\x88\x01\x01\x42\x14\n\x12_balance_and_nonce\"1\n\x0f\x42\x61lanceAndNonce\x12\x0f\n\x07\x62\x61lance\x18\x01 \x01(\x04\x12\r\n\x05nonce\x18\x02 \x01(\r\"_\n\x12\x41\x64\x64ressInfoEntries\x12I\n\x14\x61\x64\x64ress_info_entries\x18\x01 \x03(\x0b\x32+.org.dash.platform.dapi.v0.AddressInfoEntry\"m\n\x14\x41\x64\x64ressBalanceChange\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0c\x12\x19\n\x0bset_balance\x18\x02 \x01(\x04\x42\x02\x30\x01H\x00\x12\x1c\n\x0e\x61\x64\x64_to_balance\x18\x03 \x01(\x04\x42\x02\x30\x01H\x00\x42\x0b\n\toperation\"x\n\x1a\x42lockAddressBalanceChanges\x12\x18\n\x0c\x62lock_height\x18\x01 \x01(\x04\x42\x02\x30\x01\x12@\n\x07\x63hanges\x18\x02 \x03(\x0b\x32/.org.dash.platform.dapi.v0.AddressBalanceChange\"k\n\x1b\x41\x64\x64ressBalanceUpdateEntries\x12L\n\rblock_changes\x18\x01 \x03(\x0b\x32\x35.org.dash.platform.dapi.v0.BlockAddressBalanceChanges\"\xe1\x02\n\x16GetAddressInfoResponse\x12X\n\x02v0\x18\x01 \x01(\x0b\x32J.org.dash.platform.dapi.v0.GetAddressInfoResponse.GetAddressInfoResponseV0H\x00\x1a\xe1\x01\n\x18GetAddressInfoResponseV0\x12I\n\x12\x61\x64\x64ress_info_entry\x18\x01 \x01(\x0b\x32+.org.dash.platform.dapi.v0.AddressInfoEntryH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xc3\x01\n\x18GetAddressesInfosRequest\x12\\\n\x02v0\x18\x01 \x01(\x0b\x32N.org.dash.platform.dapi.v0.GetAddressesInfosRequest.GetAddressesInfosRequestV0H\x00\x1a>\n\x1aGetAddressesInfosRequestV0\x12\x11\n\taddresses\x18\x01 \x03(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xf1\x02\n\x19GetAddressesInfosResponse\x12^\n\x02v0\x18\x01 \x01(\x0b\x32P.org.dash.platform.dapi.v0.GetAddressesInfosResponse.GetAddressesInfosResponseV0H\x00\x1a\xe8\x01\n\x1bGetAddressesInfosResponseV0\x12M\n\x14\x61\x64\x64ress_info_entries\x18\x01 \x01(\x0b\x32-.org.dash.platform.dapi.v0.AddressInfoEntriesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xb5\x01\n\x1dGetAddressesTrunkStateRequest\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetAddressesTrunkStateRequest.GetAddressesTrunkStateRequestV0H\x00\x1a!\n\x1fGetAddressesTrunkStateRequestV0B\t\n\x07version\"\xaa\x02\n\x1eGetAddressesTrunkStateResponse\x12h\n\x02v0\x18\x01 \x01(\x0b\x32Z.org.dash.platform.dapi.v0.GetAddressesTrunkStateResponse.GetAddressesTrunkStateResponseV0H\x00\x1a\x92\x01\n GetAddressesTrunkStateResponseV0\x12/\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.Proof\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\t\n\x07version\"\xf0\x01\n\x1eGetAddressesBranchStateRequest\x12h\n\x02v0\x18\x01 \x01(\x0b\x32Z.org.dash.platform.dapi.v0.GetAddressesBranchStateRequest.GetAddressesBranchStateRequestV0H\x00\x1aY\n GetAddressesBranchStateRequestV0\x12\x0b\n\x03key\x18\x01 \x01(\x0c\x12\r\n\x05\x64\x65pth\x18\x02 \x01(\r\x12\x19\n\x11\x63heckpoint_height\x18\x03 \x01(\x04\x42\t\n\x07version\"\xd1\x01\n\x1fGetAddressesBranchStateResponse\x12j\n\x02v0\x18\x01 \x01(\x0b\x32\\.org.dash.platform.dapi.v0.GetAddressesBranchStateResponse.GetAddressesBranchStateResponseV0H\x00\x1a\x37\n!GetAddressesBranchStateResponseV0\x12\x12\n\nmerk_proof\x18\x02 \x01(\x0c\x42\t\n\x07version\"\x9e\x02\n%GetRecentAddressBalanceChangesRequest\x12v\n\x02v0\x18\x01 \x01(\x0b\x32h.org.dash.platform.dapi.v0.GetRecentAddressBalanceChangesRequest.GetRecentAddressBalanceChangesRequestV0H\x00\x1ar\n\'GetRecentAddressBalanceChangesRequestV0\x12\x18\n\x0cstart_height\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\r\n\x05prove\x18\x02 \x01(\x08\x12\x1e\n\x16start_height_exclusive\x18\x03 \x01(\x08\x42\t\n\x07version\"\xb8\x03\n&GetRecentAddressBalanceChangesResponse\x12x\n\x02v0\x18\x01 \x01(\x0b\x32j.org.dash.platform.dapi.v0.GetRecentAddressBalanceChangesResponse.GetRecentAddressBalanceChangesResponseV0H\x00\x1a\x88\x02\n(GetRecentAddressBalanceChangesResponseV0\x12`\n\x1e\x61\x64\x64ress_balance_update_entries\x18\x01 \x01(\x0b\x32\x36.org.dash.platform.dapi.v0.AddressBalanceUpdateEntriesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"G\n\x16\x42lockHeightCreditEntry\x12\x18\n\x0c\x62lock_height\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\x13\n\x07\x63redits\x18\x02 \x01(\x04\x42\x02\x30\x01\"\xb0\x01\n\x1d\x43ompactedAddressBalanceChange\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0c\x12\x19\n\x0bset_credits\x18\x02 \x01(\x04\x42\x02\x30\x01H\x00\x12V\n\x19\x61\x64\x64_to_credits_operations\x18\x03 \x01(\x0b\x32\x31.org.dash.platform.dapi.v0.AddToCreditsOperationsH\x00\x42\x0b\n\toperation\"\\\n\x16\x41\x64\x64ToCreditsOperations\x12\x42\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x31.org.dash.platform.dapi.v0.BlockHeightCreditEntry\"\xae\x01\n#CompactedBlockAddressBalanceChanges\x12\x1e\n\x12start_block_height\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\x1c\n\x10\x65nd_block_height\x18\x02 \x01(\x04\x42\x02\x30\x01\x12I\n\x07\x63hanges\x18\x03 \x03(\x0b\x32\x38.org.dash.platform.dapi.v0.CompactedAddressBalanceChange\"\x87\x01\n$CompactedAddressBalanceUpdateEntries\x12_\n\x17\x63ompacted_block_changes\x18\x01 \x03(\x0b\x32>.org.dash.platform.dapi.v0.CompactedBlockAddressBalanceChanges\"\xa9\x02\n.GetRecentCompactedAddressBalanceChangesRequest\x12\x88\x01\n\x02v0\x18\x01 \x01(\x0b\x32z.org.dash.platform.dapi.v0.GetRecentCompactedAddressBalanceChangesRequest.GetRecentCompactedAddressBalanceChangesRequestV0H\x00\x1a\x61\n0GetRecentCompactedAddressBalanceChangesRequestV0\x12\x1e\n\x12start_block_height\x18\x01 \x01(\x04\x42\x02\x30\x01\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\xf0\x03\n/GetRecentCompactedAddressBalanceChangesResponse\x12\x8a\x01\n\x02v0\x18\x01 \x01(\x0b\x32|.org.dash.platform.dapi.v0.GetRecentCompactedAddressBalanceChangesResponse.GetRecentCompactedAddressBalanceChangesResponseV0H\x00\x1a\xa4\x02\n1GetRecentCompactedAddressBalanceChangesResponseV0\x12s\n(compacted_address_balance_update_entries\x18\x01 \x01(\x0b\x32?.org.dash.platform.dapi.v0.CompactedAddressBalanceUpdateEntriesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xf4\x01\n GetShieldedEncryptedNotesRequest\x12l\n\x02v0\x18\x01 \x01(\x0b\x32^.org.dash.platform.dapi.v0.GetShieldedEncryptedNotesRequest.GetShieldedEncryptedNotesRequestV0H\x00\x1aW\n\"GetShieldedEncryptedNotesRequestV0\x12\x13\n\x0bstart_index\x18\x01 \x01(\x04\x12\r\n\x05\x63ount\x18\x02 \x01(\r\x12\r\n\x05prove\x18\x03 \x01(\x08\x42\t\n\x07version\"\xbc\x05\n!GetShieldedEncryptedNotesResponse\x12n\n\x02v0\x18\x01 \x01(\x0b\x32`.org.dash.platform.dapi.v0.GetShieldedEncryptedNotesResponse.GetShieldedEncryptedNotesResponseV0H\x00\x1a\x9b\x04\n#GetShieldedEncryptedNotesResponseV0\x12\x8a\x01\n\x0f\x65ncrypted_notes\x18\x01 \x01(\x0b\x32o.org.dash.platform.dapi.v0.GetShieldedEncryptedNotesResponse.GetShieldedEncryptedNotesResponseV0.EncryptedNotesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1aW\n\rEncryptedNote\x12\x11\n\tnullifier\x18\x01 \x01(\x0c\x12\x0b\n\x03\x63mx\x18\x02 \x01(\x0c\x12\x16\n\x0e\x65ncrypted_note\x18\x03 \x01(\x0c\x12\x0e\n\x06\x63v_net\x18\x04 \x01(\x0c\x1a\x91\x01\n\x0e\x45ncryptedNotes\x12\x7f\n\x07\x65ntries\x18\x01 \x03(\x0b\x32n.org.dash.platform.dapi.v0.GetShieldedEncryptedNotesResponse.GetShieldedEncryptedNotesResponseV0.EncryptedNoteB\x08\n\x06resultB\t\n\x07version\"\xb4\x01\n\x19GetShieldedAnchorsRequest\x12^\n\x02v0\x18\x01 \x01(\x0b\x32P.org.dash.platform.dapi.v0.GetShieldedAnchorsRequest.GetShieldedAnchorsRequestV0H\x00\x1a,\n\x1bGetShieldedAnchorsRequestV0\x12\r\n\x05prove\x18\x01 \x01(\x08\x42\t\n\x07version\"\xb1\x03\n\x1aGetShieldedAnchorsResponse\x12`\n\x02v0\x18\x01 \x01(\x0b\x32R.org.dash.platform.dapi.v0.GetShieldedAnchorsResponse.GetShieldedAnchorsResponseV0H\x00\x1a\xa5\x02\n\x1cGetShieldedAnchorsResponseV0\x12m\n\x07\x61nchors\x18\x01 \x01(\x0b\x32Z.org.dash.platform.dapi.v0.GetShieldedAnchorsResponse.GetShieldedAnchorsResponseV0.AnchorsH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x1a\n\x07\x41nchors\x12\x0f\n\x07\x61nchors\x18\x01 \x03(\x0c\x42\x08\n\x06resultB\t\n\x07version\"\xd8\x01\n\"GetMostRecentShieldedAnchorRequest\x12p\n\x02v0\x18\x01 \x01(\x0b\x32\x62.org.dash.platform.dapi.v0.GetMostRecentShieldedAnchorRequest.GetMostRecentShieldedAnchorRequestV0H\x00\x1a\x35\n$GetMostRecentShieldedAnchorRequestV0\x12\r\n\x05prove\x18\x01 \x01(\x08\x42\t\n\x07version\"\xdc\x02\n#GetMostRecentShieldedAnchorResponse\x12r\n\x02v0\x18\x01 \x01(\x0b\x32\x64.org.dash.platform.dapi.v0.GetMostRecentShieldedAnchorResponse.GetMostRecentShieldedAnchorResponseV0H\x00\x1a\xb5\x01\n%GetMostRecentShieldedAnchorResponseV0\x12\x10\n\x06\x61nchor\x18\x01 \x01(\x0cH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xbc\x01\n\x1bGetShieldedPoolStateRequest\x12\x62\n\x02v0\x18\x01 \x01(\x0b\x32T.org.dash.platform.dapi.v0.GetShieldedPoolStateRequest.GetShieldedPoolStateRequestV0H\x00\x1a.\n\x1dGetShieldedPoolStateRequestV0\x12\r\n\x05prove\x18\x01 \x01(\x08\x42\t\n\x07version\"\xcb\x02\n\x1cGetShieldedPoolStateResponse\x12\x64\n\x02v0\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetShieldedPoolStateResponse.GetShieldedPoolStateResponseV0H\x00\x1a\xb9\x01\n\x1eGetShieldedPoolStateResponseV0\x12\x1b\n\rtotal_balance\x18\x01 \x01(\x04\x42\x02\x30\x01H\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xc0\x01\n\x1cGetShieldedNotesCountRequest\x12\x64\n\x02v0\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetShieldedNotesCountRequest.GetShieldedNotesCountRequestV0H\x00\x1a/\n\x1eGetShieldedNotesCountRequestV0\x12\r\n\x05prove\x18\x01 \x01(\x08\x42\t\n\x07version\"\xd3\x02\n\x1dGetShieldedNotesCountResponse\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetShieldedNotesCountResponse.GetShieldedNotesCountResponseV0H\x00\x1a\xbe\x01\n\x1fGetShieldedNotesCountResponseV0\x12\x1f\n\x11total_notes_count\x18\x01 \x01(\x04\x42\x02\x30\x01H\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadataB\x08\n\x06resultB\t\n\x07version\"\xd4\x01\n\x1cGetShieldedNullifiersRequest\x12\x64\n\x02v0\x18\x01 \x01(\x0b\x32V.org.dash.platform.dapi.v0.GetShieldedNullifiersRequest.GetShieldedNullifiersRequestV0H\x00\x1a\x43\n\x1eGetShieldedNullifiersRequestV0\x12\x12\n\nnullifiers\x18\x01 \x03(\x0c\x12\r\n\x05prove\x18\x02 \x01(\x08\x42\t\n\x07version\"\x86\x05\n\x1dGetShieldedNullifiersResponse\x12\x66\n\x02v0\x18\x01 \x01(\x0b\x32X.org.dash.platform.dapi.v0.GetShieldedNullifiersResponse.GetShieldedNullifiersResponseV0H\x00\x1a\xf1\x03\n\x1fGetShieldedNullifiersResponseV0\x12\x88\x01\n\x12nullifier_statuses\x18\x01 \x01(\x0b\x32j.org.dash.platform.dapi.v0.GetShieldedNullifiersResponse.GetShieldedNullifiersResponseV0.NullifierStatusesH\x00\x12\x31\n\x05proof\x18\x02 \x01(\x0b\x32 .org.dash.platform.dapi.v0.ProofH\x00\x12=\n\x08metadata\x18\x03 \x01(\x0b\x32+.org.dash.platform.dapi.v0.ResponseMetadata\x1a\x36\n\x0fNullifierStatus\x12\x11\n\tnullifier\x18\x01 \x01(\x0c\x12\x10\n\x08is_spent\x18\x02 \x01(\x08\x1a\x8e\x01\n\x11NullifierStatuses\x12y\n\x07\x65ntries\x18\x01 \x03(\x0b\x32h.org.dash.platform.dapi.v0.GetShieldedNullifiersResponse.GetShieldedNullifiersResponseV0.NullifierStatusB\x08\n\x06resultB\t\n\x07version*Z\n\nKeyPurpose\x12\x12\n\x0e\x41UTHENTICATION\x10\x00\x12\x0e\n\nENCRYPTION\x10\x01\x12\x0e\n\nDECRYPTION\x10\x02\x12\x0c\n\x08TRANSFER\x10\x03\x12\n\n\x06VOTING\x10\x05\x32\xce\x44\n\x08Platform\x12\x93\x01\n\x18\x62roadcastStateTransition\x12:.org.dash.platform.dapi.v0.BroadcastStateTransitionRequest\x1a;.org.dash.platform.dapi.v0.BroadcastStateTransitionResponse\x12l\n\x0bgetIdentity\x12-.org.dash.platform.dapi.v0.GetIdentityRequest\x1a..org.dash.platform.dapi.v0.GetIdentityResponse\x12x\n\x0fgetIdentityKeys\x12\x31.org.dash.platform.dapi.v0.GetIdentityKeysRequest\x1a\x32.org.dash.platform.dapi.v0.GetIdentityKeysResponse\x12\x96\x01\n\x19getIdentitiesContractKeys\x12;.org.dash.platform.dapi.v0.GetIdentitiesContractKeysRequest\x1a<.org.dash.platform.dapi.v0.GetIdentitiesContractKeysResponse\x12{\n\x10getIdentityNonce\x12\x32.org.dash.platform.dapi.v0.GetIdentityNonceRequest\x1a\x33.org.dash.platform.dapi.v0.GetIdentityNonceResponse\x12\x93\x01\n\x18getIdentityContractNonce\x12:.org.dash.platform.dapi.v0.GetIdentityContractNonceRequest\x1a;.org.dash.platform.dapi.v0.GetIdentityContractNonceResponse\x12\x81\x01\n\x12getIdentityBalance\x12\x34.org.dash.platform.dapi.v0.GetIdentityBalanceRequest\x1a\x35.org.dash.platform.dapi.v0.GetIdentityBalanceResponse\x12\x8a\x01\n\x15getIdentitiesBalances\x12\x37.org.dash.platform.dapi.v0.GetIdentitiesBalancesRequest\x1a\x38.org.dash.platform.dapi.v0.GetIdentitiesBalancesResponse\x12\xa2\x01\n\x1dgetIdentityBalanceAndRevision\x12?.org.dash.platform.dapi.v0.GetIdentityBalanceAndRevisionRequest\x1a@.org.dash.platform.dapi.v0.GetIdentityBalanceAndRevisionResponse\x12\xaf\x01\n#getEvonodesProposedEpochBlocksByIds\x12\x45.org.dash.platform.dapi.v0.GetEvonodesProposedEpochBlocksByIdsRequest\x1a\x41.org.dash.platform.dapi.v0.GetEvonodesProposedEpochBlocksResponse\x12\xb3\x01\n%getEvonodesProposedEpochBlocksByRange\x12G.org.dash.platform.dapi.v0.GetEvonodesProposedEpochBlocksByRangeRequest\x1a\x41.org.dash.platform.dapi.v0.GetEvonodesProposedEpochBlocksResponse\x12x\n\x0fgetDataContract\x12\x31.org.dash.platform.dapi.v0.GetDataContractRequest\x1a\x32.org.dash.platform.dapi.v0.GetDataContractResponse\x12\x8d\x01\n\x16getDataContractHistory\x12\x38.org.dash.platform.dapi.v0.GetDataContractHistoryRequest\x1a\x39.org.dash.platform.dapi.v0.GetDataContractHistoryResponse\x12{\n\x10getDataContracts\x12\x32.org.dash.platform.dapi.v0.GetDataContractsRequest\x1a\x33.org.dash.platform.dapi.v0.GetDataContractsResponse\x12\x81\x01\n\x12getDocumentHistory\x12\x34.org.dash.platform.dapi.v0.GetDocumentHistoryRequest\x1a\x35.org.dash.platform.dapi.v0.GetDocumentHistoryResponse\x12o\n\x0cgetDocuments\x12..org.dash.platform.dapi.v0.GetDocumentsRequest\x1a/.org.dash.platform.dapi.v0.GetDocumentsResponse\x12\x99\x01\n\x1agetIdentityByPublicKeyHash\x12<.org.dash.platform.dapi.v0.GetIdentityByPublicKeyHashRequest\x1a=.org.dash.platform.dapi.v0.GetIdentityByPublicKeyHashResponse\x12\xb4\x01\n#getIdentityByNonUniquePublicKeyHash\x12\x45.org.dash.platform.dapi.v0.GetIdentityByNonUniquePublicKeyHashRequest\x1a\x46.org.dash.platform.dapi.v0.GetIdentityByNonUniquePublicKeyHashResponse\x12\x9f\x01\n\x1cwaitForStateTransitionResult\x12>.org.dash.platform.dapi.v0.WaitForStateTransitionResultRequest\x1a?.org.dash.platform.dapi.v0.WaitForStateTransitionResultResponse\x12\x81\x01\n\x12getConsensusParams\x12\x34.org.dash.platform.dapi.v0.GetConsensusParamsRequest\x1a\x35.org.dash.platform.dapi.v0.GetConsensusParamsResponse\x12\xa5\x01\n\x1egetProtocolVersionUpgradeState\x12@.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeStateRequest\x1a\x41.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeStateResponse\x12\xb4\x01\n#getProtocolVersionUpgradeVoteStatus\x12\x45.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeVoteStatusRequest\x1a\x46.org.dash.platform.dapi.v0.GetProtocolVersionUpgradeVoteStatusResponse\x12r\n\rgetEpochsInfo\x12/.org.dash.platform.dapi.v0.GetEpochsInfoRequest\x1a\x30.org.dash.platform.dapi.v0.GetEpochsInfoResponse\x12\x8d\x01\n\x16getFinalizedEpochInfos\x12\x38.org.dash.platform.dapi.v0.GetFinalizedEpochInfosRequest\x1a\x39.org.dash.platform.dapi.v0.GetFinalizedEpochInfosResponse\x12\x8a\x01\n\x15getContestedResources\x12\x37.org.dash.platform.dapi.v0.GetContestedResourcesRequest\x1a\x38.org.dash.platform.dapi.v0.GetContestedResourcesResponse\x12\xa2\x01\n\x1dgetContestedResourceVoteState\x12?.org.dash.platform.dapi.v0.GetContestedResourceVoteStateRequest\x1a@.org.dash.platform.dapi.v0.GetContestedResourceVoteStateResponse\x12\xba\x01\n%getContestedResourceVotersForIdentity\x12G.org.dash.platform.dapi.v0.GetContestedResourceVotersForIdentityRequest\x1aH.org.dash.platform.dapi.v0.GetContestedResourceVotersForIdentityResponse\x12\xae\x01\n!getContestedResourceIdentityVotes\x12\x43.org.dash.platform.dapi.v0.GetContestedResourceIdentityVotesRequest\x1a\x44.org.dash.platform.dapi.v0.GetContestedResourceIdentityVotesResponse\x12\x8a\x01\n\x15getVotePollsByEndDate\x12\x37.org.dash.platform.dapi.v0.GetVotePollsByEndDateRequest\x1a\x38.org.dash.platform.dapi.v0.GetVotePollsByEndDateResponse\x12\xa5\x01\n\x1egetPrefundedSpecializedBalance\x12@.org.dash.platform.dapi.v0.GetPrefundedSpecializedBalanceRequest\x1a\x41.org.dash.platform.dapi.v0.GetPrefundedSpecializedBalanceResponse\x12\x96\x01\n\x19getTotalCreditsInPlatform\x12;.org.dash.platform.dapi.v0.GetTotalCreditsInPlatformRequest\x1a<.org.dash.platform.dapi.v0.GetTotalCreditsInPlatformResponse\x12x\n\x0fgetPathElements\x12\x31.org.dash.platform.dapi.v0.GetPathElementsRequest\x1a\x32.org.dash.platform.dapi.v0.GetPathElementsResponse\x12\x66\n\tgetStatus\x12+.org.dash.platform.dapi.v0.GetStatusRequest\x1a,.org.dash.platform.dapi.v0.GetStatusResponse\x12\x8a\x01\n\x15getCurrentQuorumsInfo\x12\x37.org.dash.platform.dapi.v0.GetCurrentQuorumsInfoRequest\x1a\x38.org.dash.platform.dapi.v0.GetCurrentQuorumsInfoResponse\x12\x93\x01\n\x18getIdentityTokenBalances\x12:.org.dash.platform.dapi.v0.GetIdentityTokenBalancesRequest\x1a;.org.dash.platform.dapi.v0.GetIdentityTokenBalancesResponse\x12\x99\x01\n\x1agetIdentitiesTokenBalances\x12<.org.dash.platform.dapi.v0.GetIdentitiesTokenBalancesRequest\x1a=.org.dash.platform.dapi.v0.GetIdentitiesTokenBalancesResponse\x12\x8a\x01\n\x15getIdentityTokenInfos\x12\x37.org.dash.platform.dapi.v0.GetIdentityTokenInfosRequest\x1a\x38.org.dash.platform.dapi.v0.GetIdentityTokenInfosResponse\x12\x90\x01\n\x17getIdentitiesTokenInfos\x12\x39.org.dash.platform.dapi.v0.GetIdentitiesTokenInfosRequest\x1a:.org.dash.platform.dapi.v0.GetIdentitiesTokenInfosResponse\x12{\n\x10getTokenStatuses\x12\x32.org.dash.platform.dapi.v0.GetTokenStatusesRequest\x1a\x33.org.dash.platform.dapi.v0.GetTokenStatusesResponse\x12\x9f\x01\n\x1cgetTokenDirectPurchasePrices\x12>.org.dash.platform.dapi.v0.GetTokenDirectPurchasePricesRequest\x1a?.org.dash.platform.dapi.v0.GetTokenDirectPurchasePricesResponse\x12\x87\x01\n\x14getTokenContractInfo\x12\x36.org.dash.platform.dapi.v0.GetTokenContractInfoRequest\x1a\x37.org.dash.platform.dapi.v0.GetTokenContractInfoResponse\x12\xb1\x01\n\"getTokenPreProgrammedDistributions\x12\x44.org.dash.platform.dapi.v0.GetTokenPreProgrammedDistributionsRequest\x1a\x45.org.dash.platform.dapi.v0.GetTokenPreProgrammedDistributionsResponse\x12\xbd\x01\n&getTokenPerpetualDistributionLastClaim\x12H.org.dash.platform.dapi.v0.GetTokenPerpetualDistributionLastClaimRequest\x1aI.org.dash.platform.dapi.v0.GetTokenPerpetualDistributionLastClaimResponse\x12\x84\x01\n\x13getTokenTotalSupply\x12\x35.org.dash.platform.dapi.v0.GetTokenTotalSupplyRequest\x1a\x36.org.dash.platform.dapi.v0.GetTokenTotalSupplyResponse\x12o\n\x0cgetGroupInfo\x12..org.dash.platform.dapi.v0.GetGroupInfoRequest\x1a/.org.dash.platform.dapi.v0.GetGroupInfoResponse\x12r\n\rgetGroupInfos\x12/.org.dash.platform.dapi.v0.GetGroupInfosRequest\x1a\x30.org.dash.platform.dapi.v0.GetGroupInfosResponse\x12x\n\x0fgetGroupActions\x12\x31.org.dash.platform.dapi.v0.GetGroupActionsRequest\x1a\x32.org.dash.platform.dapi.v0.GetGroupActionsResponse\x12\x8a\x01\n\x15getGroupActionSigners\x12\x37.org.dash.platform.dapi.v0.GetGroupActionSignersRequest\x1a\x38.org.dash.platform.dapi.v0.GetGroupActionSignersResponse\x12u\n\x0egetAddressInfo\x12\x30.org.dash.platform.dapi.v0.GetAddressInfoRequest\x1a\x31.org.dash.platform.dapi.v0.GetAddressInfoResponse\x12~\n\x11getAddressesInfos\x12\x33.org.dash.platform.dapi.v0.GetAddressesInfosRequest\x1a\x34.org.dash.platform.dapi.v0.GetAddressesInfosResponse\x12\x8d\x01\n\x16getAddressesTrunkState\x12\x38.org.dash.platform.dapi.v0.GetAddressesTrunkStateRequest\x1a\x39.org.dash.platform.dapi.v0.GetAddressesTrunkStateResponse\x12\x90\x01\n\x17getAddressesBranchState\x12\x39.org.dash.platform.dapi.v0.GetAddressesBranchStateRequest\x1a:.org.dash.platform.dapi.v0.GetAddressesBranchStateResponse\x12\xa5\x01\n\x1egetRecentAddressBalanceChanges\x12@.org.dash.platform.dapi.v0.GetRecentAddressBalanceChangesRequest\x1a\x41.org.dash.platform.dapi.v0.GetRecentAddressBalanceChangesResponse\x12\xc0\x01\n\'getRecentCompactedAddressBalanceChanges\x12I.org.dash.platform.dapi.v0.GetRecentCompactedAddressBalanceChangesRequest\x1aJ.org.dash.platform.dapi.v0.GetRecentCompactedAddressBalanceChangesResponse\x12\x96\x01\n\x19getShieldedEncryptedNotes\x12;.org.dash.platform.dapi.v0.GetShieldedEncryptedNotesRequest\x1a<.org.dash.platform.dapi.v0.GetShieldedEncryptedNotesResponse\x12\x81\x01\n\x12getShieldedAnchors\x12\x34.org.dash.platform.dapi.v0.GetShieldedAnchorsRequest\x1a\x35.org.dash.platform.dapi.v0.GetShieldedAnchorsResponse\x12\x9c\x01\n\x1bgetMostRecentShieldedAnchor\x12=.org.dash.platform.dapi.v0.GetMostRecentShieldedAnchorRequest\x1a>.org.dash.platform.dapi.v0.GetMostRecentShieldedAnchorResponse\x12\x87\x01\n\x14getShieldedPoolState\x12\x36.org.dash.platform.dapi.v0.GetShieldedPoolStateRequest\x1a\x37.org.dash.platform.dapi.v0.GetShieldedPoolStateResponse\x12\x8a\x01\n\x15getShieldedNotesCount\x12\x37.org.dash.platform.dapi.v0.GetShieldedNotesCountRequest\x1a\x38.org.dash.platform.dapi.v0.GetShieldedNotesCountResponse\x12\x8a\x01\n\x15getShieldedNullifiers\x12\x37.org.dash.platform.dapi.v0.GetShieldedNullifiersRequest\x1a\x38.org.dash.platform.dapi.v0.GetShieldedNullifiersResponseb\x06proto3' , dependencies=[google_dot_protobuf_dot_wrappers__pb2.DESCRIPTOR,google_dot_protobuf_dot_struct__pb2.DESCRIPTOR,google_dot_protobuf_dot_timestamp__pb2.DESCRIPTOR,]) @@ -62,8 +62,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=65780, - serialized_end=65870, + serialized_start=65812, + serialized_end=65902, ) _sym_db.RegisterEnumDescriptor(_KEYPURPOSE) @@ -340,8 +340,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=28490, - serialized_end=28563, + serialized_start=28522, + serialized_end=28595, ) _sym_db.RegisterEnumDescriptor(_GETCONTESTEDRESOURCEVOTESTATEREQUEST_GETCONTESTEDRESOURCEVOTESTATEREQUESTV0_RESULTTYPE) @@ -370,8 +370,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=29485, - serialized_end=29564, + serialized_start=29517, + serialized_end=29596, ) _sym_db.RegisterEnumDescriptor(_GETCONTESTEDRESOURCEVOTESTATERESPONSE_GETCONTESTEDRESOURCEVOTESTATERESPONSEV0_FINISHEDVOTEINFO_FINISHEDVOTEOUTCOME) @@ -400,8 +400,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=33193, - serialized_end=33254, + serialized_start=33225, + serialized_end=33286, ) _sym_db.RegisterEnumDescriptor(_GETCONTESTEDRESOURCEIDENTITYVOTESRESPONSE_GETCONTESTEDRESOURCEIDENTITYVOTESRESPONSEV0_RESOURCEVOTECHOICE_VOTECHOICETYPE) @@ -425,8 +425,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=51818, - serialized_end=51856, + serialized_start=51850, + serialized_end=51888, ) _sym_db.RegisterEnumDescriptor(_GETGROUPACTIONSREQUEST_ACTIONSTATUS) @@ -450,8 +450,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=53103, - serialized_end=53138, + serialized_start=53135, + serialized_end=53170, ) _sym_db.RegisterEnumDescriptor(_GETGROUPACTIONSRESPONSE_GETGROUPACTIONSRESPONSEV0_EMERGENCYACTIONEVENT_ACTIONTYPE) @@ -475,8 +475,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=51818, - serialized_end=51856, + serialized_start=51850, + serialized_end=51888, ) _sym_db.RegisterEnumDescriptor(_GETGROUPACTIONSIGNERSREQUEST_ACTIONSTATUS) @@ -4724,6 +4724,13 @@ message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='in_key', full_name='org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry.in_key', index=4, + number=5, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], @@ -4740,9 +4747,14 @@ index=0, containing_type=None, create_key=_descriptor._internal_create_key, fields=[]), + _descriptor.OneofDescriptor( + name='_in_key', full_name='org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry._in_key', + index=1, containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[]), ], serialized_start=15662, - serialized_end=15752, + serialized_end=15784, ) _GETDOCUMENTSRESPONSE_GETDOCUMENTSRESPONSEV1_RANKEDENTRIES = _descriptor.Descriptor( @@ -4784,8 +4796,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=15755, - serialized_end=15909, + serialized_start=15787, + serialized_end=15941, ) _GETDOCUMENTSRESPONSE_GETDOCUMENTSRESPONSEV1_RESULTDATA = _descriptor.Descriptor( @@ -4848,8 +4860,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=15912, - serialized_end=16451, + serialized_start=15944, + serialized_end=16483, ) _GETDOCUMENTSRESPONSE_GETDOCUMENTSRESPONSEV1 = _descriptor.Descriptor( @@ -4899,7 +4911,7 @@ fields=[]), ], serialized_start=14164, - serialized_end=16461, + serialized_end=16493, ) _GETDOCUMENTSRESPONSE = _descriptor.Descriptor( @@ -4942,7 +4954,7 @@ fields=[]), ], serialized_start=13681, - serialized_end=16472, + serialized_end=16504, ) @@ -5015,8 +5027,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=16601, - serialized_end=16836, + serialized_start=16633, + serialized_end=16868, ) _GETDOCUMENTHISTORYREQUEST = _descriptor.Descriptor( @@ -5051,8 +5063,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=16475, - serialized_end=16847, + serialized_start=16507, + serialized_end=16879, ) @@ -5090,8 +5102,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=17253, - serialized_end=17308, + serialized_start=17285, + serialized_end=17340, ) _GETDOCUMENTHISTORYRESPONSE_GETDOCUMENTHISTORYRESPONSEV0_DOCUMENTHISTORY = _descriptor.Descriptor( @@ -5121,8 +5133,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=17311, - serialized_end=17460, + serialized_start=17343, + serialized_end=17492, ) _GETDOCUMENTHISTORYRESPONSE_GETDOCUMENTHISTORYRESPONSEV0 = _descriptor.Descriptor( @@ -5171,8 +5183,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=16979, - serialized_end=17470, + serialized_start=17011, + serialized_end=17502, ) _GETDOCUMENTHISTORYRESPONSE = _descriptor.Descriptor( @@ -5207,8 +5219,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=16850, - serialized_end=17481, + serialized_start=16882, + serialized_end=17513, ) @@ -5246,8 +5258,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=17633, - serialized_end=17710, + serialized_start=17665, + serialized_end=17742, ) _GETIDENTITYBYPUBLICKEYHASHREQUEST = _descriptor.Descriptor( @@ -5282,8 +5294,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=17484, - serialized_end=17721, + serialized_start=17516, + serialized_end=17753, ) @@ -5333,8 +5345,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=17877, - serialized_end=18059, + serialized_start=17909, + serialized_end=18091, ) _GETIDENTITYBYPUBLICKEYHASHRESPONSE = _descriptor.Descriptor( @@ -5369,8 +5381,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=17724, - serialized_end=18070, + serialized_start=17756, + serialized_end=18102, ) @@ -5420,8 +5432,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=18251, - serialized_end=18379, + serialized_start=18283, + serialized_end=18411, ) _GETIDENTITYBYNONUNIQUEPUBLICKEYHASHREQUEST = _descriptor.Descriptor( @@ -5456,8 +5468,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=18073, - serialized_end=18390, + serialized_start=18105, + serialized_end=18422, ) @@ -5493,8 +5505,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=19003, - serialized_end=19057, + serialized_start=19035, + serialized_end=19089, ) _GETIDENTITYBYNONUNIQUEPUBLICKEYHASHRESPONSE_GETIDENTITYBYNONUNIQUEPUBLICKEYHASHRESPONSEV0_IDENTITYPROVEDRESPONSE = _descriptor.Descriptor( @@ -5536,8 +5548,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=19060, - serialized_end=19226, + serialized_start=19092, + serialized_end=19258, ) _GETIDENTITYBYNONUNIQUEPUBLICKEYHASHRESPONSE_GETIDENTITYBYNONUNIQUEPUBLICKEYHASHRESPONSEV0 = _descriptor.Descriptor( @@ -5586,8 +5598,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=18574, - serialized_end=19236, + serialized_start=18606, + serialized_end=19268, ) _GETIDENTITYBYNONUNIQUEPUBLICKEYHASHRESPONSE = _descriptor.Descriptor( @@ -5622,8 +5634,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=18393, - serialized_end=19247, + serialized_start=18425, + serialized_end=19279, ) @@ -5661,8 +5673,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=19405, - serialized_end=19490, + serialized_start=19437, + serialized_end=19522, ) _WAITFORSTATETRANSITIONRESULTREQUEST = _descriptor.Descriptor( @@ -5697,8 +5709,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=19250, - serialized_end=19501, + serialized_start=19282, + serialized_end=19533, ) @@ -5748,8 +5760,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=19663, - serialized_end=19902, + serialized_start=19695, + serialized_end=19934, ) _WAITFORSTATETRANSITIONRESULTRESPONSE = _descriptor.Descriptor( @@ -5784,8 +5796,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=19504, - serialized_end=19913, + serialized_start=19536, + serialized_end=19945, ) @@ -5823,8 +5835,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=20041, - serialized_end=20101, + serialized_start=20073, + serialized_end=20133, ) _GETCONSENSUSPARAMSREQUEST = _descriptor.Descriptor( @@ -5859,8 +5871,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=19916, - serialized_end=20112, + serialized_start=19948, + serialized_end=20144, ) @@ -5905,8 +5917,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=20243, - serialized_end=20323, + serialized_start=20275, + serialized_end=20355, ) _GETCONSENSUSPARAMSRESPONSE_CONSENSUSPARAMSEVIDENCE = _descriptor.Descriptor( @@ -5950,8 +5962,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=20325, - serialized_end=20423, + serialized_start=20357, + serialized_end=20455, ) _GETCONSENSUSPARAMSRESPONSE_GETCONSENSUSPARAMSRESPONSEV0 = _descriptor.Descriptor( @@ -5988,8 +6000,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=20426, - serialized_end=20644, + serialized_start=20458, + serialized_end=20676, ) _GETCONSENSUSPARAMSRESPONSE = _descriptor.Descriptor( @@ -6024,8 +6036,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=20115, - serialized_end=20655, + serialized_start=20147, + serialized_end=20687, ) @@ -6056,8 +6068,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=20819, - serialized_end=20875, + serialized_start=20851, + serialized_end=20907, ) _GETPROTOCOLVERSIONUPGRADESTATEREQUEST = _descriptor.Descriptor( @@ -6092,8 +6104,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=20658, - serialized_end=20886, + serialized_start=20690, + serialized_end=20918, ) @@ -6124,8 +6136,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=21351, - serialized_end=21501, + serialized_start=21383, + serialized_end=21533, ) _GETPROTOCOLVERSIONUPGRADESTATERESPONSE_GETPROTOCOLVERSIONUPGRADESTATERESPONSEV0_VERSIONENTRY = _descriptor.Descriptor( @@ -6162,8 +6174,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=21503, - serialized_end=21561, + serialized_start=21535, + serialized_end=21593, ) _GETPROTOCOLVERSIONUPGRADESTATERESPONSE_GETPROTOCOLVERSIONUPGRADESTATERESPONSEV0 = _descriptor.Descriptor( @@ -6212,8 +6224,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=21054, - serialized_end=21571, + serialized_start=21086, + serialized_end=21603, ) _GETPROTOCOLVERSIONUPGRADESTATERESPONSE = _descriptor.Descriptor( @@ -6248,8 +6260,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=20889, - serialized_end=21582, + serialized_start=20921, + serialized_end=21614, ) @@ -6294,8 +6306,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=21762, - serialized_end=21865, + serialized_start=21794, + serialized_end=21897, ) _GETPROTOCOLVERSIONUPGRADEVOTESTATUSREQUEST = _descriptor.Descriptor( @@ -6330,8 +6342,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=21585, - serialized_end=21876, + serialized_start=21617, + serialized_end=21908, ) @@ -6362,8 +6374,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=22379, - serialized_end=22554, + serialized_start=22411, + serialized_end=22586, ) _GETPROTOCOLVERSIONUPGRADEVOTESTATUSRESPONSE_GETPROTOCOLVERSIONUPGRADEVOTESTATUSRESPONSEV0_VERSIONSIGNAL = _descriptor.Descriptor( @@ -6400,8 +6412,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=22556, - serialized_end=22609, + serialized_start=22588, + serialized_end=22641, ) _GETPROTOCOLVERSIONUPGRADEVOTESTATUSRESPONSE_GETPROTOCOLVERSIONUPGRADEVOTESTATUSRESPONSEV0 = _descriptor.Descriptor( @@ -6450,8 +6462,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=22060, - serialized_end=22619, + serialized_start=22092, + serialized_end=22651, ) _GETPROTOCOLVERSIONUPGRADEVOTESTATUSRESPONSE = _descriptor.Descriptor( @@ -6486,8 +6498,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=21879, - serialized_end=22630, + serialized_start=21911, + serialized_end=22662, ) @@ -6539,8 +6551,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=22743, - serialized_end=22867, + serialized_start=22775, + serialized_end=22899, ) _GETEPOCHSINFOREQUEST = _descriptor.Descriptor( @@ -6575,8 +6587,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=22633, - serialized_end=22878, + serialized_start=22665, + serialized_end=22910, ) @@ -6607,8 +6619,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=23239, - serialized_end=23356, + serialized_start=23271, + serialized_end=23388, ) _GETEPOCHSINFORESPONSE_GETEPOCHSINFORESPONSEV0_EPOCHINFO = _descriptor.Descriptor( @@ -6673,8 +6685,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=23359, - serialized_end=23525, + serialized_start=23391, + serialized_end=23557, ) _GETEPOCHSINFORESPONSE_GETEPOCHSINFORESPONSEV0 = _descriptor.Descriptor( @@ -6723,8 +6735,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=22995, - serialized_end=23535, + serialized_start=23027, + serialized_end=23567, ) _GETEPOCHSINFORESPONSE = _descriptor.Descriptor( @@ -6759,8 +6771,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=22881, - serialized_end=23546, + serialized_start=22913, + serialized_end=23578, ) @@ -6819,8 +6831,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=23687, - serialized_end=23857, + serialized_start=23719, + serialized_end=23889, ) _GETFINALIZEDEPOCHINFOSREQUEST = _descriptor.Descriptor( @@ -6855,8 +6867,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=23549, - serialized_end=23868, + serialized_start=23581, + serialized_end=23900, ) @@ -6887,8 +6899,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=24294, - serialized_end=24458, + serialized_start=24326, + serialized_end=24490, ) _GETFINALIZEDEPOCHINFOSRESPONSE_GETFINALIZEDEPOCHINFOSRESPONSEV0_FINALIZEDEPOCHINFO = _descriptor.Descriptor( @@ -7002,8 +7014,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=24461, - serialized_end=25004, + serialized_start=24493, + serialized_end=25036, ) _GETFINALIZEDEPOCHINFOSRESPONSE_GETFINALIZEDEPOCHINFOSRESPONSEV0_BLOCKPROPOSER = _descriptor.Descriptor( @@ -7040,8 +7052,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=25006, - serialized_end=25063, + serialized_start=25038, + serialized_end=25095, ) _GETFINALIZEDEPOCHINFOSRESPONSE_GETFINALIZEDEPOCHINFOSRESPONSEV0 = _descriptor.Descriptor( @@ -7090,8 +7102,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=24012, - serialized_end=25073, + serialized_start=24044, + serialized_end=25105, ) _GETFINALIZEDEPOCHINFOSRESPONSE = _descriptor.Descriptor( @@ -7126,8 +7138,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=23871, - serialized_end=25084, + serialized_start=23903, + serialized_end=25116, ) @@ -7165,8 +7177,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=25579, - serialized_end=25648, + serialized_start=25611, + serialized_end=25680, ) _GETCONTESTEDRESOURCESREQUEST_GETCONTESTEDRESOURCESREQUESTV0 = _descriptor.Descriptor( @@ -7262,8 +7274,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=25222, - serialized_end=25682, + serialized_start=25254, + serialized_end=25714, ) _GETCONTESTEDRESOURCESREQUEST = _descriptor.Descriptor( @@ -7298,8 +7310,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=25087, - serialized_end=25693, + serialized_start=25119, + serialized_end=25725, ) @@ -7330,8 +7342,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=26135, - serialized_end=26195, + serialized_start=26167, + serialized_end=26227, ) _GETCONTESTEDRESOURCESRESPONSE_GETCONTESTEDRESOURCESRESPONSEV0 = _descriptor.Descriptor( @@ -7380,8 +7392,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=25834, - serialized_end=26205, + serialized_start=25866, + serialized_end=26237, ) _GETCONTESTEDRESOURCESRESPONSE = _descriptor.Descriptor( @@ -7416,8 +7428,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=25696, - serialized_end=26216, + serialized_start=25728, + serialized_end=26248, ) @@ -7455,8 +7467,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=26729, - serialized_end=26802, + serialized_start=26761, + serialized_end=26834, ) _GETVOTEPOLLSBYENDDATEREQUEST_GETVOTEPOLLSBYENDDATEREQUESTV0_ENDATTIMEINFO = _descriptor.Descriptor( @@ -7493,8 +7505,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=26804, - serialized_end=26871, + serialized_start=26836, + serialized_end=26903, ) _GETVOTEPOLLSBYENDDATEREQUEST_GETVOTEPOLLSBYENDDATEREQUESTV0 = _descriptor.Descriptor( @@ -7579,8 +7591,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=26354, - serialized_end=26930, + serialized_start=26386, + serialized_end=26962, ) _GETVOTEPOLLSBYENDDATEREQUEST = _descriptor.Descriptor( @@ -7615,8 +7627,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=26219, - serialized_end=26941, + serialized_start=26251, + serialized_end=26973, ) @@ -7654,8 +7666,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=27390, - serialized_end=27476, + serialized_start=27422, + serialized_end=27508, ) _GETVOTEPOLLSBYENDDATERESPONSE_GETVOTEPOLLSBYENDDATERESPONSEV0_SERIALIZEDVOTEPOLLSBYTIMESTAMPS = _descriptor.Descriptor( @@ -7692,8 +7704,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=27479, - serialized_end=27694, + serialized_start=27511, + serialized_end=27726, ) _GETVOTEPOLLSBYENDDATERESPONSE_GETVOTEPOLLSBYENDDATERESPONSEV0 = _descriptor.Descriptor( @@ -7742,8 +7754,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=27082, - serialized_end=27704, + serialized_start=27114, + serialized_end=27736, ) _GETVOTEPOLLSBYENDDATERESPONSE = _descriptor.Descriptor( @@ -7778,8 +7790,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=26944, - serialized_end=27715, + serialized_start=26976, + serialized_end=27747, ) @@ -7817,8 +7829,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=28404, - serialized_end=28488, + serialized_start=28436, + serialized_end=28520, ) _GETCONTESTEDRESOURCEVOTESTATEREQUEST_GETCONTESTEDRESOURCEVOTESTATEREQUESTV0 = _descriptor.Descriptor( @@ -7915,8 +7927,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=27877, - serialized_end=28602, + serialized_start=27909, + serialized_end=28634, ) _GETCONTESTEDRESOURCEVOTESTATEREQUEST = _descriptor.Descriptor( @@ -7951,8 +7963,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=27718, - serialized_end=28613, + serialized_start=27750, + serialized_end=28645, ) @@ -8024,8 +8036,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=29113, - serialized_end=29587, + serialized_start=29145, + serialized_end=29619, ) _GETCONTESTEDRESOURCEVOTESTATERESPONSE_GETCONTESTEDRESOURCEVOTESTATERESPONSEV0_CONTESTEDRESOURCECONTENDERS = _descriptor.Descriptor( @@ -8091,8 +8103,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=29590, - serialized_end=30042, + serialized_start=29622, + serialized_end=30074, ) _GETCONTESTEDRESOURCEVOTESTATERESPONSE_GETCONTESTEDRESOURCEVOTESTATERESPONSEV0_CONTENDER = _descriptor.Descriptor( @@ -8146,8 +8158,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=30044, - serialized_end=30151, + serialized_start=30076, + serialized_end=30183, ) _GETCONTESTEDRESOURCEVOTESTATERESPONSE_GETCONTESTEDRESOURCEVOTESTATERESPONSEV0 = _descriptor.Descriptor( @@ -8196,8 +8208,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=28778, - serialized_end=30161, + serialized_start=28810, + serialized_end=30193, ) _GETCONTESTEDRESOURCEVOTESTATERESPONSE = _descriptor.Descriptor( @@ -8232,8 +8244,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=28616, - serialized_end=30172, + serialized_start=28648, + serialized_end=30204, ) @@ -8271,8 +8283,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=28404, - serialized_end=28488, + serialized_start=28436, + serialized_end=28520, ) _GETCONTESTEDRESOURCEVOTERSFORIDENTITYREQUEST_GETCONTESTEDRESOURCEVOTERSFORIDENTITYREQUESTV0 = _descriptor.Descriptor( @@ -8368,8 +8380,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=30359, - serialized_end=30889, + serialized_start=30391, + serialized_end=30921, ) _GETCONTESTEDRESOURCEVOTERSFORIDENTITYREQUEST = _descriptor.Descriptor( @@ -8404,8 +8416,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=30175, - serialized_end=30900, + serialized_start=30207, + serialized_end=30932, ) @@ -8443,8 +8455,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=31440, - serialized_end=31507, + serialized_start=31472, + serialized_end=31539, ) _GETCONTESTEDRESOURCEVOTERSFORIDENTITYRESPONSE_GETCONTESTEDRESOURCEVOTERSFORIDENTITYRESPONSEV0 = _descriptor.Descriptor( @@ -8493,8 +8505,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=31090, - serialized_end=31517, + serialized_start=31122, + serialized_end=31549, ) _GETCONTESTEDRESOURCEVOTERSFORIDENTITYRESPONSE = _descriptor.Descriptor( @@ -8529,8 +8541,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=30903, - serialized_end=31528, + serialized_start=30935, + serialized_end=31560, ) @@ -8568,8 +8580,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=32077, - serialized_end=32174, + serialized_start=32109, + serialized_end=32206, ) _GETCONTESTEDRESOURCEIDENTITYVOTESREQUEST_GETCONTESTEDRESOURCEIDENTITYVOTESREQUESTV0 = _descriptor.Descriptor( @@ -8639,8 +8651,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=31702, - serialized_end=32205, + serialized_start=31734, + serialized_end=32237, ) _GETCONTESTEDRESOURCEIDENTITYVOTESREQUEST = _descriptor.Descriptor( @@ -8675,8 +8687,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=31531, - serialized_end=32216, + serialized_start=31563, + serialized_end=32248, ) @@ -8714,8 +8726,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=32719, - serialized_end=32966, + serialized_start=32751, + serialized_end=32998, ) _GETCONTESTEDRESOURCEIDENTITYVOTESRESPONSE_GETCONTESTEDRESOURCEIDENTITYVOTESRESPONSEV0_RESOURCEVOTECHOICE = _descriptor.Descriptor( @@ -8758,8 +8770,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=32969, - serialized_end=33270, + serialized_start=33001, + serialized_end=33302, ) _GETCONTESTEDRESOURCEIDENTITYVOTESRESPONSE_GETCONTESTEDRESOURCEIDENTITYVOTESRESPONSEV0_CONTESTEDRESOURCEIDENTITYVOTE = _descriptor.Descriptor( @@ -8810,8 +8822,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=33273, - serialized_end=33550, + serialized_start=33305, + serialized_end=33582, ) _GETCONTESTEDRESOURCEIDENTITYVOTESRESPONSE_GETCONTESTEDRESOURCEIDENTITYVOTESRESPONSEV0 = _descriptor.Descriptor( @@ -8860,8 +8872,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=32393, - serialized_end=33560, + serialized_start=32425, + serialized_end=33592, ) _GETCONTESTEDRESOURCEIDENTITYVOTESRESPONSE = _descriptor.Descriptor( @@ -8896,8 +8908,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=32219, - serialized_end=33571, + serialized_start=32251, + serialized_end=33603, ) @@ -8935,8 +8947,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=33735, - serialized_end=33803, + serialized_start=33767, + serialized_end=33835, ) _GETPREFUNDEDSPECIALIZEDBALANCEREQUEST = _descriptor.Descriptor( @@ -8971,8 +8983,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=33574, - serialized_end=33814, + serialized_start=33606, + serialized_end=33846, ) @@ -9022,8 +9034,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=33982, - serialized_end=34171, + serialized_start=34014, + serialized_end=34203, ) _GETPREFUNDEDSPECIALIZEDBALANCERESPONSE = _descriptor.Descriptor( @@ -9058,8 +9070,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=33817, - serialized_end=34182, + serialized_start=33849, + serialized_end=34214, ) @@ -9090,8 +9102,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=34331, - serialized_end=34382, + serialized_start=34363, + serialized_end=34414, ) _GETTOTALCREDITSINPLATFORMREQUEST = _descriptor.Descriptor( @@ -9126,8 +9138,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=34185, - serialized_end=34393, + serialized_start=34217, + serialized_end=34425, ) @@ -9177,8 +9189,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=34546, - serialized_end=34730, + serialized_start=34578, + serialized_end=34762, ) _GETTOTALCREDITSINPLATFORMRESPONSE = _descriptor.Descriptor( @@ -9213,8 +9225,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=34396, - serialized_end=34741, + serialized_start=34428, + serialized_end=34773, ) @@ -9259,8 +9271,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=34860, - serialized_end=34929, + serialized_start=34892, + serialized_end=34961, ) _GETPATHELEMENTSREQUEST = _descriptor.Descriptor( @@ -9295,8 +9307,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=34744, - serialized_end=34940, + serialized_start=34776, + serialized_end=34972, ) @@ -9327,8 +9339,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=35313, - serialized_end=35341, + serialized_start=35345, + serialized_end=35373, ) _GETPATHELEMENTSRESPONSE_GETPATHELEMENTSRESPONSEV0 = _descriptor.Descriptor( @@ -9377,8 +9389,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=35063, - serialized_end=35351, + serialized_start=35095, + serialized_end=35383, ) _GETPATHELEMENTSRESPONSE = _descriptor.Descriptor( @@ -9413,8 +9425,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=34943, - serialized_end=35362, + serialized_start=34975, + serialized_end=35394, ) @@ -9438,8 +9450,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=35463, - serialized_end=35483, + serialized_start=35495, + serialized_end=35515, ) _GETSTATUSREQUEST = _descriptor.Descriptor( @@ -9474,8 +9486,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=35365, - serialized_end=35494, + serialized_start=35397, + serialized_end=35526, ) @@ -9530,8 +9542,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=36371, - serialized_end=36465, + serialized_start=36403, + serialized_end=36497, ) _GETSTATUSRESPONSE_GETSTATUSRESPONSEV0_VERSION_PROTOCOL_TENDERDASH = _descriptor.Descriptor( @@ -9568,8 +9580,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=36698, - serialized_end=36738, + serialized_start=36730, + serialized_end=36770, ) _GETSTATUSRESPONSE_GETSTATUSRESPONSEV0_VERSION_PROTOCOL_DRIVE = _descriptor.Descriptor( @@ -9613,8 +9625,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=36740, - serialized_end=36800, + serialized_start=36772, + serialized_end=36832, ) _GETSTATUSRESPONSE_GETSTATUSRESPONSEV0_VERSION_PROTOCOL = _descriptor.Descriptor( @@ -9651,8 +9663,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=36468, - serialized_end=36800, + serialized_start=36500, + serialized_end=36832, ) _GETSTATUSRESPONSE_GETSTATUSRESPONSEV0_VERSION = _descriptor.Descriptor( @@ -9689,8 +9701,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=36158, - serialized_end=36800, + serialized_start=36190, + serialized_end=36832, ) _GETSTATUSRESPONSE_GETSTATUSRESPONSEV0_TIME = _descriptor.Descriptor( @@ -9756,8 +9768,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=36802, - serialized_end=36929, + serialized_start=36834, + serialized_end=36961, ) _GETSTATUSRESPONSE_GETSTATUSRESPONSEV0_NODE = _descriptor.Descriptor( @@ -9799,8 +9811,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=36931, - serialized_end=36991, + serialized_start=36963, + serialized_end=37023, ) _GETSTATUSRESPONSE_GETSTATUSRESPONSEV0_CHAIN = _descriptor.Descriptor( @@ -9891,8 +9903,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=36994, - serialized_end=37301, + serialized_start=37026, + serialized_end=37333, ) _GETSTATUSRESPONSE_GETSTATUSRESPONSEV0_NETWORK = _descriptor.Descriptor( @@ -9936,8 +9948,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=37303, - serialized_end=37370, + serialized_start=37335, + serialized_end=37402, ) _GETSTATUSRESPONSE_GETSTATUSRESPONSEV0_STATESYNC = _descriptor.Descriptor( @@ -10016,8 +10028,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=37373, - serialized_end=37634, + serialized_start=37405, + serialized_end=37666, ) _GETSTATUSRESPONSE_GETSTATUSRESPONSEV0 = _descriptor.Descriptor( @@ -10082,8 +10094,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=35599, - serialized_end=37634, + serialized_start=35631, + serialized_end=37666, ) _GETSTATUSRESPONSE = _descriptor.Descriptor( @@ -10118,8 +10130,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=35497, - serialized_end=37645, + serialized_start=35529, + serialized_end=37677, ) @@ -10143,8 +10155,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=37782, - serialized_end=37814, + serialized_start=37814, + serialized_end=37846, ) _GETCURRENTQUORUMSINFOREQUEST = _descriptor.Descriptor( @@ -10179,8 +10191,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=37648, - serialized_end=37825, + serialized_start=37680, + serialized_end=37857, ) @@ -10225,8 +10237,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=37965, - serialized_end=38035, + serialized_start=37997, + serialized_end=38067, ) _GETCURRENTQUORUMSINFORESPONSE_VALIDATORSETV0 = _descriptor.Descriptor( @@ -10277,8 +10289,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=38038, - serialized_end=38213, + serialized_start=38070, + serialized_end=38245, ) _GETCURRENTQUORUMSINFORESPONSE_GETCURRENTQUORUMSINFORESPONSEV0 = _descriptor.Descriptor( @@ -10336,8 +10348,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=38216, - serialized_end=38490, + serialized_start=38248, + serialized_end=38522, ) _GETCURRENTQUORUMSINFORESPONSE = _descriptor.Descriptor( @@ -10372,8 +10384,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=37828, - serialized_end=38501, + serialized_start=37860, + serialized_end=38533, ) @@ -10418,8 +10430,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=38647, - serialized_end=38737, + serialized_start=38679, + serialized_end=38769, ) _GETIDENTITYTOKENBALANCESREQUEST = _descriptor.Descriptor( @@ -10454,8 +10466,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=38504, - serialized_end=38748, + serialized_start=38536, + serialized_end=38780, ) @@ -10498,8 +10510,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=39187, - serialized_end=39258, + serialized_start=39219, + serialized_end=39290, ) _GETIDENTITYTOKENBALANCESRESPONSE_GETIDENTITYTOKENBALANCESRESPONSEV0_TOKENBALANCES = _descriptor.Descriptor( @@ -10529,8 +10541,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=39261, - serialized_end=39415, + serialized_start=39293, + serialized_end=39447, ) _GETIDENTITYTOKENBALANCESRESPONSE_GETIDENTITYTOKENBALANCESRESPONSEV0 = _descriptor.Descriptor( @@ -10579,8 +10591,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=38898, - serialized_end=39425, + serialized_start=38930, + serialized_end=39457, ) _GETIDENTITYTOKENBALANCESRESPONSE = _descriptor.Descriptor( @@ -10615,8 +10627,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=38751, - serialized_end=39436, + serialized_start=38783, + serialized_end=39468, ) @@ -10661,8 +10673,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=39588, - serialized_end=39680, + serialized_start=39620, + serialized_end=39712, ) _GETIDENTITIESTOKENBALANCESREQUEST = _descriptor.Descriptor( @@ -10697,8 +10709,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=39439, - serialized_end=39691, + serialized_start=39471, + serialized_end=39723, ) @@ -10741,8 +10753,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=40159, - serialized_end=40241, + serialized_start=40191, + serialized_end=40273, ) _GETIDENTITIESTOKENBALANCESRESPONSE_GETIDENTITIESTOKENBALANCESRESPONSEV0_IDENTITYTOKENBALANCES = _descriptor.Descriptor( @@ -10772,8 +10784,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=40244, - serialized_end=40427, + serialized_start=40276, + serialized_end=40459, ) _GETIDENTITIESTOKENBALANCESRESPONSE_GETIDENTITIESTOKENBALANCESRESPONSEV0 = _descriptor.Descriptor( @@ -10822,8 +10834,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=39847, - serialized_end=40437, + serialized_start=39879, + serialized_end=40469, ) _GETIDENTITIESTOKENBALANCESRESPONSE = _descriptor.Descriptor( @@ -10858,8 +10870,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=39694, - serialized_end=40448, + serialized_start=39726, + serialized_end=40480, ) @@ -10904,8 +10916,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=40585, - serialized_end=40672, + serialized_start=40617, + serialized_end=40704, ) _GETIDENTITYTOKENINFOSREQUEST = _descriptor.Descriptor( @@ -10940,8 +10952,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=40451, - serialized_end=40683, + serialized_start=40483, + serialized_end=40715, ) @@ -10972,8 +10984,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=41097, - serialized_end=41137, + serialized_start=41129, + serialized_end=41169, ) _GETIDENTITYTOKENINFOSRESPONSE_GETIDENTITYTOKENINFOSRESPONSEV0_TOKENINFOENTRY = _descriptor.Descriptor( @@ -11015,8 +11027,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=41140, - serialized_end=41316, + serialized_start=41172, + serialized_end=41348, ) _GETIDENTITYTOKENINFOSRESPONSE_GETIDENTITYTOKENINFOSRESPONSEV0_TOKENINFOS = _descriptor.Descriptor( @@ -11046,8 +11058,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=41319, - serialized_end=41457, + serialized_start=41351, + serialized_end=41489, ) _GETIDENTITYTOKENINFOSRESPONSE_GETIDENTITYTOKENINFOSRESPONSEV0 = _descriptor.Descriptor( @@ -11096,8 +11108,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=40824, - serialized_end=41467, + serialized_start=40856, + serialized_end=41499, ) _GETIDENTITYTOKENINFOSRESPONSE = _descriptor.Descriptor( @@ -11132,8 +11144,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=40686, - serialized_end=41478, + serialized_start=40718, + serialized_end=41510, ) @@ -11178,8 +11190,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=41621, - serialized_end=41710, + serialized_start=41653, + serialized_end=41742, ) _GETIDENTITIESTOKENINFOSREQUEST = _descriptor.Descriptor( @@ -11214,8 +11226,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=41481, - serialized_end=41721, + serialized_start=41513, + serialized_end=41753, ) @@ -11246,8 +11258,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=41097, - serialized_end=41137, + serialized_start=41129, + serialized_end=41169, ) _GETIDENTITIESTOKENINFOSRESPONSE_GETIDENTITIESTOKENINFOSRESPONSEV0_TOKENINFOENTRY = _descriptor.Descriptor( @@ -11289,8 +11301,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=42208, - serialized_end=42391, + serialized_start=42240, + serialized_end=42423, ) _GETIDENTITIESTOKENINFOSRESPONSE_GETIDENTITIESTOKENINFOSRESPONSEV0_IDENTITYTOKENINFOS = _descriptor.Descriptor( @@ -11320,8 +11332,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=42394, - serialized_end=42545, + serialized_start=42426, + serialized_end=42577, ) _GETIDENTITIESTOKENINFOSRESPONSE_GETIDENTITIESTOKENINFOSRESPONSEV0 = _descriptor.Descriptor( @@ -11370,8 +11382,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=41868, - serialized_end=42555, + serialized_start=41900, + serialized_end=42587, ) _GETIDENTITIESTOKENINFOSRESPONSE = _descriptor.Descriptor( @@ -11406,8 +11418,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=41724, - serialized_end=42566, + serialized_start=41756, + serialized_end=42598, ) @@ -11445,8 +11457,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=42688, - serialized_end=42749, + serialized_start=42720, + serialized_end=42781, ) _GETTOKENSTATUSESREQUEST = _descriptor.Descriptor( @@ -11481,8 +11493,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=42569, - serialized_end=42760, + serialized_start=42601, + serialized_end=42792, ) @@ -11525,8 +11537,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=43150, - serialized_end=43218, + serialized_start=43182, + serialized_end=43250, ) _GETTOKENSTATUSESRESPONSE_GETTOKENSTATUSESRESPONSEV0_TOKENSTATUSES = _descriptor.Descriptor( @@ -11556,8 +11568,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=43221, - serialized_end=43357, + serialized_start=43253, + serialized_end=43389, ) _GETTOKENSTATUSESRESPONSE_GETTOKENSTATUSESRESPONSEV0 = _descriptor.Descriptor( @@ -11606,8 +11618,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=42886, - serialized_end=43367, + serialized_start=42918, + serialized_end=43399, ) _GETTOKENSTATUSESRESPONSE = _descriptor.Descriptor( @@ -11642,8 +11654,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=42763, - serialized_end=43378, + serialized_start=42795, + serialized_end=43410, ) @@ -11681,8 +11693,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=43536, - serialized_end=43609, + serialized_start=43568, + serialized_end=43641, ) _GETTOKENDIRECTPURCHASEPRICESREQUEST = _descriptor.Descriptor( @@ -11717,8 +11729,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=43381, - serialized_end=43620, + serialized_start=43413, + serialized_end=43652, ) @@ -11756,8 +11768,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=44110, - serialized_end=44161, + serialized_start=44142, + serialized_end=44193, ) _GETTOKENDIRECTPURCHASEPRICESRESPONSE_GETTOKENDIRECTPURCHASEPRICESRESPONSEV0_PRICINGSCHEDULE = _descriptor.Descriptor( @@ -11787,8 +11799,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=44164, - serialized_end=44331, + serialized_start=44196, + serialized_end=44363, ) _GETTOKENDIRECTPURCHASEPRICESRESPONSE_GETTOKENDIRECTPURCHASEPRICESRESPONSEV0_TOKENDIRECTPURCHASEPRICEENTRY = _descriptor.Descriptor( @@ -11837,8 +11849,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=44334, - serialized_end=44562, + serialized_start=44366, + serialized_end=44594, ) _GETTOKENDIRECTPURCHASEPRICESRESPONSE_GETTOKENDIRECTPURCHASEPRICESRESPONSEV0_TOKENDIRECTPURCHASEPRICES = _descriptor.Descriptor( @@ -11868,8 +11880,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=44565, - serialized_end=44765, + serialized_start=44597, + serialized_end=44797, ) _GETTOKENDIRECTPURCHASEPRICESRESPONSE_GETTOKENDIRECTPURCHASEPRICESRESPONSEV0 = _descriptor.Descriptor( @@ -11918,8 +11930,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=43782, - serialized_end=44775, + serialized_start=43814, + serialized_end=44807, ) _GETTOKENDIRECTPURCHASEPRICESRESPONSE = _descriptor.Descriptor( @@ -11954,8 +11966,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=43623, - serialized_end=44786, + serialized_start=43655, + serialized_end=44818, ) @@ -11993,8 +12005,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=44920, - serialized_end=44984, + serialized_start=44952, + serialized_end=45016, ) _GETTOKENCONTRACTINFOREQUEST = _descriptor.Descriptor( @@ -12029,8 +12041,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=44789, - serialized_end=44995, + serialized_start=44821, + serialized_end=45027, ) @@ -12068,8 +12080,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=45407, - serialized_end=45484, + serialized_start=45439, + serialized_end=45516, ) _GETTOKENCONTRACTINFORESPONSE_GETTOKENCONTRACTINFORESPONSEV0 = _descriptor.Descriptor( @@ -12118,8 +12130,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=45133, - serialized_end=45494, + serialized_start=45165, + serialized_end=45526, ) _GETTOKENCONTRACTINFORESPONSE = _descriptor.Descriptor( @@ -12154,8 +12166,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=44998, - serialized_end=45505, + serialized_start=45030, + serialized_end=45537, ) @@ -12210,8 +12222,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=45938, - serialized_end=46092, + serialized_start=45970, + serialized_end=46124, ) _GETTOKENPREPROGRAMMEDDISTRIBUTIONSREQUEST_GETTOKENPREPROGRAMMEDDISTRIBUTIONSREQUESTV0 = _descriptor.Descriptor( @@ -12272,8 +12284,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=45682, - serialized_end=46120, + serialized_start=45714, + serialized_end=46152, ) _GETTOKENPREPROGRAMMEDDISTRIBUTIONSREQUEST = _descriptor.Descriptor( @@ -12308,8 +12320,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=45508, - serialized_end=46131, + serialized_start=45540, + serialized_end=46163, ) @@ -12347,8 +12359,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=46642, - serialized_end=46704, + serialized_start=46674, + serialized_end=46736, ) _GETTOKENPREPROGRAMMEDDISTRIBUTIONSRESPONSE_GETTOKENPREPROGRAMMEDDISTRIBUTIONSRESPONSEV0_TOKENTIMEDDISTRIBUTIONENTRY = _descriptor.Descriptor( @@ -12385,8 +12397,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=46707, - serialized_end=46919, + serialized_start=46739, + serialized_end=46951, ) _GETTOKENPREPROGRAMMEDDISTRIBUTIONSRESPONSE_GETTOKENPREPROGRAMMEDDISTRIBUTIONSRESPONSEV0_TOKENDISTRIBUTIONS = _descriptor.Descriptor( @@ -12416,8 +12428,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=46922, - serialized_end=47117, + serialized_start=46954, + serialized_end=47149, ) _GETTOKENPREPROGRAMMEDDISTRIBUTIONSRESPONSE_GETTOKENPREPROGRAMMEDDISTRIBUTIONSRESPONSEV0 = _descriptor.Descriptor( @@ -12466,8 +12478,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=46312, - serialized_end=47127, + serialized_start=46344, + serialized_end=47159, ) _GETTOKENPREPROGRAMMEDDISTRIBUTIONSRESPONSE = _descriptor.Descriptor( @@ -12502,8 +12514,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=46134, - serialized_end=47138, + serialized_start=46166, + serialized_end=47170, ) @@ -12541,8 +12553,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=47327, - serialized_end=47400, + serialized_start=47359, + serialized_end=47432, ) _GETTOKENPERPETUALDISTRIBUTIONLASTCLAIMREQUEST_GETTOKENPERPETUALDISTRIBUTIONLASTCLAIMREQUESTV0 = _descriptor.Descriptor( @@ -12598,8 +12610,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=47403, - serialized_end=47644, + serialized_start=47435, + serialized_end=47676, ) _GETTOKENPERPETUALDISTRIBUTIONLASTCLAIMREQUEST = _descriptor.Descriptor( @@ -12634,8 +12646,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=47141, - serialized_end=47655, + serialized_start=47173, + serialized_end=47687, ) @@ -12692,8 +12704,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=48176, - serialized_end=48296, + serialized_start=48208, + serialized_end=48328, ) _GETTOKENPERPETUALDISTRIBUTIONLASTCLAIMRESPONSE_GETTOKENPERPETUALDISTRIBUTIONLASTCLAIMRESPONSEV0 = _descriptor.Descriptor( @@ -12742,8 +12754,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=47848, - serialized_end=48306, + serialized_start=47880, + serialized_end=48338, ) _GETTOKENPERPETUALDISTRIBUTIONLASTCLAIMRESPONSE = _descriptor.Descriptor( @@ -12778,8 +12790,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=47658, - serialized_end=48317, + serialized_start=47690, + serialized_end=48349, ) @@ -12817,8 +12829,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=48448, - serialized_end=48511, + serialized_start=48480, + serialized_end=48543, ) _GETTOKENTOTALSUPPLYREQUEST = _descriptor.Descriptor( @@ -12853,8 +12865,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=48320, - serialized_end=48522, + serialized_start=48352, + serialized_end=48554, ) @@ -12899,8 +12911,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=48943, - serialized_end=49063, + serialized_start=48975, + serialized_end=49095, ) _GETTOKENTOTALSUPPLYRESPONSE_GETTOKENTOTALSUPPLYRESPONSEV0 = _descriptor.Descriptor( @@ -12949,8 +12961,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=48657, - serialized_end=49073, + serialized_start=48689, + serialized_end=49105, ) _GETTOKENTOTALSUPPLYRESPONSE = _descriptor.Descriptor( @@ -12985,8 +12997,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=48525, - serialized_end=49084, + serialized_start=48557, + serialized_end=49116, ) @@ -13031,8 +13043,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=49194, - serialized_end=49286, + serialized_start=49226, + serialized_end=49318, ) _GETGROUPINFOREQUEST = _descriptor.Descriptor( @@ -13067,8 +13079,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=49087, - serialized_end=49297, + serialized_start=49119, + serialized_end=49329, ) @@ -13106,8 +13118,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=49655, - serialized_end=49707, + serialized_start=49687, + serialized_end=49739, ) _GETGROUPINFORESPONSE_GETGROUPINFORESPONSEV0_GROUPINFOENTRY = _descriptor.Descriptor( @@ -13144,8 +13156,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=49710, - serialized_end=49862, + serialized_start=49742, + serialized_end=49894, ) _GETGROUPINFORESPONSE_GETGROUPINFORESPONSEV0_GROUPINFO = _descriptor.Descriptor( @@ -13180,8 +13192,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=49865, - serialized_end=50003, + serialized_start=49897, + serialized_end=50035, ) _GETGROUPINFORESPONSE_GETGROUPINFORESPONSEV0 = _descriptor.Descriptor( @@ -13230,8 +13242,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=49411, - serialized_end=50013, + serialized_start=49443, + serialized_end=50045, ) _GETGROUPINFORESPONSE = _descriptor.Descriptor( @@ -13266,8 +13278,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=49300, - serialized_end=50024, + serialized_start=49332, + serialized_end=50056, ) @@ -13305,8 +13317,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=50137, - serialized_end=50254, + serialized_start=50169, + serialized_end=50286, ) _GETGROUPINFOSREQUEST_GETGROUPINFOSREQUESTV0 = _descriptor.Descriptor( @@ -13367,8 +13379,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=50257, - serialized_end=50509, + serialized_start=50289, + serialized_end=50541, ) _GETGROUPINFOSREQUEST = _descriptor.Descriptor( @@ -13403,8 +13415,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=50027, - serialized_end=50520, + serialized_start=50059, + serialized_end=50552, ) @@ -13442,8 +13454,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=49655, - serialized_end=49707, + serialized_start=49687, + serialized_end=49739, ) _GETGROUPINFOSRESPONSE_GETGROUPINFOSRESPONSEV0_GROUPPOSITIONINFOENTRY = _descriptor.Descriptor( @@ -13487,8 +13499,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=50941, - serialized_end=51136, + serialized_start=50973, + serialized_end=51168, ) _GETGROUPINFOSRESPONSE_GETGROUPINFOSRESPONSEV0_GROUPINFOS = _descriptor.Descriptor( @@ -13518,8 +13530,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=51139, - serialized_end=51269, + serialized_start=51171, + serialized_end=51301, ) _GETGROUPINFOSRESPONSE_GETGROUPINFOSRESPONSEV0 = _descriptor.Descriptor( @@ -13568,8 +13580,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=50637, - serialized_end=51279, + serialized_start=50669, + serialized_end=51311, ) _GETGROUPINFOSRESPONSE = _descriptor.Descriptor( @@ -13604,8 +13616,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=50523, - serialized_end=51290, + serialized_start=50555, + serialized_end=51322, ) @@ -13643,8 +13655,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=51409, - serialized_end=51485, + serialized_start=51441, + serialized_end=51517, ) _GETGROUPACTIONSREQUEST_GETGROUPACTIONSREQUESTV0 = _descriptor.Descriptor( @@ -13719,8 +13731,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=51488, - serialized_end=51816, + serialized_start=51520, + serialized_end=51848, ) _GETGROUPACTIONSREQUEST = _descriptor.Descriptor( @@ -13756,8 +13768,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=51293, - serialized_end=51867, + serialized_start=51325, + serialized_end=51899, ) @@ -13807,8 +13819,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=52249, - serialized_end=52340, + serialized_start=52281, + serialized_end=52372, ) _GETGROUPACTIONSRESPONSE_GETGROUPACTIONSRESPONSEV0_BURNEVENT = _descriptor.Descriptor( @@ -13857,8 +13869,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=52342, - serialized_end=52433, + serialized_start=52374, + serialized_end=52465, ) _GETGROUPACTIONSRESPONSE_GETGROUPACTIONSRESPONSEV0_FREEZEEVENT = _descriptor.Descriptor( @@ -13900,8 +13912,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=52435, - serialized_end=52509, + serialized_start=52467, + serialized_end=52541, ) _GETGROUPACTIONSRESPONSE_GETGROUPACTIONSRESPONSEV0_UNFREEZEEVENT = _descriptor.Descriptor( @@ -13943,8 +13955,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=52511, - serialized_end=52587, + serialized_start=52543, + serialized_end=52619, ) _GETGROUPACTIONSRESPONSE_GETGROUPACTIONSRESPONSEV0_DESTROYFROZENFUNDSEVENT = _descriptor.Descriptor( @@ -13993,8 +14005,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=52589, - serialized_end=52691, + serialized_start=52621, + serialized_end=52723, ) _GETGROUPACTIONSRESPONSE_GETGROUPACTIONSRESPONSEV0_SHAREDENCRYPTEDNOTE = _descriptor.Descriptor( @@ -14038,8 +14050,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=52693, - serialized_end=52793, + serialized_start=52725, + serialized_end=52825, ) _GETGROUPACTIONSRESPONSE_GETGROUPACTIONSRESPONSEV0_PERSONALENCRYPTEDNOTE = _descriptor.Descriptor( @@ -14083,8 +14095,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=52795, - serialized_end=52918, + serialized_start=52827, + serialized_end=52950, ) _GETGROUPACTIONSRESPONSE_GETGROUPACTIONSRESPONSEV0_EMERGENCYACTIONEVENT = _descriptor.Descriptor( @@ -14127,8 +14139,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=52921, - serialized_end=53154, + serialized_start=52953, + serialized_end=53186, ) _GETGROUPACTIONSRESPONSE_GETGROUPACTIONSRESPONSEV0_TOKENCONFIGUPDATEEVENT = _descriptor.Descriptor( @@ -14170,8 +14182,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=53156, - serialized_end=53256, + serialized_start=53188, + serialized_end=53288, ) _GETGROUPACTIONSRESPONSE_GETGROUPACTIONSRESPONSEV0_UPDATEDIRECTPURCHASEPRICEEVENT_PRICEFORQUANTITY = _descriptor.Descriptor( @@ -14208,8 +14220,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=44110, - serialized_end=44161, + serialized_start=44142, + serialized_end=44193, ) _GETGROUPACTIONSRESPONSE_GETGROUPACTIONSRESPONSEV0_UPDATEDIRECTPURCHASEPRICEEVENT_PRICINGSCHEDULE = _descriptor.Descriptor( @@ -14239,8 +14251,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=53548, - serialized_end=53720, + serialized_start=53580, + serialized_end=53752, ) _GETGROUPACTIONSRESPONSE_GETGROUPACTIONSRESPONSEV0_UPDATEDIRECTPURCHASEPRICEEVENT = _descriptor.Descriptor( @@ -14294,8 +14306,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=53259, - serialized_end=53745, + serialized_start=53291, + serialized_end=53777, ) _GETGROUPACTIONSRESPONSE_GETGROUPACTIONSRESPONSEV0_GROUPACTIONEVENT = _descriptor.Descriptor( @@ -14344,8 +14356,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=53748, - serialized_end=54128, + serialized_start=53780, + serialized_end=54160, ) _GETGROUPACTIONSRESPONSE_GETGROUPACTIONSRESPONSEV0_DOCUMENTEVENT = _descriptor.Descriptor( @@ -14380,8 +14392,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=54131, - serialized_end=54270, + serialized_start=54163, + serialized_end=54302, ) _GETGROUPACTIONSRESPONSE_GETGROUPACTIONSRESPONSEV0_DOCUMENTCREATEEVENT = _descriptor.Descriptor( @@ -14411,8 +14423,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=54272, - serialized_end=54319, + serialized_start=54304, + serialized_end=54351, ) _GETGROUPACTIONSRESPONSE_GETGROUPACTIONSRESPONSEV0_CONTRACTUPDATEEVENT = _descriptor.Descriptor( @@ -14442,8 +14454,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=54321, - serialized_end=54368, + serialized_start=54353, + serialized_end=54400, ) _GETGROUPACTIONSRESPONSE_GETGROUPACTIONSRESPONSEV0_CONTRACTEVENT = _descriptor.Descriptor( @@ -14478,8 +14490,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=54371, - serialized_end=54510, + serialized_start=54403, + serialized_end=54542, ) _GETGROUPACTIONSRESPONSE_GETGROUPACTIONSRESPONSEV0_TOKENEVENT = _descriptor.Descriptor( @@ -14563,8 +14575,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=54513, - serialized_end=55490, + serialized_start=54545, + serialized_end=55522, ) _GETGROUPACTIONSRESPONSE_GETGROUPACTIONSRESPONSEV0_GROUPACTIONENTRY = _descriptor.Descriptor( @@ -14601,8 +14613,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=55493, - serialized_end=55640, + serialized_start=55525, + serialized_end=55672, ) _GETGROUPACTIONSRESPONSE_GETGROUPACTIONSRESPONSEV0_GROUPACTIONS = _descriptor.Descriptor( @@ -14632,8 +14644,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=55643, - serialized_end=55775, + serialized_start=55675, + serialized_end=55807, ) _GETGROUPACTIONSRESPONSE_GETGROUPACTIONSRESPONSEV0 = _descriptor.Descriptor( @@ -14682,8 +14694,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=51990, - serialized_end=55785, + serialized_start=52022, + serialized_end=55817, ) _GETGROUPACTIONSRESPONSE = _descriptor.Descriptor( @@ -14718,8 +14730,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=51870, - serialized_end=55796, + serialized_start=51902, + serialized_end=55828, ) @@ -14778,8 +14790,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=55934, - serialized_end=56140, + serialized_start=55966, + serialized_end=56172, ) _GETGROUPACTIONSIGNERSREQUEST = _descriptor.Descriptor( @@ -14815,8 +14827,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=55799, - serialized_end=56191, + serialized_start=55831, + serialized_end=56223, ) @@ -14854,8 +14866,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=56623, - serialized_end=56676, + serialized_start=56655, + serialized_end=56708, ) _GETGROUPACTIONSIGNERSRESPONSE_GETGROUPACTIONSIGNERSRESPONSEV0_GROUPACTIONSIGNERS = _descriptor.Descriptor( @@ -14885,8 +14897,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=56679, - serialized_end=56824, + serialized_start=56711, + serialized_end=56856, ) _GETGROUPACTIONSIGNERSRESPONSE_GETGROUPACTIONSIGNERSRESPONSEV0 = _descriptor.Descriptor( @@ -14935,8 +14947,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=56332, - serialized_end=56834, + serialized_start=56364, + serialized_end=56866, ) _GETGROUPACTIONSIGNERSRESPONSE = _descriptor.Descriptor( @@ -14971,8 +14983,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=56194, - serialized_end=56845, + serialized_start=56226, + serialized_end=56877, ) @@ -15010,8 +15022,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=56961, - serialized_end=57018, + serialized_start=56993, + serialized_end=57050, ) _GETADDRESSINFOREQUEST = _descriptor.Descriptor( @@ -15046,8 +15058,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=56848, - serialized_end=57029, + serialized_start=56880, + serialized_end=57061, ) @@ -15090,8 +15102,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=57032, - serialized_end=57165, + serialized_start=57064, + serialized_end=57197, ) @@ -15129,8 +15141,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=57167, - serialized_end=57216, + serialized_start=57199, + serialized_end=57248, ) @@ -15161,8 +15173,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=57218, - serialized_end=57313, + serialized_start=57250, + serialized_end=57345, ) @@ -15212,8 +15224,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=57315, - serialized_end=57424, + serialized_start=57347, + serialized_end=57456, ) @@ -15251,8 +15263,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=57426, - serialized_end=57546, + serialized_start=57458, + serialized_end=57578, ) @@ -15283,8 +15295,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=57548, - serialized_end=57655, + serialized_start=57580, + serialized_end=57687, ) @@ -15334,8 +15346,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=57775, - serialized_end=58000, + serialized_start=57807, + serialized_end=58032, ) _GETADDRESSINFORESPONSE = _descriptor.Descriptor( @@ -15370,8 +15382,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=57658, - serialized_end=58011, + serialized_start=57690, + serialized_end=58043, ) @@ -15409,8 +15421,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=58136, - serialized_end=58198, + serialized_start=58168, + serialized_end=58230, ) _GETADDRESSESINFOSREQUEST = _descriptor.Descriptor( @@ -15445,8 +15457,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=58014, - serialized_end=58209, + serialized_start=58046, + serialized_end=58241, ) @@ -15496,8 +15508,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=58338, - serialized_end=58570, + serialized_start=58370, + serialized_end=58602, ) _GETADDRESSESINFOSRESPONSE = _descriptor.Descriptor( @@ -15532,8 +15544,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=58212, - serialized_end=58581, + serialized_start=58244, + serialized_end=58613, ) @@ -15557,8 +15569,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=58721, - serialized_end=58754, + serialized_start=58753, + serialized_end=58786, ) _GETADDRESSESTRUNKSTATEREQUEST = _descriptor.Descriptor( @@ -15593,8 +15605,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=58584, - serialized_end=58765, + serialized_start=58616, + serialized_end=58797, ) @@ -15632,8 +15644,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=58909, - serialized_end=59055, + serialized_start=58941, + serialized_end=59087, ) _GETADDRESSESTRUNKSTATERESPONSE = _descriptor.Descriptor( @@ -15668,8 +15680,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=58768, - serialized_end=59066, + serialized_start=58800, + serialized_end=59098, ) @@ -15714,8 +15726,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=59209, - serialized_end=59298, + serialized_start=59241, + serialized_end=59330, ) _GETADDRESSESBRANCHSTATEREQUEST = _descriptor.Descriptor( @@ -15750,8 +15762,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=59069, - serialized_end=59309, + serialized_start=59101, + serialized_end=59341, ) @@ -15782,8 +15794,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=59455, - serialized_end=59510, + serialized_start=59487, + serialized_end=59542, ) _GETADDRESSESBRANCHSTATERESPONSE = _descriptor.Descriptor( @@ -15818,8 +15830,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=59312, - serialized_end=59521, + serialized_start=59344, + serialized_end=59553, ) @@ -15864,8 +15876,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=59685, - serialized_end=59799, + serialized_start=59717, + serialized_end=59831, ) _GETRECENTADDRESSBALANCECHANGESREQUEST = _descriptor.Descriptor( @@ -15900,8 +15912,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=59524, - serialized_end=59810, + serialized_start=59556, + serialized_end=59842, ) @@ -15951,8 +15963,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=59978, - serialized_end=60242, + serialized_start=60010, + serialized_end=60274, ) _GETRECENTADDRESSBALANCECHANGESRESPONSE = _descriptor.Descriptor( @@ -15987,8 +15999,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=59813, - serialized_end=60253, + serialized_start=59845, + serialized_end=60285, ) @@ -16026,8 +16038,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=60255, - serialized_end=60326, + serialized_start=60287, + serialized_end=60358, ) @@ -16077,8 +16089,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=60329, - serialized_end=60505, + serialized_start=60361, + serialized_end=60537, ) @@ -16109,8 +16121,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=60507, - serialized_end=60599, + serialized_start=60539, + serialized_end=60631, ) @@ -16155,8 +16167,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=60602, - serialized_end=60776, + serialized_start=60634, + serialized_end=60808, ) @@ -16187,8 +16199,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=60779, - serialized_end=60914, + serialized_start=60811, + serialized_end=60946, ) @@ -16226,8 +16238,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=61106, - serialized_end=61203, + serialized_start=61138, + serialized_end=61235, ) _GETRECENTCOMPACTEDADDRESSBALANCECHANGESREQUEST = _descriptor.Descriptor( @@ -16262,8 +16274,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=60917, - serialized_end=61214, + serialized_start=60949, + serialized_end=61246, ) @@ -16313,8 +16325,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=61410, - serialized_end=61702, + serialized_start=61442, + serialized_end=61734, ) _GETRECENTCOMPACTEDADDRESSBALANCECHANGESRESPONSE = _descriptor.Descriptor( @@ -16349,8 +16361,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=61217, - serialized_end=61713, + serialized_start=61249, + serialized_end=61745, ) @@ -16395,8 +16407,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=61862, - serialized_end=61949, + serialized_start=61894, + serialized_end=61981, ) _GETSHIELDEDENCRYPTEDNOTESREQUEST = _descriptor.Descriptor( @@ -16431,8 +16443,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=61716, - serialized_end=61960, + serialized_start=61748, + serialized_end=61992, ) @@ -16484,8 +16496,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=62407, - serialized_end=62494, + serialized_start=62439, + serialized_end=62526, ) _GETSHIELDEDENCRYPTEDNOTESRESPONSE_GETSHIELDEDENCRYPTEDNOTESRESPONSEV0_ENCRYPTEDNOTES = _descriptor.Descriptor( @@ -16515,8 +16527,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=62497, - serialized_end=62642, + serialized_start=62529, + serialized_end=62674, ) _GETSHIELDEDENCRYPTEDNOTESRESPONSE_GETSHIELDEDENCRYPTEDNOTESRESPONSEV0 = _descriptor.Descriptor( @@ -16565,8 +16577,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=62113, - serialized_end=62652, + serialized_start=62145, + serialized_end=62684, ) _GETSHIELDEDENCRYPTEDNOTESRESPONSE = _descriptor.Descriptor( @@ -16601,8 +16613,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=61963, - serialized_end=62663, + serialized_start=61995, + serialized_end=62695, ) @@ -16633,8 +16645,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=62791, - serialized_end=62835, + serialized_start=62823, + serialized_end=62867, ) _GETSHIELDEDANCHORSREQUEST = _descriptor.Descriptor( @@ -16669,8 +16681,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=62666, - serialized_end=62846, + serialized_start=62698, + serialized_end=62878, ) @@ -16701,8 +16713,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=63235, - serialized_end=63261, + serialized_start=63267, + serialized_end=63293, ) _GETSHIELDEDANCHORSRESPONSE_GETSHIELDEDANCHORSRESPONSEV0 = _descriptor.Descriptor( @@ -16751,8 +16763,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=62978, - serialized_end=63271, + serialized_start=63010, + serialized_end=63303, ) _GETSHIELDEDANCHORSRESPONSE = _descriptor.Descriptor( @@ -16787,8 +16799,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=62849, - serialized_end=63282, + serialized_start=62881, + serialized_end=63314, ) @@ -16819,8 +16831,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=63437, - serialized_end=63490, + serialized_start=63469, + serialized_end=63522, ) _GETMOSTRECENTSHIELDEDANCHORREQUEST = _descriptor.Descriptor( @@ -16855,8 +16867,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=63285, - serialized_end=63501, + serialized_start=63317, + serialized_end=63533, ) @@ -16906,8 +16918,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=63660, - serialized_end=63841, + serialized_start=63692, + serialized_end=63873, ) _GETMOSTRECENTSHIELDEDANCHORRESPONSE = _descriptor.Descriptor( @@ -16942,8 +16954,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=63504, - serialized_end=63852, + serialized_start=63536, + serialized_end=63884, ) @@ -16974,8 +16986,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=63986, - serialized_end=64032, + serialized_start=64018, + serialized_end=64064, ) _GETSHIELDEDPOOLSTATEREQUEST = _descriptor.Descriptor( @@ -17010,8 +17022,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=63855, - serialized_end=64043, + serialized_start=63887, + serialized_end=64075, ) @@ -17061,8 +17073,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=64181, - serialized_end=64366, + serialized_start=64213, + serialized_end=64398, ) _GETSHIELDEDPOOLSTATERESPONSE = _descriptor.Descriptor( @@ -17097,8 +17109,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=64046, - serialized_end=64377, + serialized_start=64078, + serialized_end=64409, ) @@ -17129,8 +17141,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=64514, - serialized_end=64561, + serialized_start=64546, + serialized_end=64593, ) _GETSHIELDEDNOTESCOUNTREQUEST = _descriptor.Descriptor( @@ -17165,8 +17177,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=64380, - serialized_end=64572, + serialized_start=64412, + serialized_end=64604, ) @@ -17216,8 +17228,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=64713, - serialized_end=64903, + serialized_start=64745, + serialized_end=64935, ) _GETSHIELDEDNOTESCOUNTRESPONSE = _descriptor.Descriptor( @@ -17252,8 +17264,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=64575, - serialized_end=64914, + serialized_start=64607, + serialized_end=64946, ) @@ -17291,8 +17303,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=65051, - serialized_end=65118, + serialized_start=65083, + serialized_end=65150, ) _GETSHIELDEDNULLIFIERSREQUEST = _descriptor.Descriptor( @@ -17327,8 +17339,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=64917, - serialized_end=65129, + serialized_start=64949, + serialized_end=65161, ) @@ -17366,8 +17378,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=65558, - serialized_end=65612, + serialized_start=65590, + serialized_end=65644, ) _GETSHIELDEDNULLIFIERSRESPONSE_GETSHIELDEDNULLIFIERSRESPONSEV0_NULLIFIERSTATUSES = _descriptor.Descriptor( @@ -17397,8 +17409,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=65615, - serialized_end=65757, + serialized_start=65647, + serialized_end=65789, ) _GETSHIELDEDNULLIFIERSRESPONSE_GETSHIELDEDNULLIFIERSRESPONSEV0 = _descriptor.Descriptor( @@ -17447,8 +17459,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=65270, - serialized_end=65767, + serialized_start=65302, + serialized_end=65799, ) _GETSHIELDEDNULLIFIERSRESPONSE = _descriptor.Descriptor( @@ -17483,8 +17495,8 @@ create_key=_descriptor._internal_create_key, fields=[]), ], - serialized_start=65132, - serialized_end=65778, + serialized_start=65164, + serialized_end=65810, ) _GETIDENTITYREQUEST_GETIDENTITYREQUESTV0.containing_type = _GETIDENTITYREQUEST @@ -17934,6 +17946,9 @@ _GETDOCUMENTSRESPONSE_GETDOCUMENTSRESPONSEV1_RANKEDENTRY.oneofs_by_name['value'].fields.append( _GETDOCUMENTSRESPONSE_GETDOCUMENTSRESPONSEV1_RANKEDENTRY.fields_by_name['avg']) _GETDOCUMENTSRESPONSE_GETDOCUMENTSRESPONSEV1_RANKEDENTRY.fields_by_name['avg'].containing_oneof = _GETDOCUMENTSRESPONSE_GETDOCUMENTSRESPONSEV1_RANKEDENTRY.oneofs_by_name['value'] +_GETDOCUMENTSRESPONSE_GETDOCUMENTSRESPONSEV1_RANKEDENTRY.oneofs_by_name['_in_key'].fields.append( + _GETDOCUMENTSRESPONSE_GETDOCUMENTSRESPONSEV1_RANKEDENTRY.fields_by_name['in_key']) +_GETDOCUMENTSRESPONSE_GETDOCUMENTSRESPONSEV1_RANKEDENTRY.fields_by_name['in_key'].containing_oneof = _GETDOCUMENTSRESPONSE_GETDOCUMENTSRESPONSEV1_RANKEDENTRY.oneofs_by_name['_in_key'] _GETDOCUMENTSRESPONSE_GETDOCUMENTSRESPONSEV1_RANKEDENTRIES.fields_by_name['entries'].message_type = _GETDOCUMENTSRESPONSE_GETDOCUMENTSRESPONSEV1_RANKEDENTRY _GETDOCUMENTSRESPONSE_GETDOCUMENTSRESPONSEV1_RANKEDENTRIES.containing_type = _GETDOCUMENTSRESPONSE_GETDOCUMENTSRESPONSEV1 _GETDOCUMENTSRESPONSE_GETDOCUMENTSRESPONSEV1_RANKEDENTRIES.oneofs_by_name['_skipped'].fields.append( @@ -22475,8 +22490,8 @@ index=0, serialized_options=None, create_key=_descriptor._internal_create_key, - serialized_start=65873, - serialized_end=74655, + serialized_start=65905, + serialized_end=74687, methods=[ _descriptor.MethodDescriptor( name='broadcastStateTransition', diff --git a/packages/dapi-grpc/clients/platform/v0/web/platform_pb.d.ts b/packages/dapi-grpc/clients/platform/v0/web/platform_pb.d.ts index c394bd38fe9..0921efbd9f0 100644 --- a/packages/dapi-grpc/clients/platform/v0/web/platform_pb.d.ts +++ b/packages/dapi-grpc/clients/platform/v0/web/platform_pb.d.ts @@ -3223,6 +3223,13 @@ export namespace GetDocumentsResponse { getAvg(): number; setAvg(value: number): void; + hasInKey(): boolean; + clearInKey(): void; + getInKey(): Uint8Array | string; + getInKey_asU8(): Uint8Array; + getInKey_asB64(): string; + setInKey(value: Uint8Array | string): void; + getValueCase(): RankedEntry.ValueCase; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): RankedEntry.AsObject; @@ -3240,6 +3247,7 @@ export namespace GetDocumentsResponse { count: string, sum: string, avg: number, + inKey: Uint8Array | string, } export enum ValueCase { diff --git a/packages/dapi-grpc/clients/platform/v0/web/platform_pb.js b/packages/dapi-grpc/clients/platform/v0/web/platform_pb.js index 10f2641e792..6bb7e0ddfde 100644 --- a/packages/dapi-grpc/clients/platform/v0/web/platform_pb.js +++ b/packages/dapi-grpc/clients/platform/v0/web/platform_pb.js @@ -30659,7 +30659,8 @@ proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.Rank key: msg.getKey_asB64(), count: jspb.Message.getFieldWithDefault(msg, 2, "0"), sum: jspb.Message.getFieldWithDefault(msg, 3, "0"), - avg: jspb.Message.getFloatingPointFieldWithDefault(msg, 4, 0.0) + avg: jspb.Message.getFloatingPointFieldWithDefault(msg, 4, 0.0), + inKey: msg.getInKey_asB64() }; if (includeInstance) { @@ -30712,6 +30713,10 @@ proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.Rank var value = /** @type {number} */ (reader.readDouble()); msg.setAvg(value); break; + case 5: + var value = /** @type {!Uint8Array} */ (reader.readBytes()); + msg.setInKey(value); + break; default: reader.skipField(); break; @@ -30769,6 +30774,13 @@ proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.Rank f ); } + f = /** @type {!(string|Uint8Array)} */ (jspb.Message.getField(message, 5)); + if (f != null) { + writer.writeBytes( + 5, + f + ); + } }; @@ -30922,6 +30934,66 @@ proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.Rank }; +/** + * optional bytes in_key = 5; + * @return {string} + */ +proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry.prototype.getInKey = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 5, "")); +}; + + +/** + * optional bytes in_key = 5; + * This is a type-conversion wrapper around `getInKey()` + * @return {string} + */ +proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry.prototype.getInKey_asB64 = function() { + return /** @type {string} */ (jspb.Message.bytesAsB64( + this.getInKey())); +}; + + +/** + * optional bytes in_key = 5; + * Note that Uint8Array is not supported on all browsers. + * @see http://caniuse.com/Uint8Array + * This is a type-conversion wrapper around `getInKey()` + * @return {!Uint8Array} + */ +proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry.prototype.getInKey_asU8 = function() { + return /** @type {!Uint8Array} */ (jspb.Message.bytesAsU8( + this.getInKey())); +}; + + +/** + * @param {!(string|Uint8Array)} value + * @return {!proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry} returns this + */ +proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry.prototype.setInKey = function(value) { + return jspb.Message.setField(this, 5, value); +}; + + +/** + * Clears the field making it undefined. + * @return {!proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry} returns this + */ +proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry.prototype.clearInKey = function() { + return jspb.Message.setField(this, 5, undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.org.dash.platform.dapi.v0.GetDocumentsResponse.GetDocumentsResponseV1.RankedEntry.prototype.hasInKey = function() { + return jspb.Message.getField(this, 5) != null; +}; + + /** * List of repeated fields within this message type. diff --git a/packages/dapi-grpc/protos/platform/v0/platform.proto b/packages/dapi-grpc/protos/platform/v0/platform.proto index 0ad3a197fd0..eddacd5bc64 100644 --- a/packages/dapi-grpc/protos/platform/v0/platform.proto +++ b/packages/dapi-grpc/protos/platform/v0/platform.proto @@ -893,11 +893,11 @@ message GetDocumentsRequest { // - a is the In field AND b is the range field, in that order → existing compound distinct shape; entries carry both `in_key` (= a's value) and `key` (= b's value). // // `select=, group_by=[p], order_by=[]` (protocol v14+) — **ranked mode**: - // - exactly one `group_by` property, exactly one `order_by` clause naming the select's aggregate (`f` for `SUM(f)` / `AVG(f)`, the `$count` sentinel for `COUNT(*)`), a `limit` in `1 ..= 100`, an optional `offset`, and no `having` / `start_at`, on an index declaring the matching `rankedCountable` / `rankedSummable` / `rankedAverageable` axis → ranked executor, answered in `ResultData.ranked`. On a single-property ranked index no `where` is accepted; on a compound ranked index every leading index property must be pinned with an `EQUAL` where clause (one per property, `group_by` names the trailing property), selecting which prefix's ranking is read. + // - exactly one `group_by` property, exactly one `order_by` clause naming the select's aggregate (`f` for `SUM(f)` / `AVG(f)`, the `$count` sentinel for `COUNT(*)`), a `limit` in `1 ..= 100`, an optional `offset`, and no `having` / `start_at`, on an index declaring the matching `rankedCountable` / `rankedSummable` / `rankedAverageable` axis → ranked executor, answered in `ResultData.ranked`. On a single-property ranked index no `where` is accepted; on a compound ranked index every leading index property must be pinned (one clause per property, `group_by` names the trailing property) — `EQUAL` pins one prefix, and **at most one** clause may be `IN` (2..=10 distinct elements, `null` legal), fanning the walk out across one prefix branch per element and merging by `(aggregate, encoded prefix, group key)`; merged entries carry `in_key`. `offset` is rejected together with `IN` (rank-skip is per-secondary). // - `DESC` is the "top n" reading (walk the axis from the largest aggregate down), `ASC` the "bottom n" reading. Worked example: `SELECT AVG(grade) GROUP BY restaurantId ORDER BY grade DESC LIMIT 1 OFFSET 4` is the 5th-best restaurant. // // `select=, group_by=[p], having=[ ]` (protocol v14+) — **having-range mode**: - // - exactly one `group_by` property, exactly one `having` clause whose aggregate is the select's aggregate, an operator describing one contiguous range (`EQUAL`, `GREATER_THAN[_OR_EQUALS]`, `LESS_THAN[_OR_EQUALS]`, `BETWEEN*`; `NOT_EQUAL` / `IN` rejected), a `limit` in `1 ..= 100`, an optional `order_by` naming the same aggregate (walk direction; ascending by default), and no `offset` / `start_at` / `start_after`, on an index declaring the matching ranked axis → having-range executor, answered in `ResultData.ranked`. `where` follows the same rule as ranked mode: none on a single-property ranked index; exactly one `EQUAL` pin per leading index property on a compound ranked index. + // - exactly one `group_by` property, exactly one `having` clause whose aggregate is the select's aggregate, an operator describing one contiguous range (`EQUAL`, `GREATER_THAN[_OR_EQUALS]`, `LESS_THAN[_OR_EQUALS]`, `BETWEEN*`; `NOT_EQUAL` / `IN` rejected), a `limit` in `1 ..= 100`, an optional `order_by` naming the same aggregate (walk direction; ascending by default), and no `offset` / `start_at` / `start_after`, on an index declaring the matching ranked axis → having-range executor, answered in `ResultData.ranked`. `where` follows the same rule as ranked mode: none on a single-property ranked index; exactly one pin per leading index property on a compound ranked index, at most one of them an `IN` (2..=10 distinct elements) that fans the bound out across prefix branches and merges, entries carrying `in_key`. // - no offset or cursor pagination: a page cut at `limit` continues only by tightening the bound past the last *distinct* aggregate value seen; a cut inside a tie (several groups sharing the boundary aggregate) cannot be continued, so size `limit` above the widest expected tie. // // **Rejected shapes** (return `Unsupported`): @@ -1394,6 +1394,14 @@ message GetDocumentsResponse { // than hardcoding the literal. double avg = 4; } + // The prefix branch this entry came from, set **only** on an + // `IN`-pinned request (see the supported-shape table): the + // encoded index-key bytes of the `IN` property's pinned value — + // empty bytes for the `null` (absent-value) branch. Absent on + // single-prefix responses. The same group key can legally appear + // under two prefixes, so `(in_key, key)` is the entry's identity + // on a merged page, exactly as on `CountEntry`. + optional bytes in_key = 5; } // Ranked result entries. **Entry order IS the ranking order** — diff --git a/packages/rs-drive-abci/src/query/document_query/v1/dispatch/mod.rs b/packages/rs-drive-abci/src/query/document_query/v1/dispatch/mod.rs index 4c2d0315f48..92b1d09ed93 100644 --- a/packages/rs-drive-abci/src/query/document_query/v1/dispatch/mod.rs +++ b/packages/rs-drive-abci/src/query/document_query/v1/dispatch/mod.rs @@ -34,6 +34,10 @@ use drive::query::{RankedEntry as DriveRankedEntry, RankedEntryValue}; /// silent about). fn into_v1_ranked_entry(e: DriveRankedEntry) -> RankedEntry { RankedEntry { + // Set exactly when the request carried an `IN` prefix pin — + // drive's merge tags entries with their branch, single-branch + // responses stay untagged. + in_key: e.in_key, key: e.key, value: Some(match e.value { RankedEntryValue::Count(count) => ranked_entry::Value::Count(count), diff --git a/packages/rs-drive-abci/src/query/document_query/v1/tests.rs b/packages/rs-drive-abci/src/query/document_query/v1/tests.rs index 50fadc2e293..4af467aef5c 100644 --- a/packages/rs-drive-abci/src/query/document_query/v1/tests.rs +++ b/packages/rs-drive-abci/src/query/document_query/v1/tests.rs @@ -3478,6 +3478,88 @@ mod having_range_tests { } } + /// The `IN`-pinned form end to end on the wire: `WHERE identityId + /// IN [X, Y] GROUP BY class HAVING AVG(grade) > 80 LIMIT 10` fans + /// out across both identities' secondaries and answers one merged + /// `ResultData.ranked` page whose entries carry `in_key`; the + /// proved variant returns the branch container as its Proof + /// payload. Merge/proof semantics are pinned in rs-drive's suites; + /// this pins the wire encoding, routing, and `in_key` mapping. + #[test] + fn in_pinned_having_is_served_end_to_end() { + let (platform, state, version) = setup_platform(None, Network::Testnet, None); + let contract = register_grades_compound(&platform, version); + let identity_x = [1u8; 32]; + let identity_y = [2u8; 32]; + insert_grade_docs( + &platform, + &contract, + 21_000, + &[ + (identity_x, "art", 90), + (identity_x, "math", 60), + (identity_y, "science", 95), + ], + version, + ); + + let mut request = having_request( + &contract, + "grade", + select(v1_select::Function::Avg, "grade"), + hc( + having_aggregate::Function::Avg, + "grade", + having_clause::Operator::GreaterThan, + Value::U64(80), + ), + Vec::new(), + Some(10), + false, + ); + request.group_by = vec!["class".to_string()]; + request.where_clauses = vec![wc( + "identityId", + ProtoWhereOperator::In, + Value::Array(vec![ + Value::Bytes(identity_y.to_vec()), + Value::Bytes(identity_x.to_vec()), + ]), + )]; + + let page = ranked_page(&platform, &state, request.clone(), version); + assert_eq!( + group_keys(&page.entries), + vec!["art", "science"], + "merged ascending: X's art (90) then Y's science (95); X's math \ + (60) misses the bound" + ); + assert_eq!( + page.entries + .iter() + .map(|e| e.in_key.clone()) + .collect::>(), + vec![Some(identity_x.to_vec()), Some(identity_y.to_vec())], + "merged entries carry their branch's in_key on the wire" + ); + + // The proved variant answers with a Proof payload (the branch + // container — decoded and verified client-side, pinned in + // rs-drive's tamper suite). + request.prove = true; + let result = platform + .query_documents_v1(request, &state, version) + .expect("query should succeed"); + assert!(result.errors.is_empty(), "got {:?}", result.errors); + match result.data { + Some(GetDocumentsResponseV1 { + result: Some(get_documents_response_v1::Result::Proof(proof)), + .. + }) => assert!(!proof.grovedb_proof.is_empty()), + other => panic!("expected a Proof response, got {:?}", other), + } + } + /// The pinned-prefix form end to end on the wire: `WHERE identityId /// = X GROUP BY class HAVING AVG(grade) > 80 LIMIT 10` routes to /// the same having executor, descends to X's terminal `class` tree, @@ -4011,7 +4093,7 @@ mod having_trust_boundary { document_type_name: "grade".to_string(), index, bounds: mode.bounds, - equality_prefix_values: Vec::new(), + prefix_branches: vec![Vec::new()], descending: mode.descending, limit: mode.limit, } diff --git a/packages/rs-drive-proof-verifier/src/proof/document_having.rs b/packages/rs-drive-proof-verifier/src/proof/document_having.rs index ea6069a6c10..eeedc8bb581 100644 --- a/packages/rs-drive-proof-verifier/src/proof/document_having.rs +++ b/packages/rs-drive-proof-verifier/src/proof/document_having.rs @@ -236,6 +236,7 @@ mod tests { fn count_entry(key: &str, count: u64) -> ProtoRankedEntry { ProtoRankedEntry { + in_key: None, key: key.as_bytes().to_vec(), value: Some(ranked_entry::Value::Count(count)), } diff --git a/packages/rs-drive-proof-verifier/src/proof/document_ranked.rs b/packages/rs-drive-proof-verifier/src/proof/document_ranked.rs index 24f06524e7b..d6dd115bc2f 100644 --- a/packages/rs-drive-proof-verifier/src/proof/document_ranked.rs +++ b/packages/rs-drive-proof-verifier/src/proof/document_ranked.rs @@ -275,6 +275,9 @@ pub(crate) fn ranked_entry_from_proto(entry: &ProtoRankedEntry) -> Result ProtoRankedEntry { ProtoRankedEntry { + in_key: None, key: key.as_bytes().to_vec(), value: Some(ranked_entry::Value::Count(count)), } @@ -424,6 +428,7 @@ mod tests { /// that no fixed point maps to. fn avg_entry_raw(key: &str, avg: f64) -> ProtoRankedEntry { ProtoRankedEntry { + in_key: None, key: key.as_bytes().to_vec(), value: Some(ranked_entry::Value::Avg(avg)), } @@ -566,6 +571,7 @@ mod tests { #[test] fn decodes_signed_sum_entries() { let response = ranked_response(vec![ProtoRankedEntry { + in_key: None, key: b"refunds".to_vec(), value: Some(ranked_entry::Value::Sum(-1_000)), }]); @@ -666,6 +672,7 @@ mod tests { #[test] fn rejects_an_entry_with_no_value() { let response = ranked_response(vec![ProtoRankedEntry { + in_key: None, key: b"alpha".to_vec(), value: None, }]); @@ -734,10 +741,12 @@ mod tests { fn from_verified_carries_both_halves_of_the_page() { let entries = vec![ RankedEntry { + in_key: None, key: b"gamma".to_vec(), value: RankedEntryValue::Count(9), }, RankedEntry { + in_key: None, key: b"alpha".to_vec(), value: RankedEntryValue::Count(2), }, diff --git a/packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/batched_group_drain.rs b/packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/batched_group_drain.rs index d81d73c4267..519455a2117 100644 --- a/packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/batched_group_drain.rs +++ b/packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/batched_group_drain.rs @@ -375,7 +375,7 @@ fn verified_page( ) .expect("the fixture declares this axis"), axis: axis.ranked, - equality_prefix_values: Vec::new(), + prefix_branches: vec![Vec::new()], descending: !ascending, k: 100, offset: 0, diff --git a/packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/ranked_index_e2e_tests.rs b/packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/ranked_index_e2e_tests.rs index 390621a9886..62e65e08bc7 100644 --- a/packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/ranked_index_e2e_tests.rs +++ b/packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/ranked_index_e2e_tests.rs @@ -1573,7 +1573,7 @@ fn verified_ranked_avg_page( document_type_name: "review".to_string(), index: find_ranked_index_for_axis(indexes, GROUP_PROPERTY, &[], RankedAxis::Avg, "grade") .expect("the fixture declares rankedAverageable on grade"), - equality_prefix_values: vec![], + prefix_branches: vec![vec![]], axis: RankedAxis::Avg, descending: true, k: limit as u16, diff --git a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs index 6707461d8d5..f93f0a9f901 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs @@ -11,6 +11,9 @@ //! Whole module is gated `feature = "server"` via the parent's //! `pub mod execute_range;` declaration. +use super::super::drive_document_ranked_query::branches::{ + encode_branch_proofs, merge_branch_pages, +}; use super::super::drive_document_ranked_query::{RankedAxis, RankedEntry, RankedEntryValue}; use super::{AxisRangeBounds, DriveDocumentHavingQuery}; use crate::drive::Drive; @@ -38,9 +41,37 @@ impl DriveDocumentHavingQuery<'_> { drive: &Drive, transaction: TransactionArg, platform_version: &PlatformVersion, + ) -> Result, Error> { + if self.prefix_branches.len() > 1 { + // One bounded walk per branch, each fetching up to the full + // limit (the merge lemma needs every branch's own in-bound + // prefix), merged with the shared comparator. + let per_branch = (0..self.prefix_branches.len()) + .map(|branch| { + self.execute_range_no_proof_branch(branch, drive, transaction, platform_version) + }) + .collect::, Error>>()?; + return merge_branch_pages( + per_branch, + &self.prefix_branches, + self.descending, + self.limit as usize, + ); + } + self.execute_range_no_proof_branch(0, drive, transaction, platform_version) + } + + /// One branch's in-bound page — the entire pre-`IN` executor, + /// parameterized by which prefix branch's terminal tree it walks. + fn execute_range_no_proof_branch( + &self, + branch: usize, + drive: &Drive, + transaction: TransactionArg, + platform_version: &PlatformVersion, ) -> Result, Error> { let grove_version = &platform_version.drive.grove_version; - let path = self.indexed_property_name_tree_path()?; + let path = self.indexed_property_name_tree_path(branch)?; let path_refs: Vec<&[u8]> = path.iter().map(|segment| segment.as_slice()).collect(); // Costs are destructured away rather than `.unwrap()`-ed, same @@ -61,6 +92,7 @@ impl DriveDocumentHavingQuery<'_> { .map_err(|e| Error::GroveDB(Box::new(e)))? .into_iter() .map(|(count, key)| RankedEntry { + in_key: None, key, value: RankedEntryValue::Count(count), }) @@ -80,6 +112,7 @@ impl DriveDocumentHavingQuery<'_> { .map_err(|e| Error::GroveDB(Box::new(e)))? .into_iter() .map(|(sum, key)| RankedEntry { + in_key: None, key, value: RankedEntryValue::Sum(sum), }) @@ -99,6 +132,7 @@ impl DriveDocumentHavingQuery<'_> { .map_err(|e| Error::GroveDB(Box::new(e)))? .into_iter() .map(|(avg, key)| RankedEntry { + in_key: None, key, value: RankedEntryValue::AvgFixedPoint(avg), }) @@ -141,9 +175,36 @@ impl DriveDocumentHavingQuery<'_> { drive: &Drive, transaction: TransactionArg, platform_version: &PlatformVersion, + ) -> Result, Error> { + if self.prefix_branches.len() > 1 { + // One indexed-axis range proof per branch, framed into the + // versioned container in canonical branch order. + let proofs = (0..self.prefix_branches.len()) + .map(|branch| { + self.execute_range_with_proof_branch( + branch, + drive, + transaction, + platform_version, + ) + }) + .collect::, Error>>()?; + return Ok(encode_branch_proofs(&proofs)); + } + self.execute_range_with_proof_branch(0, drive, transaction, platform_version) + } + + /// One branch's proof — the entire pre-`IN` prover, parameterized by + /// the prefix branch. + fn execute_range_with_proof_branch( + &self, + branch: usize, + drive: &Drive, + transaction: TransactionArg, + platform_version: &PlatformVersion, ) -> Result, Error> { let grove_version = &platform_version.drive.grove_version; - let path = self.indexed_property_name_tree_path()?; + let path = self.indexed_property_name_tree_path(branch)?; let path_refs: Vec<&[u8]> = path.iter().map(|segment| segment.as_slice()).collect(); let secondary_query = self.bounds.merk_query(self.descending); diff --git a/packages/rs-drive/src/query/drive_document_having_query/mod.rs b/packages/rs-drive/src/query/drive_document_having_query/mod.rs index f0418fc798a..15ebf3e5f40 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/mod.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/mod.rs @@ -73,11 +73,11 @@ use std::collections::BTreeMap; #[cfg(any(feature = "server", feature = "verify"))] use super::drive_document_ranked_query::index_picker::{ - encode_equality_prefix_values, find_ranked_index_for_axis, no_covering_index_message, + encode_prefix_branches, find_ranked_index_for_axis, no_covering_index_message, }; #[cfg(any(feature = "server", feature = "verify"))] use super::drive_document_ranked_query::{ - path::indexed_property_name_tree_path_for_index, RankedAxis, + path::indexed_property_name_tree_path_for_index, PrefixPin, RankedAxis, }; #[cfg(any(feature = "server", feature = "verify"))] use crate::error::query::QuerySyntaxError; @@ -247,7 +247,9 @@ pub struct DocumentHavingMode { /// exactly one per leading property of the covering compound index, /// in request order (the resolver re-orders them into index order /// when it encodes the path). Empty for the single-property form. - pub equality_pins: Vec<(String, Value)>, + /// At most one pin carries several values (the `IN` pin); see + /// [`PrefixPin`]. + pub prefix_pins: Vec, } /// A resolved having-range query. Shared by the prover and the verifier — @@ -267,15 +269,15 @@ pub struct DriveDocumentHavingQuery<'a> { pub document_type_name: String, /// The covering ranked index. Its **last** property is the `GROUP /// BY` property and the final path segment; any leading properties - /// are pinned by [`Self::equality_prefix_values`]. + /// are pinned by [`Self::prefix_branches`]. pub index: &'a Index, - /// Encoded index-key bytes of each leading index property's pinned - /// value, in index-property order — empty for a single-property - /// index. Part of the prover/verifier agreement exactly as on the - /// ranked surface: the segments feed straight into the shared path - /// builder. Produced by - /// [`super::drive_document_ranked_query::index_picker::encode_equality_prefix_values`]. - pub equality_prefix_values: Vec>, + /// The prefix **branches** — one inner `Vec>` of encoded + /// path segments per branch, in index-property order; always at + /// least one branch, several exactly when the request carried a + /// multi-element `IN` pin. Part of the prover/verifier agreement + /// exactly as on the ranked surface. Produced by + /// [`super::drive_document_ranked_query::index_picker::encode_prefix_branches`]. + pub prefix_branches: Vec>>, /// Inclusive bounds on the aggregate. Carry the axis; the index must /// declare the matching `ranked_*` flag. pub bounds: AxisRangeBounds, @@ -297,17 +299,17 @@ pub struct DriveDocumentHavingQuery<'a> { #[cfg(any(feature = "server", feature = "verify"))] impl DriveDocumentHavingQuery<'_> { - /// Path of the terminal property-name tree the axis secondary hangs - /// off — identical to the ranked surface's path (including the - /// pinned-prefix segments of a compound index), because both read - /// the same indexed tree. See + /// Path of one branch's terminal property-name tree — identical to + /// the ranked surface's path (including the pinned-prefix segments + /// of a compound index), because both read the same indexed + /// tree(s). See /// [`DriveDocumentRankedQuery::indexed_property_name_tree_path`](super::drive_document_ranked_query::DriveDocumentRankedQuery::indexed_property_name_tree_path). - pub fn indexed_property_name_tree_path(&self) -> Result>, Error> { + pub fn indexed_property_name_tree_path(&self, branch: usize) -> Result>, Error> { indexed_property_name_tree_path_for_index( &self.contract_id, &self.document_type_name, self.index, - &self.equality_prefix_values, + &self.prefix_branches[branch], ) } } @@ -338,9 +340,9 @@ pub fn resolve_having_query_for_mode<'a>( ) -> Result, Error> { let axis = mode.bounds.axis(); let pin_fields: Vec = mode - .equality_pins + .prefix_pins .iter() - .map(|(field, _)| field.clone()) + .map(|pin| pin.field.clone()) .collect(); let index = find_ranked_index_for_axis( indexes, @@ -355,19 +357,19 @@ pub fn resolve_having_query_for_mode<'a>( "having-range", axis, &mode.group_by_property, - &mode.equality_pins, + &mode.prefix_pins, &mode.aggregate_field, ), )) })?; - let equality_prefix_values = - encode_equality_prefix_values(document_type, index, &mode.equality_pins, platform_version)?; + let prefix_branches = + encode_prefix_branches(document_type, index, &mode.prefix_pins, platform_version)?; Ok(DriveDocumentHavingQuery { document_type, contract_id, document_type_name, index, - equality_prefix_values, + prefix_branches, bounds: mode.bounds, descending: mode.descending, limit: mode.limit, diff --git a/packages/rs-drive/src/query/drive_document_having_query/mode_detection/v0/mod.rs b/packages/rs-drive/src/query/drive_document_having_query/mode_detection/v0/mod.rs index 9cefa259130..0d20e3cb2e4 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/mode_detection/v0/mod.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/mode_detection/v0/mod.rs @@ -7,7 +7,7 @@ use super::{AxisRangeBounds, DocumentHavingMode, MAX_HAVING_LIMIT}; use crate::error::query::QuerySyntaxError; use crate::error::Error; use crate::query::drive_document_ranked_query::mode_detection::{ - equality_pins_from_where_clauses, ranked_order_key, + prefix_pins_from_where_clauses, ranked_order_key, }; use crate::query::drive_document_ranked_query::{RankedAxis, RankedPaginationInputs}; use crate::query::having::{ @@ -29,10 +29,12 @@ use grovedb::element::indexed::AVG_FIXED_POINT_SCALE; /// ``` /// /// with no `OFFSET`, no `START AT` / `START AFTER`, exactly one -/// `GROUP BY` property, `WHERE` clauses (when present) that are -/// equality pins on distinct properties — one per leading property of a -/// covering compound ranked index, selecting which prefix's secondary -/// the bound reads — exactly one `HAVING` clause whose aggregate +/// `GROUP BY` property, `WHERE` clauses (when present) that pin the +/// covering compound ranked index's leading properties — one clause per +/// property, each an equality, at most one of them an `IN` whose +/// elements fan the bound out across one prefix branch per element +/// (merged deterministically; see the ranked surface's +/// `prefix_pins_from_where_clauses`) — exactly one `HAVING` clause whose aggregate /// **is the selected aggregate** (same function, same field), an operator /// from the contiguous-range family (`=`, `>`, `>=`, `<`, `<=`, and the /// four `BETWEEN*` variants — `!=` and `IN` describe non-contiguous @@ -244,7 +246,7 @@ pub fn detect_having_mode_v0( // pin per leading property selects which prefix's secondary the // bound reads. Shape-only here; the index picker enforces the // exact-cover rule. - let equality_pins = equality_pins_from_where_clauses(where_clauses)?; + let prefix_pins = prefix_pins_from_where_clauses(where_clauses)?; // ---- LIMIT: required, 1 ..= MAX_HAVING_LIMIT --------------------- // @@ -312,7 +314,7 @@ pub fn detect_having_mode_v0( limit, group_by_property, aggregate_field, - equality_pins, + prefix_pins, }) } diff --git a/packages/rs-drive/src/query/drive_document_having_query/tests.rs b/packages/rs-drive/src/query/drive_document_having_query/tests.rs index 9bfd3277323..3904bebe0a4 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/tests.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/tests.rs @@ -1496,10 +1496,12 @@ mod identifier_group_keys { entries, vec![ RankedEntry { + in_key: None, key: just_above.to_vec(), value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(161, 2)), }, RankedEntry { + in_key: None, key: well_above.to_vec(), value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(180, 2)), }, @@ -1817,10 +1819,12 @@ mod pinned_prefix { entries, vec![ RankedEntry { + in_key: None, key: b"english".to_vec(), value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(161, 2)), }, RankedEntry { + in_key: None, key: b"art".to_vec(), value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(180, 2)), }, @@ -1852,10 +1856,12 @@ mod pinned_prefix { y_entries, vec![ RankedEntry { + in_key: None, key: b"math".to_vec(), value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(185, 2)), }, RankedEntry { + in_key: None, key: b"science".to_vec(), value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(190, 2)), }, @@ -1893,29 +1899,54 @@ mod pinned_prefix { /// on a property that is not the index's leading property fails /// resolution. #[test] - fn in_prefix_and_wrong_pins_are_rejected() { + fn in_prefix_merges_branches_and_wrong_pins_are_rejected() { let (drive, contract) = setup_grades_compound_ranked(); insert_two_identities(&drive, &contract); + // `identityId IN [X, Y]` bounds each identity's own secondary + // and merges: ascending aggregate order, with every X entry + // (in_key = X's 32 bytes, all `1`s) sorting before Y's on the + // branch tie-break where aggregates tie. Bound is AVG > 80. let in_clause = vec![WhereClause { field: PREFIX_PROPERTY.to_string(), operator: WhereOperator::In, value: Value::Array(vec![ - Value::Identifier(IDENTITY_X), Value::Identifier(IDENTITY_Y), + Value::Identifier(IDENTITY_X), ]), }]; - let error = run(&drive, &contract, &in_clause, &[], false) - .expect_err("IN prefixes are not yet supported"); - match error { - Error::Query(QuerySyntaxError::Unsupported(message)) => { - assert!( - message.contains("IN") && message.contains("not yet supported"), - "the IN rejection must say it is a not-yet capability, got: {message}" - ); - } - other => panic!("expected Unsupported for an IN prefix, got {other:?}"), - } + let entries = entries_of( + run(&drive, &contract, &in_clause, &[], false).expect("the IN bound is served"), + ); + assert_eq!( + entries, + vec![ + RankedEntry { + in_key: Some(IDENTITY_X.to_vec()), + key: b"english".to_vec(), + value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(161, 2)), + }, + RankedEntry { + in_key: Some(IDENTITY_X.to_vec()), + key: b"art".to_vec(), + value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(180, 2)), + }, + RankedEntry { + in_key: Some(IDENTITY_Y.to_vec()), + key: b"math".to_vec(), + value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(185, 2)), + }, + RankedEntry { + in_key: Some(IDENTITY_Y.to_vec()), + key: b"science".to_vec(), + value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(190, 2)), + }, + ], + "the merged bound: X's english 80.5 and art 90, then Y's math \ + 92.5 and science 95, in ascending aggregate order with \ + branch-tagged entries — element order in the request must \ + not matter" + ); // A pin on the wrong property: `grade` is not the index's // leading property, so nothing covers [grade, class]. @@ -2049,6 +2080,7 @@ mod pinned_prefix { assert_eq!( entries, vec![RankedEntry { + in_key: None, key: b"math".to_vec(), value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(170, 2)), }], @@ -2091,8 +2123,8 @@ mod pinned_prefix { ) .expect("the compound index covers the null-pinned request"); assert_eq!( - query.equality_prefix_values, - vec![Vec::::new()], + query.prefix_branches, + vec![vec![Vec::::new()]], "a null pin must encode as the write path's empty segment" ); let (root_hash, verified) = query @@ -2108,4 +2140,160 @@ mod pinned_prefix { .expect("root hash must be readable"), ); } + + /// A `null` element mixed with a real value in one `IN` pin: the + /// null branch is the write path's empty segment, which sorts + /// **first** in canonical branch order, and the merged bound covers + /// both subtrees — proved through the branch container. + #[test] + fn a_mixed_null_in_pin_bounds_both_prefixes_and_proves() { + const TAGGED_DOCTYPE: &str = "taggedGrade"; + let (drive, contract) = setup_grades_compound_ranked(); + let pv = platform_version(); + let document_type = contract + .document_type_for_name(TAGGED_DOCTYPE) + .expect("taggedGrade doctype exists"); + + for (i, (tag, class, grade)) in [ + (None, "math", 90i64), + (Some("honors"), "math", 95), + (Some("honors"), "art", 60), + ] + .iter() + .enumerate() + { + let mut doc: Document = document_type + .random_document(Some(9000 + i as u64), pv) + .expect("random document"); + let mut props = BTreeMap::new(); + if let Some(tag) = tag { + props.insert("tag".to_string(), Value::Text(tag.to_string())); + } + props.insert(GROUP_PROPERTY.to_string(), Value::Text(class.to_string())); + props.insert("grade".to_string(), Value::I64(*grade)); + doc.set_properties(props); + drive + .add_document_for_contract( + DocumentAndContractInfo { + owned_document_info: OwnedDocumentInfo { + document_info: DocumentRefInfo((&doc, None)), + owner_id: None, + }, + contract: &contract, + document_type, + }, + false, + BlockInfo::default(), + true, + None, + pv, + None, + ) + .expect("expected to insert a tagged grade document"); + } + + let mixed_pin = vec![WhereClause { + field: "tag".to_string(), + operator: WhereOperator::In, + value: Value::Array(vec![Value::Text("honors".to_string()), Value::Null]), + }]; + let group_by = vec![GROUP_PROPERTY.to_string()]; + let having = vec![clause( + HavingAggregateFunction::Avg, + "grade", + HavingOperator::GreaterThan, + Value::U64(80), + )]; + let request = |prove: bool| DocumentHavingRequest { + contract: &contract, + document_type, + group_by: &group_by, + select: SelectProjection::avg("grade"), + having: &having, + order_by: &[], + where_clauses: &mixed_pin, + limit: Some(10), + offset: None, + has_start_at: false, + prove, + }; + + let entries = match drive + .execute_document_having_request(request(false), None, pv) + .expect("the mixed-null IN bound is served") + { + DocumentHavingResponse::Entries(entries) => entries, + DocumentHavingResponse::Proof(_) => panic!("expected entries, got a proof"), + }; + assert_eq!( + entries, + vec![ + RankedEntry { + in_key: Some(Vec::new()), + key: b"math".to_vec(), + value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(90, 1)), + }, + RankedEntry { + in_key: Some(b"honors".to_vec()), + key: b"math".to_vec(), + value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(95, 1)), + }, + ], + "both prefixes' in-bound groups, ascending; the same group key \ + appears twice, disambiguated by in_key, with the null branch's \ + empty in_key present (not None)" + ); + + let proof = match drive + .execute_document_having_request(request(true), None, pv) + .expect("the mixed-null IN prove succeeds") + { + DocumentHavingResponse::Proof(proof) => proof, + DocumentHavingResponse::Entries(_) => panic!("expected a proof, got entries"), + }; + let mode = detect_having_mode( + &SelectProjection::avg("grade"), + &group_by, + &having, + &[], + &mixed_pin, + RankedPaginationInputs { + limit: Some(10), + offset: None, + has_start_at: false, + }, + pv, + ) + .expect("the mixed-null case is well-formed"); + let query = resolve_having_query_for_mode( + contract.id_ref().to_buffer(), + document_type, + TAGGED_DOCTYPE.to_string(), + contract + .document_types() + .get(TAGGED_DOCTYPE) + .expect("taggedGrade doctype exists") + .indexes(), + &mode, + pv, + ) + .expect("the compound index covers the mixed-null request"); + assert_eq!( + query.prefix_branches, + vec![vec![Vec::new()], vec![b"honors".to_vec()]], + "canonical branch order: the empty (null) segment first" + ); + let (root_hash, verified) = query + .verify_having_range_proof(&proof, pv) + .expect("the mixed-null branch container must verify"); + assert_eq!(verified, entries); + assert_eq!( + root_hash, + drive + .grove + .root_hash(None, &pv.drive.grove_version) + .unwrap() + .expect("root hash must be readable"), + ); + } } diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs b/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs new file mode 100644 index 00000000000..253f866db65 --- /dev/null +++ b/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs @@ -0,0 +1,194 @@ +//! Branch mechanics for `IN`-pinned ranked / having-range queries — +//! shared by the executors (walk one secondary per branch, merge) and +//! the verifiers (verify one proof per branch, re-merge), so both sides +//! implement one comparator and one proof-container layout. +//! +//! ## Why a merge needs no proof of its own +//! +//! Each branch's walk is independently proved complete against the same +//! root hash, and the merged page is a *deterministic function* of the +//! branch pages: any union entry that precedes a returned entry in the +//! merge order is preceded, within its own branch, by fewer than `limit` +//! entries — so it is in its branch's returned page. The union's first +//! `limit` entries are therefore contained in the union of the branch +//! pages, and re-merging verified branch pages reconstructs a complete, +//! correctly ordered page. (This is the same argument for both surfaces: +//! "first `limit` in merge order restricted to the branch" is top-k for +//! ranked and the in-bound range prefix for having.) +//! +//! ## The merge order +//! +//! `(aggregate in walk direction, branch segment ascending, group key in +//! walk direction)`. The middle term is the canonical branch tie-break: +//! the **encoded** segment bytes of the branch's `IN` position, fixed +//! ascending regardless of walk direction, independent of the caller's +//! element order — which also makes `null` (the empty segment) sort +//! first deterministically. + +use super::{RankedEntry, RankedEntryValue}; +use crate::error::drive::DriveError; +use crate::error::Error; +use std::cmp::Ordering; + +/// Version byte of the branch-proof container. Bumped only with a +/// method-version bump on the prove/verify pair — the container is part +/// of the prover/verifier agreement, not a transport detail. +const BRANCH_PROOF_CONTAINER_VERSION: u8 = 1; + +/// The position (index into a branch's segment list) at which the +/// branches differ — the `IN` pin's position in the covering index's +/// leading properties. `None` when there is a single branch (no `IN`), +/// or when branches are degenerate duplicates (the encoder rejects +/// those, so it is unreachable off the resolver path). +pub fn varying_position(branches: &[Vec>]) -> Option { + let first = branches.first()?; + for other in &branches[1..] { + for (position, (a, b)) in first.iter().zip(other.iter()).enumerate() { + if a != b { + return Some(position); + } + } + } + None +} + +/// The `in_key` for one branch: the encoded segment at the varying +/// position. `None` for single-branch queries — entries of an un-branched +/// response carry no discriminator, keeping the shape byte-identical to +/// the pre-`IN` surface. +pub fn branch_in_key(branches: &[Vec>], branch: usize) -> Option> { + if branches.len() < 2 { + return None; + } + let position = varying_position(branches)?; + branches.get(branch)?.get(position).cloned() +} + +/// Compare two entries' aggregates on the same axis. The executors and +/// verifiers only ever merge entries of one axis, so a variant mismatch +/// is corrupted state, reported rather than ordered. +fn aggregate_cmp(a: &RankedEntryValue, b: &RankedEntryValue) -> Result { + match (a, b) { + (RankedEntryValue::Count(a), RankedEntryValue::Count(b)) => Ok(a.cmp(b)), + (RankedEntryValue::Sum(a), RankedEntryValue::Sum(b)) => Ok(a.cmp(b)), + (RankedEntryValue::AvgFixedPoint(a), RankedEntryValue::AvgFixedPoint(b)) => Ok(a.cmp(b)), + _ => Err(Error::Drive(DriveError::CorruptedDriveState( + "branch merge compared entries from different aggregate axes".to_string(), + ))), + } +} + +/// Merge per-branch pages into the final page: tag each entry with its +/// branch's `in_key`, order by the merge comparator, cut at `limit`. +/// +/// `per_branch` must be indexed identically to `branches` (the resolver +/// produces both in canonical branch order). Branch pages arrive sorted +/// by the walk; the merged set is small (≤ branches × limit), so a +/// plain total sort is used instead of a k-way heap — simpler to keep +/// byte-identical between server and verifier. +pub fn merge_branch_pages( + per_branch: Vec>, + branches: &[Vec>], + descending: bool, + limit: usize, +) -> Result, Error> { + let mut merged: Vec = Vec::with_capacity(per_branch.iter().map(Vec::len).sum()); + for (branch, entries) in per_branch.into_iter().enumerate() { + let in_key = branch_in_key(branches, branch); + merged.extend(entries.into_iter().map(|mut entry| { + entry.in_key = in_key.clone(); + entry + })); + } + let mut comparison_error: Option = None; + merged.sort_by(|a, b| { + let aggregate = match aggregate_cmp(&a.value, &b.value) { + Ok(ordering) => ordering, + Err(e) => { + comparison_error.get_or_insert(e); + Ordering::Equal + } + }; + let directional = |ordering: Ordering| { + if descending { + ordering.reverse() + } else { + ordering + } + }; + directional(aggregate) + .then_with(|| a.in_key.cmp(&b.in_key)) + .then_with(|| directional(a.key.cmp(&b.key))) + }); + if let Some(e) = comparison_error { + return Err(e); + } + merged.truncate(limit); + Ok(merged) +} + +/// Frame per-branch grovedb proofs into the single opaque byte string +/// the wire's `Proof` carries: version byte, `u16` branch count, then +/// each proof length-prefixed with a `u32` (all big-endian). Used only +/// when there are two or more branches — a single-branch proof stays +/// the raw grovedb envelope, byte-identical to the pre-`IN` surface. +pub fn encode_branch_proofs(proofs: &[Vec]) -> Vec { + let mut out = Vec::with_capacity(3 + proofs.iter().map(|p| 4 + p.len()).sum::()); + out.push(BRANCH_PROOF_CONTAINER_VERSION); + out.extend_from_slice(&(proofs.len() as u16).to_be_bytes()); + for proof in proofs { + out.extend_from_slice(&(proof.len() as u32).to_be_bytes()); + out.extend_from_slice(proof); + } + out +} + +/// Parse the branch-proof container, requiring exactly +/// `expected_branches` proofs — the verifier derives that count from +/// its own resolution of the request, so a server cannot drop or +/// duplicate a branch without the container failing to parse. Trailing +/// bytes are rejected: an envelope is exactly its declared content. +pub fn decode_branch_proofs(bytes: &[u8], expected_branches: usize) -> Result>, Error> { + let malformed = |what: &str| { + Error::Drive(DriveError::CorruptedDriveState(format!( + "branch-proof container: {what}" + ))) + }; + let (&version, mut rest) = bytes.split_first().ok_or_else(|| malformed("empty"))?; + if version != BRANCH_PROOF_CONTAINER_VERSION { + return Err(malformed(&format!( + "unknown container version {version}; expected {BRANCH_PROOF_CONTAINER_VERSION}" + ))); + } + if rest.len() < 2 { + return Err(malformed("truncated branch count")); + } + let (count_bytes, tail) = rest.split_at(2); + let count = u16::from_be_bytes([count_bytes[0], count_bytes[1]]) as usize; + rest = tail; + if count != expected_branches { + return Err(malformed(&format!( + "container carries {count} branch proofs; the request resolves to \ + {expected_branches} branches" + ))); + } + let mut proofs = Vec::with_capacity(count); + for _ in 0..count { + if rest.len() < 4 { + return Err(malformed("truncated proof length")); + } + let (len_bytes, tail) = rest.split_at(4); + let len = + u32::from_be_bytes([len_bytes[0], len_bytes[1], len_bytes[2], len_bytes[3]]) as usize; + if tail.len() < len { + return Err(malformed("truncated proof body")); + } + let (proof, tail) = tail.split_at(len); + proofs.push(proof.to_vec()); + rest = tail; + } + if !rest.is_empty() { + return Err(malformed("trailing bytes after the declared proofs")); + } + Ok(proofs) +} diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs index 9cda62c9234..e445ff6c64b 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs @@ -10,6 +10,7 @@ //! Whole module is gated `feature = "server"` via the parent's //! `pub mod execute_top_k;` declaration. +use super::branches::{encode_branch_proofs, merge_branch_pages}; use super::{DriveDocumentRankedQuery, RankedAxis, RankedEntry, RankedEntryValue, RankedPage}; use crate::drive::Drive; use crate::error::drive::DriveError; @@ -70,9 +71,49 @@ impl DriveDocumentRankedQuery<'_> { drive: &Drive, transaction: TransactionArg, platform_version: &PlatformVersion, + ) -> Result { + if self.prefix_branches.len() > 1 { + // One walk per branch, each fetching a full page (the merge + // lemma needs every branch's own top-k), merged with the + // shared comparator. `offset` is grammar-rejected with `IN`, + // so `skipped` is always 0 here. + let per_branch = (0..self.prefix_branches.len()) + .map(|branch| { + Ok(self + .execute_top_k_no_proof_branch( + branch, + drive, + transaction, + platform_version, + )? + .entries) + }) + .collect::, Error>>()?; + let entries = merge_branch_pages( + per_branch, + &self.prefix_branches, + self.descending, + self.k as usize, + )?; + return Ok(RankedPage { + skipped: 0, + entries, + }); + } + self.execute_top_k_no_proof_branch(0, drive, transaction, platform_version) + } + + /// One branch's page — the entire pre-`IN` executor, parameterized + /// by which prefix branch's terminal tree it walks. + fn execute_top_k_no_proof_branch( + &self, + branch: usize, + drive: &Drive, + transaction: TransactionArg, + platform_version: &PlatformVersion, ) -> Result { let grove_version = &platform_version.drive.grove_version; - let path = self.indexed_property_name_tree_path()?; + let path = self.indexed_property_name_tree_path(branch)?; let path_refs: Vec<&[u8]> = path.iter().map(|segment| segment.as_slice()).collect(); let offset = self.offset as u64; @@ -101,6 +142,7 @@ impl DriveDocumentRankedQuery<'_> { entries .into_iter() .map(|(count, key)| RankedEntry { + in_key: None, key, value: RankedEntryValue::Count(count), }) @@ -123,6 +165,7 @@ impl DriveDocumentRankedQuery<'_> { entries .into_iter() .map(|(sum, key)| RankedEntry { + in_key: None, key, value: RankedEntryValue::Sum(sum), }) @@ -145,6 +188,7 @@ impl DriveDocumentRankedQuery<'_> { entries .into_iter() .map(|(avg, key)| RankedEntry { + in_key: None, key, value: RankedEntryValue::AvgFixedPoint(avg), }) @@ -206,9 +250,38 @@ impl DriveDocumentRankedQuery<'_> { drive: &Drive, transaction: TransactionArg, platform_version: &PlatformVersion, + ) -> Result, Error> { + if self.prefix_branches.len() > 1 { + // One indexed-axis proof per branch, framed into the + // versioned container in canonical branch order. The + // verifier re-derives the branch set from the request, so a + // dropped, duplicated, or reordered branch fails there. + let proofs = (0..self.prefix_branches.len()) + .map(|branch| { + self.execute_top_k_with_proof_branch( + branch, + drive, + transaction, + platform_version, + ) + }) + .collect::, Error>>()?; + return Ok(encode_branch_proofs(&proofs)); + } + self.execute_top_k_with_proof_branch(0, drive, transaction, platform_version) + } + + /// One branch's proof — the entire pre-`IN` prover, parameterized by + /// the prefix branch. + fn execute_top_k_with_proof_branch( + &self, + branch: usize, + drive: &Drive, + transaction: TransactionArg, + platform_version: &PlatformVersion, ) -> Result, Error> { let grove_version = &platform_version.drive.grove_version; - let path = self.indexed_property_name_tree_path()?; + let path = self.indexed_property_name_tree_path(branch)?; let path_refs: Vec<&[u8]> = path.iter().map(|segment| segment.as_slice()).collect(); // Same destructure-don't-unwrap rationale as the no-proof arm. diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs b/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs index 5f5408fbf30..8e315b68916 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs @@ -7,7 +7,7 @@ //! and the SDK verifier both call these so they land on the same index //! (and therefore the same grove path) for the same request. -use super::{DocumentRankedMode, DriveDocumentRankedQuery, RankedAxis}; +use super::{DocumentRankedMode, DriveDocumentRankedQuery, PrefixPin, RankedAxis}; use crate::error::query::QuerySyntaxError; use crate::error::Error; use dpp::data_contract::document_type::methods::DocumentTypeV0Methods; @@ -104,9 +104,9 @@ pub fn find_ranked_index_for_mode<'b>( mode: &DocumentRankedMode, ) -> Option<&'b Index> { let pin_fields: Vec = mode - .equality_pins + .prefix_pins .iter() - .map(|(field, _)| field.clone()) + .map(|pin| pin.field.clone()) .collect(); find_ranked_index_for_axis( indexes, @@ -152,19 +152,19 @@ pub fn resolve_ranked_query_for_mode<'a>( "ranked", mode.axis, &mode.group_by_property, - &mode.equality_pins, + &mode.prefix_pins, &mode.aggregate_field, ), )) })?; - let equality_prefix_values = - encode_equality_prefix_values(document_type, index, &mode.equality_pins, platform_version)?; + let prefix_branches = + encode_prefix_branches(document_type, index, &mode.prefix_pins, platform_version)?; Ok(DriveDocumentRankedQuery { document_type, contract_id, document_type_name, index, - equality_prefix_values, + prefix_branches, axis: mode.axis, descending: mode.descending, k: mode.k, @@ -182,17 +182,17 @@ pub fn no_covering_index_message( surface: &str, axis: RankedAxis, group_by_property: &str, - equality_pins: &[(String, Value)], + prefix_pins: &[PrefixPin], aggregate_field: &str, ) -> String { let pin_fields = || { - equality_pins + prefix_pins .iter() - .map(|(field, _)| field.as_str()) + .map(|pin| pin.field.as_str()) .collect::>() .join(", ") }; - let index_shape = if equality_pins.is_empty() { + let index_shape = if prefix_pins.is_empty() { format!("a single-property index on `{group_by_property}`") } else { format!( @@ -204,7 +204,7 @@ pub fn no_covering_index_message( format!( "no ranked index covers `group_by = [{group_by_property}]`{} on the {axis:?} axis \ for this {surface} query: the document type needs {index_shape} declaring `{}`{}", - if equality_pins.is_empty() { + if prefix_pins.is_empty() { String::new() } else { format!(" with equality pins on [{}]", pin_fields()) @@ -218,61 +218,105 @@ pub fn no_covering_index_message( ) } -/// Encode the equality pins into the grove path's prefix-value -/// segments: for each **leading** property of `index`, in index order, -/// the pinned value's index-key bytes -/// (`DocumentType::serialize_value_for_key` — the same encoding the -/// write path used to key that prefix's value tree). +/// Encode the resolved prefix pins into **branches** — one +/// `Vec>` of prefix path segments per branch, in index-property +/// order (the same order and encoding the write path used to key those +/// prefix value trees). A request with only `==` pins yields exactly +/// one branch; the (at most one) `IN` pin yields one branch per +/// element. /// /// This is part of the prover/verifier agreement: server executors and /// the SDK's proof helpers both come through here, so a pinned value -/// can only ever name one subtree, identically on both sides. +/// can only ever name one subtree — and a branch *set* only ever one +/// ordered subtree list — identically on both sides. Branch order is +/// canonical: ascending by encoded segment bytes, independent of the +/// caller's element order (which also makes `null`, the empty segment, +/// sort first deterministically). /// /// `index` must have been picked by [`find_ranked_index_for_axis`] /// against these same pins — every leading property is then guaranteed -/// a pin. A value the property's type cannot encode (a string against -/// an integer property, an out-of-range integer) is a caller error, -/// reported as a query-syntax rejection naming the property. -pub fn encode_equality_prefix_values( +/// a pin. A value the property's type cannot encode is a caller error +/// naming the property; two `IN` elements that encode to the same +/// segment (two spellings of one value) are one branch and are rejected +/// as a duplicate rather than walked twice. +pub fn encode_prefix_branches( document_type: DocumentTypeRef, index: &Index, - equality_pins: &[(String, Value)], + prefix_pins: &[PrefixPin], platform_version: &PlatformVersion, -) -> Result>, Error> { +) -> Result>>, Error> { let leading = &index.properties[..index.properties.len().saturating_sub(1)]; - leading + let per_property: Vec>> = leading .iter() .map(|property| { - let (_, value) = equality_pins + let pin = prefix_pins .iter() - .find(|(field, _)| field == &property.name) + .find(|pin| pin.field == property.name) .ok_or_else(|| { Error::Query(QuerySyntaxError::InvalidWhereClauseComponents( "internal resolution mismatch: the picked compound ranked index has \ - a leading property with no equality pin — the index picker and the \ - prefix encoder disagreed on the pins", + a leading property with no pin — the index picker and the prefix \ + encoder disagreed on the pins", )) })?; - // A null pin addresses the subtree the write walkers create - // for an **absent** value: they encode it as - // `get_raw_for_document_type(..).unwrap_or_default()` — an - // empty path segment — for user and system properties alike. - // Null must short-circuit here because the system-property - // encoders (`$updatedAt`, `$creatorId`, …) reject null before - // any encoding happens, which would make the stored - // empty-segment prefix unaddressable. - if value.is_null() { - return Ok(Vec::new()); - } - document_type - .serialize_value_for_key(&property.name, value, platform_version) - .map_err(|e| { - Error::Query(QuerySyntaxError::InvalidParameter(format!( - "the equality pin on `{}` does not encode as that property's \ - index key: {e}", - property.name - ))) + let mut encoded = pin + .values + .iter() + .map(|value| { + // A null pin addresses the subtree the write walkers + // create for an **absent** value: they encode it as + // `get_raw_for_document_type(..).unwrap_or_default()` + // — an empty path segment — for user and system + // properties alike. Null must short-circuit here + // because the system-property encoders (`$updatedAt`, + // `$creatorId`, …) reject null before any encoding + // happens, which would make the stored empty-segment + // prefix unaddressable. + if value.is_null() { + return Ok(Vec::new()); + } + document_type + .serialize_value_for_key(&property.name, value, platform_version) + .map_err(|e| { + Error::Query(QuerySyntaxError::InvalidParameter(format!( + "the pin on `{}` does not encode as that property's \ + index key: {e}", + property.name + ))) + }) }) + .collect::, Error>>()?; + if encoded.len() > 1 { + encoded.sort(); + if encoded.windows(2).any(|pair| pair[0] == pair[1]) { + return Err(Error::Query( + QuerySyntaxError::InvalidWhereClauseComponents( + "an `IN` pin's elements encode to the same index key: two \ + spellings of one value are one prefix branch — deduplicate \ + the element list", + ), + )); + } + } + Ok(encoded) }) - .collect() + .collect::, Error>>()?; + + // The grammar admits at most one multi-value pin, so this product + // is |IN| branches (or exactly one), already in canonical order + // because the only varying position was sorted above. + let mut branches: Vec>> = vec![Vec::with_capacity(leading.len())]; + for candidates in per_property { + branches = branches + .into_iter() + .flat_map(|prefix| { + candidates.iter().map(move |segment| { + let mut branch = prefix.clone(); + branch.push(segment.clone()); + branch + }) + }) + .collect(); + } + Ok(branches) } diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs b/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs index a72ccd7b7f4..97427885029 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs @@ -84,6 +84,8 @@ use dpp::platform_value::Value; #[cfg(any(feature = "server", feature = "verify"))] pub use grovedb::element::indexed::AVG_FIXED_POINT_SCALE as RANKED_AVG_SCALE; +#[cfg(any(feature = "server", feature = "verify"))] +pub mod branches; #[cfg(any(feature = "server", feature = "verify"))] pub mod index_picker; #[cfg(any(feature = "server", feature = "verify"))] @@ -127,6 +129,20 @@ mod tests; #[cfg(any(feature = "server", feature = "verify"))] pub const MAX_RANKED_LIMIT: u16 = 100; +/// Hard ceiling on the element count of the (at most one) `IN` prefix +/// pin — the number of prefix **branches** one ranked / having-range +/// request may fan out into. +/// +/// Each element is one full secondary walk and one proof branch, each +/// carrying up to `limit` committed entries plus boundary commitments, +/// so worst-case proof size is `MAX_PREFIX_IN_BRANCHES × +/// MAX_RANKED_LIMIT` entries (≈100–150 KB at the ceiling). A hard +/// rejection rather than a clamp, for the same reason as the limit: the +/// branch set is echoed in the proof container and re-checked by the +/// verifier. +#[cfg(any(feature = "server", feature = "verify"))] +pub const MAX_PREFIX_IN_BRANCHES: usize = 10; + /// The `ORDER BY` field name that means "the group's `COUNT(*)`". /// /// `COUNT(*)` has no field to name, so the ranked grammar needs some @@ -256,6 +272,13 @@ pub struct RankedEntry { pub key: Vec, /// The group's aggregate on the requested axis. pub value: RankedEntryValue, + /// The branch this entry came from, on an `IN`-pinned request: the + /// encoded index-key segment of the `IN` position's pinned value + /// (empty bytes for the `null` branch). `None` on single-branch + /// responses — the same group key can appear under two prefixes, so + /// only a merged page needs the discriminator. See + /// [`branches::branch_in_key`]. + pub in_key: Option>, } /// A resolved ranked query. Shared by the prover and the verifier — both @@ -279,17 +302,20 @@ pub struct DriveDocumentRankedQuery<'a> { pub document_type_name: String, /// The covering ranked index. Its **last** property is the `GROUP /// BY` property and the final path segment; any leading properties - /// are pinned by [`Self::equality_prefix_values`]. + /// are pinned by [`Self::prefix_branches`]. pub index: &'a Index, - /// Encoded index-key bytes of each leading index property's pinned - /// value, in index-property order — empty for a single-property - /// index. Together with `index` these determine the grove path - /// (each leading property contributes a name segment and a value - /// segment), so they are as much a part of the prover/verifier - /// agreement as the path builder itself. Produced by - /// [`index_picker::encode_equality_prefix_values`] from the - /// request's equality `where` pins. - pub equality_prefix_values: Vec>, + /// The prefix **branches** — one inner `Vec>` of encoded + /// index-key path segments per branch, each in index-property + /// order. Always at least one branch; a single-property index or an + /// all-`==` pinned request has exactly one (possibly empty) branch, + /// and the (at most one) `IN` pin contributes one branch per + /// element, in canonical encoded-ascending order. Together with + /// `index` these determine the grove path(s), so the branch set is + /// as much a part of the prover/verifier agreement as the path + /// builder itself. Produced by + /// [`index_picker::encode_prefix_branches`] from the request's + /// `where` pins. + pub prefix_branches: Vec>>, /// Which aggregate the groups are ranked by. Must be covered by /// `index`'s matching `ranked_*` flag. pub axis: RankedAxis, @@ -423,12 +449,27 @@ pub struct DocumentRankedMode { /// [`RankedAxis::Count`] (`COUNT(*)`); the index's `summable` /// property for [`RankedAxis::Sum`] / [`RankedAxis::Avg`]. pub aggregate_field: String, - /// The equality `where` pins, `(property, value)` per clause — - /// exactly one per leading property of the covering compound index, - /// in whatever order the request supplied them (the resolver - /// re-orders them into index-property order when it encodes the - /// path). Empty for the single-property form. Shape-validated only: - /// the index-aware checks (does a compound index exist whose leading - /// properties these pin?) live in [`index_picker`]. - pub equality_pins: Vec<(String, Value)>, + /// The `where` pins — exactly one per leading property of the + /// covering compound index, in whatever order the request supplied + /// them (the resolver re-orders them into index-property order when + /// it encodes the path). Empty for the single-property form. + /// Shape-validated only: the index-aware checks (does a compound + /// index exist whose leading properties these pin?) live in + /// [`index_picker`]. + pub prefix_pins: Vec, +} + +/// One pinned leading property of the covering compound index. +/// +/// An `==` clause pins exactly one value; the (at most one) `IN` clause +/// pins several, each element selecting its own prefix **branch** — the +/// executors walk one secondary per branch and merge deterministically. +/// A single-element `IN` is normalized to an equality pin at grammar +/// time, so `values.len() > 1` is exactly "this is the branching pin". +#[derive(Debug, Clone, PartialEq)] +pub struct PrefixPin { + /// The pinned property's name. + pub field: String, + /// The pinned value(s); never empty. + pub values: Vec, } diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/mod.rs b/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/mod.rs index 17b2d46b5f5..2c80fe16696 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/mod.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/mod.rs @@ -88,4 +88,4 @@ pub fn ranked_order_key(select: &SelectProjection) -> &str { mod v0; // Re-exported so the dispatcher's callers (`drive_dispatcher`, the // test suites) keep addressing the frozen grammar by its old path. -pub use v0::{detect_ranked_mode_v0, equality_pins_from_where_clauses}; +pub use v0::{detect_ranked_mode_v0, prefix_pins_from_where_clauses}; diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/v0/mod.rs b/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/v0/mod.rs index abe883991e6..48248bf9efe 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/v0/mod.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/v0/mod.rs @@ -3,6 +3,7 @@ //! this file is a v0 internal: a later grammar version gets its own //! `vN/` sibling rather than editing this one. +use super::super::{PrefixPin, MAX_PREFIX_IN_BRANCHES}; use super::ranked_order_key; use super::{ DocumentRankedMode, RankedAxis, RankedPaginationInputs, MAX_RANKED_LIMIT, @@ -22,46 +23,26 @@ use dpp::platform_value::Value; /// Both surfaces read a compound index's per-prefix secondary by /// descending through one prefix value tree per **leading** index /// property, and only an equality clause names a single value tree to -/// descend into. So the grammar is: every `where` clause must be an -/// equality (`==`), each on a distinct property. `IN` is rejected -/// separately from the other operators because it *will* eventually be -/// serviceable (one branch per element, once multi-`IN` branching lands -/// on the document query surface) — the message says so — while a range -/// operator on a prefix property can never pin a single subtree. +/// descend into. So the grammar is: every `where` clause is an equality +/// (`==`) on a distinct property, except that **at most one** clause may +/// be an `IN` — each of its elements selects its own prefix *branch*, +/// and the executors walk one secondary per branch and merge (see +/// [`MAX_PREFIX_IN_BRANCHES`] for the fan-out ceiling). A +/// range operator on a prefix property can never pin a subtree and is +/// rejected outright. A single-element `IN` is normalized to an +/// equality pin, so the degenerate case is byte-identical to `==`. /// /// Shape-only, like everything in this module: whether the pinned /// properties are exactly the leading properties of a covering compound -/// index is the index picker's call. -pub fn equality_pins_from_where_clauses( +/// index is the index picker's call, and element distinctness is +/// enforced post-encoding by the prefix encoder (two spellings of one +/// value are one branch, and must be rejected as a duplicate). +pub fn prefix_pins_from_where_clauses( where_clauses: &[WhereClause], -) -> Result, Error> { - let mut pins: Vec<(String, Value)> = Vec::with_capacity(where_clauses.len()); +) -> Result, Error> { + let mut pins: Vec = Vec::with_capacity(where_clauses.len()); + let mut branching_field: Option<&str> = None; for clause in where_clauses { - match clause.operator { - WhereOperator::Equal => {} - WhereOperator::In => { - return Err(Error::Query(QuerySyntaxError::Unsupported(format!( - "`{} IN …` is not yet supported on a ranked / having-range query's \ - prefix properties: each `IN` element names a different prefix value \ - tree, so serving it means one secondary walk per element and a merged \ - result — a future capability layered on multi-`IN` branching. Pin \ - each leading index property with `==`, or issue one request per \ - value.", - clause.field - )))); - } - _ => { - return Err(Error::Query( - QuerySyntaxError::InvalidWhereClauseComponents( - "a ranked / having-range query's `where` clauses must pin the covering \ - compound index's leading properties with `==`: the per-prefix \ - secondary lives under one prefix value tree per leading property, and \ - only an equality names a single value tree to descend into — a range \ - operator cannot pin a prefix", - ), - )); - } - } if clause.field.is_empty() { return Err(Error::Query( QuerySyntaxError::InvalidWhereClauseComponents( @@ -69,15 +50,72 @@ pub fn equality_pins_from_where_clauses( ), )); } - if pins.iter().any(|(field, _)| field == &clause.field) { + if pins.iter().any(|pin| pin.field == clause.field) { return Err(Error::Query( QuerySyntaxError::InvalidWhereClauseComponents( "a ranked / having-range query pins the same property twice: each leading \ - index property takes exactly one equality pin", + index property takes exactly one pin", ), )); } - pins.push((clause.field.clone(), clause.value.clone())); + let values = match clause.operator { + WhereOperator::Equal => vec![clause.value.clone()], + WhereOperator::In => { + if let Some(first) = branching_field { + return Err(Error::Query(QuerySyntaxError::Unsupported(format!( + "a ranked / having-range query takes at most one `IN` across its \ + prefix properties (`{first}` already carries it): several `IN`s \ + multiply into a cartesian product of prefix branches, each a \ + separate secondary walk inside one proof — a fan-out the branch \ + ceiling exists to prevent. Pin `{}` with `==`.", + clause.field + )))); + } + let Value::Array(elements) = &clause.value else { + return Err(Error::Query( + QuerySyntaxError::InvalidWhereClauseComponents( + "an `IN` pin's right operand must be an array of candidate values", + ), + )); + }; + if elements.is_empty() { + return Err(Error::Query( + QuerySyntaxError::InvalidWhereClauseComponents( + "an `IN` pin's element list is empty: it can match nothing, and \ + a pin that cannot match any prefix is a caller error", + ), + )); + } + if elements.len() > MAX_PREFIX_IN_BRANCHES { + return Err(Error::Query(QuerySyntaxError::InvalidParameter(format!( + "an `IN` pin fans out into one secondary walk (and one proof \ + branch) per element; {} elements exceeds the ceiling of {}. \ + Split the request.", + elements.len(), + MAX_PREFIX_IN_BRANCHES + )))); + } + if elements.len() > 1 { + branching_field = Some(clause.field.as_str()); + } + elements.clone() + } + _ => { + return Err(Error::Query( + QuerySyntaxError::InvalidWhereClauseComponents( + "a ranked / having-range query's `where` clauses must pin the covering \ + compound index's leading properties with `==` (or one `IN`): the \ + per-prefix secondary lives under one prefix value tree per leading \ + property, and only equality names value trees to descend into — a \ + range operator cannot pin a prefix", + ), + )); + } + }; + pins.push(PrefixPin { + field: clause.field.clone(), + values, + }); } Ok(pins) } @@ -98,7 +136,7 @@ pub fn equality_pins_from_where_clauses( /// `m ≥ 0`. `WHERE` clauses, when present, must be **equality pins** on /// distinct properties — one per leading property of a covering /// compound ranked index (see -/// [`equality_pins_from_where_clauses`]); the ranking then reads that +/// [`prefix_pins_from_where_clauses`]); the ranking then reads that /// pinned prefix's own secondary. With no `where` the covering index is /// single-property, exactly as before. /// @@ -263,7 +301,7 @@ pub fn detect_ranked_mode_v0( // serve). Anything other than a distinct-property equality is // rejected loudly here; whether the pinned set matches a covering // index's leading properties exactly is the index picker's call. - let equality_pins = equality_pins_from_where_clauses(where_clauses)?; + let prefix_pins = prefix_pins_from_where_clauses(where_clauses)?; // ---- LIMIT: required, 1 ..= MAX_RANKED_LIMIT --------------------- // @@ -328,6 +366,24 @@ pub fn detect_ranked_mode_v0( ))); } + // ---- OFFSET × IN: mutually exclusive ----------------------------- + // + // Rank-skip is served from counted subtree commitments *inside one + // secondary*; there is no counted structure spanning a branch + // union, so a cross-branch offset would have to walk (and prove) + // the skipped region in every branch — silently expensive. Callers + // who need deep pages issue per-prefix requests, where offset works + // exactly as documented. + if offset > 0 && prefix_pins.iter().any(|pin| pin.values.len() > 1) { + return Err(Error::Query(QuerySyntaxError::InvalidLimit( + "`OFFSET` cannot combine with an `IN` prefix pin: rank-skip is attested from \ + one secondary's counted commitments, and an `IN` merges several secondaries \ + with no counted structure over the union. Page one prefix at a time (`==` \ + pin + `OFFSET`), or drop the offset." + .to_string(), + ))); + } + Ok(DocumentRankedMode { axis, descending, @@ -335,6 +391,6 @@ pub fn detect_ranked_mode_v0( offset, group_by_property, aggregate_field, - equality_pins, + prefix_pins, }) } diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/path.rs b/packages/rs-drive/src/query/drive_document_ranked_query/path.rs index ed3dccf0620..ec737825776 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/path.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/path.rs @@ -101,12 +101,15 @@ impl DriveDocumentRankedQuery<'_> { /// Errors when the number of encoded prefix values does not match /// the index's leading-property count — the fail-closed backstop /// for a caller that resolved the query against the wrong index. - pub fn indexed_property_name_tree_path(&self) -> Result>, Error> { + /// + /// `branch` indexes into [`Self::prefix_branches`]; single-branch + /// queries (no `IN` pin) always pass `0`. + pub fn indexed_property_name_tree_path(&self, branch: usize) -> Result>, Error> { indexed_property_name_tree_path_for_index( &self.contract_id, &self.document_type_name, self.index, - &self.equality_prefix_values, + &self.prefix_branches[branch], ) } } diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs index 0052ab8388e..e5986b5a33c 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs @@ -495,9 +495,9 @@ fn sum_and_avg_selects_require_a_field() { /// properties. Detection is shape-only: an equality clause becomes a /// pin (whether a compound index actually covers it is the resolver's /// call — see `pins_without_a_covering_compound_index_are_rejected`); -/// anything that is not a distinct-property equality is refused -/// loudly, `IN` with its own not-yet message because it will become -/// serviceable once multi-`IN` branching lands. +/// anything that is not a distinct-property equality — or the one +/// permitted `IN`, which resolves to a multi-value branching pin — is +/// refused loudly. #[test] fn where_clauses_resolve_to_equality_pins_and_reject_everything_else() { // Equality pin: accepted at detection, carried in the mode. @@ -516,8 +516,11 @@ fn where_clauses_resolve_to_equality_pins_and_reject_everything_else() { ) .expect("an equality pin is a well-formed prefix pin"); assert_eq!( - mode.equality_pins, - vec![("chefId".to_string(), Value::Text("alpha".to_string()))] + mode.prefix_pins, + vec![PrefixPin { + field: "chefId".to_string(), + values: vec![Value::Text("alpha".to_string())], + }] ); // A range operator can never pin a single prefix value tree. @@ -540,14 +543,17 @@ fn where_clauses_resolve_to_equality_pins_and_reject_everything_else() { Error::Query(QuerySyntaxError::InvalidWhereClauseComponents(_)) )); - // `IN` is rejected with its own message: v1 pins are equality-only, - // multi-`IN` branching is a future capability. + // `IN` resolves to a branching pin — one value per element — and a + // single-element `IN` is exactly an equality pin. let in_clause = vec![WhereClause { field: "chefId".to_string(), operator: WhereOperator::In, - value: Value::Array(vec![Value::Text("alpha".to_string())]), + value: Value::Array(vec![ + Value::Text("beta".to_string()), + Value::Text("alpha".to_string()), + ]), }]; - let error = detect_ranked_mode_v0( + let mode = detect_ranked_mode_v0( &SelectProjection::avg("grade"), &group_by(), &[], @@ -555,16 +561,19 @@ fn where_clauses_resolve_to_equality_pins_and_reject_everything_else() { &in_clause, page(Some(2), None), ) - .expect_err("IN prefixes are not yet supported"); - match error { - Error::Query(QuerySyntaxError::Unsupported(message)) => { - assert!( - message.contains("IN") && message.contains("not yet supported"), - "the IN rejection must say it is a not-yet capability, got: {message}" - ); - } - other => panic!("expected Unsupported for an IN prefix, got {other:?}"), - } + .expect("an IN pin is a well-formed branching pin"); + assert_eq!( + mode.prefix_pins, + vec![PrefixPin { + field: "chefId".to_string(), + values: vec![ + Value::Text("beta".to_string()), + Value::Text("alpha".to_string()), + ], + }], + "the pin carries the elements verbatim; canonical branch order \ + is the encoder's job, not the grammar's" + ); // The same property pinned twice is a caller error, not a silent // last-write-wins. @@ -2121,7 +2130,7 @@ mod pinned_prefix { use super::super::drive_dispatcher::{DocumentRankedRequest, DocumentRankedResponse}; use super::super::index_picker::resolve_ranked_query_for_mode; - use super::super::mode_detection::detect_ranked_mode; + use super::super::mode_detection::{detect_ranked_mode, detect_ranked_mode_v0}; use super::super::{DriveDocumentRankedQuery, RankedEntry, RankedEntryValue}; use crate::drive::Drive; use crate::error::query::QuerySyntaxError; @@ -2335,10 +2344,12 @@ mod pinned_prefix { page.entries, vec![ RankedEntry { + in_key: None, key: b"art".to_vec(), value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(180, 2)), }, RankedEntry { + in_key: None, key: b"english".to_vec(), value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(161, 2)), }, @@ -2477,10 +2488,12 @@ mod pinned_prefix { page.entries, vec![ RankedEntry { + in_key: None, key: b"math".to_vec(), value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(170, 2)), }, RankedEntry { + in_key: None, key: b"science".to_vec(), value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(60, 1)), }, @@ -2526,8 +2539,8 @@ mod pinned_prefix { ) .expect("the compound index covers the null-pinned request"); assert_eq!( - query.equality_prefix_values, - vec![Vec::::new()], + query.prefix_branches, + vec![vec![Vec::::new()]], "a null pin must encode as the write path's empty segment" ); let (root_hash, verified) = query @@ -2544,6 +2557,361 @@ mod pinned_prefix { ); } + fn in_pin(identities: &[[u8; 32]]) -> Vec { + vec![WhereClause { + field: PREFIX_PROPERTY.to_string(), + operator: WhereOperator::In, + value: Value::Array( + identities + .iter() + .map(|identity| Value::Identifier(*identity)) + .collect(), + ), + }] + } + + /// `identityId IN [X, Y]` walks each identity's own secondary and + /// merges: descending aggregate order, entries tagged with their + /// branch's `in_key`, and a **cross-prefix aggregate tie** breaking + /// by encoded prefix ascending (X's 32 `1`-bytes before Y's `2`s) — + /// the comparator's middle term, observable only here. The proof is + /// a branch container, round-tripped through the shared resolver + /// against the live root hash. + #[test] + fn in_pinned_top_k_merges_branches_and_proves() { + let (drive, contract) = setup_grades_compound_ranked(); + insert_grades( + &drive, + &contract, + &[ + // X: art 90, math 80. Y: science 95, history 90 — the + // two 90s are the cross-prefix tie. + (IDENTITY_X, "art", 90), + (IDENTITY_X, "math", 80), + (IDENTITY_Y, "science", 95), + (IDENTITY_Y, "history", 90), + ], + ); + + // Request order [Y, X] deliberately reversed: canonical branch + // order is by encoded prefix, not by element order. + let pins = in_pin(&[IDENTITY_Y, IDENTITY_X]); + let page = match run(&drive, &contract, &pins, 3, false).expect("read succeeds") { + DocumentRankedResponse::Entries(page) => page, + DocumentRankedResponse::Proof(_) => panic!("expected entries, got a proof"), + }; + assert_eq!(page.skipped, 0); + assert_eq!( + page.entries, + vec![ + RankedEntry { + in_key: Some(IDENTITY_Y.to_vec()), + key: b"science".to_vec(), + value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(95, 1)), + }, + RankedEntry { + in_key: Some(IDENTITY_X.to_vec()), + key: b"art".to_vec(), + value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(90, 1)), + }, + RankedEntry { + in_key: Some(IDENTITY_Y.to_vec()), + key: b"history".to_vec(), + value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(90, 1)), + }, + ], + "top 3 across the union: science 95, then the 90–90 tie broken \ + by encoded prefix ascending (X's art before Y's history)" + ); + + let proof = match run(&drive, &contract, &pins, 3, true).expect("prove succeeds") { + DocumentRankedResponse::Proof(proof) => proof, + DocumentRankedResponse::Entries(_) => panic!("expected a proof, got entries"), + }; + let query = client_side_query(&contract, &pins, 3); + assert_eq!(query.prefix_branches.len(), 2, "two branches resolved"); + let (root_hash, verified) = query + .verify_ranked_top_k_proof(&proof, platform_version()) + .expect("the branch container must verify"); + assert_eq!(verified.entries, page.entries); + assert_eq!( + root_hash, + drive + .grove + .root_hash(None, &platform_version().drive.grove_version) + .unwrap() + .expect("root hash must be readable"), + ); + } + + /// The tamper matrix on the branch container: a response whose + /// branch proofs are reordered, dropped, duplicated, re-versioned, + /// or padded must fail verification — never silently reorder or + /// shrink the merged page. + #[test] + fn tampered_branch_containers_do_not_verify() { + let (drive, contract) = setup_grades_compound_ranked(); + insert_grades( + &drive, + &contract, + &[(IDENTITY_X, "art", 90), (IDENTITY_Y, "science", 95)], + ); + let pins = in_pin(&[IDENTITY_X, IDENTITY_Y]); + let proof = match run(&drive, &contract, &pins, 2, true).expect("prove succeeds") { + DocumentRankedResponse::Proof(proof) => proof, + DocumentRankedResponse::Entries(_) => panic!("expected a proof, got entries"), + }; + let query = client_side_query(&contract, &pins, 2); + + // Baseline sanity: untampered verifies. + query + .verify_ranked_top_k_proof(&proof, platform_version()) + .expect("untampered container verifies"); + + // Decompose the container by hand: [version][u16 count]([u32 len][bytes])* + assert_eq!(proof[0], 1, "container version byte"); + let count = u16::from_be_bytes([proof[1], proof[2]]) as usize; + assert_eq!(count, 2); + let mut branch_proofs = Vec::new(); + let mut rest = &proof[3..]; + for _ in 0..count { + let len = u32::from_be_bytes([rest[0], rest[1], rest[2], rest[3]]) as usize; + branch_proofs.push(rest[4..4 + len].to_vec()); + rest = &rest[4 + len..]; + } + let reframe = |proofs: &[Vec]| { + let mut out = vec![1u8]; + out.extend_from_slice(&(proofs.len() as u16).to_be_bytes()); + for p in proofs { + out.extend_from_slice(&(p.len() as u32).to_be_bytes()); + out.extend_from_slice(p); + } + out + }; + + // Reordered: branch 0's proof is verified against branch 1's + // path and fails inside grovedb. + let reordered = reframe(&[branch_proofs[1].clone(), branch_proofs[0].clone()]); + assert!( + query + .verify_ranked_top_k_proof(&reordered, platform_version()) + .is_err(), + "reordered branch proofs must not verify" + ); + + // Dropped: count mismatch against the query's own resolution. + let dropped = reframe(&[branch_proofs[0].clone()]); + assert!( + query + .verify_ranked_top_k_proof(&dropped, platform_version()) + .is_err(), + "a dropped branch must not verify" + ); + + // Duplicated: same count, but branch 1's slot holds branch 0's + // proof — wrong path again. + let duplicated = reframe(&[branch_proofs[0].clone(), branch_proofs[0].clone()]); + assert!( + query + .verify_ranked_top_k_proof(&duplicated, platform_version()) + .is_err(), + "a duplicated branch proof must not verify" + ); + + // Unknown container version. + let mut reversioned = proof.clone(); + reversioned[0] = 2; + assert!( + query + .verify_ranked_top_k_proof(&reversioned, platform_version()) + .is_err(), + "an unknown container version must not verify" + ); + + // Trailing bytes after the declared proofs. + let mut padded = proof.clone(); + padded.push(0); + assert!( + query + .verify_ranked_top_k_proof(&padded, platform_version()) + .is_err(), + "trailing bytes must not verify" + ); + } + + /// A single-element `IN` is normalized to an equality pin at + /// grammar time: same resolved branch set, same entries, and the + /// **same proof bytes** — the degenerate case is byte-identical to + /// `==`, so no client can observe which spelling was used. + #[test] + fn single_element_in_is_byte_identical_to_an_equality_pin() { + let (drive, contract) = setup_grades_compound_ranked(); + insert_grades(&drive, &contract, &[(IDENTITY_X, "art", 90)]); + + let equality = pin(IDENTITY_X); + let single_in = in_pin(&[IDENTITY_X]); + + let eq_query = client_side_query(&contract, &equality, 2); + let in_query = client_side_query(&contract, &single_in, 2); + assert_eq!(eq_query.prefix_branches, in_query.prefix_branches); + + let eq_proof = match run(&drive, &contract, &equality, 2, true).expect("prove") { + DocumentRankedResponse::Proof(proof) => proof, + DocumentRankedResponse::Entries(_) => panic!("expected a proof"), + }; + let in_proof = match run(&drive, &contract, &single_in, 2, true).expect("prove") { + DocumentRankedResponse::Proof(proof) => proof, + DocumentRankedResponse::Entries(_) => panic!("expected a proof"), + }; + assert_eq!(eq_proof, in_proof, "no container for a single branch"); + + let eq_page = match run(&drive, &contract, &equality, 2, false).expect("read") { + DocumentRankedResponse::Entries(page) => page, + DocumentRankedResponse::Proof(_) => panic!("expected entries"), + }; + assert!( + eq_page.entries.iter().all(|e| e.in_key.is_none()), + "single-branch entries carry no in_key" + ); + } + + /// `OFFSET` is rejected together with an `IN` pin — rank-skip is + /// attested per-secondary and has no meaning across a branch union. + #[test] + fn offset_is_rejected_with_an_in_pin() { + let error = detect_ranked_mode_v0( + &SelectProjection::avg("grade"), + &vec![CLASS_PROPERTY.to_string()], + &[], + &[OrderClause { + field: "grade".to_string(), + ascending: false, + }], + &in_pin(&[IDENTITY_X, IDENTITY_Y]), + RankedPaginationInputs { + limit: Some(2), + offset: Some(1), + has_start_at: false, + }, + ) + .expect_err("offset cannot combine with IN"); + match error { + Error::Query(QuerySyntaxError::InvalidLimit(message)) => { + assert!( + message.contains("OFFSET") && message.contains("IN"), + "the rejection must name the offset × IN exclusion, got: {message}" + ); + } + other => panic!("expected InvalidLimit, got {other:?}"), + } + } + + /// Grammar rejections around the `IN` pin: over-cap element lists, + /// empty lists, a second `IN`, a non-array operand, and elements + /// that encode to the same segment. + #[test] + fn in_pin_shape_rejections() { + let detect = |where_clauses: &[WhereClause]| { + detect_ranked_mode_v0( + &SelectProjection::avg("grade"), + &vec![CLASS_PROPERTY.to_string()], + &[], + &[OrderClause { + field: "grade".to_string(), + ascending: false, + }], + where_clauses, + RankedPaginationInputs { + limit: Some(2), + offset: None, + has_start_at: false, + }, + ) + }; + + // Over the branch ceiling. + let identities: Vec<[u8; 32]> = (0..11).map(|i| [i as u8 + 1; 32]).collect(); + let error = detect(&in_pin(&identities)).expect_err("11 branches is over the cap"); + match error { + Error::Query(QuerySyntaxError::InvalidParameter(message)) => { + assert!( + message.contains("ceiling of 10"), + "the rejection must name the ceiling, got: {message}" + ); + } + other => panic!("expected InvalidParameter, got {other:?}"), + } + + // Empty element list. + let empty = vec![WhereClause { + field: PREFIX_PROPERTY.to_string(), + operator: WhereOperator::In, + value: Value::Array(vec![]), + }]; + assert!(detect(&empty).is_err(), "an empty IN list must be rejected"); + + // A second IN (on another property) once one already branches. + let two_ins = vec![ + WhereClause { + field: PREFIX_PROPERTY.to_string(), + operator: WhereOperator::In, + value: Value::Array(vec![ + Value::Identifier(IDENTITY_X), + Value::Identifier(IDENTITY_Y), + ]), + }, + WhereClause { + field: "other".to_string(), + operator: WhereOperator::In, + value: Value::Array(vec![Value::U64(1), Value::U64(2)]), + }, + ]; + let error = detect(&two_ins).expect_err("two branching INs must be rejected"); + assert!( + matches!(error, Error::Query(QuerySyntaxError::Unsupported(_))), + "expected Unsupported for a second IN, got {error:?}" + ); + + // Non-array operand. + let scalar = vec![WhereClause { + field: PREFIX_PROPERTY.to_string(), + operator: WhereOperator::In, + value: Value::U64(7), + }]; + assert!( + detect(&scalar).is_err(), + "a scalar IN operand must be rejected" + ); + + // Duplicate elements surface at the encoder (post-encoding + // distinctness), through the resolver. + let (_, contract) = setup_grades_compound_ranked(); + let duplicated = in_pin(&[IDENTITY_X, IDENTITY_X]); + let mode = detect(&duplicated).expect("shape-valid; duplicates are the encoder's call"); + let error = resolve_ranked_query_for_mode( + contract.id_ref().to_buffer(), + contract + .document_type_for_name(DOCUMENT_TYPE) + .expect("grade doctype exists"), + DOCUMENT_TYPE.to_string(), + contract + .document_types() + .get(DOCUMENT_TYPE) + .expect("grade doctype exists") + .indexes(), + &mode, + platform_version(), + ) + .expect_err("duplicate encoded elements must be rejected"); + assert!( + matches!( + error, + Error::Query(QuerySyntaxError::InvalidWhereClauseComponents(_)) + ), + "expected the duplicate-encoding rejection, got {error:?}" + ); + } + /// An unpinned request over the compound-only contract has no /// covering index — there is no global cross-prefix ordering to /// serve, so the rejection names the missing coverage. diff --git a/packages/rs-drive/src/verify/document_having/verify_having_range_proof/v0/mod.rs b/packages/rs-drive/src/verify/document_having/verify_having_range_proof/v0/mod.rs index 9caf4f9df85..c220c77c00f 100644 --- a/packages/rs-drive/src/verify/document_having/verify_having_range_proof/v0/mod.rs +++ b/packages/rs-drive/src/verify/document_having/verify_having_range_proof/v0/mod.rs @@ -1,5 +1,8 @@ use crate::error::drive::DriveError; use crate::error::Error; +use crate::query::drive_document_ranked_query::branches::{ + decode_branch_proofs, merge_branch_pages, +}; use crate::query::{DriveDocumentHavingQuery, RankedAxis, RankedEntry, RankedEntryValue}; use crate::verify::RootHash; use dpp::version::PlatformVersion; @@ -43,7 +46,54 @@ impl DriveDocumentHavingQuery<'_> { proof: &[u8], platform_version: &PlatformVersion, ) -> Result<(RootHash, Vec), Error> { - let path = self.indexed_property_name_tree_path()?; + if self.prefix_branches.len() > 1 { + // `IN`-pinned request: same branch-container discipline as + // the ranked verifier — count from this query's own + // resolution, one root hash across branches, page re-derived + // by the shared merge. + let branch_proofs = decode_branch_proofs(proof, self.prefix_branches.len())?; + let mut root_hash: Option = None; + let mut per_branch = Vec::with_capacity(branch_proofs.len()); + for (branch, branch_proof) in branch_proofs.iter().enumerate() { + let (branch_root, entries) = + self.verify_having_range_proof_v0_branch(branch, branch_proof)?; + match root_hash { + None => root_hash = Some(branch_root), + Some(existing) if existing == branch_root => {} + Some(_) => { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "branch proofs attest different root hashes: every branch of \ + one response must be proved against one platform state" + .to_string(), + ))); + } + } + per_branch.push(entries); + } + let entries = merge_branch_pages( + per_branch, + &self.prefix_branches, + self.descending, + self.limit as usize, + )?; + let root_hash = root_hash.ok_or_else(|| { + Error::Drive(DriveError::CorruptedDriveState( + "branch container verified to zero branches".to_string(), + )) + })?; + return Ok((root_hash, entries)); + } + self.verify_having_range_proof_v0_branch(0, proof) + } + + /// One branch's verification — the entire pre-`IN` verifier, + /// parameterized by the prefix branch. + fn verify_having_range_proof_v0_branch( + &self, + branch: usize, + proof: &[u8], + ) -> Result<(RootHash, Vec), Error> { + let path = self.indexed_property_name_tree_path(branch)?; let path_refs: Vec<&[u8]> = path.iter().map(|segment| segment.as_slice()).collect(); let secondary_query = self.bounds.merk_query(self.descending); @@ -77,6 +127,7 @@ impl DriveDocumentHavingQuery<'_> { .into_iter() .map(|entry| entry.key_pair()) .map(|(count, key)| RankedEntry { + in_key: None, key, value: RankedEntryValue::Count(count), }) @@ -85,6 +136,7 @@ impl DriveDocumentHavingQuery<'_> { .into_iter() .map(|entry| entry.key_pair()) .map(|(sum, key)| RankedEntry { + in_key: None, key, value: RankedEntryValue::Sum(sum), }) @@ -93,6 +145,7 @@ impl DriveDocumentHavingQuery<'_> { .into_iter() .map(|entry| entry.key_pair()) .map(|(avg, key)| RankedEntry { + in_key: None, key, value: RankedEntryValue::AvgFixedPoint(avg), }) diff --git a/packages/rs-drive/src/verify/document_ranked/verify_ranked_top_k_proof/v0/mod.rs b/packages/rs-drive/src/verify/document_ranked/verify_ranked_top_k_proof/v0/mod.rs index 82f5b0a2320..cf03cdb18e4 100644 --- a/packages/rs-drive/src/verify/document_ranked/verify_ranked_top_k_proof/v0/mod.rs +++ b/packages/rs-drive/src/verify/document_ranked/verify_ranked_top_k_proof/v0/mod.rs @@ -1,5 +1,8 @@ use crate::error::drive::DriveError; use crate::error::Error; +use crate::query::drive_document_ranked_query::branches::{ + decode_branch_proofs, merge_branch_pages, +}; use crate::query::{ DriveDocumentRankedQuery, RankedAxis, RankedEntry, RankedEntryValue, RankedPage, }; @@ -51,7 +54,64 @@ impl DriveDocumentRankedQuery<'_> { proof: &[u8], platform_version: &PlatformVersion, ) -> Result<(RootHash, RankedPage), Error> { - let path = self.indexed_property_name_tree_path()?; + if self.prefix_branches.len() > 1 { + // `IN`-pinned request: the proof bytes are the branch + // container. The branch count comes from *this* query's own + // resolution, so a container with a dropped, duplicated, or + // added branch fails to parse; a reordered or substituted + // branch proof fails its branch's own path verification; and + // all branches must attest one root hash. The page is then + // re-derived by the shared merge — the client never trusts a + // server-side merge. + let branch_proofs = decode_branch_proofs(proof, self.prefix_branches.len())?; + let mut root_hash: Option = None; + let mut per_branch = Vec::with_capacity(branch_proofs.len()); + for (branch, branch_proof) in branch_proofs.iter().enumerate() { + let (branch_root, page) = + self.verify_ranked_top_k_proof_v0_branch(branch, branch_proof)?; + match root_hash { + None => root_hash = Some(branch_root), + Some(existing) if existing == branch_root => {} + Some(_) => { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "branch proofs attest different root hashes: every branch of \ + one response must be proved against one platform state" + .to_string(), + ))); + } + } + per_branch.push(page.entries); + } + let entries = merge_branch_pages( + per_branch, + &self.prefix_branches, + self.descending, + self.k as usize, + )?; + let root_hash = root_hash.ok_or_else(|| { + Error::Drive(DriveError::CorruptedDriveState( + "branch container verified to zero branches".to_string(), + )) + })?; + return Ok(( + root_hash, + RankedPage { + skipped: 0, + entries, + }, + )); + } + self.verify_ranked_top_k_proof_v0_branch(0, proof) + } + + /// One branch's verification — the entire pre-`IN` verifier, + /// parameterized by the prefix branch. + fn verify_ranked_top_k_proof_v0_branch( + &self, + branch: usize, + proof: &[u8], + ) -> Result<(RootHash, RankedPage), Error> { + let path = self.indexed_property_name_tree_path(branch)?; let path_refs: Vec<&[u8]> = path.iter().map(|segment| segment.as_slice()).collect(); let result = GroveDb::verify_indexed_axis_top_k_paginated( @@ -70,6 +130,7 @@ impl DriveDocumentRankedQuery<'_> { .into_iter() .map(|entry| entry.key_pair()) .map(|(count, key)| RankedEntry { + in_key: None, key, value: RankedEntryValue::Count(count), }) @@ -78,6 +139,7 @@ impl DriveDocumentRankedQuery<'_> { .into_iter() .map(|entry| entry.key_pair()) .map(|(sum, key)| RankedEntry { + in_key: None, key, value: RankedEntryValue::Sum(sum), }) @@ -86,6 +148,7 @@ impl DriveDocumentRankedQuery<'_> { .into_iter() .map(|entry| entry.key_pair()) .map(|(avg, key)| RankedEntry { + in_key: None, key, value: RankedEntryValue::AvgFixedPoint(avg), }) diff --git a/packages/rs-sdk/src/mock/requests.rs b/packages/rs-sdk/src/mock/requests.rs index 9015c19a583..6edeea0546c 100644 --- a/packages/rs-sdk/src/mock/requests.rs +++ b/packages/rs-sdk/src/mock/requests.rs @@ -732,7 +732,7 @@ impl MockResponse for drive_proof_verifier::DocumentSplitAverages { /// is already an `i128`. One numeric column keeps the tuple flat while /// the tag preserves which axis produced it, so a mock expectation /// can't quietly turn a count into a sum. -type DocumentRankedPage = (u64, Vec<(Vec, u8, i128)>); +type DocumentRankedPage = (u64, Vec<(Vec, u8, i128, Option>)>); const RANKED_TAG_COUNT: u8 = 0; const RANKED_TAG_SUM: u8 = 1; @@ -741,18 +741,21 @@ const RANKED_TAG_AVG: u8 = 2; impl MockResponse for drive_proof_verifier::DocumentRankedEntries { fn mock_serialize(&self, _sdk: &MockDashPlatformSdk) -> Vec { let bincode_config = standard(); - let triples: Vec<(Vec, u8, i128)> = self + let triples: Vec<(Vec, u8, i128, Option>)> = self .entries .iter() .map(|e| match e.value { - drive_proof_verifier::RankedEntryValue::Count(count) => { - (e.key.clone(), RANKED_TAG_COUNT, count as i128) - } + drive_proof_verifier::RankedEntryValue::Count(count) => ( + e.key.clone(), + RANKED_TAG_COUNT, + count as i128, + e.in_key.clone(), + ), drive_proof_verifier::RankedEntryValue::Sum(sum) => { - (e.key.clone(), RANKED_TAG_SUM, sum as i128) + (e.key.clone(), RANKED_TAG_SUM, sum as i128, e.in_key.clone()) } drive_proof_verifier::RankedEntryValue::AvgFixedPoint(avg) => { - (e.key.clone(), RANKED_TAG_AVG, avg) + (e.key.clone(), RANKED_TAG_AVG, avg, e.in_key.clone()) } }) .collect(); @@ -769,7 +772,7 @@ impl MockResponse for drive_proof_verifier::DocumentRankedEntries { bincode::decode_from_slice(buf, bincode_config).expect("decode DocumentRankedEntries"); let entries: Vec = triples .into_iter() - .map(|(key, tag, value)| { + .map(|(key, tag, value, in_key)| { let value = match tag { RANKED_TAG_COUNT => drive_proof_verifier::RankedEntryValue::Count( u64::try_from(value).expect("a Count entry round-trips through i128"), @@ -780,7 +783,7 @@ impl MockResponse for drive_proof_verifier::DocumentRankedEntries { RANKED_TAG_AVG => drive_proof_verifier::RankedEntryValue::AvgFixedPoint(value), other => panic!("unknown ranked axis tag {other} in mock expectation"), }; - drive_proof_verifier::RankedEntry { key, value } + drive_proof_verifier::RankedEntry { in_key, key, value } }) .collect(); drive_proof_verifier::DocumentRankedEntries { From 7628738d9d05979e16599e9e8e41e7e7fbc4eda1 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 13 Aug 2026 22:24:57 +0700 Subject: [PATCH 02/25] fix(drive): satisfy workspace clippy under all-features Co-Authored-By: Claude Fable 5 --- .../rs-drive/src/query/drive_document_having_query/mod.rs | 2 -- .../src/query/drive_document_ranked_query/index_picker.rs | 1 - .../rs-drive/src/query/drive_document_ranked_query/tests.rs | 4 ++-- packages/rs-sdk/src/mock/requests.rs | 6 ++++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/rs-drive/src/query/drive_document_having_query/mod.rs b/packages/rs-drive/src/query/drive_document_having_query/mod.rs index 15ebf3e5f40..d1c56e56532 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/mod.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/mod.rs @@ -65,8 +65,6 @@ #[cfg(any(feature = "server", feature = "verify"))] use dpp::data_contract::document_type::{DocumentTypeRef, Index}; #[cfg(any(feature = "server", feature = "verify"))] -use dpp::platform_value::Value; -#[cfg(any(feature = "server", feature = "verify"))] use dpp::version::PlatformVersion; #[cfg(any(feature = "server", feature = "verify"))] use std::collections::BTreeMap; diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs b/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs index 8e315b68916..63cc322e45b 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs @@ -12,7 +12,6 @@ use crate::error::query::QuerySyntaxError; use crate::error::Error; use dpp::data_contract::document_type::methods::DocumentTypeV0Methods; use dpp::data_contract::document_type::{DocumentTypeRef, Index}; -use dpp::platform_value::Value; use dpp::version::PlatformVersion; use std::collections::BTreeMap; diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs index e5986b5a33c..fcbc64dc19e 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs @@ -2781,7 +2781,7 @@ mod pinned_prefix { fn offset_is_rejected_with_an_in_pin() { let error = detect_ranked_mode_v0( &SelectProjection::avg("grade"), - &vec![CLASS_PROPERTY.to_string()], + &[CLASS_PROPERTY.to_string()], &[], &[OrderClause { field: "grade".to_string(), @@ -2814,7 +2814,7 @@ mod pinned_prefix { let detect = |where_clauses: &[WhereClause]| { detect_ranked_mode_v0( &SelectProjection::avg("grade"), - &vec![CLASS_PROPERTY.to_string()], + &[CLASS_PROPERTY.to_string()], &[], &[OrderClause { field: "grade".to_string(), diff --git a/packages/rs-sdk/src/mock/requests.rs b/packages/rs-sdk/src/mock/requests.rs index 6edeea0546c..8cd7c1c445d 100644 --- a/packages/rs-sdk/src/mock/requests.rs +++ b/packages/rs-sdk/src/mock/requests.rs @@ -732,7 +732,9 @@ impl MockResponse for drive_proof_verifier::DocumentSplitAverages { /// is already an `i128`. One numeric column keeps the tuple flat while /// the tag preserves which axis produced it, so a mock expectation /// can't quietly turn a count into a sum. -type DocumentRankedPage = (u64, Vec<(Vec, u8, i128, Option>)>); +/// One mock-serialized ranked entry: `(key, axis tag, value, in_key)`. +type MockRankedEntry = (Vec, u8, i128, Option>); +type DocumentRankedPage = (u64, Vec); const RANKED_TAG_COUNT: u8 = 0; const RANKED_TAG_SUM: u8 = 1; @@ -741,7 +743,7 @@ const RANKED_TAG_AVG: u8 = 2; impl MockResponse for drive_proof_verifier::DocumentRankedEntries { fn mock_serialize(&self, _sdk: &MockDashPlatformSdk) -> Vec { let bincode_config = standard(); - let triples: Vec<(Vec, u8, i128, Option>)> = self + let triples: Vec = self .entries .iter() .map(|e| match e.value { From ee1bb0e5933a94b0062ea4417fc33eadb1e1304d Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 13 Aug 2026 22:45:34 +0700 Subject: [PATCH 03/25] fix(drive): singleton IN normalization is clause-order independent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The one-IN budget was charged before checking whether the current clause was a singleton, so `a IN [1,2] AND b IN [3]` rejected while the reversed order passed. Only multi-element INs now count against the budget, in either order — a singleton is an equality pin, as documented. Both orders pinned in in_pin_shape_rejections. Co-Authored-By: Claude Fable 5 --- .../mode_detection/v0/mod.rs | 24 ++++--- .../drive_document_ranked_query/tests.rs | 65 +++++++++++++------ 2 files changed, 58 insertions(+), 31 deletions(-) diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/v0/mod.rs b/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/v0/mod.rs index 48248bf9efe..6e46a79d0b0 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/v0/mod.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/v0/mod.rs @@ -61,16 +61,6 @@ pub fn prefix_pins_from_where_clauses( let values = match clause.operator { WhereOperator::Equal => vec![clause.value.clone()], WhereOperator::In => { - if let Some(first) = branching_field { - return Err(Error::Query(QuerySyntaxError::Unsupported(format!( - "a ranked / having-range query takes at most one `IN` across its \ - prefix properties (`{first}` already carries it): several `IN`s \ - multiply into a cartesian product of prefix branches, each a \ - separate secondary walk inside one proof — a fan-out the branch \ - ceiling exists to prevent. Pin `{}` with `==`.", - clause.field - )))); - } let Value::Array(elements) = &clause.value else { return Err(Error::Query( QuerySyntaxError::InvalidWhereClauseComponents( @@ -95,7 +85,21 @@ pub fn prefix_pins_from_where_clauses( MAX_PREFIX_IN_BRANCHES )))); } + // Only a multi-element `IN` branches; a singleton is an + // equality pin and never counts against the one-`IN` + // budget — in either clause order. if elements.len() > 1 { + if let Some(first) = branching_field { + return Err(Error::Query(QuerySyntaxError::Unsupported(format!( + "a ranked / having-range query takes at most one branching \ + `IN` across its prefix properties (`{first}` already carries \ + it): several `IN`s multiply into a cartesian product of \ + prefix branches, each a separate secondary walk inside one \ + proof — a fan-out the branch ceiling exists to prevent. Pin \ + `{}` with `==` or a single-element `IN`.", + clause.field + )))); + } branching_field = Some(clause.field.as_str()); } elements.clone() diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs index fcbc64dc19e..cb0225fde03 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs @@ -2850,27 +2850,50 @@ mod pinned_prefix { }]; assert!(detect(&empty).is_err(), "an empty IN list must be rejected"); - // A second IN (on another property) once one already branches. - let two_ins = vec![ - WhereClause { - field: PREFIX_PROPERTY.to_string(), - operator: WhereOperator::In, - value: Value::Array(vec![ - Value::Identifier(IDENTITY_X), - Value::Identifier(IDENTITY_Y), - ]), - }, - WhereClause { - field: "other".to_string(), - operator: WhereOperator::In, - value: Value::Array(vec![Value::U64(1), Value::U64(2)]), - }, - ]; - let error = detect(&two_ins).expect_err("two branching INs must be rejected"); - assert!( - matches!(error, Error::Query(QuerySyntaxError::Unsupported(_))), - "expected Unsupported for a second IN, got {error:?}" - ); + // Two *branching* INs are rejected in either clause order. + let multi = |field: &str| WhereClause { + field: field.to_string(), + operator: WhereOperator::In, + value: Value::Array(vec![Value::U64(1), Value::U64(2)]), + }; + for two_ins in [ + vec![multi(PREFIX_PROPERTY), multi("other")], + vec![multi("other"), multi(PREFIX_PROPERTY)], + ] { + let error = detect(&two_ins).expect_err("two branching INs must be rejected"); + assert!( + matches!(error, Error::Query(QuerySyntaxError::Unsupported(_))), + "expected Unsupported for a second branching IN, got {error:?}" + ); + } + + // A singleton IN is an equality pin and never counts against + // the one-`IN` budget — in either clause order relative to the + // branching one. + let singleton = WhereClause { + field: "other".to_string(), + operator: WhereOperator::In, + value: Value::Array(vec![Value::U64(1)]), + }; + for mixed in [ + vec![multi(PREFIX_PROPERTY), singleton.clone()], + vec![singleton.clone(), multi(PREFIX_PROPERTY)], + ] { + let mode = detect(&mixed) + .expect("a singleton IN alongside a branching IN is well-formed either way"); + assert_eq!( + mode.prefix_pins + .iter() + .map(|pin| pin.values.len()) + .collect::>(), + if mixed[0].field == "other" { + vec![1, 2] + } else { + vec![2, 1] + }, + "one branching pin and one singleton pin, in clause order" + ); + } // Non-array operand. let scalar = vec![WhereClause { From ad3d724e75b0ecd0da18ceaa487fbd084f8d0dfd Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 14 Aug 2026 00:26:14 +0700 Subject: [PATCH 04/25] feat(drive)!: single branched grovedb envelope replaces the proof container The IN-pinned prove and verify paths now use grovedb's branched indexed-axis proofs (dashpay/grovedb#793): shared ancestor layers appear once, the branching level is one multi-key Merk proof binding every branch's value tree, each branch carries only its tail, and one root hash is reconstructed for the whole envelope. The length-prefixed container of per-branch proofs is deleted, along with the cross-branch root-hash equality assertion it required; the platform keeps the merge comparator, in_key tagging, and grove-path decomposition. grovedb pin bumped to the PR branch. The deep tamper matrix (reordered keys, duplicated or dropped tails, echo mismatches) moved to grovedb's own suite where the envelope now lives; the platform test pins corrupted and truncated bytes plus the two envelope shapes never cross-verifying. Review fixes folded in: encode_prefix_branches validates pin shape itself (non-empty values, at most one branching pin), branch indexing fails closed instead of panicking, and the no-covering-index and having-grammar docs describe the IN-inclusive pin rule. Co-Authored-By: Claude Fable 5 --- .../execute_range.rs | 33 ++-- .../query/drive_document_having_query/mod.rs | 11 +- .../mode_detection/v0/mod.rs | 6 +- .../drive_document_ranked_query/branches.rs | 172 ++++++++++-------- .../execute_top_k.rs | 41 +++-- .../index_picker.rs | 32 +++- .../query/drive_document_ranked_query/path.rs | 13 +- .../drive_document_ranked_query/tests.rs | 91 ++++----- .../verify_having_range_proof/v0/mod.rs | 68 ++++--- .../verify_ranked_top_k_proof/v0/mod.rs | 75 ++++---- 10 files changed, 309 insertions(+), 233 deletions(-) diff --git a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs index f93f0a9f901..ca11527dbf4 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs @@ -12,7 +12,7 @@ //! `pub mod execute_range;` declaration. use super::super::drive_document_ranked_query::branches::{ - encode_branch_proofs, merge_branch_pages, + decompose_branch_paths, merge_branch_pages, }; use super::super::drive_document_ranked_query::{RankedAxis, RankedEntry, RankedEntryValue}; use super::{AxisRangeBounds, DriveDocumentHavingQuery}; @@ -177,19 +177,26 @@ impl DriveDocumentHavingQuery<'_> { platform_version: &PlatformVersion, ) -> Result, Error> { if self.prefix_branches.len() > 1 { - // One indexed-axis range proof per branch, framed into the - // versioned container in canonical branch order. - let proofs = (0..self.prefix_branches.len()) - .map(|branch| { - self.execute_range_with_proof_branch( - branch, - drive, - transaction, - platform_version, - ) - }) + // One grovedb **branched** envelope — see the ranked + // executor's multi-branch arm for the shape. + let grove_version = &platform_version.drive.grove_version; + let paths = (0..self.prefix_branches.len()) + .map(|branch| self.indexed_property_name_tree_path(branch)) .collect::, Error>>()?; - return Ok(encode_branch_proofs(&proofs)); + let (prefix, keys, suffix) = decompose_branch_paths(&paths)?; + let prefix_refs: Vec<&[u8]> = prefix.iter().map(|s| s.as_slice()).collect(); + let suffix_refs: Vec<&[u8]> = suffix.iter().map(|s| s.as_slice()).collect(); + let CostContext { value, cost: _ } = drive.grove.prove_indexed_axis_query_branched( + &prefix_refs, + &keys, + &suffix_refs, + self.bounds.axis().into(), + self.bounds.merk_query(self.descending), + Some(self.limit), + transaction, + grove_version, + ); + return value.map_err(|e| Error::GroveDB(Box::new(e))); } self.execute_range_with_proof_branch(0, drive, transaction, platform_version) } diff --git a/packages/rs-drive/src/query/drive_document_having_query/mod.rs b/packages/rs-drive/src/query/drive_document_having_query/mod.rs index d1c56e56532..964d264d3a4 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/mod.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/mod.rs @@ -78,6 +78,8 @@ use super::drive_document_ranked_query::{ path::indexed_property_name_tree_path_for_index, PrefixPin, RankedAxis, }; #[cfg(any(feature = "server", feature = "verify"))] +use crate::error::drive::DriveError; +#[cfg(any(feature = "server", feature = "verify"))] use crate::error::query::QuerySyntaxError; #[cfg(any(feature = "server", feature = "verify"))] use crate::error::Error; @@ -303,11 +305,18 @@ impl DriveDocumentHavingQuery<'_> { /// tree(s). See /// [`DriveDocumentRankedQuery::indexed_property_name_tree_path`](super::drive_document_ranked_query::DriveDocumentRankedQuery::indexed_property_name_tree_path). pub fn indexed_property_name_tree_path(&self, branch: usize) -> Result>, Error> { + let prefix_values = + self.prefix_branches + .get(branch) + .ok_or(Error::Drive(DriveError::NotSupported( + "ranked and having-range queries addressed a prefix branch outside the \ + query's resolved branch set", + )))?; indexed_property_name_tree_path_for_index( &self.contract_id, &self.document_type_name, self.index, - &self.prefix_branches[branch], + prefix_values, ) } } diff --git a/packages/rs-drive/src/query/drive_document_having_query/mode_detection/v0/mod.rs b/packages/rs-drive/src/query/drive_document_having_query/mode_detection/v0/mod.rs index 0d20e3cb2e4..e690a4ab5f9 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/mode_detection/v0/mod.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/mode_detection/v0/mod.rs @@ -31,9 +31,9 @@ use grovedb::element::indexed::AVG_FIXED_POINT_SCALE; /// with no `OFFSET`, no `START AT` / `START AFTER`, exactly one /// `GROUP BY` property, `WHERE` clauses (when present) that pin the /// covering compound ranked index's leading properties — one clause per -/// property, each an equality, at most one of them an `IN` whose -/// elements fan the bound out across one prefix branch per element -/// (merged deterministically; see the ranked surface's +/// property, each an equality except that **at most one** clause may be +/// an `IN` whose elements fan the bound out across one prefix branch +/// per element (merged deterministically; see the ranked surface's /// `prefix_pins_from_where_clauses`) — exactly one `HAVING` clause whose aggregate /// **is the selected aggregate** (same function, same field), an operator /// from the contiguous-range family (`=`, `>`, `>=`, `<`, `<=`, and the diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs b/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs index 253f866db65..4a1b1ed4a0d 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs @@ -1,20 +1,23 @@ //! Branch mechanics for `IN`-pinned ranked / having-range queries — //! shared by the executors (walk one secondary per branch, merge) and -//! the verifiers (verify one proof per branch, re-merge), so both sides -//! implement one comparator and one proof-container layout. +//! the verifiers (verify one branched envelope, re-merge), so both +//! sides implement one comparator and one grove-path decomposition. //! //! ## Why a merge needs no proof of its own //! -//! Each branch's walk is independently proved complete against the same -//! root hash, and the merged page is a *deterministic function* of the -//! branch pages: any union entry that precedes a returned entry in the -//! merge order is preceded, within its own branch, by fewer than `limit` +//! Each branch's walk is proved complete inside one grovedb **branched +//! envelope** (shared ancestor layers once, one multi-key proof at the +//! branching level, one secondary proof per branch, one root hash), +//! and the merged page is a *deterministic function* of the branch +//! pages: any union entry that precedes a returned entry in the merge +//! order is preceded, within its own branch, by fewer than `limit` //! entries — so it is in its branch's returned page. The union's first //! `limit` entries are therefore contained in the union of the branch -//! pages, and re-merging verified branch pages reconstructs a complete, -//! correctly ordered page. (This is the same argument for both surfaces: -//! "first `limit` in merge order restricted to the branch" is top-k for -//! ranked and the in-bound range prefix for having.) +//! pages, and re-merging verified branch pages reconstructs a +//! complete, correctly ordered page. (This is the same argument for +//! both surfaces: "first `limit` in merge order restricted to the +//! branch" is top-k for ranked and the in-bound range prefix for +//! having.) //! //! ## The merge order //! @@ -25,16 +28,12 @@ //! element order — which also makes `null` (the empty segment) sort //! first deterministically. -use super::{RankedEntry, RankedEntryValue}; +use super::{RankedAxis, RankedEntry, RankedEntryValue}; use crate::error::drive::DriveError; use crate::error::Error; +use grovedb::operations::proof::indexed_axis::AxisEntries; use std::cmp::Ordering; -/// Version byte of the branch-proof container. Bumped only with a -/// method-version bump on the prove/verify pair — the container is part -/// of the prover/verifier agreement, not a transport detail. -const BRANCH_PROOF_CONTAINER_VERSION: u8 = 1; - /// The position (index into a branch's segment list) at which the /// branches differ — the `IN` pin's position in the covering index's /// leading properties. `None` when there is a single branch (no `IN`), @@ -127,68 +126,89 @@ pub fn merge_branch_pages( Ok(merged) } -/// Frame per-branch grovedb proofs into the single opaque byte string -/// the wire's `Proof` carries: version byte, `u16` branch count, then -/// each proof length-prefixed with a `u32` (all big-endian). Used only -/// when there are two or more branches — a single-branch proof stays -/// the raw grovedb envelope, byte-identical to the pre-`IN` surface. -pub fn encode_branch_proofs(proofs: &[Vec]) -> Vec { - let mut out = Vec::with_capacity(3 + proofs.iter().map(|p| 4 + p.len()).sum::()); - out.push(BRANCH_PROOF_CONTAINER_VERSION); - out.extend_from_slice(&(proofs.len() as u16).to_be_bytes()); - for proof in proofs { - out.extend_from_slice(&(proof.len() as u32).to_be_bytes()); - out.extend_from_slice(proof); - } - out -} - -/// Parse the branch-proof container, requiring exactly -/// `expected_branches` proofs — the verifier derives that count from -/// its own resolution of the request, so a server cannot drop or -/// duplicate a branch without the container failing to parse. Trailing -/// bytes are rejected: an envelope is exactly its declared content. -pub fn decode_branch_proofs(bytes: &[u8], expected_branches: usize) -> Result>, Error> { - let malformed = |what: &str| { - Error::Drive(DriveError::CorruptedDriveState(format!( - "branch-proof container: {what}" - ))) - }; - let (&version, mut rest) = bytes.split_first().ok_or_else(|| malformed("empty"))?; - if version != BRANCH_PROOF_CONTAINER_VERSION { - return Err(malformed(&format!( - "unknown container version {version}; expected {BRANCH_PROOF_CONTAINER_VERSION}" - ))); - } - if rest.len() < 2 { - return Err(malformed("truncated branch count")); - } - let (count_bytes, tail) = rest.split_at(2); - let count = u16::from_be_bytes([count_bytes[0], count_bytes[1]]) as usize; - rest = tail; - if count != expected_branches { - return Err(malformed(&format!( - "container carries {count} branch proofs; the request resolves to \ - {expected_branches} branches" - ))); - } - let mut proofs = Vec::with_capacity(count); - for _ in 0..count { - if rest.len() < 4 { - return Err(malformed("truncated proof length")); +/// Decompose per-branch grove paths into the `(shared prefix, branch +/// keys, shared suffix)` triple grovedb's branched proof primitives +/// take. The paths differ at exactly one segment position by +/// construction (one `IN` pin); anything else is an internal +/// resolution error. +pub fn decompose_branch_paths( + paths: &[Vec>], +) -> Result<(Vec>, Vec>, Vec>), Error> { + let first = paths.first().ok_or_else(|| { + Error::Drive(DriveError::CorruptedDriveState( + "branch decomposition over zero paths".to_string(), + )) + })?; + let mut varying: Option = None; + for other in &paths[1..] { + if other.len() != first.len() { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "branch paths of different lengths".to_string(), + ))); } - let (len_bytes, tail) = rest.split_at(4); - let len = - u32::from_be_bytes([len_bytes[0], len_bytes[1], len_bytes[2], len_bytes[3]]) as usize; - if tail.len() < len { - return Err(malformed("truncated proof body")); + for (position, (a, b)) in first.iter().zip(other.iter()).enumerate() { + if a != b { + match varying { + None => varying = Some(position), + Some(existing) if existing == position => {} + Some(_) => { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "branch paths differ at more than one segment".to_string(), + ))); + } + } + } } - let (proof, tail) = tail.split_at(len); - proofs.push(proof.to_vec()); - rest = tail; } - if !rest.is_empty() { - return Err(malformed("trailing bytes after the declared proofs")); + let position = varying.ok_or_else(|| { + Error::Drive(DriveError::CorruptedDriveState( + "branch paths are identical; the encoder rejects duplicate branches".to_string(), + )) + })?; + let prefix = first[..position].to_vec(); + let keys = paths + .iter() + .map(|path| path[position].clone()) + .collect::>(); + let suffix = first[position + 1..].to_vec(); + Ok((prefix, keys, suffix)) +} + +/// Translate one branch's verified [`AxisEntries`] into drive entries +/// on the requested axis — the same mapping the single-path verifiers +/// perform, shared here so both surfaces' branched verifiers agree. +pub fn axis_entries_to_ranked( + axis: RankedAxis, + entries: AxisEntries, +) -> Result, Error> { + match (axis, entries) { + (RankedAxis::Count, AxisEntries::Count(entries)) => Ok(entries + .into_iter() + .map(|(count, key)| RankedEntry { + in_key: None, + key, + value: RankedEntryValue::Count(count), + }) + .collect()), + (RankedAxis::Sum, AxisEntries::Sum(entries)) => Ok(entries + .into_iter() + .map(|(sum, key)| RankedEntry { + in_key: None, + key, + value: RankedEntryValue::Sum(sum), + }) + .collect()), + (RankedAxis::Avg, AxisEntries::Avg(entries)) => Ok(entries + .into_iter() + .map(|(avg, key)| RankedEntry { + in_key: None, + key, + value: RankedEntryValue::AvgFixedPoint(avg), + }) + .collect()), + (axis, other) => Err(Error::Drive(DriveError::CorruptedDriveState(format!( + "a branch of a {axis:?} proof verified to {} entries of a different axis shape", + other.len() + )))), } - Ok(proofs) } diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs index e445ff6c64b..5881fcfc3ce 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs @@ -10,7 +10,7 @@ //! Whole module is gated `feature = "server"` via the parent's //! `pub mod execute_top_k;` declaration. -use super::branches::{encode_branch_proofs, merge_branch_pages}; +use super::branches::{decompose_branch_paths, merge_branch_pages}; use super::{DriveDocumentRankedQuery, RankedAxis, RankedEntry, RankedEntryValue, RankedPage}; use crate::drive::Drive; use crate::error::drive::DriveError; @@ -252,21 +252,32 @@ impl DriveDocumentRankedQuery<'_> { platform_version: &PlatformVersion, ) -> Result, Error> { if self.prefix_branches.len() > 1 { - // One indexed-axis proof per branch, framed into the - // versioned container in canonical branch order. The - // verifier re-derives the branch set from the request, so a - // dropped, duplicated, or reordered branch fails there. - let proofs = (0..self.prefix_branches.len()) - .map(|branch| { - self.execute_top_k_with_proof_branch( - branch, - drive, - transaction, - platform_version, - ) - }) + // One grovedb **branched** envelope: shared ancestor layers + // once, one multi-key proof at the branching level, one + // secondary proof per branch — a single proof with a single + // root hash. The verifier re-derives the branch set from + // the request, so a dropped, duplicated, or reordered + // branch fails there. + let grove_version = &platform_version.drive.grove_version; + let paths = (0..self.prefix_branches.len()) + .map(|branch| self.indexed_property_name_tree_path(branch)) .collect::, Error>>()?; - return Ok(encode_branch_proofs(&proofs)); + let (prefix, keys, suffix) = decompose_branch_paths(&paths)?; + let prefix_refs: Vec<&[u8]> = prefix.iter().map(|s| s.as_slice()).collect(); + let suffix_refs: Vec<&[u8]> = suffix.iter().map(|s| s.as_slice()).collect(); + let CostContext { value, cost: _ } = + drive.grove.prove_indexed_axis_top_k_paginated_branched( + &prefix_refs, + &keys, + &suffix_refs, + self.axis.into(), + self.k, + self.offset as u64, + self.descending, + transaction, + grove_version, + ); + return value.map_err(|e| Error::GroveDB(Box::new(e))); } self.execute_top_k_with_proof_branch(0, drive, transaction, platform_version) } diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs b/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs index 63cc322e45b..eb0ddcc0ade 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs @@ -196,7 +196,7 @@ pub fn no_covering_index_message( } else { format!( "a compound index on [{}, {group_by_property}] (every leading property pinned \ - by an equality `where` clause, the trailing property grouped over)", + by an equality or `IN` `where` clause, the trailing property grouped over)", pin_fields() ) }; @@ -206,7 +206,7 @@ pub fn no_covering_index_message( if prefix_pins.is_empty() { String::new() } else { - format!(" with equality pins on [{}]", pin_fields()) + format!(" with pins on [{}]", pin_fields()) }, axis.required_index_keyword(), if aggregate_field.is_empty() { @@ -301,6 +301,34 @@ pub fn encode_prefix_branches( }) .collect::, Error>>()?; + // Defense in depth at the shared choke point: the grammar enforces + // both invariants upstream, but this function is `pub` and the + // prover/verifier agreement hangs off it, so a mis-built pin set + // must fail here rather than collapse to zero branches (a + // downstream panic) or fan out into an unbounded cartesian product + // (which would also break the one-varying-position assumption + // `in_key` and the merge order rely on). + if per_property.iter().any(|candidates| candidates.is_empty()) { + return Err(Error::Query( + QuerySyntaxError::InvalidWhereClauseComponents( + "internal resolution mismatch: a prefix pin carries no values", + ), + )); + } + if per_property + .iter() + .filter(|candidates| candidates.len() > 1) + .count() + > 1 + { + return Err(Error::Query( + QuerySyntaxError::InvalidWhereClauseComponents( + "internal resolution mismatch: more than one branching pin — the grammar \ + admits at most one `IN` across the prefix properties", + ), + )); + } + // The grammar admits at most one multi-value pin, so this product // is |IN| branches (or exactly one), already in canonical order // because the only varying position was sorted above. diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/path.rs b/packages/rs-drive/src/query/drive_document_ranked_query/path.rs index ec737825776..ddb5380d79c 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/path.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/path.rs @@ -24,7 +24,7 @@ use dpp::data_contract::document_type::Index; /// tree. See [`DriveDocumentRankedQuery::indexed_property_name_tree_path`] /// for the segment layout. /// -/// `equality_prefix_values` carries the **encoded index-key bytes** of +/// The branch's prefix segments carry the **encoded index-key bytes** of /// each leading property's pinned value, in index-property order — one /// per property before the terminal one. Empty for a single-property /// index. The arity must match exactly: a compound index's terminal @@ -84,7 +84,7 @@ impl DriveDocumentRankedQuery<'_> { /// For a compound index `[p1, …, pn]`, each leading property /// contributes two segments — its name and the **encoded index-key /// bytes of its pinned value** (from - /// [`Self::equality_prefix_values`]) — and the terminal property + /// [`Self::prefix_branches`]) — and the terminal property /// name closes the path: /// /// ```text @@ -105,11 +105,18 @@ impl DriveDocumentRankedQuery<'_> { /// `branch` indexes into [`Self::prefix_branches`]; single-branch /// queries (no `IN` pin) always pass `0`. pub fn indexed_property_name_tree_path(&self, branch: usize) -> Result>, Error> { + let prefix_values = + self.prefix_branches + .get(branch) + .ok_or(Error::Drive(DriveError::NotSupported( + "ranked and having-range queries addressed a prefix branch outside the \ + query's resolved branch set", + )))?; indexed_property_name_tree_path_for_index( &self.contract_id, &self.document_type_name, self.index, - &self.prefix_branches[branch], + prefix_values, ) } } diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs index cb0225fde03..a086839bdf7 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs @@ -2644,12 +2644,15 @@ mod pinned_prefix { ); } - /// The tamper matrix on the branch container: a response whose - /// branch proofs are reordered, dropped, duplicated, re-versioned, - /// or padded must fail verification — never silently reorder or - /// shrink the merged page. + /// Platform-level tamper cases on the branched envelope. The deep + /// matrix — reordered branch keys, duplicated or dropped branch + /// tails, echo mismatches — is pinned in grovedb's + /// `indexed_axis_branched_proof_tests`, since the envelope is one + /// grovedb proof now; here we pin what the platform layer itself + /// must not confuse: corrupted bytes, and the single-branch / + /// multi-branch envelope shapes never cross-verifying. #[test] - fn tampered_branch_containers_do_not_verify() { + fn tampered_or_mismatched_branched_proofs_do_not_verify() { let (drive, contract) = setup_grades_compound_ranked(); insert_grades( &drive, @@ -2666,76 +2669,48 @@ mod pinned_prefix { // Baseline sanity: untampered verifies. query .verify_ranked_top_k_proof(&proof, platform_version()) - .expect("untampered container verifies"); - - // Decompose the container by hand: [version][u16 count]([u32 len][bytes])* - assert_eq!(proof[0], 1, "container version byte"); - let count = u16::from_be_bytes([proof[1], proof[2]]) as usize; - assert_eq!(count, 2); - let mut branch_proofs = Vec::new(); - let mut rest = &proof[3..]; - for _ in 0..count { - let len = u32::from_be_bytes([rest[0], rest[1], rest[2], rest[3]]) as usize; - branch_proofs.push(rest[4..4 + len].to_vec()); - rest = &rest[4 + len..]; - } - let reframe = |proofs: &[Vec]| { - let mut out = vec![1u8]; - out.extend_from_slice(&(proofs.len() as u16).to_be_bytes()); - for p in proofs { - out.extend_from_slice(&(p.len() as u32).to_be_bytes()); - out.extend_from_slice(p); - } - out - }; - - // Reordered: branch 0's proof is verified against branch 1's - // path and fails inside grovedb. - let reordered = reframe(&[branch_proofs[1].clone(), branch_proofs[0].clone()]); - assert!( - query - .verify_ranked_top_k_proof(&reordered, platform_version()) - .is_err(), - "reordered branch proofs must not verify" - ); + .expect("untampered branched envelope verifies"); - // Dropped: count mismatch against the query's own resolution. - let dropped = reframe(&[branch_proofs[0].clone()]); + // Corrupted bytes. + let mut corrupted = proof.clone(); + let mid = corrupted.len() / 2; + corrupted[mid] ^= 0xFF; assert!( query - .verify_ranked_top_k_proof(&dropped, platform_version()) + .verify_ranked_top_k_proof(&corrupted, platform_version()) .is_err(), - "a dropped branch must not verify" + "a flipped byte must not verify" ); - // Duplicated: same count, but branch 1's slot holds branch 0's - // proof — wrong path again. - let duplicated = reframe(&[branch_proofs[0].clone(), branch_proofs[0].clone()]); + // Truncated bytes. + let truncated = &proof[..proof.len() - 8]; assert!( query - .verify_ranked_top_k_proof(&duplicated, platform_version()) + .verify_ranked_top_k_proof(truncated, platform_version()) .is_err(), - "a duplicated branch proof must not verify" + "a truncated envelope must not verify" ); - // Unknown container version. - let mut reversioned = proof.clone(); - reversioned[0] = 2; + // A single-pin proof must not verify under the branched query, + // nor a branched proof under a single-pin query — the two + // envelope shapes are distinct grovedb types. + let single = pin(IDENTITY_X); + let single_proof = match run(&drive, &contract, &single, 2, true).expect("prove") { + DocumentRankedResponse::Proof(proof) => proof, + DocumentRankedResponse::Entries(_) => panic!("expected a proof"), + }; assert!( query - .verify_ranked_top_k_proof(&reversioned, platform_version()) + .verify_ranked_top_k_proof(&single_proof, platform_version()) .is_err(), - "an unknown container version must not verify" + "a single-branch envelope must not verify under an IN query" ); - - // Trailing bytes after the declared proofs. - let mut padded = proof.clone(); - padded.push(0); + let single_query = client_side_query(&contract, &single, 2); assert!( - query - .verify_ranked_top_k_proof(&padded, platform_version()) + single_query + .verify_ranked_top_k_proof(&proof, platform_version()) .is_err(), - "trailing bytes must not verify" + "a branched envelope must not verify under a single-pin query" ); } diff --git a/packages/rs-drive/src/verify/document_having/verify_having_range_proof/v0/mod.rs b/packages/rs-drive/src/verify/document_having/verify_having_range_proof/v0/mod.rs index c220c77c00f..f912a3f64a9 100644 --- a/packages/rs-drive/src/verify/document_having/verify_having_range_proof/v0/mod.rs +++ b/packages/rs-drive/src/verify/document_having/verify_having_range_proof/v0/mod.rs @@ -1,7 +1,7 @@ use crate::error::drive::DriveError; use crate::error::Error; use crate::query::drive_document_ranked_query::branches::{ - decode_branch_proofs, merge_branch_pages, + axis_entries_to_ranked, decompose_branch_paths, merge_branch_pages, }; use crate::query::{DriveDocumentHavingQuery, RankedAxis, RankedEntry, RankedEntryValue}; use crate::verify::RootHash; @@ -47,41 +47,51 @@ impl DriveDocumentHavingQuery<'_> { platform_version: &PlatformVersion, ) -> Result<(RootHash, Vec), Error> { if self.prefix_branches.len() > 1 { - // `IN`-pinned request: same branch-container discipline as - // the ranked verifier — count from this query's own - // resolution, one root hash across branches, page re-derived - // by the shared merge. - let branch_proofs = decode_branch_proofs(proof, self.prefix_branches.len())?; - let mut root_hash: Option = None; - let mut per_branch = Vec::with_capacity(branch_proofs.len()); - for (branch, branch_proof) in branch_proofs.iter().enumerate() { - let (branch_root, entries) = - self.verify_having_range_proof_v0_branch(branch, branch_proof)?; - match root_hash { - None => root_hash = Some(branch_root), - Some(existing) if existing == branch_root => {} - Some(_) => { - return Err(Error::Drive(DriveError::CorruptedDriveState( - "branch proofs attest different root hashes: every branch of \ - one response must be proved against one platform state" - .to_string(), - ))); + // `IN`-pinned request: one grovedb branched envelope — same + // discipline as the ranked verifier (branch set from this + // query's own resolution, tails bound to keys by the + // branching-level proof, one root hash, page re-derived by + // the shared merge). + let paths = (0..self.prefix_branches.len()) + .map(|branch| self.indexed_property_name_tree_path(branch)) + .collect::, Error>>()?; + let (prefix, keys, suffix) = decompose_branch_paths(&paths)?; + let prefix_refs: Vec<&[u8]> = prefix.iter().map(|s| s.as_slice()).collect(); + let suffix_refs: Vec<&[u8]> = suffix.iter().map(|s| s.as_slice()).collect(); + let axis = self.bounds.axis(); + let result = grovedb::GroveDb::verify_indexed_axis_query_branched( + proof, + &prefix_refs, + &keys, + &suffix_refs, + axis.into(), + self.bounds.merk_query(self.descending), + Some(self.limit), + ) + .map_err(|e| Error::GroveDB(Box::new(e)))?; + let per_branch = result + .branches + .into_iter() + .map(|entries| { + let entries = axis_entries_to_ranked(axis, entries)?; + if entries.len() > self.limit as usize { + return Err(Error::Drive(DriveError::CorruptedDriveState(format!( + "a branch of a having range proof verified to {} entries for \ + limit = {}", + entries.len(), + self.limit + )))); } - } - per_branch.push(entries); - } + Ok(entries) + }) + .collect::, Error>>()?; let entries = merge_branch_pages( per_branch, &self.prefix_branches, self.descending, self.limit as usize, )?; - let root_hash = root_hash.ok_or_else(|| { - Error::Drive(DriveError::CorruptedDriveState( - "branch container verified to zero branches".to_string(), - )) - })?; - return Ok((root_hash, entries)); + return Ok((result.root_hash, entries)); } self.verify_having_range_proof_v0_branch(0, proof) } diff --git a/packages/rs-drive/src/verify/document_ranked/verify_ranked_top_k_proof/v0/mod.rs b/packages/rs-drive/src/verify/document_ranked/verify_ranked_top_k_proof/v0/mod.rs index cf03cdb18e4..ff6c09a31fb 100644 --- a/packages/rs-drive/src/verify/document_ranked/verify_ranked_top_k_proof/v0/mod.rs +++ b/packages/rs-drive/src/verify/document_ranked/verify_ranked_top_k_proof/v0/mod.rs @@ -1,7 +1,7 @@ use crate::error::drive::DriveError; use crate::error::Error; use crate::query::drive_document_ranked_query::branches::{ - decode_branch_proofs, merge_branch_pages, + axis_entries_to_ranked, decompose_branch_paths, merge_branch_pages, }; use crate::query::{ DriveDocumentRankedQuery, RankedAxis, RankedEntry, RankedEntryValue, RankedPage, @@ -55,46 +55,55 @@ impl DriveDocumentRankedQuery<'_> { platform_version: &PlatformVersion, ) -> Result<(RootHash, RankedPage), Error> { if self.prefix_branches.len() > 1 { - // `IN`-pinned request: the proof bytes are the branch - // container. The branch count comes from *this* query's own - // resolution, so a container with a dropped, duplicated, or - // added branch fails to parse; a reordered or substituted - // branch proof fails its branch's own path verification; and - // all branches must attest one root hash. The page is then - // re-derived by the shared merge — the client never trusts a - // server-side merge. - let branch_proofs = decode_branch_proofs(proof, self.prefix_branches.len())?; - let mut root_hash: Option = None; - let mut per_branch = Vec::with_capacity(branch_proofs.len()); - for (branch, branch_proof) in branch_proofs.iter().enumerate() { - let (branch_root, page) = - self.verify_ranked_top_k_proof_v0_branch(branch, branch_proof)?; - match root_hash { - None => root_hash = Some(branch_root), - Some(existing) if existing == branch_root => {} - Some(_) => { - return Err(Error::Drive(DriveError::CorruptedDriveState( - "branch proofs attest different root hashes: every branch of \ - one response must be proved against one platform state" - .to_string(), - ))); + // `IN`-pinned request: the proof is one grovedb branched + // envelope. The branch set (and its order) comes from + // *this* query's own resolution; grovedb binds each branch + // tail to its branch key through the branching-level + // multi-key proof, reconstructs one root hash, and echoes + // `(axis, k, offset, direction)`. The page is then + // re-derived by the shared merge — the client never trusts + // a server-side merge. + let paths = (0..self.prefix_branches.len()) + .map(|branch| self.indexed_property_name_tree_path(branch)) + .collect::, Error>>()?; + let (prefix, keys, suffix) = decompose_branch_paths(&paths)?; + let prefix_refs: Vec<&[u8]> = prefix.iter().map(|s| s.as_slice()).collect(); + let suffix_refs: Vec<&[u8]> = suffix.iter().map(|s| s.as_slice()).collect(); + let result = grovedb::GroveDb::verify_indexed_axis_top_k_paginated_branched( + proof, + &prefix_refs, + &keys, + &suffix_refs, + self.axis.into(), + self.k, + self.offset as u64, + self.descending, + ) + .map_err(|e| Error::GroveDB(Box::new(e)))?; + let per_branch = result + .branches + .into_iter() + .map(|(_skipped, entries)| { + let entries = axis_entries_to_ranked(self.axis, entries)?; + if entries.len() > self.k as usize { + return Err(Error::Drive(DriveError::CorruptedDriveState(format!( + "a branch of a ranked top-k proof verified to {} entries for \ + k = {}", + entries.len(), + self.k + )))); } - } - per_branch.push(page.entries); - } + Ok(entries) + }) + .collect::, Error>>()?; let entries = merge_branch_pages( per_branch, &self.prefix_branches, self.descending, self.k as usize, )?; - let root_hash = root_hash.ok_or_else(|| { - Error::Drive(DriveError::CorruptedDriveState( - "branch container verified to zero branches".to_string(), - )) - })?; return Ok(( - root_hash, + result.root_hash, RankedPage { skipped: 0, entries, From 75e6b4e9abd48c47cbdb19d9fb7dcc2815032a44 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 14 Aug 2026 01:47:18 +0700 Subject: [PATCH 05/25] fix(drive)!: an absent IN element contributes an empty branch, not an error prefix IN [existing, absent] previously aborted the whole request the moment any selected prefix had no documents, losing the existing branches' valid results - the advertised union semantics were incomplete. Now an element whose prefix subtree was never created contributes the empty page on both execution paths: - Proved: grovedb's branched envelope authenticates the absence at the branching level (the exact-key multi-key proof proves both presence and absence), carries no tail for the absent branch, and rejects absence forgery in both directions - claiming a present key absent or grafting a tail onto an absent key both fail verification. - Unproved: the executors check the branch key at the branching Merk and treat a missing key as the empty branch, so proved and unproved execution stay equivalent. Presence is decided at the branching Merk itself: deeper breakage under a present key stays an error, and the single-==-pin contract is untouched (an unknown pinned value still errors rather than fabricating an empty page; its test still pins that). grovedb pin bumped to the absence-aware revision; round-trip coverage on both surfaces via never-written IN elements. Co-Authored-By: Claude Fable 5 --- book/src/drive/document-ranked-trees.md | 2 +- .../protos/platform/v0/platform.proto | 4 +- .../execute_range.rs | 31 +++++++++++++ .../drive_document_having_query/tests.rs | 33 ++++++++++++++ .../execute_top_k.rs | 43 ++++++++++++++++-- .../drive_document_ranked_query/tests.rs | 45 +++++++++++++++++++ 6 files changed, 152 insertions(+), 6 deletions(-) diff --git a/book/src/drive/document-ranked-trees.md b/book/src/drive/document-ranked-trees.md index 85cc2d34c8e..9dde105d7bc 100644 --- a/book/src/drive/document-ranked-trees.md +++ b/book/src/drive/document-ranked-trees.md @@ -341,7 +341,7 @@ Note that the fixture puts each shape on its **own document type**. That's not a | Top / bottom K groups by sum of a property | `rankedSummable: true` on an index with `summable: ""` + `rangeSummable: true` | | Top / bottom K groups by average of a property | `rankedAverageable: true` on an index with `averageable: ""` + `rangeAverageable: true` (or the count+sum longhand) | | Two rankings on one index (e.g. by count *and* by average) | Both keywords. The tree is a PCPSIT carrying both axes in its TLV; you pay one secondary Merk per axis on every write. | -| A ranking filtered by another property (`top 5 restaurants in London`) | A **compound ranked index** with the filter property leading: `[city, restaurantId]` with the ranked flags. Each city gets its own secondary; the query pins the prefix with an equality `where` (`WHERE city == "London" GROUP BY restaurantId ORDER BY DESC LIMIT 5`). Equality pins select one prefix; at most one pin may be an `IN` (2..=10 distinct elements, `null` legal for the absent-value prefix), which walks one secondary per element and merges by `(aggregate, encoded prefix, group key)` with per-branch proofs in one container — entries then carry `in_key`. A range operator on the prefix stays rejected, `OFFSET` is rejected together with `IN`, and there is still no global cross-prefix ordering beyond that merge. | +| A ranking filtered by another property (`top 5 restaurants in London`) | A **compound ranked index** with the filter property leading: `[city, restaurantId]` with the ranked flags. Each city gets its own secondary; the query pins the prefix with an equality `where` (`WHERE city == "London" GROUP BY restaurantId ORDER BY DESC LIMIT 5`). Equality pins select one prefix; at most one pin may be an `IN` (2..=10 distinct elements, `null` legal for the absent-value prefix, and a never-written element contributes an empty branch), which walks one secondary per element and merges by `(aggregate, encoded prefix, group key)` with per-branch proofs in one container — entries then carry `in_key`. A range operator on the prefix stays rejected, `OFFSET` is rejected together with `IN`, and there is still no global cross-prefix ordering beyond that merge. | | A ranking on a unique or contested index | Not available, and not meaningful: every group holds at most one document. | | Range aggregates without ranking (the 4.0 surface) | Just the `range*` flags. Ranking is strictly additive — adding it never changes what a range query returns. | | Nothing ranking-aware (default) | Don't set any `ranked*` flag. The terminal property-name tree keeps the type its range flags give it. | diff --git a/packages/dapi-grpc/protos/platform/v0/platform.proto b/packages/dapi-grpc/protos/platform/v0/platform.proto index eddacd5bc64..18a82d2ffc9 100644 --- a/packages/dapi-grpc/protos/platform/v0/platform.proto +++ b/packages/dapi-grpc/protos/platform/v0/platform.proto @@ -893,11 +893,11 @@ message GetDocumentsRequest { // - a is the In field AND b is the range field, in that order → existing compound distinct shape; entries carry both `in_key` (= a's value) and `key` (= b's value). // // `select=, group_by=[p], order_by=[]` (protocol v14+) — **ranked mode**: - // - exactly one `group_by` property, exactly one `order_by` clause naming the select's aggregate (`f` for `SUM(f)` / `AVG(f)`, the `$count` sentinel for `COUNT(*)`), a `limit` in `1 ..= 100`, an optional `offset`, and no `having` / `start_at`, on an index declaring the matching `rankedCountable` / `rankedSummable` / `rankedAverageable` axis → ranked executor, answered in `ResultData.ranked`. On a single-property ranked index no `where` is accepted; on a compound ranked index every leading index property must be pinned (one clause per property, `group_by` names the trailing property) — `EQUAL` pins one prefix, and **at most one** clause may be `IN` (2..=10 distinct elements, `null` legal), fanning the walk out across one prefix branch per element and merging by `(aggregate, encoded prefix, group key)`; merged entries carry `in_key`. `offset` is rejected together with `IN` (rank-skip is per-secondary). + // - exactly one `group_by` property, exactly one `order_by` clause naming the select's aggregate (`f` for `SUM(f)` / `AVG(f)`, the `$count` sentinel for `COUNT(*)`), a `limit` in `1 ..= 100`, an optional `offset`, and no `having` / `start_at`, on an index declaring the matching `rankedCountable` / `rankedSummable` / `rankedAverageable` axis → ranked executor, answered in `ResultData.ranked`. On a single-property ranked index no `where` is accepted; on a compound ranked index every leading index property must be pinned (one clause per property, `group_by` names the trailing property) — `EQUAL` pins one prefix, and **at most one** clause may be `IN` (2..=10 distinct elements, `null` legal; an element whose prefix was never written contributes an empty branch — union semantics), fanning the walk out across one prefix branch per element and merging by `(aggregate, encoded prefix, group key)`; merged entries carry `in_key`. `offset` is rejected together with `IN` (rank-skip is per-secondary). // - `DESC` is the "top n" reading (walk the axis from the largest aggregate down), `ASC` the "bottom n" reading. Worked example: `SELECT AVG(grade) GROUP BY restaurantId ORDER BY grade DESC LIMIT 1 OFFSET 4` is the 5th-best restaurant. // // `select=, group_by=[p], having=[ ]` (protocol v14+) — **having-range mode**: - // - exactly one `group_by` property, exactly one `having` clause whose aggregate is the select's aggregate, an operator describing one contiguous range (`EQUAL`, `GREATER_THAN[_OR_EQUALS]`, `LESS_THAN[_OR_EQUALS]`, `BETWEEN*`; `NOT_EQUAL` / `IN` rejected), a `limit` in `1 ..= 100`, an optional `order_by` naming the same aggregate (walk direction; ascending by default), and no `offset` / `start_at` / `start_after`, on an index declaring the matching ranked axis → having-range executor, answered in `ResultData.ranked`. `where` follows the same rule as ranked mode: none on a single-property ranked index; exactly one pin per leading index property on a compound ranked index, at most one of them an `IN` (2..=10 distinct elements) that fans the bound out across prefix branches and merges, entries carrying `in_key`. + // - exactly one `group_by` property, exactly one `having` clause whose aggregate is the select's aggregate, an operator describing one contiguous range (`EQUAL`, `GREATER_THAN[_OR_EQUALS]`, `LESS_THAN[_OR_EQUALS]`, `BETWEEN*`; `NOT_EQUAL` / `IN` rejected), a `limit` in `1 ..= 100`, an optional `order_by` naming the same aggregate (walk direction; ascending by default), and no `offset` / `start_at` / `start_after`, on an index declaring the matching ranked axis → having-range executor, answered in `ResultData.ranked`. `where` follows the same rule as ranked mode: none on a single-property ranked index; exactly one pin per leading index property on a compound ranked index, at most one of them an `IN` (2..=10 distinct elements; a never-written element contributes an empty branch) that fans the bound out across prefix branches and merges, entries carrying `in_key`. // - no offset or cursor pagination: a page cut at `limit` continues only by tightening the bound past the last *distinct* aggregate value seen; a cut inside a tie (several groups sharing the boundary aggregate) cannot be continued, so size `limit` above the widest expected tie. // // **Rejected shapes** (return `Unsupported`): diff --git a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs index ca11527dbf4..bfc9669f0ed 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs @@ -48,6 +48,12 @@ impl DriveDocumentHavingQuery<'_> { // prefix), merged with the shared comparator. let per_branch = (0..self.prefix_branches.len()) .map(|branch| { + // Absent element = empty branch; same union + // semantics as the ranked surface and the proved + // path's authenticated absence. + if self.branch_is_absent(branch, drive, transaction, platform_version)? { + return Ok(Vec::new()); + } self.execute_range_no_proof_branch(branch, drive, transaction, platform_version) }) .collect::, Error>>()?; @@ -61,6 +67,31 @@ impl DriveDocumentHavingQuery<'_> { self.execute_range_no_proof_branch(0, drive, transaction, platform_version) } + /// Whether this branch's key is absent at the branching Merk — see + /// the ranked sibling for the contract. + pub(crate) fn branch_is_absent( + &self, + branch: usize, + drive: &Drive, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + let grove_version = &platform_version.drive.grove_version; + let paths = (0..self.prefix_branches.len()) + .map(|b| self.indexed_property_name_tree_path(b)) + .collect::, Error>>()?; + let (prefix, keys, _suffix) = + super::super::drive_document_ranked_query::branches::decompose_branch_paths(&paths)?; + let prefix_refs: Vec<&[u8]> = prefix.iter().map(|s| s.as_slice()).collect(); + let CostContext { value, cost: _ } = drive.grove.get_raw_optional( + prefix_refs.as_slice().into(), + &keys[branch], + transaction, + grove_version, + ); + Ok(value.map_err(|e| Error::GroveDB(Box::new(e)))?.is_none()) + } + /// One branch's in-bound page — the entire pre-`IN` executor, /// parameterized by which prefix branch's terminal tree it walks. fn execute_range_no_proof_branch( diff --git a/packages/rs-drive/src/query/drive_document_having_query/tests.rs b/packages/rs-drive/src/query/drive_document_having_query/tests.rs index 3904bebe0a4..17e086c2197 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/tests.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/tests.rs @@ -2296,4 +2296,37 @@ mod pinned_prefix { .expect("root hash must be readable"), ); } + + /// The having sibling of the ranked surface's absent-element test: + /// `identityId IN [X, never-written]` bounds X's groups and treats + /// the absent branch as empty, on both the read and the proved + /// path. + #[test] + fn an_absent_in_element_contributes_an_empty_branch() { + let (drive, contract) = setup_grades_compound_ranked(); + insert_grades(&drive, &contract, 3000, &[(IDENTITY_X, "art", 90)]); + + let never_written = [9u8; 32]; + let pins = vec![WhereClause { + field: PREFIX_PROPERTY.to_string(), + operator: WhereOperator::In, + value: Value::Array(vec![ + Value::Identifier(IDENTITY_X), + Value::Identifier(never_written), + ]), + }]; + let entries = + entries_of(run(&drive, &contract, &pins, &[], false).expect("the read succeeds")); + assert_eq!( + entries, + vec![RankedEntry { + in_key: Some(IDENTITY_X.to_vec()), + key: b"art".to_vec(), + value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(90, 1)), + }], + "only the existing branch is bounded; the absent one is empty, not an error" + ); + + assert_proof_round_trips(&drive, &contract, &pins, &[], &entries); + } } diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs index 5881fcfc3ce..21b62042965 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs @@ -26,8 +26,11 @@ impl DriveDocumentRankedQuery<'_> { /// the direction and the tie contract. /// /// Fewer than `k` entries is normal (the index simply has fewer - /// groups than `offset + k`) and is not an error. A missing path - /// *is* an error rather than an empty result: the indexed + /// groups than `offset + k`) and is not an error. On an `IN`-pinned + /// request, an element whose prefix was never written contributes + /// an **empty branch** (union semantics). A missing path under a + /// single `==` pin — or under a *present* branch key — *is* an + /// error rather than an empty result: the indexed /// property-name tree is created when the contract is registered, so /// its absence means the contract-level state is not what the /// request claims, not that the ranking is empty. (An index with no @@ -76,9 +79,18 @@ impl DriveDocumentRankedQuery<'_> { // One walk per branch, each fetching a full page (the merge // lemma needs every branch's own top-k), merged with the // shared comparator. `offset` is grammar-rejected with `IN`, - // so `skipped` is always 0 here. + // so `skipped` is always 0 here. An `IN` element whose + // prefix was never written contributes the empty page — + // union semantics, decided at the branching Merk exactly + // like the proved path's authenticated absence (deeper + // breakage under a *present* key stays an error, and the + // single-`==`-pin contract is untouched: there, an unknown + // value is still an error, not an empty page). let per_branch = (0..self.prefix_branches.len()) .map(|branch| { + if self.branch_is_absent(branch, drive, transaction, platform_version)? { + return Ok(Vec::new()); + } Ok(self .execute_top_k_no_proof_branch( branch, @@ -103,6 +115,31 @@ impl DriveDocumentRankedQuery<'_> { self.execute_top_k_no_proof_branch(0, drive, transaction, platform_version) } + /// Whether this branch's key is absent at the branching Merk — the + /// union's empty-set case. Mirrors the proved path, where grovedb + /// authenticates the same absence at the same level. + pub(crate) fn branch_is_absent( + &self, + branch: usize, + drive: &Drive, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + let grove_version = &platform_version.drive.grove_version; + let paths = (0..self.prefix_branches.len()) + .map(|b| self.indexed_property_name_tree_path(b)) + .collect::, Error>>()?; + let (prefix, keys, _suffix) = super::branches::decompose_branch_paths(&paths)?; + let prefix_refs: Vec<&[u8]> = prefix.iter().map(|s| s.as_slice()).collect(); + let CostContext { value, cost: _ } = drive.grove.get_raw_optional( + prefix_refs.as_slice().into(), + &keys[branch], + transaction, + grove_version, + ); + Ok(value.map_err(|e| Error::GroveDB(Box::new(e)))?.is_none()) + } + /// One branch's page — the entire pre-`IN` executor, parameterized /// by which prefix branch's terminal tree it walks. fn execute_top_k_no_proof_branch( diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs index a086839bdf7..a7edea10511 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs @@ -2910,6 +2910,51 @@ mod pinned_prefix { ); } + /// An `IN` element whose prefix was never written contributes the + /// **empty branch** — union semantics — while the single-`==`-pin + /// contract keeps erroring on an unknown value (pinned separately + /// by `unknown_prefix_value_errors_rather_than_fabricating_an_empty_page`). + /// The proved path authenticates the absence inside the branched + /// envelope, so read, proof, and verification agree. + #[test] + fn an_absent_in_element_contributes_an_empty_branch() { + let (drive, contract) = setup_grades_compound_ranked(); + insert_grades(&drive, &contract, &[(IDENTITY_X, "art", 90)]); + + let never_written = [9u8; 32]; + let pins = in_pin(&[IDENTITY_X, never_written]); + let page = match run(&drive, &contract, &pins, 2, false).expect("read succeeds") { + DocumentRankedResponse::Entries(page) => page, + DocumentRankedResponse::Proof(_) => panic!("expected entries, got a proof"), + }; + assert_eq!( + page.entries, + vec![RankedEntry { + in_key: Some(IDENTITY_X.to_vec()), + key: b"art".to_vec(), + value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(90, 1)), + }], + "only the existing branch contributes; the absent one is empty, not an error" + ); + + let proof = match run(&drive, &contract, &pins, 2, true).expect("prove succeeds") { + DocumentRankedResponse::Proof(proof) => proof, + DocumentRankedResponse::Entries(_) => panic!("expected a proof, got entries"), + }; + let (root_hash, verified) = client_side_query(&contract, &pins, 2) + .verify_ranked_top_k_proof(&proof, platform_version()) + .expect("the envelope authenticates the absent branch"); + assert_eq!(verified.entries, page.entries); + assert_eq!( + root_hash, + drive + .grove + .root_hash(None, &platform_version().drive.grove_version) + .unwrap() + .expect("root hash must be readable"), + ); + } + /// An unpinned request over the compound-only contract has no /// covering index — there is no global cross-prefix ordering to /// serve, so the rejection names the missing coverage. From 050938ea9c215fc6460eb016659e92f7c2fc5edc Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 24 Aug 2026 09:51:42 +0200 Subject: [PATCH 06/25] feat(drive)!: branched IN proofs ride the unified PathQuery envelope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The orphaned grovedb branch this feature was built against carried bespoke `prove/verify_indexed_axis_*_branched` entry points; grovedb develop landed the same capability as a query shape instead (dashpay/grovedb#799): `PathQuery::new_branched_axis(prefix, branch_keys, suffix, axis_query)` proved through the standard `prove_query` and verified through `verify_path_query`, which returns per branch key — in query order — the proved entries, or None for a branch key whose absence the branching-level Merk proof authenticates (the empty-branch reading the unproved path already gives an absent `IN` element). Both provers build that query from the same `decompose_branch_paths` triple as before; both verifiers reconstruct it from the request, so axis, k/bounds, limit, direction and the branch set are bound by construction, and re-derive the page with the shared merge. The verifiers additionally require the returned branch set to equal the resolved one. The having bounds pass as inclusive i128 pairs (`AxisRangeBounds::inclusive_bounds_i128`) matching `AxisTraversal::Bounded`; entry mapping follows the canonical-reference rows (`IndexedAxisEntry::key_pair`, grovedb #817). Co-Authored-By: Claude Fable 5 --- Cargo.lock | 1 + packages/rs-drive/Cargo.toml | 1 + .../execute_range.rs | 27 ++++---- .../query/drive_document_having_query/mod.rs | 12 ++++ .../drive_document_ranked_query/branches.rs | 11 +++- .../execute_top_k.rs | 22 +++---- .../verify_having_range_proof/v0/mod.rs | 56 ++++++++++++----- .../verify_ranked_top_k_proof/v0/mod.rs | 62 ++++++++++++++----- 8 files changed, 136 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f468853a69c..d3d7b4d2f12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2195,6 +2195,7 @@ dependencies = [ "grovedb-costs", "grovedb-epoch-based-storage-flags", "grovedb-path", + "grovedb-query", "grovedb-storage", "grovedb-version", "hex", diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index 54012ec1f1f..275da37c4e1 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -58,6 +58,7 @@ grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "753a11f14c9a grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "753a11f14c9a4bc72bf2d5302751dd43d174621e", optional = true } grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "753a11f14c9a4bc72bf2d5302751dd43d174621e" } grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "753a11f14c9a4bc72bf2d5302751dd43d174621e" } +grovedb-query = { git = "https://github.com/dashpay/grovedb", rev = "753a11f14c9a4bc72bf2d5302751dd43d174621e" } [dev-dependencies] criterion = "0.5" diff --git a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs index bfc9669f0ed..496c6a094a3 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs @@ -20,8 +20,10 @@ use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; use dpp::version::PlatformVersion; +use grovedb::PathQuery; use grovedb::TransactionArg; use grovedb_costs::CostContext; +use grovedb_query::AxisQuery; impl DriveDocumentHavingQuery<'_> { /// Read the matching groups directly from the axis secondary: every @@ -215,18 +217,21 @@ impl DriveDocumentHavingQuery<'_> { .map(|branch| self.indexed_property_name_tree_path(branch)) .collect::, Error>>()?; let (prefix, keys, suffix) = decompose_branch_paths(&paths)?; - let prefix_refs: Vec<&[u8]> = prefix.iter().map(|s| s.as_slice()).collect(); - let suffix_refs: Vec<&[u8]> = suffix.iter().map(|s| s.as_slice()).collect(); - let CostContext { value, cost: _ } = drive.grove.prove_indexed_axis_query_branched( - &prefix_refs, - &keys, - &suffix_refs, - self.bounds.axis().into(), - self.bounds.merk_query(self.descending), - Some(self.limit), - transaction, - grove_version, + let (lo, hi) = self.bounds.inclusive_bounds_i128(); + let path_query = PathQuery::new_branched_axis( + prefix, + keys, + suffix, + AxisQuery::bounded( + self.bounds.axis().into(), + lo, + hi, + self.limit, + self.descending, + ), ); + let CostContext { value, cost: _ } = + drive.grove.prove_query(&path_query, None, grove_version); return value.map_err(|e| Error::GroveDB(Box::new(e))); } self.execute_range_with_proof_branch(0, drive, transaction, platform_version) diff --git a/packages/rs-drive/src/query/drive_document_having_query/mod.rs b/packages/rs-drive/src/query/drive_document_having_query/mod.rs index 964d264d3a4..291ccb762f3 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/mod.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/mod.rs @@ -163,6 +163,18 @@ impl AxisRangeBounds { } } + /// The bounds as inclusive `i128` values in the axis's own domain — + /// the form `AxisTraversal::Bounded` carries in the unified + /// `PathQuery`. Count and sum widen losslessly; avg is already + /// `i128` fixed point. + pub fn inclusive_bounds_i128(&self) -> (i128, i128) { + match *self { + AxisRangeBounds::Count { lo, hi } => (lo as i128, hi as i128), + AxisRangeBounds::Sum { lo, hi } => (lo as i128, hi as i128), + AxisRangeBounds::Avg { lo, hi } => (lo, hi), + } + } + /// The bounds as a byte range over the axis secondary's keyspace: /// `(inclusive_lower, exclusive_upper)`, with `None` for an upper /// bound at the axis's type maximum (no representable successor — diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs b/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs index 4a1b1ed4a0d..a8c104b6be0 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs @@ -126,14 +126,16 @@ pub fn merge_branch_pages( Ok(merged) } +/// The `(shared prefix, branch keys, shared suffix)` decomposition of a +/// branch path set — the triple `PathQuery::new_branched_axis` takes. +pub type BranchPathDecomposition = (Vec>, Vec>, Vec>); + /// Decompose per-branch grove paths into the `(shared prefix, branch /// keys, shared suffix)` triple grovedb's branched proof primitives /// take. The paths differ at exactly one segment position by /// construction (one `IN` pin); anything else is an internal /// resolution error. -pub fn decompose_branch_paths( - paths: &[Vec>], -) -> Result<(Vec>, Vec>, Vec>), Error> { +pub fn decompose_branch_paths(paths: &[Vec>]) -> Result { let first = paths.first().ok_or_else(|| { Error::Drive(DriveError::CorruptedDriveState( "branch decomposition over zero paths".to_string(), @@ -184,6 +186,7 @@ pub fn axis_entries_to_ranked( match (axis, entries) { (RankedAxis::Count, AxisEntries::Count(entries)) => Ok(entries .into_iter() + .map(|entry| entry.key_pair()) .map(|(count, key)| RankedEntry { in_key: None, key, @@ -192,6 +195,7 @@ pub fn axis_entries_to_ranked( .collect()), (RankedAxis::Sum, AxisEntries::Sum(entries)) => Ok(entries .into_iter() + .map(|entry| entry.key_pair()) .map(|(sum, key)| RankedEntry { in_key: None, key, @@ -200,6 +204,7 @@ pub fn axis_entries_to_ranked( .collect()), (RankedAxis::Avg, AxisEntries::Avg(entries)) => Ok(entries .into_iter() + .map(|entry| entry.key_pair()) .map(|(avg, key)| RankedEntry { in_key: None, key, diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs index 21b62042965..99d0bbac242 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs @@ -16,8 +16,9 @@ use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; use dpp::version::PlatformVersion; -use grovedb::{IndexedTopKKeysPage, TransactionArg}; +use grovedb::{IndexedTopKKeysPage, PathQuery, TransactionArg}; use grovedb_costs::CostContext; +use grovedb_query::AxisQuery; impl DriveDocumentRankedQuery<'_> { /// Read one page of the ranking directly from the axis secondary: @@ -300,20 +301,19 @@ impl DriveDocumentRankedQuery<'_> { .map(|branch| self.indexed_property_name_tree_path(branch)) .collect::, Error>>()?; let (prefix, keys, suffix) = decompose_branch_paths(&paths)?; - let prefix_refs: Vec<&[u8]> = prefix.iter().map(|s| s.as_slice()).collect(); - let suffix_refs: Vec<&[u8]> = suffix.iter().map(|s| s.as_slice()).collect(); - let CostContext { value, cost: _ } = - drive.grove.prove_indexed_axis_top_k_paginated_branched( - &prefix_refs, - &keys, - &suffix_refs, + let path_query = PathQuery::new_branched_axis( + prefix, + keys, + suffix, + AxisQuery::top_k( self.axis.into(), self.k, self.offset as u64, self.descending, - transaction, - grove_version, - ); + ), + ); + let CostContext { value, cost: _ } = + drive.grove.prove_query(&path_query, None, grove_version); return value.map_err(|e| Error::GroveDB(Box::new(e))); } self.execute_top_k_with_proof_branch(0, drive, transaction, platform_version) diff --git a/packages/rs-drive/src/verify/document_having/verify_having_range_proof/v0/mod.rs b/packages/rs-drive/src/verify/document_having/verify_having_range_proof/v0/mod.rs index f912a3f64a9..38511ab75a6 100644 --- a/packages/rs-drive/src/verify/document_having/verify_having_range_proof/v0/mod.rs +++ b/packages/rs-drive/src/verify/document_having/verify_having_range_proof/v0/mod.rs @@ -7,7 +7,10 @@ use crate::query::{DriveDocumentHavingQuery, RankedAxis, RankedEntry, RankedEntr use crate::verify::RootHash; use dpp::version::PlatformVersion; use grovedb::operations::proof::indexed_axis::AxisEntries; +use grovedb::operations::proof::VerifiedPathQuery; use grovedb::GroveDb; +use grovedb::PathQuery; +use grovedb_query::AxisQuery; impl DriveDocumentHavingQuery<'_> { /// v0 of [`Self::verify_having_range_proof`]. @@ -56,24 +59,46 @@ impl DriveDocumentHavingQuery<'_> { .map(|branch| self.indexed_property_name_tree_path(branch)) .collect::, Error>>()?; let (prefix, keys, suffix) = decompose_branch_paths(&paths)?; - let prefix_refs: Vec<&[u8]> = prefix.iter().map(|s| s.as_slice()).collect(); - let suffix_refs: Vec<&[u8]> = suffix.iter().map(|s| s.as_slice()).collect(); let axis = self.bounds.axis(); - let result = grovedb::GroveDb::verify_indexed_axis_query_branched( + let (lo, hi) = self.bounds.inclusive_bounds_i128(); + let path_query = PathQuery::new_branched_axis( + prefix, + keys.clone(), + suffix, + AxisQuery::bounded(axis.into(), lo, hi, self.limit, self.descending), + ); + let verified = GroveDb::verify_path_query( proof, - &prefix_refs, - &keys, - &suffix_refs, - axis.into(), - self.bounds.merk_query(self.descending), - Some(self.limit), + &path_query, + &platform_version.drive.grove_version, ) .map_err(|e| Error::GroveDB(Box::new(e)))?; - let per_branch = result - .branches + let VerifiedPathQuery::BranchedAxisEntries { + root_hash, + branches, + } = verified + else { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "a branched having range proof verified to a non-branched shape".to_string(), + ))); + }; + if branches.len() != keys.len() || branches.iter().map(|(key, _)| key).ne(keys.iter()) { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "a branched having range proof verified a different branch set than the \ + request resolved" + .to_string(), + ))); + } + let per_branch = branches .into_iter() - .map(|entries| { - let entries = axis_entries_to_ranked(axis, entries)?; + .map(|(_key, entries)| { + // Authenticated absence of a branch key = an empty page, + // the same reading the unproved path gives an absent + // `IN` element. + let entries = match entries { + None => Vec::new(), + Some(entries) => axis_entries_to_ranked(axis, entries)?, + }; if entries.len() > self.limit as usize { return Err(Error::Drive(DriveError::CorruptedDriveState(format!( "a branch of a having range proof verified to {} entries for \ @@ -91,9 +116,9 @@ impl DriveDocumentHavingQuery<'_> { self.descending, self.limit as usize, )?; - return Ok((result.root_hash, entries)); + return Ok((root_hash, entries)); } - self.verify_having_range_proof_v0_branch(0, proof) + self.verify_having_range_proof_v0_branch(0, proof, platform_version) } /// One branch's verification — the entire pre-`IN` verifier, @@ -102,6 +127,7 @@ impl DriveDocumentHavingQuery<'_> { &self, branch: usize, proof: &[u8], + platform_version: &PlatformVersion, ) -> Result<(RootHash, Vec), Error> { let path = self.indexed_property_name_tree_path(branch)?; let path_refs: Vec<&[u8]> = path.iter().map(|segment| segment.as_slice()).collect(); diff --git a/packages/rs-drive/src/verify/document_ranked/verify_ranked_top_k_proof/v0/mod.rs b/packages/rs-drive/src/verify/document_ranked/verify_ranked_top_k_proof/v0/mod.rs index ff6c09a31fb..40f1594cc32 100644 --- a/packages/rs-drive/src/verify/document_ranked/verify_ranked_top_k_proof/v0/mod.rs +++ b/packages/rs-drive/src/verify/document_ranked/verify_ranked_top_k_proof/v0/mod.rs @@ -9,7 +9,10 @@ use crate::query::{ use crate::verify::RootHash; use dpp::version::PlatformVersion; use grovedb::operations::proof::indexed_axis::AxisEntries; +use grovedb::operations::proof::VerifiedPathQuery; use grovedb::GroveDb; +use grovedb::PathQuery; +use grovedb_query::AxisQuery; impl DriveDocumentRankedQuery<'_> { /// v0 of [`Self::verify_ranked_top_k_proof`]. @@ -67,24 +70,50 @@ impl DriveDocumentRankedQuery<'_> { .map(|branch| self.indexed_property_name_tree_path(branch)) .collect::, Error>>()?; let (prefix, keys, suffix) = decompose_branch_paths(&paths)?; - let prefix_refs: Vec<&[u8]> = prefix.iter().map(|s| s.as_slice()).collect(); - let suffix_refs: Vec<&[u8]> = suffix.iter().map(|s| s.as_slice()).collect(); - let result = grovedb::GroveDb::verify_indexed_axis_top_k_paginated_branched( + let path_query = PathQuery::new_branched_axis( + prefix, + keys.clone(), + suffix, + AxisQuery::top_k( + self.axis.into(), + self.k, + self.offset as u64, + self.descending, + ), + ); + let verified = GroveDb::verify_path_query( proof, - &prefix_refs, - &keys, - &suffix_refs, - self.axis.into(), - self.k, - self.offset as u64, - self.descending, + &path_query, + &platform_version.drive.grove_version, ) .map_err(|e| Error::GroveDB(Box::new(e)))?; - let per_branch = result - .branches + let VerifiedPathQuery::BranchedAxisEntries { + root_hash, + branches, + } = verified + else { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "a branched ranked top-k proof verified to a non-branched shape".to_string(), + ))); + }; + if branches.len() != keys.len() || branches.iter().map(|(key, _)| key).ne(keys.iter()) { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "a branched ranked top-k proof verified a different branch set than the \ + request resolved" + .to_string(), + ))); + } + let per_branch = branches .into_iter() - .map(|(_skipped, entries)| { - let entries = axis_entries_to_ranked(self.axis, entries)?; + .map(|(_key, entries)| { + // A branch key whose absence the branching-level Merk + // proof authenticates contributes an empty page — the + // same reading the unproved path gives an absent `IN` + // element. + let entries = match entries { + None => Vec::new(), + Some(entries) => axis_entries_to_ranked(self.axis, entries)?, + }; if entries.len() > self.k as usize { return Err(Error::Drive(DriveError::CorruptedDriveState(format!( "a branch of a ranked top-k proof verified to {} entries for \ @@ -103,14 +132,14 @@ impl DriveDocumentRankedQuery<'_> { self.k as usize, )?; return Ok(( - result.root_hash, + root_hash, RankedPage { skipped: 0, entries, }, )); } - self.verify_ranked_top_k_proof_v0_branch(0, proof) + self.verify_ranked_top_k_proof_v0_branch(0, proof, platform_version) } /// One branch's verification — the entire pre-`IN` verifier, @@ -119,6 +148,7 @@ impl DriveDocumentRankedQuery<'_> { &self, branch: usize, proof: &[u8], + platform_version: &PlatformVersion, ) -> Result<(RootHash, RankedPage), Error> { let path = self.indexed_property_name_tree_path(branch)?; let path_refs: Vec<&[u8]> = path.iter().map(|segment| segment.as_slice()).collect(); From b2a72cc56127ca87218f475ce0ecc2833f4078d3 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 25 Aug 2026 09:23:56 +0200 Subject: [PATCH 07/25] =?UTF-8?q?fix(drive)!:=20close=20the=20branched-IN?= =?UTF-8?q?=20edge=20cases=20=E2=80=94=20deep=20absence,=20null=20pins,=20?= =?UTF-8?q?transactional=20proves?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three review findings on the branched (IN-pinned) ranked/having surface: - Absence is authenticated at ANY depth of a branch's chain, so the unproved executors now walk the whole branch-key-plus-suffix chain (`branch_subpath_is_absent`): an IN element whose value tree exists via some other pin value but whose deeper pinned path was never written is an empty branch, exactly as grovedb's branched reader and prover treat it — not an error that discards the other branches. - A single null pin combined with an IN is rejected at the shared encoder: null addresses its prefix through an empty path segment the branched proof grammar cannot express, so serving it unproved while the prove fails would be a proved/unproved divergence. null as an ELEMENT of the IN stays legal — it is a branch key the envelope addresses and authenticates like any other (pinned by the existing mixed-null having test). - grovedb's unified prove_query proves committed state only (it opens its own transaction), so a branched prove under a caller transaction fails closed with NotSupported on both surfaces instead of silently proving a different snapshot than the unproved read serves. Also per review: encode_prefix_branches enforces MAX_PREFIX_IN_BRANCHES itself (it is pub and the fan-out hangs off it), and the module docs and book row now describe the shipped IN semantics and the unified branched PathQuery envelope. New dualGrade fixture doctype ([identityId, tag, class]) exercises the two-leading-property cases; four regression tests. Co-Authored-By: Claude Fable 5 --- book/src/drive/document-ranked-trees.md | 2 +- .../execute_range.rs | 24 +- .../drive_document_ranked_query/branches.rs | 43 +++ .../execute_top_k.rs | 26 +- .../index_picker.rs | 37 +++ .../query/drive_document_ranked_query/mod.rs | 10 +- .../drive_document_ranked_query/tests.rs | 293 +++++++++++++++++- .../grades-compound-ranked-contract.json | 58 +++- 8 files changed, 474 insertions(+), 19 deletions(-) diff --git a/book/src/drive/document-ranked-trees.md b/book/src/drive/document-ranked-trees.md index 9dde105d7bc..8fd8c8ff549 100644 --- a/book/src/drive/document-ranked-trees.md +++ b/book/src/drive/document-ranked-trees.md @@ -341,7 +341,7 @@ Note that the fixture puts each shape on its **own document type**. That's not a | Top / bottom K groups by sum of a property | `rankedSummable: true` on an index with `summable: ""` + `rangeSummable: true` | | Top / bottom K groups by average of a property | `rankedAverageable: true` on an index with `averageable: ""` + `rangeAverageable: true` (or the count+sum longhand) | | Two rankings on one index (e.g. by count *and* by average) | Both keywords. The tree is a PCPSIT carrying both axes in its TLV; you pay one secondary Merk per axis on every write. | -| A ranking filtered by another property (`top 5 restaurants in London`) | A **compound ranked index** with the filter property leading: `[city, restaurantId]` with the ranked flags. Each city gets its own secondary; the query pins the prefix with an equality `where` (`WHERE city == "London" GROUP BY restaurantId ORDER BY DESC LIMIT 5`). Equality pins select one prefix; at most one pin may be an `IN` (2..=10 distinct elements, `null` legal for the absent-value prefix, and a never-written element contributes an empty branch), which walks one secondary per element and merges by `(aggregate, encoded prefix, group key)` with per-branch proofs in one container — entries then carry `in_key`. A range operator on the prefix stays rejected, `OFFSET` is rejected together with `IN`, and there is still no global cross-prefix ordering beyond that merge. | +| A ranking filtered by another property (`top 5 restaurants in London`) | A **compound ranked index** with the filter property leading: `[city, restaurantId]` with the ranked flags. Each city gets its own secondary; the query pins the prefix with an equality `where` (`WHERE city == "London" GROUP BY restaurantId ORDER BY DESC LIMIT 5`). Equality pins select one prefix; at most one pin may be an `IN` (2..=10 distinct elements; a never-written element — or one whose deeper pinned path was never written — contributes an empty branch), which walks one secondary per element and merges by `(aggregate, encoded prefix, group key)`, proved in one branched `PathQuery` envelope (shared ancestors proved once, per-element authenticated absence) — entries then carry `in_key`. A `null` pin stays legal on its own but cannot combine with an `IN` (null addresses its prefix through an empty path segment the branched proof cannot express), and branched proofs are generated from committed state only. A range operator on the prefix stays rejected, `OFFSET` is rejected together with `IN`, and there is still no global cross-prefix ordering beyond that merge. | | A ranking on a unique or contested index | Not available, and not meaningful: every group holds at most one document. | | Range aggregates without ranking (the 4.0 surface) | Just the `range*` flags. Ranking is strictly additive — adding it never changes what a range query returns. | | Nothing ranking-aware (default) | Don't set any `ranked*` flag. The terminal property-name tree keeps the type its range flags give it. | diff --git a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs index 496c6a094a3..75e8bc75425 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs @@ -82,16 +82,16 @@ impl DriveDocumentHavingQuery<'_> { let paths = (0..self.prefix_branches.len()) .map(|b| self.indexed_property_name_tree_path(b)) .collect::, Error>>()?; - let (prefix, keys, _suffix) = + let (prefix, _keys, _suffix) = super::super::drive_document_ranked_query::branches::decompose_branch_paths(&paths)?; - let prefix_refs: Vec<&[u8]> = prefix.iter().map(|s| s.as_slice()).collect(); - let CostContext { value, cost: _ } = drive.grove.get_raw_optional( - prefix_refs.as_slice().into(), - &keys[branch], + let full_path = self.indexed_property_name_tree_path(branch)?; + super::super::drive_document_ranked_query::branches::branch_subpath_is_absent( + &drive.grove, + &full_path, + prefix.len(), transaction, grove_version, - ); - Ok(value.map_err(|e| Error::GroveDB(Box::new(e)))?.is_none()) + ) } /// One branch's in-bound page — the entire pre-`IN` executor, @@ -210,6 +210,16 @@ impl DriveDocumentHavingQuery<'_> { platform_version: &PlatformVersion, ) -> Result, Error> { if self.prefix_branches.len() > 1 { + // Same fail-closed rule as the ranked prover: grovedb's unified + // `prove_query` proves committed state only and cannot see the + // caller's transaction. + if transaction.is_some() { + return Err(Error::Drive(DriveError::NotSupported( + "an IN-pinned (branched) having-range proof is generated from committed \ + state only: grovedb's unified prove_query cannot see the caller's \ + transaction — prove per prefix element, or commit first", + ))); + } // One grovedb **branched** envelope — see the ranked // executor's multi-branch arm for the shape. let grove_version = &platform_version.drive.grove_version; diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs b/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs index a8c104b6be0..7f330714855 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs @@ -32,6 +32,12 @@ use super::{RankedAxis, RankedEntry, RankedEntryValue}; use crate::error::drive::DriveError; use crate::error::Error; use grovedb::operations::proof::indexed_axis::AxisEntries; +#[cfg(feature = "server")] +use grovedb::{GroveDb, TransactionArg}; +#[cfg(feature = "server")] +use grovedb_costs::CostContext; +#[cfg(feature = "server")] +use grovedb_version::version::GroveVersion; use std::cmp::Ordering; /// The position (index into a branch's segment list) at which the @@ -176,6 +182,43 @@ pub fn decompose_branch_paths(paths: &[Vec>]) -> Result], + shared_prefix_len: usize, + transaction: TransactionArg, + grove_version: &GroveVersion, +) -> Result { + for depth in shared_prefix_len..full_path.len() { + let parent: Vec<&[u8]> = full_path[..depth].iter().map(|s| s.as_slice()).collect(); + let CostContext { value, cost: _ } = grove.get_raw_optional( + parent.as_slice().into(), + &full_path[depth], + transaction, + grove_version, + ); + if value.map_err(|e| Error::GroveDB(Box::new(e)))?.is_none() { + return Ok(true); + } + } + Ok(false) +} + /// Translate one branch's verified [`AxisEntries`] into drive entries /// on the requested axis — the same mapping the single-path verifiers /// perform, shared here so both surfaces' branched verifiers agree. diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs index 99d0bbac242..5df97efdef9 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs @@ -130,15 +130,15 @@ impl DriveDocumentRankedQuery<'_> { let paths = (0..self.prefix_branches.len()) .map(|b| self.indexed_property_name_tree_path(b)) .collect::, Error>>()?; - let (prefix, keys, _suffix) = super::branches::decompose_branch_paths(&paths)?; - let prefix_refs: Vec<&[u8]> = prefix.iter().map(|s| s.as_slice()).collect(); - let CostContext { value, cost: _ } = drive.grove.get_raw_optional( - prefix_refs.as_slice().into(), - &keys[branch], + let (prefix, _keys, _suffix) = super::branches::decompose_branch_paths(&paths)?; + let full_path = self.indexed_property_name_tree_path(branch)?; + super::branches::branch_subpath_is_absent( + &drive.grove, + &full_path, + prefix.len(), transaction, grove_version, - ); - Ok(value.map_err(|e| Error::GroveDB(Box::new(e)))?.is_none()) + ) } /// One branch's page — the entire pre-`IN` executor, parameterized @@ -290,6 +290,18 @@ impl DriveDocumentRankedQuery<'_> { platform_version: &PlatformVersion, ) -> Result, Error> { if self.prefix_branches.len() > 1 { + // grovedb's unified `prove_query` proves COMMITTED state only — + // it opens its own transaction internally and cannot see the + // caller's. Serving a proof for a different snapshot than the + // unproved read would silently desynchronize the two paths, so a + // transactional branched prove fails closed instead. + if transaction.is_some() { + return Err(Error::Drive(DriveError::NotSupported( + "an IN-pinned (branched) ranked proof is generated from committed state \ + only: grovedb's unified prove_query cannot see the caller's transaction \ + — prove per prefix element, or commit first", + ))); + } // One grovedb **branched** envelope: shared ancestor layers // once, one multi-key proof at the branching level, one // secondary proof per branch — a single proof with a single diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs b/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs index eb0ddcc0ade..db558511249 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs @@ -329,6 +329,31 @@ pub fn encode_prefix_branches( )); } + // A single `null` pin encodes as the empty path segment; the branched + // proof grammar (`PathQuery::new_branched_axis`) cannot address an + // empty segment in the shared prefix or suffix, so a null `==` pin + // combined with an `IN` would serve the unproved read and fail the + // prove — the exact proved/unproved divergence this surface forbids. + // Rejected for any non-branching pin position, conservatively: issue + // one request per `IN` element to combine null pins with multiple + // prefixes. `null` as an ELEMENT of the `IN` itself stays legal — it + // is a branch key, which the envelope addresses and authenticates + // like any other. + let has_branching_pin = per_property.iter().any(|candidates| candidates.len() > 1); + if has_branching_pin + && per_property + .iter() + .any(|candidates| candidates.len() == 1 && candidates[0].is_empty()) + { + return Err(Error::Query( + QuerySyntaxError::InvalidWhereClauseComponents( + "an `IN` prefix pin cannot be combined with a `null` pin: null addresses the \ + absent-value prefix through an empty path segment, which the branched proof \ + cannot express — issue one request per `IN` element instead", + ), + )); + } + // The grammar admits at most one multi-value pin, so this product // is |IN| branches (or exactly one), already in canonical order // because the only varying position was sorted above. @@ -345,5 +370,17 @@ pub fn encode_prefix_branches( }) .collect(); } + // The documented hard ceiling on branch fan-out, enforced at the + // shared choke point too: this function is `pub`, and everything + // downstream (encoding, sorting, per-branch walks, proof size) is + // linear in the branch count. + if branches.len() > super::MAX_PREFIX_IN_BRANCHES { + return Err(Error::Query( + QuerySyntaxError::InvalidWhereClauseComponents( + "an `IN` prefix pin fans out into more branches than the ranked surface serves \ + — narrow the element list or issue several requests", + ), + )); + } Ok(branches) } diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs b/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs index 97427885029..65ee04e882e 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs @@ -50,8 +50,14 @@ //! on the grouped (terminal) property itself would ask for a //! *filtered* ranking, which no secondary can express — it is sorted //! by aggregate, not by group key — and is rejected rather than -//! silently ignored, as is any non-equality prefix clause (`IN` -//! included: one walk per element is a future multi-`IN` capability). +//! silently ignored, as is any non-equality prefix clause except one +//! `IN`: exactly one leading pin may carry 2..=[`MAX_PREFIX_IN_BRANCHES`] +//! distinct elements, read as one walk per element and merged by +//! `(aggregate, encoded pin, group key)`, proved in a single branched +//! `PathQuery` envelope with per-element authenticated absence. A +//! `null` pin cannot combine with an `IN` (null addresses its prefix +//! through an empty path segment the branched proof cannot express), +//! and `OFFSET` is rejected together with `IN`. //! 2. **`limit` is mandatory, `offset` is depth-bounded, `start_at` is //! refused.** //! `limit` is the `k` of the walk and the ranked surface has no diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs index a7edea10511..46c9c2878aa 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs @@ -2129,8 +2129,9 @@ mod pinned_prefix { //! and its paginated proof. use super::super::drive_dispatcher::{DocumentRankedRequest, DocumentRankedResponse}; - use super::super::index_picker::resolve_ranked_query_for_mode; + use super::super::index_picker::{encode_prefix_branches, resolve_ranked_query_for_mode}; use super::super::mode_detection::{detect_ranked_mode, detect_ranked_mode_v0}; + use super::super::PrefixPin; use super::super::{DriveDocumentRankedQuery, RankedEntry, RankedEntryValue}; use crate::drive::Drive; use crate::error::query::QuerySyntaxError; @@ -2152,6 +2153,7 @@ mod pinned_prefix { use dpp::tests::json_document::json_document_to_contract; use dpp::version::PlatformVersion; use grovedb::element::indexed::compute_avg_fixed_point; + use grovedb::TransactionArg; use std::collections::BTreeMap; const PREFIX_PROPERTY: &str = "identityId"; @@ -2973,4 +2975,293 @@ mod pinned_prefix { "expected a no-covering-index rejection, got {error:?}" ); } + + const DUAL_DOCTYPE: &str = "dualGrade"; + + fn insert_dual_grades( + drive: &Drive, + contract: &DataContract, + rows: &[([u8; 32], &str, &str, i64)], + ) { + let pv = platform_version(); + let document_type = contract + .document_type_for_name(DUAL_DOCTYPE) + .expect("dualGrade doctype exists"); + for (i, (identity, tag, class, grade)) in rows.iter().enumerate() { + let mut doc: Document = document_type + .random_document(Some(8000 + i as u64), pv) + .expect("random document"); + let mut props = BTreeMap::new(); + props.insert(PREFIX_PROPERTY.to_string(), Value::Identifier(*identity)); + props.insert("tag".to_string(), Value::Text(tag.to_string())); + props.insert(CLASS_PROPERTY.to_string(), Value::Text(class.to_string())); + props.insert("grade".to_string(), Value::I64(*grade)); + doc.set_properties(props); + drive + .add_document_for_contract( + DocumentAndContractInfo { + owned_document_info: OwnedDocumentInfo { + document_info: DocumentRefInfo((&doc, None)), + owner_id: None, + }, + contract, + document_type, + }, + false, + BlockInfo::default(), + true, + None, + pv, + None, + ) + .expect("expected to insert a dualGrade document"); + } + } + + fn run_dual( + drive: &Drive, + contract: &DataContract, + where_clauses: &[WhereClause], + limit: u32, + prove: bool, + transaction: TransactionArg, + ) -> Result { + let group_by = vec![CLASS_PROPERTY.to_string()]; + let order_by = vec![OrderClause { + field: "grade".to_string(), + ascending: false, + }]; + drive.execute_document_ranked_request( + DocumentRankedRequest { + contract, + document_type: contract + .document_type_for_name(DUAL_DOCTYPE) + .expect("dualGrade doctype exists"), + group_by: &group_by, + select: SelectProjection::avg("grade"), + having: &[], + order_by: &order_by, + where_clauses, + limit: Some(limit), + offset: None, + has_start_at: false, + prove, + }, + transaction, + platform_version(), + ) + } + + /// An `IN` element whose branch-key tree EXISTS (another pin value was + /// written under it) but whose deeper pinned path was never written is + /// an ABSENT branch — empty page, union semantics — not an error that + /// discards the other branches' results. grovedb's branched reader and + /// prover authenticate absence at any depth of the branch chain, so the + /// unproved executor must walk the whole chain too. + #[test] + fn an_in_element_with_an_absent_deeper_pin_contributes_an_empty_branch() { + let (drive, contract) = setup_grades_compound_ranked(); + // Y's tree exists (via tag "t2"), but Y/t1 was never written. + insert_dual_grades( + &drive, + &contract, + &[ + (IDENTITY_X, "t1", "math", 90), + (IDENTITY_Y, "t2", "math", 80), + ], + ); + + let mut pins = in_pin(&[IDENTITY_X, IDENTITY_Y]); + pins.push(WhereClause { + field: "tag".to_string(), + operator: WhereOperator::Equal, + value: Value::Text("t1".to_string()), + }); + + let page = match run_dual(&drive, &contract, &pins, 2, false, None) + .expect("a present branch key with an absent deeper pin must not error") + { + DocumentRankedResponse::Entries(page) => page, + DocumentRankedResponse::Proof(_) => panic!("expected entries, got a proof"), + }; + assert_eq!( + page.entries, + vec![RankedEntry { + in_key: Some(IDENTITY_X.to_vec()), + key: b"math".to_vec(), + value: RankedEntryValue::AvgFixedPoint(compute_avg_fixed_point(90, 1)), + }], + "X/t1 contributes; Y (present key, absent t1 suffix) is an empty branch" + ); + + // The proved path must agree byte-for-byte with the unproved one. + let proof = match run_dual(&drive, &contract, &pins, 2, true, None) + .expect("the branched envelope authenticates the absent suffix") + { + DocumentRankedResponse::Proof(proof) => proof, + DocumentRankedResponse::Entries(_) => panic!("expected a proof, got entries"), + }; + let group_by = vec![CLASS_PROPERTY.to_string()]; + let order_by = vec![OrderClause { + field: "grade".to_string(), + ascending: false, + }]; + let pv = platform_version(); + let mode = detect_ranked_mode( + &SelectProjection::avg("grade"), + &group_by, + &[], + &order_by, + &pins, + RankedPaginationInputs { + limit: Some(2), + offset: None, + has_start_at: false, + }, + pv, + ) + .expect("well-formed"); + let query = resolve_ranked_query_for_mode( + contract.id_ref().to_buffer(), + contract + .document_type_for_name(DUAL_DOCTYPE) + .expect("dualGrade doctype exists"), + DUAL_DOCTYPE.to_string(), + contract + .document_types() + .get(DUAL_DOCTYPE) + .expect("dualGrade doctype exists") + .indexes(), + &mode, + pv, + ) + .expect("covered"); + let (root_hash, verified) = query + .verify_ranked_top_k_proof(&proof, pv) + .expect("the proof verifies"); + assert_eq!(verified.entries, page.entries); + assert_eq!( + root_hash, + drive + .grove + .root_hash(None, &pv.drive.grove_version) + .unwrap() + .expect("root hash must be readable"), + ); + } + + /// A `null` pin addresses its prefix through an EMPTY path segment, + /// which the branched proof grammar cannot express — so combining it + /// with an `IN` is rejected at the grammar instead of serving the + /// unproved read and failing the prove. + #[test] + fn a_null_pin_cannot_combine_with_an_in() { + let (drive, contract) = setup_grades_compound_ranked(); + insert_dual_grades(&drive, &contract, &[(IDENTITY_X, "t1", "math", 90)]); + + let mut pins = in_pin(&[IDENTITY_X, IDENTITY_Y]); + pins.push(WhereClause { + field: "tag".to_string(), + operator: WhereOperator::Equal, + value: Value::Null, + }); + + let error = run_dual(&drive, &contract, &pins, 2, false, None) + .expect_err("null x IN must be rejected"); + assert!( + matches!( + &error, + Error::Query(QuerySyntaxError::InvalidWhereClauseComponents(message)) + if message.contains("`null` pin") + ), + "the rejection must name the null x IN exclusion, got {error:?}" + ); + } + + /// grovedb's unified `prove_query` proves committed state only, so an + /// `IN`-pinned prove under a caller transaction fails closed instead of + /// silently proving a different snapshot than the unproved read serves. + #[test] + fn a_branched_prove_rejects_a_transaction() { + let (drive, contract) = setup_grades_compound_ranked(); + insert_grades(&drive, &contract, &[(IDENTITY_X, "math", 80)]); + + let transaction = drive.grove.start_transaction(); + let pins = in_pin(&[IDENTITY_X, IDENTITY_Y]); + let group_by = vec![CLASS_PROPERTY.to_string()]; + let order_by = vec![OrderClause { + field: "grade".to_string(), + ascending: false, + }]; + let error = drive + .execute_document_ranked_request( + DocumentRankedRequest { + contract: &contract, + document_type: contract + .document_type_for_name(DOCUMENT_TYPE) + .expect("grade doctype exists"), + group_by: &group_by, + select: SelectProjection::avg("grade"), + having: &[], + order_by: &order_by, + where_clauses: &pins, + limit: Some(2), + offset: None, + has_start_at: false, + prove: true, + }, + Some(&transaction), + platform_version(), + ) + .expect_err("a transactional branched prove must fail closed"); + assert!( + matches!( + &error, + Error::Drive(crate::error::drive::DriveError::NotSupported(message)) + if message.contains("committed state") + ), + "expected the committed-state-only rejection, got {error:?}" + ); + + // The unproved read under the same transaction still serves. + let page = match run(&drive, &contract, &pins, 2, false) + .expect("the unproved read is unaffected") + { + DocumentRankedResponse::Entries(page) => page, + DocumentRankedResponse::Proof(_) => panic!("expected entries"), + }; + assert_eq!(page.entries.len(), 1); + } + + /// The public encoder enforces the documented branch ceiling itself: + /// its callers' grammar checks are not the only line of defense. + #[test] + fn the_prefix_encoder_enforces_the_branch_ceiling() { + let (_drive, contract) = setup_grades_compound_ranked(); + let document_type = contract + .document_type_for_name(DOCUMENT_TYPE) + .expect("grade doctype exists"); + let index = contract + .document_types() + .get(DOCUMENT_TYPE) + .expect("grade doctype exists") + .indexes() + .values() + .next() + .expect("the compound ranked index exists"); + let pins = vec![PrefixPin { + field: PREFIX_PROPERTY.to_string(), + values: (0u8..=10).map(|i| Value::Identifier([i; 32])).collect(), + }]; + let error = encode_prefix_branches(document_type, index, &pins, platform_version()) + .expect_err("11 branches must exceed the ceiling"); + assert!( + matches!( + &error, + Error::Query(QuerySyntaxError::InvalidWhereClauseComponents(message)) + if message.contains("more branches") + ), + "expected the fan-out ceiling rejection, got {error:?}" + ); + } } diff --git a/packages/rs-drive/tests/supporting_files/contract/grades/grades-compound-ranked-contract.json b/packages/rs-drive/tests/supporting_files/contract/grades/grades-compound-ranked-contract.json index df4179096ce..b1e5959910a 100644 --- a/packages/rs-drive/tests/supporting_files/contract/grades/grades-compound-ranked-contract.json +++ b/packages/rs-drive/tests/supporting_files/contract/grades/grades-compound-ranked-contract.json @@ -95,6 +95,62 @@ "grade" ], "additionalProperties": false + }, + "dualGrade": { + "type": "object", + "documentsMutable": false, + "canBeDeleted": false, + "indices": [ + { + "name": "byIdentityTagAndClass", + "properties": [ + { + "identityId": "asc" + }, + { + "tag": "asc" + }, + { + "class": "asc" + } + ], + "averageable": "grade", + "rangeAverageable": true, + "rankedAverageable": true + } + ], + "properties": { + "identityId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "position": 0, + "contentMediaType": "application/x.dash.dpp.identifier" + }, + "tag": { + "type": "string", + "maxLength": 32, + "position": 1 + }, + "class": { + "type": "string", + "maxLength": 32, + "position": 2 + }, + "grade": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "position": 3 + } + }, + "required": [ + "identityId", + "class", + "grade" + ], + "additionalProperties": false } } -} \ No newline at end of file +} From 6180852a2aef31da1b06f7ba5c8efbbdeeee3df1 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 25 Aug 2026 12:04:58 +0200 Subject: [PATCH 08/25] fix(drive)!: read the whole branched union from one snapshot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The unproved multi-branch read performed each absence probe and each branch walk as its own GroveDB operation, so a block commit landing between calls could merge branch pages that never coexisted in one committed state. Both executors now issue ONE grovedb call — the same `PathQuery::new_branched_axis` the prover builds, run through `run_path_query` with the keys-only projection — so every absence decision and every branch page comes from a single snapshot, absence at any depth is the branched reader's empty branch, and the caller's transaction reaches the read end-to-end (regression test: a branch written only inside a transaction is visible through it and authenticated absent without it). The per-branch loop, the absence probes and the chain-walk helper are gone. Also per review: `branches` is crate-private; `encode_prefix_branches` rejects oversized pins BEFORE any encoding work (the post-product count stays as a backstop); the platform.proto rejected-shape bullets, the "proof container" wording and a dead doc link now describe the shipped IN grammar and the unified branched envelope (no checked-in generated file embeds the old proto text, so no client regen is triggered). Co-Authored-By: Claude Fable 5 --- .../protos/platform/v0/platform.proto | 4 +- .../execute_range.rs | 94 +++++++++------ .../drive_document_ranked_query/branches.rs | 74 ++++++------ .../execute_top_k.rs | 114 ++++++++++-------- .../index_picker.rs | 17 ++- .../query/drive_document_ranked_query/mod.rs | 6 +- .../drive_document_ranked_query/tests.rs | 99 +++++++++++++++ 7 files changed, 277 insertions(+), 131 deletions(-) diff --git a/packages/dapi-grpc/protos/platform/v0/platform.proto b/packages/dapi-grpc/protos/platform/v0/platform.proto index 18a82d2ffc9..e02b9fd0907 100644 --- a/packages/dapi-grpc/protos/platform/v0/platform.proto +++ b/packages/dapi-grpc/protos/platform/v0/platform.proto @@ -901,8 +901,8 @@ message GetDocumentsRequest { // - no offset or cursor pagination: a page cut at `limit` continues only by tightening the bound past the last *distinct* aggregate value seen; a cut inside a tie (several groups sharing the boundary aggregate) cannot be continued, so size `limit` above the widest expected tie. // // **Rejected shapes** (return `Unsupported`): - // - any non-empty `having` on protocol v13 and earlier; at v14+, any `having` shape outside having-range mode above (multiple clauses, an aggregate other than the select's, `NOT_EQUAL` / `IN`, a `where` shape other than the compound-index equality pins above, or a carried `offset` / cursor). - // - at v14+: a ranked-shaped request carrying a `where` shape other than the compound-index equality pins above (a non-`EQUAL` operator, a repeated or non-leading property, or a missing pin), a `start_at` / `start_after` cursor, more than one `order_by`, or an `order_by` naming anything but the selected aggregate. + // - any non-empty `having` on protocol v13 and earlier; at v14+, any `having` shape outside having-range mode above (multiple clauses, an aggregate other than the select's, `NOT_EQUAL` / `IN` as the having operator, a `where` shape other than the compound-index prefix pins above — equality pins plus at most one bounded `IN` — or a carried `offset` / cursor). + // - at v14+: a ranked-shaped request carrying a `where` shape other than the compound-index prefix pins above (an operator other than `EQUAL` or the one permitted `IN`, more than one `IN`, a `null` pin combined with an `IN`, a repeated or non-leading property, or a missing pin), a `start_at` / `start_after` cursor, more than one `order_by`, or an `order_by` naming anything but the selected aggregate. // - `select=DOCUMENTS` with non-empty `group_by`. // - `select=COUNT` with `group_by` on a field that is not constrained by an `In` or range where clause. // - `select=COUNT` with `group_by.len() > 2`. diff --git a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs index 75e8bc75425..c85387ffb94 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs @@ -12,7 +12,7 @@ //! `pub mod execute_range;` declaration. use super::super::drive_document_ranked_query::branches::{ - decompose_branch_paths, merge_branch_pages, + axis_keys_to_ranked, decompose_branch_paths, merge_branch_pages, }; use super::super::drive_document_ranked_query::{RankedAxis, RankedEntry, RankedEntryValue}; use super::{AxisRangeBounds, DriveDocumentHavingQuery}; @@ -20,8 +20,9 @@ use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; use dpp::version::PlatformVersion; -use grovedb::PathQuery; +use grovedb::query_result_type::QueryResultType; use grovedb::TransactionArg; +use grovedb::{PathQuery, PathQueryRun}; use grovedb_costs::CostContext; use grovedb_query::AxisQuery; @@ -45,18 +46,60 @@ impl DriveDocumentHavingQuery<'_> { platform_version: &PlatformVersion, ) -> Result, Error> { if self.prefix_branches.len() > 1 { - // One bounded walk per branch, each fetching up to the full - // limit (the merge lemma needs every branch's own in-bound - // prefix), merged with the shared comparator. - let per_branch = (0..self.prefix_branches.len()) - .map(|branch| { - // Absent element = empty branch; same union - // semantics as the ranked surface and the proved - // path's authenticated absence. - if self.branch_is_absent(branch, drive, transaction, platform_version)? { - return Ok(Vec::new()); + // One grovedb call for the whole union — same single-snapshot + // rule as the ranked executor: absence at any depth is the + // branched reader's empty branch, and every branch page comes + // from one snapshot under the caller's transaction. + let grove_version = &platform_version.drive.grove_version; + let paths = (0..self.prefix_branches.len()) + .map(|branch| self.indexed_property_name_tree_path(branch)) + .collect::, Error>>()?; + let (prefix, keys, suffix) = decompose_branch_paths(&paths)?; + let axis = self.bounds.axis(); + let (lo, hi) = self.bounds.inclusive_bounds_i128(); + let path_query = PathQuery::new_branched_axis( + prefix, + keys.clone(), + suffix, + AxisQuery::bounded(axis.into(), lo, hi, self.limit, self.descending).keys_only(), + ); + let CostContext { value, cost: _ } = drive.grove.run_path_query( + &path_query, + true, + true, + true, + QueryResultType::QueryKeyElementPairResultType, + transaction, + grove_version, + ); + let run = value.map_err(|e| Error::GroveDB(Box::new(e)))?; + let PathQueryRun::BranchedAxisKeys(branches) = run else { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "a branched keys-only having read returned a non-branched shape".to_string(), + ))); + }; + if branches.len() != keys.len() || branches.iter().map(|(key, _)| key).ne(keys.iter()) { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "a branched having read returned a different branch set than the request \ + resolved" + .to_string(), + ))); + } + let per_branch = branches + .into_iter() + .map(|(_key, page)| { + let entries = match page { + None => Vec::new(), + Some(page) => axis_keys_to_ranked(axis, page)?, + }; + if entries.len() > self.limit as usize { + return Err(Error::Drive(DriveError::CorruptedDriveState(format!( + "a branch of a having read returned {} entries for limit = {}", + entries.len(), + self.limit + )))); } - self.execute_range_no_proof_branch(branch, drive, transaction, platform_version) + Ok(entries) }) .collect::, Error>>()?; return merge_branch_pages( @@ -69,31 +112,6 @@ impl DriveDocumentHavingQuery<'_> { self.execute_range_no_proof_branch(0, drive, transaction, platform_version) } - /// Whether this branch's key is absent at the branching Merk — see - /// the ranked sibling for the contract. - pub(crate) fn branch_is_absent( - &self, - branch: usize, - drive: &Drive, - transaction: TransactionArg, - platform_version: &PlatformVersion, - ) -> Result { - let grove_version = &platform_version.drive.grove_version; - let paths = (0..self.prefix_branches.len()) - .map(|b| self.indexed_property_name_tree_path(b)) - .collect::, Error>>()?; - let (prefix, _keys, _suffix) = - super::super::drive_document_ranked_query::branches::decompose_branch_paths(&paths)?; - let full_path = self.indexed_property_name_tree_path(branch)?; - super::super::drive_document_ranked_query::branches::branch_subpath_is_absent( - &drive.grove, - &full_path, - prefix.len(), - transaction, - grove_version, - ) - } - /// One branch's in-bound page — the entire pre-`IN` executor, /// parameterized by which prefix branch's terminal tree it walks. fn execute_range_no_proof_branch( diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs b/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs index 7f330714855..5268b2aecac 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs @@ -33,11 +33,7 @@ use crate::error::drive::DriveError; use crate::error::Error; use grovedb::operations::proof::indexed_axis::AxisEntries; #[cfg(feature = "server")] -use grovedb::{GroveDb, TransactionArg}; -#[cfg(feature = "server")] -use grovedb_costs::CostContext; -#[cfg(feature = "server")] -use grovedb_version::version::GroveVersion; +use grovedb::AxisKeys; use std::cmp::Ordering; /// The position (index into a branch's segment list) at which the @@ -182,41 +178,43 @@ pub fn decompose_branch_paths(paths: &[Vec>]) -> Result], - shared_prefix_len: usize, - transaction: TransactionArg, - grove_version: &GroveVersion, -) -> Result { - for depth in shared_prefix_len..full_path.len() { - let parent: Vec<&[u8]> = full_path[..depth].iter().map(|s| s.as_slice()).collect(); - let CostContext { value, cost: _ } = grove.get_raw_optional( - parent.as_slice().into(), - &full_path[depth], - transaction, - grove_version, - ); - if value.map_err(|e| Error::GroveDB(Box::new(e)))?.is_none() { - return Ok(true); - } +pub(crate) fn axis_keys_to_ranked( + axis: RankedAxis, + keys: AxisKeys, +) -> Result, Error> { + match (axis, keys) { + (RankedAxis::Count, AxisKeys::Count(pairs)) => Ok(pairs + .into_iter() + .map(|(count, key)| RankedEntry { + in_key: None, + key, + value: RankedEntryValue::Count(count), + }) + .collect()), + (RankedAxis::Sum, AxisKeys::Sum(pairs)) => Ok(pairs + .into_iter() + .map(|(sum, key)| RankedEntry { + in_key: None, + key, + value: RankedEntryValue::Sum(sum), + }) + .collect()), + (RankedAxis::Avg, AxisKeys::Avg(pairs)) => Ok(pairs + .into_iter() + .map(|(avg, key)| RankedEntry { + in_key: None, + key, + value: RankedEntryValue::AvgFixedPoint(avg), + }) + .collect()), + (axis, _) => Err(Error::Drive(DriveError::CorruptedDriveState(format!( + "a branch of a {axis:?} read returned keys of a different axis shape" + )))), } - Ok(false) } /// Translate one branch's verified [`AxisEntries`] into drive entries diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs index 5df97efdef9..a50edbc5672 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs @@ -10,13 +10,14 @@ //! Whole module is gated `feature = "server"` via the parent's //! `pub mod execute_top_k;` declaration. -use super::branches::{decompose_branch_paths, merge_branch_pages}; +use super::branches::{axis_keys_to_ranked, decompose_branch_paths, merge_branch_pages}; use super::{DriveDocumentRankedQuery, RankedAxis, RankedEntry, RankedEntryValue, RankedPage}; use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; use dpp::version::PlatformVersion; -use grovedb::{IndexedTopKKeysPage, PathQuery, TransactionArg}; +use grovedb::query_result_type::QueryResultType; +use grovedb::{IndexedTopKKeysPage, PathQuery, PathQueryRun, TransactionArg}; use grovedb_costs::CostContext; use grovedb_query::AxisQuery; @@ -77,29 +78,69 @@ impl DriveDocumentRankedQuery<'_> { platform_version: &PlatformVersion, ) -> Result { if self.prefix_branches.len() > 1 { - // One walk per branch, each fetching a full page (the merge - // lemma needs every branch's own top-k), merged with the - // shared comparator. `offset` is grammar-rejected with `IN`, - // so `skipped` is always 0 here. An `IN` element whose - // prefix was never written contributes the empty page — - // union semantics, decided at the branching Merk exactly - // like the proved path's authenticated absence (deeper - // breakage under a *present* key stays an error, and the - // single-`==`-pin contract is untouched: there, an unknown - // value is still an error, not an empty page). - let per_branch = (0..self.prefix_branches.len()) - .map(|branch| { - if self.branch_is_absent(branch, drive, transaction, platform_version)? { - return Ok(Vec::new()); + // The whole union is read through ONE grovedb call — a branched + // keys-only PathQuery — so every absence decision and every + // branch page comes from the same snapshot (and the same + // caller transaction): per-branch reads interleaved with a + // block commit could merge pages that never coexisted in one + // committed state. Absence at any depth of a branch's chain is + // the branched reader's empty branch, exactly as the proved + // path authenticates it. `offset` is grammar-rejected with + // `IN`, so `skipped` is always 0 here. + let grove_version = &platform_version.drive.grove_version; + let paths = (0..self.prefix_branches.len()) + .map(|branch| self.indexed_property_name_tree_path(branch)) + .collect::, Error>>()?; + let (prefix, keys, suffix) = decompose_branch_paths(&paths)?; + let path_query = PathQuery::new_branched_axis( + prefix, + keys.clone(), + suffix, + AxisQuery::top_k( + self.axis.into(), + self.k, + self.offset as u64, + self.descending, + ) + .keys_only(), + ); + let CostContext { value, cost: _ } = drive.grove.run_path_query( + &path_query, + true, + true, + true, + QueryResultType::QueryKeyElementPairResultType, + transaction, + grove_version, + ); + let run = value.map_err(|e| Error::GroveDB(Box::new(e)))?; + let PathQueryRun::BranchedAxisKeys(branches) = run else { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "a branched keys-only ranked read returned a non-branched shape".to_string(), + ))); + }; + if branches.len() != keys.len() || branches.iter().map(|(key, _)| key).ne(keys.iter()) { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "a branched ranked read returned a different branch set than the request \ + resolved" + .to_string(), + ))); + } + let per_branch = branches + .into_iter() + .map(|(_key, page)| { + let entries = match page { + None => Vec::new(), + Some(page) => axis_keys_to_ranked(self.axis, page)?, + }; + if entries.len() > self.k as usize { + return Err(Error::Drive(DriveError::CorruptedDriveState(format!( + "a branch of a ranked read returned {} entries for k = {}", + entries.len(), + self.k + )))); } - Ok(self - .execute_top_k_no_proof_branch( - branch, - drive, - transaction, - platform_version, - )? - .entries) + Ok(entries) }) .collect::, Error>>()?; let entries = merge_branch_pages( @@ -116,31 +157,6 @@ impl DriveDocumentRankedQuery<'_> { self.execute_top_k_no_proof_branch(0, drive, transaction, platform_version) } - /// Whether this branch's key is absent at the branching Merk — the - /// union's empty-set case. Mirrors the proved path, where grovedb - /// authenticates the same absence at the same level. - pub(crate) fn branch_is_absent( - &self, - branch: usize, - drive: &Drive, - transaction: TransactionArg, - platform_version: &PlatformVersion, - ) -> Result { - let grove_version = &platform_version.drive.grove_version; - let paths = (0..self.prefix_branches.len()) - .map(|b| self.indexed_property_name_tree_path(b)) - .collect::, Error>>()?; - let (prefix, _keys, _suffix) = super::branches::decompose_branch_paths(&paths)?; - let full_path = self.indexed_property_name_tree_path(branch)?; - super::branches::branch_subpath_is_absent( - &drive.grove, - &full_path, - prefix.len(), - transaction, - grove_version, - ) - } - /// One branch's page — the entire pre-`IN` executor, parameterized /// by which prefix branch's terminal tree it walks. fn execute_top_k_no_proof_branch( diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs b/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs index db558511249..c23aa59002c 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs @@ -27,7 +27,7 @@ use std::collections::BTreeMap; /// - every **leading** property is pinned: each appears (by name) among /// `equality_pin_fields`. Lengths matching plus the pins being /// distinct (enforced upstream by -/// [`super::mode_detection::equality_pins_from_where_clauses`]) makes +/// [`super::mode_detection::prefix_pins_from_where_clauses`]) makes /// this set equality, so no pin is left over either; /// - it declares the ranking keyword for `axis` /// ([`RankedAxis::required_index_keyword`]); @@ -245,6 +245,21 @@ pub fn encode_prefix_branches( platform_version: &PlatformVersion, ) -> Result>>, Error> { let leading = &index.properties[..index.properties.len().saturating_sub(1)]; + // Enforced BEFORE any encoding: the ceiling bounds every downstream + // cost (encode, sort, clone, walk, proof size), so an oversized pin + // must not buy that work first. The post-product branch count check + // below stays as a backstop. + if prefix_pins + .iter() + .any(|pin| pin.values.len() > super::MAX_PREFIX_IN_BRANCHES) + { + return Err(Error::Query( + QuerySyntaxError::InvalidWhereClauseComponents( + "an `IN` prefix pin fans out into more branches than the ranked surface serves \ + — narrow the element list or issue several requests", + ), + )); + } let per_property: Vec>> = leading .iter() .map(|property| { diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs b/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs index 65ee04e882e..f78308962a9 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs @@ -91,7 +91,7 @@ use dpp::platform_value::Value; pub use grovedb::element::indexed::AVG_FIXED_POINT_SCALE as RANKED_AVG_SCALE; #[cfg(any(feature = "server", feature = "verify"))] -pub mod branches; +pub(crate) mod branches; #[cfg(any(feature = "server", feature = "verify"))] pub mod index_picker; #[cfg(any(feature = "server", feature = "verify"))] @@ -144,8 +144,8 @@ pub const MAX_RANKED_LIMIT: u16 = 100; /// so worst-case proof size is `MAX_PREFIX_IN_BRANCHES × /// MAX_RANKED_LIMIT` entries (≈100–150 KB at the ceiling). A hard /// rejection rather than a clamp, for the same reason as the limit: the -/// branch set is echoed in the proof container and re-checked by the -/// verifier. +/// branch set is bound into the branched proof envelope and re-checked +/// by the verifier. #[cfg(any(feature = "server", feature = "verify"))] pub const MAX_PREFIX_IN_BRANCHES: usize = 10; diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs index 46c9c2878aa..8f1d89441cf 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs @@ -3178,6 +3178,105 @@ mod pinned_prefix { ); } + /// The branched unproved read is ONE grovedb call executed under the + /// caller's transaction — every absence decision and branch page from + /// one snapshot. A branch written only inside the transaction is + /// visible through it and invisible without it. + #[test] + fn a_branched_unproved_read_honors_the_transaction() { + let (drive, contract) = setup_grades_compound_ranked(); + let pv = platform_version(); + insert_grades(&drive, &contract, &[(IDENTITY_X, "math", 80)]); + + let transaction = drive.grove.start_transaction(); + let document_type = contract + .document_type_for_name(DOCUMENT_TYPE) + .expect("grade doctype exists"); + let mut doc: Document = document_type + .random_document(Some(9000), pv) + .expect("random document"); + let mut props = BTreeMap::new(); + props.insert(PREFIX_PROPERTY.to_string(), Value::Identifier(IDENTITY_Y)); + props.insert( + CLASS_PROPERTY.to_string(), + Value::Text("science".to_string()), + ); + props.insert("grade".to_string(), Value::I64(95)); + doc.set_properties(props); + drive + .add_document_for_contract( + DocumentAndContractInfo { + owned_document_info: OwnedDocumentInfo { + document_info: DocumentRefInfo((&doc, None)), + owner_id: None, + }, + contract: &contract, + document_type, + }, + false, + BlockInfo::default(), + true, + Some(&transaction), + pv, + None, + ) + .expect("expected to insert Y's grade inside the transaction"); + + let pins = in_pin(&[IDENTITY_X, IDENTITY_Y]); + let group_by = vec![CLASS_PROPERTY.to_string()]; + let order_by = vec![OrderClause { + field: "grade".to_string(), + ascending: false, + }]; + let request = || DocumentRankedRequest { + contract: &contract, + document_type, + group_by: &group_by, + select: SelectProjection::avg("grade"), + having: &[], + order_by: &order_by, + where_clauses: &pins, + limit: Some(4), + offset: None, + has_start_at: false, + prove: false, + }; + + let with_tx = match drive + .execute_document_ranked_request(request(), Some(&transaction), pv) + .expect("the transactional branched read serves") + { + DocumentRankedResponse::Entries(page) => page, + DocumentRankedResponse::Proof(_) => panic!("expected entries"), + }; + assert_eq!( + with_tx + .entries + .iter() + .map(|e| e.in_key.clone()) + .collect::>(), + vec![Some(IDENTITY_Y.to_vec()), Some(IDENTITY_X.to_vec())], + "under the transaction both branches contribute (Y's 95 outranks X's 80)" + ); + + let without_tx = match drive + .execute_document_ranked_request(request(), None, pv) + .expect("the committed branched read serves") + { + DocumentRankedResponse::Entries(page) => page, + DocumentRankedResponse::Proof(_) => panic!("expected entries"), + }; + assert_eq!( + without_tx + .entries + .iter() + .map(|e| e.in_key.clone()) + .collect::>(), + vec![Some(IDENTITY_X.to_vec())], + "without the transaction Y's uncommitted branch is authenticated absent" + ); + } + /// grovedb's unified `prove_query` proves committed state only, so an /// `IN`-pinned prove under a caller transaction fails closed instead of /// silently proving a different snapshot than the unproved read serves. From 28a991687fafc5684f2ead0e1fcf291bf40a195f Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 25 Aug 2026 13:47:50 +0200 Subject: [PATCH 09/25] docs(drive): align remaining wording with deep absence, bounded IN, and the branched envelope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review: the executor doc no longer calls a missing path under a present branch key an error (it is the empty branch, at any depth); the book's compound-prefix paragraph, both request structs' `where_clauses` comments and the shared path-builder docs admit the single bounded `IN` alongside equality pins; the ABCI wire test names the unified branched `PathQuery` envelope instead of a container. The round-3 executor comments also no longer claim a storage-level snapshot for `None` reads: grovedb has no snapshot-pinned read primitive, so the comment now states the actual model — one grovedb call under the caller's transaction, DAPI's committed-height guard operationally, and `prove = true` for authenticated cross-branch consistency. Co-Authored-By: Claude Fable 5 --- book/src/drive/document-ranked-trees.md | 2 +- .../src/query/document_query/v1/tests.rs | 8 ++--- .../drive_dispatcher.rs | 6 ++-- .../execute_range.rs | 10 +++--- .../drive_dispatcher.rs | 6 ++-- .../execute_top_k.rs | 33 +++++++++++-------- .../query/drive_document_ranked_query/path.rs | 10 +++--- 7 files changed, 43 insertions(+), 32 deletions(-) diff --git a/book/src/drive/document-ranked-trees.md b/book/src/drive/document-ranked-trees.md index 8fd8c8ff549..9590e6c30f7 100644 --- a/book/src/drive/document-ranked-trees.md +++ b/book/src/drive/document-ranked-trees.md @@ -231,7 +231,7 @@ Every ranked read — and, on the prove path, every ranked proof — is issued a / // e.g. b"restaurantId" ``` -The children of that tree are the *groups*: one value tree per distinct value of the last index property, keyed by the raw index-key bytes of that value (for a `string` property, its UTF-8 bytes — e.g. `b"alpha"`). The secondary entries a top-k read returns are keyed by those same group keys. A compound index `[a, b]` inserts ` / ` between the doctype and the terminal `` level — the value segment comes from the request's equality `where` pin on `a`, encoded with the same `serialize_value_for_key` the write path used to key that prefix's value tree, so the walk lands on **that prefix's own** indexed tree and secondary. +The children of that tree are the *groups*: one value tree per distinct value of the last index property, keyed by the raw index-key bytes of that value (for a `string` property, its UTF-8 bytes — e.g. `b"alpha"`). The secondary entries a top-k read returns are keyed by those same group keys. A compound index `[a, b]` inserts ` / ` between the doctype and the terminal `` level — the value segment comes from the request's `where` pin on `a` — an equality pin, or one element of the single permitted `IN` (one branch per element) — encoded with the same `serialize_value_for_key` the write path used to key that prefix's value tree, so the walk lands on **that prefix's own** indexed tree and secondary. Prover and verifier build this path through the same function, `DriveDocumentRankedQuery::indexed_property_name_tree_path` (with the pinned prefix values encoded by the shared resolver, `resolve_ranked_query_for_mode`), which is why they agree on the root hash by construction. diff --git a/packages/rs-drive-abci/src/query/document_query/v1/tests.rs b/packages/rs-drive-abci/src/query/document_query/v1/tests.rs index 4af467aef5c..8949e97450c 100644 --- a/packages/rs-drive-abci/src/query/document_query/v1/tests.rs +++ b/packages/rs-drive-abci/src/query/document_query/v1/tests.rs @@ -3482,7 +3482,7 @@ mod having_range_tests { /// IN [X, Y] GROUP BY class HAVING AVG(grade) > 80 LIMIT 10` fans /// out across both identities' secondaries and answers one merged /// `ResultData.ranked` page whose entries carry `in_key`; the - /// proved variant returns the branch container as its Proof + /// proved variant returns the unified branched `PathQuery` envelope as its Proof /// payload. Merge/proof semantics are pinned in rs-drive's suites; /// this pins the wire encoding, routing, and `in_key` mapping. #[test] @@ -3543,9 +3543,9 @@ mod having_range_tests { "merged entries carry their branch's in_key on the wire" ); - // The proved variant answers with a Proof payload (the branch - // container — decoded and verified client-side, pinned in - // rs-drive's tamper suite). + // The proved variant answers with a Proof payload (the unified + // branched `PathQuery` envelope — verified client-side, pinned + // in rs-drive's tamper suite). request.prove = true; let result = platform .query_documents_v1(request, &state, version) diff --git a/packages/rs-drive/src/query/drive_document_having_query/drive_dispatcher.rs b/packages/rs-drive/src/query/drive_document_having_query/drive_dispatcher.rs index b484460081b..e00d38feb14 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/drive_dispatcher.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/drive_dispatcher.rs @@ -44,8 +44,10 @@ pub struct DocumentHavingRequest<'a> { /// one, naming the selected aggregate. pub order_by: &'a [OrderClause], /// Structured `where` clauses. Empty for the single-property form; - /// equality pins on the covering compound index's leading - /// properties for the pinned-prefix form. + /// pins on the covering compound index's leading properties for + /// the pinned-prefix form: one equality pin per property, of which + /// at most one may instead be a bounded `IN` (one branch per + /// element, merged; entries then carry `in_key`). pub where_clauses: &'a [WhereClause], /// Request `limit`. **Required**; `1 ..= MAX_HAVING_LIMIT`. pub limit: Option, diff --git a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs index c85387ffb94..f363e9e395a 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs @@ -46,10 +46,12 @@ impl DriveDocumentHavingQuery<'_> { platform_version: &PlatformVersion, ) -> Result, Error> { if self.prefix_branches.len() > 1 { - // One grovedb call for the whole union — same single-snapshot - // rule as the ranked executor: absence at any depth is the - // branched reader's empty branch, and every branch page comes - // from one snapshot under the caller's transaction. + // One grovedb call for the whole union under the caller's + // transaction — same read-consistency model as the ranked + // executor (see its comment: no storage-level snapshot for + // `None` reads exists in grovedb yet; DAPI's committed-height + // guard and the proved path carry consistency). Absence at any + // depth is the branched reader's empty branch. let grove_version = &platform_version.drive.grove_version; let paths = (0..self.prefix_branches.len()) .map(|branch| self.indexed_property_name_tree_path(branch)) diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/drive_dispatcher.rs b/packages/rs-drive/src/query/drive_document_ranked_query/drive_dispatcher.rs index 23bd902fe6e..694443d2ab5 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/drive_dispatcher.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/drive_dispatcher.rs @@ -61,8 +61,10 @@ pub struct DocumentRankedRequest<'a> { /// field); its direction is the ranking direction. pub order_by: &'a [OrderClause], /// Structured `where` clauses. Empty for the single-property form; - /// equality pins on the covering compound index's leading - /// properties for the pinned-prefix form. + /// pins on the covering compound index's leading properties for + /// the pinned-prefix form: one equality pin per property, of which + /// at most one may instead be a bounded `IN` (one branch per + /// element, merged; entries then carry `in_key`). pub where_clauses: &'a [WhereClause], /// Request `limit` — the ranking's `k`. **Required**; there is no /// server default a verifying client could reproduce. diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs index a50edbc5672..b4b0e3cf020 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs @@ -29,15 +29,16 @@ impl DriveDocumentRankedQuery<'_> { /// /// Fewer than `k` entries is normal (the index simply has fewer /// groups than `offset + k`) and is not an error. On an `IN`-pinned - /// request, an element whose prefix was never written contributes - /// an **empty branch** (union semantics). A missing path under a - /// single `==` pin — or under a *present* branch key — *is* an - /// error rather than an empty result: the indexed - /// property-name tree is created when the contract is registered, so - /// its absence means the contract-level state is not what the - /// request claims, not that the ranking is empty. (An index with no - /// documents yet has the tree, with an empty secondary, and yields - /// an empty entry list.) + /// request, an element whose branch chain is missing at ANY depth — + /// the branch key itself, or any deeper pinned segment under a + /// *present* key — contributes an **empty branch** (union + /// semantics), exactly as the proved envelope authenticates it. A + /// missing path under a single `==` pin *is* an error rather than + /// an empty result: the indexed property-name tree is created when + /// the contract is registered, so its absence means the + /// contract-level state is not what the request claims, not that + /// the ranking is empty. (An index with no documents yet has the + /// tree, with an empty secondary, and yields an empty entry list.) /// /// The paginated grovedb primitive is used unconditionally, with /// `offset = 0` standing in for an unpaginated request, so the @@ -79,11 +80,15 @@ impl DriveDocumentRankedQuery<'_> { ) -> Result { if self.prefix_branches.len() > 1 { // The whole union is read through ONE grovedb call — a branched - // keys-only PathQuery — so every absence decision and every - // branch page comes from the same snapshot (and the same - // caller transaction): per-branch reads interleaved with a - // block commit could merge pages that never coexisted in one - // committed state. Absence at any depth of a branch's chain is + // keys-only PathQuery — under the caller's transaction, the + // narrowest read window grovedb offers an unproved read today + // (a `None` read is NOT a storage-level snapshot: grovedb has + // no snapshot-pinned read primitive yet, so as with every + // cross-subtree unproved read, torn reads across a concurrent + // commit are excluded operationally by DAPI's committed-height + // guard, and clients needing authenticated cross-branch + // consistency use `prove = true`, whose envelope binds one + // committed root). Absence at any depth of a branch's chain is // the branched reader's empty branch, exactly as the proved // path authenticates it. `offset` is grammar-rejected with // `IN`, so `skipped` is always 0 here. diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/path.rs b/packages/rs-drive/src/query/drive_document_ranked_query/path.rs index ddb5380d79c..05d0ba0d037 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/path.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/path.rs @@ -29,9 +29,9 @@ use dpp::data_contract::document_type::Index; /// per property before the terminal one. Empty for a single-property /// index. The arity must match exactly: a compound index's terminal /// tree sits under one prefix value tree per leading property, and only -/// an equality `where` clause can name those values, so a missing or -/// surplus value means the caller resolved the wrong index — a typed -/// error, not a guess. +/// a `where` pin (an equality, or one element of the single permitted +/// `IN`) can name those values, so a missing or surplus value means the +/// caller resolved the wrong index — a typed error, not a guess. pub(crate) fn indexed_property_name_tree_path_for_index( contract_id: &[u8; 32], document_type_name: &str, @@ -49,8 +49,8 @@ pub(crate) fn indexed_property_name_tree_path_for_index( "ranked and having-range queries over a compound index require exactly one \ encoded equality value per leading index property: the axis secondary lives \ on the index's terminal property-name tree, which for a compound index sits \ - under one prefix value tree per leading property, and only an equality \ - `where` clause can name those values", + under one prefix value tree per leading property, and only a `where` pin (an \ + equality, or one element of the single permitted `IN`) can name those values", ))); } let mut path = Vec::with_capacity(5 + 2 * leading_properties.len()); From 8bdc0625fc74438303a12083c74e4f5b1fb6b76e Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 25 Aug 2026 14:01:15 +0200 Subject: [PATCH 10/25] docs(drive,dapi-grpc): singleton-IN normalization, non-zero-offset wording, test strengthening MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review: the wire contract, the book row, the ranked module docs and the mirrored Objective-C client comment now state that a single-element IN normalizes to the equality pin, and that it is a NON-ZERO offset that is rejected together with IN (OFFSET 0 is the offset-free request — rejecting it would contradict the pinned absent-offset equivalence, so the docs follow the guard rather than the guard tightening). The singleton-IN test now reads the IN spelling too and requires byte-identical pages; the transactional-prove test's follow-up read is described as the committed read it is; the renamed having test's doc describes branch merging instead of the old rejection. The Objective-C header's grammar block is hand-synced with the proto comment it embeds (comment-only divergence left over from the earlier proto edit). Co-Authored-By: Claude Fable 5 --- book/src/drive/document-ranked-trees.md | 2 +- .../platform/v0/objective-c/Platform.pbobjc.h | 6 +++--- .../dapi-grpc/protos/platform/v0/platform.proto | 2 +- .../src/query/drive_document_having_query/tests.rs | 8 ++++---- .../src/query/drive_document_ranked_query/mod.rs | 3 ++- .../src/query/drive_document_ranked_query/tests.rs | 14 ++++++++++++-- 6 files changed, 23 insertions(+), 12 deletions(-) diff --git a/book/src/drive/document-ranked-trees.md b/book/src/drive/document-ranked-trees.md index 9590e6c30f7..41f97f23c62 100644 --- a/book/src/drive/document-ranked-trees.md +++ b/book/src/drive/document-ranked-trees.md @@ -341,7 +341,7 @@ Note that the fixture puts each shape on its **own document type**. That's not a | Top / bottom K groups by sum of a property | `rankedSummable: true` on an index with `summable: ""` + `rangeSummable: true` | | Top / bottom K groups by average of a property | `rankedAverageable: true` on an index with `averageable: ""` + `rangeAverageable: true` (or the count+sum longhand) | | Two rankings on one index (e.g. by count *and* by average) | Both keywords. The tree is a PCPSIT carrying both axes in its TLV; you pay one secondary Merk per axis on every write. | -| A ranking filtered by another property (`top 5 restaurants in London`) | A **compound ranked index** with the filter property leading: `[city, restaurantId]` with the ranked flags. Each city gets its own secondary; the query pins the prefix with an equality `where` (`WHERE city == "London" GROUP BY restaurantId ORDER BY DESC LIMIT 5`). Equality pins select one prefix; at most one pin may be an `IN` (2..=10 distinct elements; a never-written element — or one whose deeper pinned path was never written — contributes an empty branch), which walks one secondary per element and merges by `(aggregate, encoded prefix, group key)`, proved in one branched `PathQuery` envelope (shared ancestors proved once, per-element authenticated absence) — entries then carry `in_key`. A `null` pin stays legal on its own but cannot combine with an `IN` (null addresses its prefix through an empty path segment the branched proof cannot express), and branched proofs are generated from committed state only. A range operator on the prefix stays rejected, `OFFSET` is rejected together with `IN`, and there is still no global cross-prefix ordering beyond that merge. | +| A ranking filtered by another property (`top 5 restaurants in London`) | A **compound ranked index** with the filter property leading: `[city, restaurantId]` with the ranked flags. Each city gets its own secondary; the query pins the prefix with an equality `where` (`WHERE city == "London" GROUP BY restaurantId ORDER BY DESC LIMIT 5`). Equality pins select one prefix; at most one pin may be an `IN` (2..=10 distinct elements; a never-written element — or one whose deeper pinned path was never written — contributes an empty branch), which walks one secondary per element and merges by `(aggregate, encoded prefix, group key)`, proved in one branched `PathQuery` envelope (shared ancestors proved once, per-element authenticated absence) — entries then carry `in_key`. A single-element `IN` normalizes to the equality pin; a `null` pin stays legal on its own but cannot combine with an `IN` (null addresses its prefix through an empty path segment the branched proof cannot express); a non-zero `OFFSET` is rejected together with `IN`; and branched proofs are generated from committed state only. A range operator on the prefix stays rejected, a non-zero `OFFSET` is rejected together with `IN`, and there is still no global cross-prefix ordering beyond that merge. | | A ranking on a unique or contested index | Not available, and not meaningful: every group holds at most one document. | | Range aggregates without ranking (the 4.0 surface) | Just the `range*` flags. Ranking is strictly additive — adding it never changes what a range query returns. | | Nothing ranking-aware (default) | Don't set any `ranked*` flag. The terminal property-name tree keeps the type its range flags give it. | diff --git a/packages/dapi-grpc/clients/platform/v0/objective-c/Platform.pbobjc.h b/packages/dapi-grpc/clients/platform/v0/objective-c/Platform.pbobjc.h index cb78d1656b0..e9cbdbbe832 100644 --- a/packages/dapi-grpc/clients/platform/v0/objective-c/Platform.pbobjc.h +++ b/packages/dapi-grpc/clients/platform/v0/objective-c/Platform.pbobjc.h @@ -2941,7 +2941,7 @@ typedef GPB_ENUM(GetDocumentsRequest_GetDocumentsRequestV1_Start_OneOfCase) { * - a is the In field AND b is the range field, in that order → existing compound distinct shape; entries carry both `in_key` (= a's value) and `key` (= b's value). * * `select=, group_by=[p], order_by=[]` (protocol v14+) — **ranked mode**: - * - exactly one `group_by` property, exactly one `order_by` clause naming the select's aggregate (`f` for `SUM(f)` / `AVG(f)`, the `$count` sentinel for `COUNT(*)`), a `limit` in `1 ..= 100`, an optional `offset`, and no `having` / `start_at`, on an index declaring the matching `rankedCountable` / `rankedSummable` / `rankedAverageable` axis → ranked executor, answered in `ResultData.ranked`. On a single-property ranked index no `where` is accepted; on a compound ranked index every leading index property must be pinned (one clause per property, `group_by` names the trailing property) — `EQUAL` pins one prefix, and **at most one** clause may be `IN` (2..=10 distinct elements, `null` legal), fanning the walk out across one prefix branch per element and merging by `(aggregate, encoded prefix, group key)`; merged entries carry `in_key`. `offset` is rejected together with `IN` (rank-skip is per-secondary). + * - exactly one `group_by` property, exactly one `order_by` clause naming the select's aggregate (`f` for `SUM(f)` / `AVG(f)`, the `$count` sentinel for `COUNT(*)`), a `limit` in `1 ..= 100`, an optional `offset`, and no `having` / `start_at`, on an index declaring the matching `rankedCountable` / `rankedSummable` / `rankedAverageable` axis → ranked executor, answered in `ResultData.ranked`. On a single-property ranked index no `where` is accepted; on a compound ranked index every leading index property must be pinned (one clause per property, `group_by` names the trailing property) — `EQUAL` pins one prefix, and **at most one** clause may be `IN` (2..=10 distinct elements, `null` legal; a single-element `IN` normalizes to the equality pin; an element whose prefix was never written — at any depth of its pinned chain — contributes an empty branch, union semantics), fanning the walk out across one prefix branch per element and merging by `(aggregate, encoded prefix, group key)`; merged entries carry `in_key`. A non-zero `offset` is rejected together with `IN` (rank-skip is per-secondary; `OFFSET 0` is the offset-free request). * - `DESC` is the "top n" reading (walk the axis from the largest aggregate down), `ASC` the "bottom n" reading. Worked example: `SELECT AVG(grade) GROUP BY restaurantId ORDER BY grade DESC LIMIT 1 OFFSET 4` is the 5th-best restaurant. * * `select=, group_by=[p], having=[ ]` (protocol v14+) — **having-range mode**: @@ -2949,8 +2949,8 @@ typedef GPB_ENUM(GetDocumentsRequest_GetDocumentsRequestV1_Start_OneOfCase) { * - no offset or cursor pagination: a page cut at `limit` continues only by tightening the bound past the last *distinct* aggregate value seen; a cut inside a tie (several groups sharing the boundary aggregate) cannot be continued, so size `limit` above the widest expected tie. * * **Rejected shapes** (return `Unsupported`): - * - any non-empty `having` on protocol v13 and earlier; at v14+, any `having` shape outside having-range mode above (multiple clauses, an aggregate other than the select's, `NOT_EQUAL` / `IN`, a `where` shape other than the compound-index equality pins above, or a carried `offset` / cursor). - * - at v14+: a ranked-shaped request carrying a `where` shape other than the compound-index equality pins above (a non-`EQUAL` operator, a repeated or non-leading property, or a missing pin), a `start_at` / `start_after` cursor, more than one `order_by`, or an `order_by` naming anything but the selected aggregate. + * - any non-empty `having` on protocol v13 and earlier; at v14+, any `having` shape outside having-range mode above (multiple clauses, an aggregate other than the select's, `NOT_EQUAL` / `IN` as the having operator, a `where` shape other than the compound-index prefix pins above — equality pins plus at most one bounded `IN` — or a carried `offset` / cursor). + * - at v14+: a ranked-shaped request carrying a `where` shape other than the compound-index prefix pins above (an operator other than `EQUAL` or the one permitted `IN`, more than one `IN`, a `null` pin combined with an `IN`, a repeated or non-leading property, or a missing pin), a `start_at` / `start_after` cursor, more than one `order_by`, or an `order_by` naming anything but the selected aggregate. * - `select=DOCUMENTS` with non-empty `group_by`. * - `select=COUNT` with `group_by` on a field that is not constrained by an `In` or range where clause. * - `select=COUNT` with `group_by.len() > 2`. diff --git a/packages/dapi-grpc/protos/platform/v0/platform.proto b/packages/dapi-grpc/protos/platform/v0/platform.proto index e02b9fd0907..b83820dd349 100644 --- a/packages/dapi-grpc/protos/platform/v0/platform.proto +++ b/packages/dapi-grpc/protos/platform/v0/platform.proto @@ -893,7 +893,7 @@ message GetDocumentsRequest { // - a is the In field AND b is the range field, in that order → existing compound distinct shape; entries carry both `in_key` (= a's value) and `key` (= b's value). // // `select=, group_by=[p], order_by=[]` (protocol v14+) — **ranked mode**: - // - exactly one `group_by` property, exactly one `order_by` clause naming the select's aggregate (`f` for `SUM(f)` / `AVG(f)`, the `$count` sentinel for `COUNT(*)`), a `limit` in `1 ..= 100`, an optional `offset`, and no `having` / `start_at`, on an index declaring the matching `rankedCountable` / `rankedSummable` / `rankedAverageable` axis → ranked executor, answered in `ResultData.ranked`. On a single-property ranked index no `where` is accepted; on a compound ranked index every leading index property must be pinned (one clause per property, `group_by` names the trailing property) — `EQUAL` pins one prefix, and **at most one** clause may be `IN` (2..=10 distinct elements, `null` legal; an element whose prefix was never written contributes an empty branch — union semantics), fanning the walk out across one prefix branch per element and merging by `(aggregate, encoded prefix, group key)`; merged entries carry `in_key`. `offset` is rejected together with `IN` (rank-skip is per-secondary). + // - exactly one `group_by` property, exactly one `order_by` clause naming the select's aggregate (`f` for `SUM(f)` / `AVG(f)`, the `$count` sentinel for `COUNT(*)`), a `limit` in `1 ..= 100`, an optional `offset`, and no `having` / `start_at`, on an index declaring the matching `rankedCountable` / `rankedSummable` / `rankedAverageable` axis → ranked executor, answered in `ResultData.ranked`. On a single-property ranked index no `where` is accepted; on a compound ranked index every leading index property must be pinned (one clause per property, `group_by` names the trailing property) — `EQUAL` pins one prefix, and **at most one** clause may be `IN` (2..=10 distinct elements, `null` legal; a single-element `IN` normalizes to the equality pin; an element whose prefix was never written — at any depth of its pinned chain — contributes an empty branch, union semantics), fanning the walk out across one prefix branch per element and merging by `(aggregate, encoded prefix, group key)`; merged entries carry `in_key`. A non-zero `offset` is rejected together with `IN` (rank-skip is per-secondary; `OFFSET 0` is the offset-free request). // - `DESC` is the "top n" reading (walk the axis from the largest aggregate down), `ASC` the "bottom n" reading. Worked example: `SELECT AVG(grade) GROUP BY restaurantId ORDER BY grade DESC LIMIT 1 OFFSET 4` is the 5th-best restaurant. // // `select=, group_by=[p], having=[ ]` (protocol v14+) — **having-range mode**: diff --git a/packages/rs-drive/src/query/drive_document_having_query/tests.rs b/packages/rs-drive/src/query/drive_document_having_query/tests.rs index 17e086c2197..a489fa4a25c 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/tests.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/tests.rs @@ -1894,10 +1894,10 @@ mod pinned_prefix { } } - /// `IN` on the prefix is rejected at detection with the - /// not-yet-supported message (v1 pins are equality-only), and a pin - /// on a property that is not the index's leading property fails - /// resolution. + /// `IN` on the leading prefix property resolves to one branch per + /// element, each bounded separately and merged (entries carrying + /// `in_key`), while a pin on a property that is not the index's + /// leading property still fails resolution. #[test] fn in_prefix_merges_branches_and_wrong_pins_are_rejected() { let (drive, contract) = setup_grades_compound_ranked(); diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs b/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs index f78308962a9..dd3f9e31007 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs @@ -52,7 +52,8 @@ //! by aggregate, not by group key — and is rejected rather than //! silently ignored, as is any non-equality prefix clause except one //! `IN`: exactly one leading pin may carry 2..=[`MAX_PREFIX_IN_BRANCHES`] -//! distinct elements, read as one walk per element and merged by +//! distinct elements (a single-element `IN` normalizes to the +//! equality pin), read as one walk per element and merged by //! `(aggregate, encoded pin, group key)`, proved in a single branched //! `PathQuery` envelope with per-element authenticated absence. A //! `null` pin cannot combine with an `IN` (null addresses its prefix diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs index 8f1d89441cf..0a81abdbf58 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs @@ -2746,6 +2746,14 @@ mod pinned_prefix { DocumentRankedResponse::Entries(page) => page, DocumentRankedResponse::Proof(_) => panic!("expected entries"), }; + let in_page = match run(&drive, &contract, &single_in, 2, false).expect("read") { + DocumentRankedResponse::Entries(page) => page, + DocumentRankedResponse::Proof(_) => panic!("expected entries"), + }; + assert_eq!( + eq_page.entries, in_page.entries, + "a singleton IN reads exactly what the equality pin reads" + ); assert!( eq_page.entries.iter().all(|e| e.in_key.is_none()), "single-branch entries carry no in_key" @@ -3322,9 +3330,11 @@ mod pinned_prefix { "expected the committed-state-only rejection, got {error:?}" ); - // The unproved read under the same transaction still serves. + // The unproved read is unaffected by the failed prove; it serves + // committed state (the transactional case is pinned by + // `a_branched_unproved_read_honors_the_transaction`). let page = match run(&drive, &contract, &pins, 2, false) - .expect("the unproved read is unaffected") + .expect("the committed unproved read is unaffected") { DocumentRankedResponse::Entries(page) => page, DocumentRankedResponse::Proof(_) => panic!("expected entries"), From 7b29ea7ebd32f88b56feb8532236ffa65e827d77 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 25 Aug 2026 14:38:00 +0200 Subject: [PATCH 11/25] fix(drive): validate branched reads against one committed root The single branched grovedb call is not yet a single-snapshot read at the pinned grovedb revision: its branched arm forwards the caller's TransactionArg to each per-branch suffix probe and each axis walk, so the production None path opens one implicit transaction per operation - and even a hoisted optimistic transaction would not pin a snapshot, since its reads see latest committed state. A block commit landing inside the call could still merge branch pages from states that never coexisted. Both surfaces' multi-branch reads therefore run inside a root-hash bracket - optimistic concurrency validation: read the committed root hash, run the whole union, re-read the hash, accept only an untorn window. Equal endpoint hashes mean no commit interleaved (every Platform commit advances monotonic block metadata, so a window cannot tear back to a byte-identical root), which is the same guarantee a pinned snapshot would give, delivered by detection rather than new storage-layer machinery. A torn window is discarded whole - its page or error may both be artifacts of the tear - and retried; persistent churn fails closed after three windows with the new retryable DriveError::ConcurrentStateChurn instead of looping. Deterministic regression tests drive commits into the window through the production None path: one tear retries and serves the stable window, endless tears exhaust the budget and fail closed. Co-Authored-By: Claude Fable 5 --- packages/rs-drive/src/error/drive.rs | 7 + .../execute_range.rs | 105 ++++++++------- .../drive_document_ranked_query/branches.rs | 70 +++++++++- .../execute_top_k.rs | 124 ++++++++++-------- .../drive_document_ranked_query/tests.rs | 82 ++++++++++++ 5 files changed, 284 insertions(+), 104 deletions(-) diff --git a/packages/rs-drive/src/error/drive.rs b/packages/rs-drive/src/error/drive.rs index 3412c9df1aa..79b505e9f3c 100644 --- a/packages/rs-drive/src/error/drive.rs +++ b/packages/rs-drive/src/error/drive.rs @@ -147,6 +147,13 @@ pub enum DriveError { #[error("corrupted drive state error: {0}")] CorruptedDriveState(String), + /// A multi-operation read raced concurrent block commits and could + /// not observe one committed state within its retry budget. Nothing + /// is corrupted — the condition is transient and the request is safe + /// to retry. + #[error("concurrent state churn during a read: {0}")] + ConcurrentStateChurn(&'static str), + /// Error #[error("corrupted cache state error: {0}")] CorruptedCacheState(String), diff --git a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs index f363e9e395a..4454fa51bba 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs @@ -12,7 +12,7 @@ //! `pub mod execute_range;` declaration. use super::super::drive_document_ranked_query::branches::{ - axis_keys_to_ranked, decompose_branch_paths, merge_branch_pages, + axis_keys_to_ranked, decompose_branch_paths, merge_branch_pages, read_branches_at_one_root, }; use super::super::drive_document_ranked_query::{RankedAxis, RankedEntry, RankedEntryValue}; use super::{AxisRangeBounds, DriveDocumentHavingQuery}; @@ -47,10 +47,10 @@ impl DriveDocumentHavingQuery<'_> { ) -> Result, Error> { if self.prefix_branches.len() > 1 { // One grovedb call for the whole union under the caller's - // transaction — same read-consistency model as the ranked - // executor (see its comment: no storage-level snapshot for - // `None` reads exists in grovedb yet; DAPI's committed-height - // guard and the proved path carry consistency). Absence at any + // transaction, bracketed against the committed root hash — + // same one-committed-state contract as the ranked executor + // (see its comment and the ranked surface's + // `branches::read_branches_at_one_root`). Absence at any // depth is the branched reader's empty branch. let grove_version = &platform_version.drive.grove_version; let paths = (0..self.prefix_branches.len()) @@ -65,51 +65,56 @@ impl DriveDocumentHavingQuery<'_> { suffix, AxisQuery::bounded(axis.into(), lo, hi, self.limit, self.descending).keys_only(), ); - let CostContext { value, cost: _ } = drive.grove.run_path_query( - &path_query, - true, - true, - true, - QueryResultType::QueryKeyElementPairResultType, - transaction, - grove_version, - ); - let run = value.map_err(|e| Error::GroveDB(Box::new(e)))?; - let PathQueryRun::BranchedAxisKeys(branches) = run else { - return Err(Error::Drive(DriveError::CorruptedDriveState( - "a branched keys-only having read returned a non-branched shape".to_string(), - ))); - }; - if branches.len() != keys.len() || branches.iter().map(|(key, _)| key).ne(keys.iter()) { - return Err(Error::Drive(DriveError::CorruptedDriveState( - "a branched having read returned a different branch set than the request \ - resolved" - .to_string(), - ))); - } - let per_branch = branches - .into_iter() - .map(|(_key, page)| { - let entries = match page { - None => Vec::new(), - Some(page) => axis_keys_to_ranked(axis, page)?, - }; - if entries.len() > self.limit as usize { - return Err(Error::Drive(DriveError::CorruptedDriveState(format!( - "a branch of a having read returned {} entries for limit = {}", - entries.len(), - self.limit - )))); - } - Ok(entries) - }) - .collect::, Error>>()?; - return merge_branch_pages( - per_branch, - &self.prefix_branches, - self.descending, - self.limit as usize, - ); + return read_branches_at_one_root(&drive.grove, transaction, grove_version, || { + let CostContext { value, cost: _ } = drive.grove.run_path_query( + &path_query, + true, + true, + true, + QueryResultType::QueryKeyElementPairResultType, + transaction, + grove_version, + ); + let run = value.map_err(|e| Error::GroveDB(Box::new(e)))?; + let PathQueryRun::BranchedAxisKeys(branches) = run else { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "a branched keys-only having read returned a non-branched shape" + .to_string(), + ))); + }; + if branches.len() != keys.len() + || branches.iter().map(|(key, _)| key).ne(keys.iter()) + { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "a branched having read returned a different branch set than the request \ + resolved" + .to_string(), + ))); + } + let per_branch = branches + .into_iter() + .map(|(_key, page)| { + let entries = match page { + None => Vec::new(), + Some(page) => axis_keys_to_ranked(axis, page)?, + }; + if entries.len() > self.limit as usize { + return Err(Error::Drive(DriveError::CorruptedDriveState(format!( + "a branch of a having read returned {} entries for limit = {}", + entries.len(), + self.limit + )))); + } + Ok(entries) + }) + .collect::, Error>>()?; + merge_branch_pages( + per_branch, + &self.prefix_branches, + self.descending, + self.limit as usize, + ) + }); } self.execute_range_no_proof_branch(0, drive, transaction, platform_version) } diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs b/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs index 5268b2aecac..d7682c20bd0 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs @@ -33,7 +33,11 @@ use crate::error::drive::DriveError; use crate::error::Error; use grovedb::operations::proof::indexed_axis::AxisEntries; #[cfg(feature = "server")] -use grovedb::AxisKeys; +use grovedb::{AxisKeys, GroveDb, TransactionArg}; +#[cfg(feature = "server")] +use grovedb_costs::CostContext; +#[cfg(feature = "server")] +use grovedb_version::version::GroveVersion; use std::cmp::Ordering; /// The position (index into a branch's segment list) at which the @@ -178,6 +182,70 @@ pub fn decompose_branch_paths(paths: &[Vec>]) -> Result( + grove: &GroveDb, + transaction: TransactionArg, + grove_version: &GroveVersion, + mut read: impl FnMut() -> Result, +) -> Result { + for _ in 0..BRANCHED_READ_ATTEMPTS { + let CostContext { value, cost: _ } = grove.root_hash(transaction, grove_version); + let before = value.map_err(|e| Error::GroveDB(Box::new(e)))?; + let result = read(); + let CostContext { value, cost: _ } = grove.root_hash(transaction, grove_version); + let after = value.map_err(|e| Error::GroveDB(Box::new(e)))?; + if before == after { + return result; + } + } + Err(Error::Drive(DriveError::ConcurrentStateChurn( + "a branched read raced concurrent commits and could not observe one committed \ + state within its retry budget — retry the request", + ))) +} + /// Translate one branch's keys-only [`AxisKeys`] page into drive entries /// on the requested axis — the unproved twin of [`axis_entries_to_ranked`], /// used by the single-snapshot branched reads. diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs index b4b0e3cf020..3811b974b62 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs @@ -32,7 +32,11 @@ impl DriveDocumentRankedQuery<'_> { /// request, an element whose branch chain is missing at ANY depth — /// the branch key itself, or any deeper pinned segment under a /// *present* key — contributes an **empty branch** (union - /// semantics), exactly as the proved envelope authenticates it. A + /// semantics), exactly as the proved envelope authenticates it, and + /// the union is served from **one committed state**: the branched + /// read is bracketed against the committed root hash and retried if + /// a concurrent commit tears the window + /// (`branches::read_branches_at_one_root`). A /// missing path under a single `==` pin *is* an error rather than /// an empty result: the indexed property-name tree is created when /// the contract is registered, so its absence means the @@ -80,18 +84,20 @@ impl DriveDocumentRankedQuery<'_> { ) -> Result { if self.prefix_branches.len() > 1 { // The whole union is read through ONE grovedb call — a branched - // keys-only PathQuery — under the caller's transaction, the - // narrowest read window grovedb offers an unproved read today - // (a `None` read is NOT a storage-level snapshot: grovedb has - // no snapshot-pinned read primitive yet, so as with every - // cross-subtree unproved read, torn reads across a concurrent - // commit are excluded operationally by DAPI's committed-height - // guard, and clients needing authenticated cross-branch - // consistency use `prove = true`, whose envelope binds one - // committed root). Absence at any depth of a branch's chain is - // the branched reader's empty branch, exactly as the proved - // path authenticates it. `offset` is grammar-rejected with - // `IN`, so `skipped` is always 0 here. + // keys-only PathQuery — under the caller's transaction, and the + // call is bracketed against the committed root hash. At the + // pinned grovedb revision the branched read still opens one + // implicit transaction per suffix probe and per branch walk + // when no transaction is supplied (and an optimistic + // transaction would not pin a snapshot either), so the bracket + // VALIDATES what pinning would otherwise guarantee: equal root + // hashes around the call mean every probe and walk observed + // one committed state, and a torn window is retried rather + // than served (`branches::read_branches_at_one_root`). + // Absence at any depth of a branch's chain is the branched + // reader's empty branch, exactly as the proved path + // authenticates it. `offset` is grammar-rejected with `IN`, + // so `skipped` is always 0 here. let grove_version = &platform_version.drive.grove_version; let paths = (0..self.prefix_branches.len()) .map(|branch| self.indexed_property_name_tree_path(branch)) @@ -109,50 +115,62 @@ impl DriveDocumentRankedQuery<'_> { ) .keys_only(), ); - let CostContext { value, cost: _ } = drive.grove.run_path_query( - &path_query, - true, - true, - true, - QueryResultType::QueryKeyElementPairResultType, + let entries = super::branches::read_branches_at_one_root( + &drive.grove, transaction, grove_version, - ); - let run = value.map_err(|e| Error::GroveDB(Box::new(e)))?; - let PathQueryRun::BranchedAxisKeys(branches) = run else { - return Err(Error::Drive(DriveError::CorruptedDriveState( - "a branched keys-only ranked read returned a non-branched shape".to_string(), - ))); - }; - if branches.len() != keys.len() || branches.iter().map(|(key, _)| key).ne(keys.iter()) { - return Err(Error::Drive(DriveError::CorruptedDriveState( - "a branched ranked read returned a different branch set than the request \ - resolved" - .to_string(), - ))); - } - let per_branch = branches - .into_iter() - .map(|(_key, page)| { - let entries = match page { - None => Vec::new(), - Some(page) => axis_keys_to_ranked(self.axis, page)?, + || { + let CostContext { value, cost: _ } = drive.grove.run_path_query( + &path_query, + true, + true, + true, + QueryResultType::QueryKeyElementPairResultType, + transaction, + grove_version, + ); + let run = value.map_err(|e| Error::GroveDB(Box::new(e)))?; + let PathQueryRun::BranchedAxisKeys(branches) = run else { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "a branched keys-only ranked read returned a non-branched shape" + .to_string(), + ))); }; - if entries.len() > self.k as usize { - return Err(Error::Drive(DriveError::CorruptedDriveState(format!( - "a branch of a ranked read returned {} entries for k = {}", - entries.len(), - self.k - )))); + if branches.len() != keys.len() + || branches.iter().map(|(key, _)| key).ne(keys.iter()) + { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "a branched ranked read returned a different branch set than the \ + request resolved" + .to_string(), + ))); } - Ok(entries) - }) - .collect::, Error>>()?; - let entries = merge_branch_pages( - per_branch, - &self.prefix_branches, - self.descending, - self.k as usize, + let per_branch = branches + .into_iter() + .map(|(_key, page)| { + let entries = match page { + None => Vec::new(), + Some(page) => axis_keys_to_ranked(self.axis, page)?, + }; + if entries.len() > self.k as usize { + return Err(Error::Drive(DriveError::CorruptedDriveState( + format!( + "a branch of a ranked read returned {} entries for k = {}", + entries.len(), + self.k + ), + ))); + } + Ok(entries) + }) + .collect::, Error>>()?; + merge_branch_pages( + per_branch, + &self.prefix_branches, + self.descending, + self.k as usize, + ) + }, )?; return Ok(RankedPage { skipped: 0, diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs index 0a81abdbf58..d3e18ad175d 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs @@ -3373,4 +3373,86 @@ mod pinned_prefix { "expected the fan-out ceiling rejection, got {error:?}" ); } + + /// Commit one unrelated item so the grove root hash moves — a + /// stand-in for a block commit landing inside a branched read's + /// bracketed window. + fn commit_unrelated_churn(drive: &Drive, nonce: u8) { + use crate::drive::RootTree; + use grovedb::Element; + use grovedb_costs::CostContext; + use grovedb_path::SubtreePath; + let misc: [&[u8]; 1] = [Into::<&[u8; 1]>::into(RootTree::Misc)]; + let CostContext { value, cost: _ } = drive.grove.insert( + SubtreePath::from(misc.as_ref()), + &[b'c', b'h', b'u', b'r', b'n', nonce], + Element::new_item(vec![nonce]), + None, + None, + &platform_version().drive.grove_version, + ); + value.expect("expected to commit the unrelated churn item"); + } + + /// A commit landing inside a branched read's window is detected by + /// the root-hash bracket and the whole union is retried against the + /// new committed state — a served page can never mix two states. + #[test] + fn a_branched_read_retries_across_a_concurrent_commit() { + let (drive, _contract) = setup_grades_compound_ranked(); + let grove_version = &platform_version().drive.grove_version; + let mut window = 0u8; + let result = super::super::branches::read_branches_at_one_root( + &drive.grove, + None, + grove_version, + || { + window += 1; + if window == 1 { + // A "block commit" lands mid-window. + commit_unrelated_churn(&drive, window); + } + Ok(window) + }, + ); + assert_eq!( + result.expect("the second, untorn window serves"), + 2, + "the torn first window must be discarded and retried" + ); + } + + /// Persistent churn exhausts the retry budget and fails closed with + /// a retryable error instead of serving a page that may mix + /// committed states — or looping unboundedly. + #[test] + fn a_branched_read_racing_every_window_fails_closed() { + let (drive, _contract) = setup_grades_compound_ranked(); + let grove_version = &platform_version().drive.grove_version; + let mut window = 0u8; + let error = super::super::branches::read_branches_at_one_root( + &drive.grove, + None, + grove_version, + || { + window += 1; + commit_unrelated_churn(&drive, window); + Ok(window) + }, + ) + .expect_err("every window is torn, so the read must fail closed"); + assert_eq!( + window as usize, + super::super::branches::BRANCHED_READ_ATTEMPTS, + "exactly the retry budget is spent" + ); + assert!( + matches!( + &error, + Error::Drive(crate::error::drive::DriveError::ConcurrentStateChurn(message)) + if message.contains("retry") + ), + "expected the concurrent-churn rejection, got {error:?}" + ); + } } From 71c3630da51bae09f73ad8a84eaa77fa9d2c6fe1 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 25 Aug 2026 18:04:05 +0200 Subject: [PATCH 12/25] fix(drive)!: pin branched reads to a grovedb snapshot The root-hash bracket could not prove its own premise: platform state bytes persist via put_aux OUTSIDE the authenticated root, so an A->B->A window (a commit and its exact revert inside the read) keeps the endpoint hashes equal while the branches were read from B. The bracket, its retry budget, and DriveError::ConcurrentStateChurn are deleted. In their place, grovedb (pin ad012ded, dashpay/grovedb#831) gains snapshot-pinned read transactions: start_snapshot_read_transaction() begins an optimistic transaction with a snapshot requested, and the prefixed transaction contexts route every get and raw iterator through read options carrying that snapshot (a plain transaction's null snapshot handle leaves reads on latest committed state, so existing callers are unaffected). Both branched executors now run their single branched call under such a transaction whenever the caller supplies none, so every per-branch absence probe and axis walk reads ONE RocksDB snapshot - the storage-level guarantee, not a validation of it. A caller transaction is still used as-is. Regression test a_branched_read_is_pinned_to_one_committed_state commits a new branch and a new group between snapshot and read: the snapshot read returns the pre-commit union (absence included), the committed read the post-commit union. grovedb-side pinning is tested at the pin (snapshot_read_transaction_pins_a_branched_read_to_one_committed_state). Also per review: detect_ranked_mode_v0's public contract and the prefix-pin helper intro now describe the bounded IN, branch merge and non-zero-offset rejection, and the having executor's doc distinguishes deep absence on branched reads from the single-pin missing-path error. Co-Authored-By: Claude Fable 5 --- Cargo.lock | 32 ++-- packages/rs-dpp/Cargo.toml | 2 +- packages/rs-drive-abci/Cargo.toml | 8 +- packages/rs-drive/Cargo.toml | 14 +- packages/rs-drive/src/error/drive.rs | 7 - .../execute_range.rs | 135 +++++++------- .../drive_document_ranked_query/branches.rs | 70 +------- .../execute_top_k.rs | 131 +++++++------- .../mode_detection/v0/mod.rs | 26 ++- .../drive_document_ranked_query/tests.rs | 170 ++++++++++-------- packages/rs-platform-version/Cargo.toml | 2 +- packages/rs-platform-wallet/Cargo.toml | 2 +- packages/rs-sdk/Cargo.toml | 2 +- 13 files changed, 277 insertions(+), 324 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d3d7b4d2f12..d8f1c123d06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2974,7 +2974,7 @@ dependencies = [ [[package]] name = "grovedb" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=753a11f14c9a4bc72bf2d5302751dd43d174621e#753a11f14c9a4bc72bf2d5302751dd43d174621e" +source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" dependencies = [ "axum 0.8.9", "bincode", @@ -3013,7 +3013,7 @@ dependencies = [ [[package]] name = "grovedb-bulk-append-tree" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=753a11f14c9a4bc72bf2d5302751dd43d174621e#753a11f14c9a4bc72bf2d5302751dd43d174621e" +source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" dependencies = [ "bincode", "blake3", @@ -3031,7 +3031,7 @@ dependencies = [ [[package]] name = "grovedb-commitment-tree" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=753a11f14c9a4bc72bf2d5302751dd43d174621e#753a11f14c9a4bc72bf2d5302751dd43d174621e" +source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" dependencies = [ "blake3", "grovedb-bulk-append-tree", @@ -3048,7 +3048,7 @@ dependencies = [ [[package]] name = "grovedb-costs" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=753a11f14c9a4bc72bf2d5302751dd43d174621e#753a11f14c9a4bc72bf2d5302751dd43d174621e" +source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" dependencies = [ "integer-encoding", "intmap", @@ -3058,7 +3058,7 @@ dependencies = [ [[package]] name = "grovedb-dense-fixed-sized-merkle-tree" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=753a11f14c9a4bc72bf2d5302751dd43d174621e#753a11f14c9a4bc72bf2d5302751dd43d174621e" +source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" dependencies = [ "bincode", "blake3", @@ -3072,7 +3072,7 @@ dependencies = [ [[package]] name = "grovedb-element" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=753a11f14c9a4bc72bf2d5302751dd43d174621e#753a11f14c9a4bc72bf2d5302751dd43d174621e" +source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" dependencies = [ "bincode", "bincode_derive", @@ -3088,7 +3088,7 @@ dependencies = [ [[package]] name = "grovedb-epoch-based-storage-flags" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=753a11f14c9a4bc72bf2d5302751dd43d174621e#753a11f14c9a4bc72bf2d5302751dd43d174621e" +source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" dependencies = [ "grovedb-costs", "hex", @@ -3100,7 +3100,7 @@ dependencies = [ [[package]] name = "grovedb-merk" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=753a11f14c9a4bc72bf2d5302751dd43d174621e#753a11f14c9a4bc72bf2d5302751dd43d174621e" +source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" dependencies = [ "bincode", "bincode_derive", @@ -3126,7 +3126,7 @@ dependencies = [ [[package]] name = "grovedb-merkle-mountain-range" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=753a11f14c9a4bc72bf2d5302751dd43d174621e#753a11f14c9a4bc72bf2d5302751dd43d174621e" +source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" dependencies = [ "bincode", "blake3", @@ -3139,7 +3139,7 @@ dependencies = [ [[package]] name = "grovedb-path" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=753a11f14c9a4bc72bf2d5302751dd43d174621e#753a11f14c9a4bc72bf2d5302751dd43d174621e" +source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" dependencies = [ "hex", ] @@ -3147,7 +3147,7 @@ dependencies = [ [[package]] name = "grovedb-private-document-store" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=753a11f14c9a4bc72bf2d5302751dd43d174621e#753a11f14c9a4bc72bf2d5302751dd43d174621e" +source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" dependencies = [ "blake3", "grovedb-bulk-append-tree", @@ -3160,7 +3160,7 @@ dependencies = [ [[package]] name = "grovedb-query" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=753a11f14c9a4bc72bf2d5302751dd43d174621e#753a11f14c9a4bc72bf2d5302751dd43d174621e" +source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" dependencies = [ "bincode", "byteorder", @@ -3176,7 +3176,7 @@ dependencies = [ [[package]] name = "grovedb-storage" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=753a11f14c9a4bc72bf2d5302751dd43d174621e#753a11f14c9a4bc72bf2d5302751dd43d174621e" +source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" dependencies = [ "blake3", "grovedb-costs", @@ -3195,7 +3195,7 @@ dependencies = [ [[package]] name = "grovedb-version" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=753a11f14c9a4bc72bf2d5302751dd43d174621e#753a11f14c9a4bc72bf2d5302751dd43d174621e" +source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" dependencies = [ "thiserror 2.0.18", "versioned-feature-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3204,7 +3204,7 @@ dependencies = [ [[package]] name = "grovedb-visualize" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=753a11f14c9a4bc72bf2d5302751dd43d174621e#753a11f14c9a4bc72bf2d5302751dd43d174621e" +source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" dependencies = [ "hex", "itertools 0.14.0", @@ -3213,7 +3213,7 @@ dependencies = [ [[package]] name = "grovedbg-types" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=753a11f14c9a4bc72bf2d5302751dd43d174621e#753a11f14c9a4bc72bf2d5302751dd43d174621e" +source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" dependencies = [ "serde", "serde_with 3.21.0", diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index 0759b188069..f20fb672e10 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -71,7 +71,7 @@ strum = { version = "0.26", features = ["derive"] } json-schema-compatibility-validator = { path = '../rs-json-schema-compatibility-validator', optional = true } once_cell = "1.19.0" tracing = { version = "0.1.41" } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "753a11f14c9a4bc72bf2d5302751dd43d174621e", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034", optional = true } [dev-dependencies] tokio = { version = "1.40", features = ["full"] } diff --git a/packages/rs-drive-abci/Cargo.toml b/packages/rs-drive-abci/Cargo.toml index 6d8a854e32d..7c443ce937b 100644 --- a/packages/rs-drive-abci/Cargo.toml +++ b/packages/rs-drive-abci/Cargo.toml @@ -82,7 +82,7 @@ derive_more = { version = "1.0", features = ["from", "deref", "deref_mut"] } async-trait = "0.1.77" console-subscriber = { version = "0.4", optional = true } bls-signatures = { git = "https://github.com/dashpay/bls-signatures", rev = "0842b17583888e8f46c252a4ee84cdfd58e0546f", optional = true } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "753a11f14c9a4bc72bf2d5302751dd43d174621e" } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034" } nonempty = "0.11" # Shielded-pool snapshot needs raw RocksDB SstFileWriter + ingest_external_file_cf # bindings, and blake3 for the snapshot-file checksum. @@ -107,7 +107,7 @@ dpp = { path = "../rs-dpp", default-features = false, features = [ drive = { path = "../rs-drive", features = ["fixtures-and-mocks"] } drive-proof-verifier = { path = "../rs-drive-proof-verifier" } strategy-tests = { path = "../strategy-tests" } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "753a11f14c9a4bc72bf2d5302751dd43d174621e", features = ["client"] } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034", features = ["client"] } assert_matches = "1.5.0" drive-abci = { path = ".", features = ["testing-config", "mocks", "shielded_test_data"] } bls-signatures = { git = "https://github.com/dashpay/bls-signatures", rev = "0842b17583888e8f46c252a4ee84cdfd58e0546f" } @@ -121,8 +121,8 @@ integer-encoding = { version = "4.0.0" } # For dump_only_default_and_aux_cfs_under_shielded_subtree_prefix — same # subtree-prefix algorithm grovedb uses internally. -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "753a11f14c9a4bc72bf2d5302751dd43d174621e" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "753a11f14c9a4bc72bf2d5302751dd43d174621e" } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034" } [features] default = ["bls-signatures"] diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index 275da37c4e1..e99f9aa6c33 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -52,13 +52,13 @@ enum-map = { version = "2.0.3", optional = true } intmap = { version = "3.0.1", features = ["serde"], optional = true } chrono = { version = "0.4.35", optional = true } itertools = { version = "0.13", optional = true } -grovedb = { git = "https://github.com/dashpay/grovedb", rev = "753a11f14c9a4bc72bf2d5302751dd43d174621e", optional = true, default-features = false } -grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "753a11f14c9a4bc72bf2d5302751dd43d174621e", optional = true } -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "753a11f14c9a4bc72bf2d5302751dd43d174621e" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "753a11f14c9a4bc72bf2d5302751dd43d174621e", optional = true } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "753a11f14c9a4bc72bf2d5302751dd43d174621e" } -grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "753a11f14c9a4bc72bf2d5302751dd43d174621e" } -grovedb-query = { git = "https://github.com/dashpay/grovedb", rev = "753a11f14c9a4bc72bf2d5302751dd43d174621e" } +grovedb = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034", optional = true, default-features = false } +grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034", optional = true } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034", optional = true } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034" } +grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034" } +grovedb-query = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034" } [dev-dependencies] criterion = "0.5" diff --git a/packages/rs-drive/src/error/drive.rs b/packages/rs-drive/src/error/drive.rs index 79b505e9f3c..3412c9df1aa 100644 --- a/packages/rs-drive/src/error/drive.rs +++ b/packages/rs-drive/src/error/drive.rs @@ -147,13 +147,6 @@ pub enum DriveError { #[error("corrupted drive state error: {0}")] CorruptedDriveState(String), - /// A multi-operation read raced concurrent block commits and could - /// not observe one committed state within its retry budget. Nothing - /// is corrupted — the condition is transient and the request is safe - /// to retry. - #[error("concurrent state churn during a read: {0}")] - ConcurrentStateChurn(&'static str), - /// Error #[error("corrupted cache state error: {0}")] CorruptedCacheState(String), diff --git a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs index 4454fa51bba..044af3a28dc 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs @@ -12,7 +12,7 @@ //! `pub mod execute_range;` declaration. use super::super::drive_document_ranked_query::branches::{ - axis_keys_to_ranked, decompose_branch_paths, merge_branch_pages, read_branches_at_one_root, + axis_keys_to_ranked, decompose_branch_paths, merge_branch_pages, }; use super::super::drive_document_ranked_query::{RankedAxis, RankedEntry, RankedEntryValue}; use super::{AxisRangeBounds, DriveDocumentHavingQuery}; @@ -33,12 +33,20 @@ impl DriveDocumentHavingQuery<'_> { /// /// Fewer than `limit` entries is normal (fewer groups match) and is /// not an error; exactly `limit` entries may mean the match set was - /// cut. A missing path *is* an error rather than an empty result, - /// for the same reason as on the ranked surface: the indexed - /// property-name tree is created at contract registration, so its - /// absence means the contract-level state is not what the request - /// claims. (An index with no documents has the tree, with an empty - /// secondary, and yields an empty entry list.) + /// cut. + /// + /// Missing paths follow the ranked surface's rule. Under a single + /// `==` pin (or no pins) a missing path *is* an error rather than an + /// empty result: the indexed property-name tree is created at + /// contract registration, so its absence means the contract-level + /// state is not what the request claims. On an `IN`-pinned request, + /// an element whose branch chain is missing at ANY depth — the + /// branch key, or any deeper pinned segment under a *present* key — + /// contributes an **empty branch** instead (union semantics, exactly + /// as the proved envelope authenticates it), and the union is served + /// from one committed state (a `None` read runs under a grovedb + /// snapshot read transaction). An index with no documents has the + /// tree, with an empty secondary, and yields an empty entry list. pub fn execute_range_no_proof( &self, drive: &Drive, @@ -46,12 +54,12 @@ impl DriveDocumentHavingQuery<'_> { platform_version: &PlatformVersion, ) -> Result, Error> { if self.prefix_branches.len() > 1 { - // One grovedb call for the whole union under the caller's - // transaction, bracketed against the committed root hash — - // same one-committed-state contract as the ranked executor - // (see its comment and the ranked surface's - // `branches::read_branches_at_one_root`). Absence at any - // depth is the branched reader's empty branch. + // One grovedb call for the whole union, pinned to ONE + // committed state — a `None` read runs under a grovedb + // snapshot read transaction, a caller transaction is used + // as-is; same contract as the ranked executor (see its + // comment). Absence at any depth is the branched reader's + // empty branch. let grove_version = &platform_version.drive.grove_version; let paths = (0..self.prefix_branches.len()) .map(|branch| self.indexed_property_name_tree_path(branch)) @@ -65,56 +73,57 @@ impl DriveDocumentHavingQuery<'_> { suffix, AxisQuery::bounded(axis.into(), lo, hi, self.limit, self.descending).keys_only(), ); - return read_branches_at_one_root(&drive.grove, transaction, grove_version, || { - let CostContext { value, cost: _ } = drive.grove.run_path_query( - &path_query, - true, - true, - true, - QueryResultType::QueryKeyElementPairResultType, - transaction, - grove_version, - ); - let run = value.map_err(|e| Error::GroveDB(Box::new(e)))?; - let PathQueryRun::BranchedAxisKeys(branches) = run else { - return Err(Error::Drive(DriveError::CorruptedDriveState( - "a branched keys-only having read returned a non-branched shape" - .to_string(), - ))); - }; - if branches.len() != keys.len() - || branches.iter().map(|(key, _)| key).ne(keys.iter()) - { - return Err(Error::Drive(DriveError::CorruptedDriveState( - "a branched having read returned a different branch set than the request \ - resolved" - .to_string(), - ))); - } - let per_branch = branches - .into_iter() - .map(|(_key, page)| { - let entries = match page { - None => Vec::new(), - Some(page) => axis_keys_to_ranked(axis, page)?, - }; - if entries.len() > self.limit as usize { - return Err(Error::Drive(DriveError::CorruptedDriveState(format!( - "a branch of a having read returned {} entries for limit = {}", - entries.len(), - self.limit - )))); - } - Ok(entries) - }) - .collect::, Error>>()?; - merge_branch_pages( - per_branch, - &self.prefix_branches, - self.descending, - self.limit as usize, - ) - }); + let snapshot_transaction = if transaction.is_none() { + Some(drive.grove.start_snapshot_read_transaction()) + } else { + None + }; + let read_transaction = snapshot_transaction.as_ref().or(transaction); + let CostContext { value, cost: _ } = drive.grove.run_path_query( + &path_query, + true, + true, + true, + QueryResultType::QueryKeyElementPairResultType, + read_transaction, + grove_version, + ); + let run = value.map_err(|e| Error::GroveDB(Box::new(e)))?; + let PathQueryRun::BranchedAxisKeys(branches) = run else { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "a branched keys-only having read returned a non-branched shape".to_string(), + ))); + }; + if branches.len() != keys.len() || branches.iter().map(|(key, _)| key).ne(keys.iter()) { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "a branched having read returned a different branch set than the request \ + resolved" + .to_string(), + ))); + } + let per_branch = branches + .into_iter() + .map(|(_key, page)| { + let entries = match page { + None => Vec::new(), + Some(page) => axis_keys_to_ranked(axis, page)?, + }; + if entries.len() > self.limit as usize { + return Err(Error::Drive(DriveError::CorruptedDriveState(format!( + "a branch of a having read returned {} entries for limit = {}", + entries.len(), + self.limit + )))); + } + Ok(entries) + }) + .collect::, Error>>()?; + return merge_branch_pages( + per_branch, + &self.prefix_branches, + self.descending, + self.limit as usize, + ); } self.execute_range_no_proof_branch(0, drive, transaction, platform_version) } diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs b/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs index d7682c20bd0..5268b2aecac 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs @@ -33,11 +33,7 @@ use crate::error::drive::DriveError; use crate::error::Error; use grovedb::operations::proof::indexed_axis::AxisEntries; #[cfg(feature = "server")] -use grovedb::{AxisKeys, GroveDb, TransactionArg}; -#[cfg(feature = "server")] -use grovedb_costs::CostContext; -#[cfg(feature = "server")] -use grovedb_version::version::GroveVersion; +use grovedb::AxisKeys; use std::cmp::Ordering; /// The position (index into a branch's segment list) at which the @@ -182,70 +178,6 @@ pub fn decompose_branch_paths(paths: &[Vec>]) -> Result( - grove: &GroveDb, - transaction: TransactionArg, - grove_version: &GroveVersion, - mut read: impl FnMut() -> Result, -) -> Result { - for _ in 0..BRANCHED_READ_ATTEMPTS { - let CostContext { value, cost: _ } = grove.root_hash(transaction, grove_version); - let before = value.map_err(|e| Error::GroveDB(Box::new(e)))?; - let result = read(); - let CostContext { value, cost: _ } = grove.root_hash(transaction, grove_version); - let after = value.map_err(|e| Error::GroveDB(Box::new(e)))?; - if before == after { - return result; - } - } - Err(Error::Drive(DriveError::ConcurrentStateChurn( - "a branched read raced concurrent commits and could not observe one committed \ - state within its retry budget — retry the request", - ))) -} - /// Translate one branch's keys-only [`AxisKeys`] page into drive entries /// on the requested axis — the unproved twin of [`axis_entries_to_ranked`], /// used by the single-snapshot branched reads. diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs index 3811b974b62..9f601df2882 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs @@ -33,10 +33,9 @@ impl DriveDocumentRankedQuery<'_> { /// the branch key itself, or any deeper pinned segment under a /// *present* key — contributes an **empty branch** (union /// semantics), exactly as the proved envelope authenticates it, and - /// the union is served from **one committed state**: the branched - /// read is bracketed against the committed root hash and retried if - /// a concurrent commit tears the window - /// (`branches::read_branches_at_one_root`). A + /// the union is served from **one committed state**: a `None` read + /// runs under a grovedb snapshot read transaction, so every + /// per-branch probe and walk reads the same RocksDB snapshot. A /// missing path under a single `==` pin *is* an error rather than /// an empty result: the indexed property-name tree is created when /// the contract is registered, so its absence means the @@ -84,20 +83,16 @@ impl DriveDocumentRankedQuery<'_> { ) -> Result { if self.prefix_branches.len() > 1 { // The whole union is read through ONE grovedb call — a branched - // keys-only PathQuery — under the caller's transaction, and the - // call is bracketed against the committed root hash. At the - // pinned grovedb revision the branched read still opens one - // implicit transaction per suffix probe and per branch walk - // when no transaction is supplied (and an optimistic - // transaction would not pin a snapshot either), so the bracket - // VALIDATES what pinning would otherwise guarantee: equal root - // hashes around the call mean every probe and walk observed - // one committed state, and a torn window is retried rather - // than served (`branches::read_branches_at_one_root`). - // Absence at any depth of a branch's chain is the branched - // reader's empty branch, exactly as the proved path - // authenticates it. `offset` is grammar-rejected with `IN`, - // so `skipped` is always 0 here. + // keys-only PathQuery — pinned to ONE committed state: a `None` + // read runs under a grovedb snapshot read transaction, so every + // per-branch absence probe and axis walk inside the branched + // arm reads the same RocksDB snapshot and a block commit + // landing mid-read cannot mix committed states into the merged + // page. A caller transaction is used as-is — a transactional + // reader asks for its own writes over its base. Absence at any + // depth of a branch's chain is the branched reader's empty + // branch, exactly as the proved path authenticates it. `offset` + // is grammar-rejected with `IN`, so `skipped` is always 0 here. let grove_version = &platform_version.drive.grove_version; let paths = (0..self.prefix_branches.len()) .map(|branch| self.indexed_property_name_tree_path(branch)) @@ -115,62 +110,56 @@ impl DriveDocumentRankedQuery<'_> { ) .keys_only(), ); - let entries = super::branches::read_branches_at_one_root( - &drive.grove, - transaction, + let snapshot_transaction = if transaction.is_none() { + Some(drive.grove.start_snapshot_read_transaction()) + } else { + None + }; + let read_transaction = snapshot_transaction.as_ref().or(transaction); + let CostContext { value, cost: _ } = drive.grove.run_path_query( + &path_query, + true, + true, + true, + QueryResultType::QueryKeyElementPairResultType, + read_transaction, grove_version, - || { - let CostContext { value, cost: _ } = drive.grove.run_path_query( - &path_query, - true, - true, - true, - QueryResultType::QueryKeyElementPairResultType, - transaction, - grove_version, - ); - let run = value.map_err(|e| Error::GroveDB(Box::new(e)))?; - let PathQueryRun::BranchedAxisKeys(branches) = run else { - return Err(Error::Drive(DriveError::CorruptedDriveState( - "a branched keys-only ranked read returned a non-branched shape" - .to_string(), - ))); + ); + let run = value.map_err(|e| Error::GroveDB(Box::new(e)))?; + let PathQueryRun::BranchedAxisKeys(branches) = run else { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "a branched keys-only ranked read returned a non-branched shape".to_string(), + ))); + }; + if branches.len() != keys.len() || branches.iter().map(|(key, _)| key).ne(keys.iter()) { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "a branched ranked read returned a different branch set than the request \ + resolved" + .to_string(), + ))); + } + let per_branch = branches + .into_iter() + .map(|(_key, page)| { + let entries = match page { + None => Vec::new(), + Some(page) => axis_keys_to_ranked(self.axis, page)?, }; - if branches.len() != keys.len() - || branches.iter().map(|(key, _)| key).ne(keys.iter()) - { - return Err(Error::Drive(DriveError::CorruptedDriveState( - "a branched ranked read returned a different branch set than the \ - request resolved" - .to_string(), - ))); + if entries.len() > self.k as usize { + return Err(Error::Drive(DriveError::CorruptedDriveState(format!( + "a branch of a ranked read returned {} entries for k = {}", + entries.len(), + self.k + )))); } - let per_branch = branches - .into_iter() - .map(|(_key, page)| { - let entries = match page { - None => Vec::new(), - Some(page) => axis_keys_to_ranked(self.axis, page)?, - }; - if entries.len() > self.k as usize { - return Err(Error::Drive(DriveError::CorruptedDriveState( - format!( - "a branch of a ranked read returned {} entries for k = {}", - entries.len(), - self.k - ), - ))); - } - Ok(entries) - }) - .collect::, Error>>()?; - merge_branch_pages( - per_branch, - &self.prefix_branches, - self.descending, - self.k as usize, - ) - }, + Ok(entries) + }) + .collect::, Error>>()?; + let entries = merge_branch_pages( + per_branch, + &self.prefix_branches, + self.descending, + self.k as usize, )?; return Ok(RankedPage { skipped: 0, diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/v0/mod.rs b/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/v0/mod.rs index 6e46a79d0b0..003bb7e745b 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/v0/mod.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/v0/mod.rs @@ -16,9 +16,10 @@ use crate::query::projection::{SelectFunction, SelectProjection}; use crate::query::{OrderClause, WhereClause, WhereOperator}; use dpp::platform_value::Value; -/// Translate a request's `where` clauses into equality pins — -/// `(property, value)` pairs, one per clause — for the ranked and -/// having-range surfaces. +/// Translate a request's `where` clauses into prefix pins — one +/// [`PrefixPin`] per clause, carrying the pinned property and its +/// value(s): one value from an `==` clause, several from the (at most +/// one) `IN` — for the ranked and having-range surfaces. /// /// Both surfaces read a compound index's per-prefix secondary by /// descending through one prefix value tree per **leading** index @@ -137,12 +138,19 @@ pub fn prefix_pins_from_where_clauses( /// with no `HAVING`, no `START AT` / `START AFTER`, exactly one /// `GROUP BY` property, exactly one `ORDER BY` clause naming the /// selected aggregate, `1 ≤ n ≤` [`MAX_RANKED_LIMIT`], and any -/// `m ≥ 0`. `WHERE` clauses, when present, must be **equality pins** on -/// distinct properties — one per leading property of a covering -/// compound ranked index (see -/// [`prefix_pins_from_where_clauses`]); the ranking then reads that -/// pinned prefix's own secondary. With no `where` the covering index is -/// single-property, exactly as before. +/// `m ≥ 0`. `WHERE` clauses, when present, pin distinct properties — +/// one per leading property of a covering compound ranked index — each +/// an **equality**, except that at most one may be a bounded **`IN`** +/// (2..=10 distinct elements; a singleton `IN` normalizes to `==` — see +/// [`prefix_pins_from_where_clauses`]). A `==`-pinned request reads +/// that pinned prefix's own secondary; an `IN` fans the read out into +/// one prefix branch per element, walked separately and merged +/// deterministically, with merged entries carrying their branch's +/// `in_key`. A **non-zero** `OFFSET` is rejected together with the +/// `IN`: the counted rank-skip is attested per-secondary and cannot +/// span the union (`OFFSET 0` stays legal as the offset-free +/// spelling). With no `where` the covering index is single-property, +/// exactly as before. /// /// `DESC` walks the axis from the largest aggregate down (the "top n" /// reading), `ASC` from the smallest up (the "bottom n" reading). diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs index d3e18ad175d..c759da5398c 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs @@ -3374,85 +3374,107 @@ mod pinned_prefix { ); } - /// Commit one unrelated item so the grove root hash moves — a - /// stand-in for a block commit landing inside a branched read's - /// bracketed window. - fn commit_unrelated_churn(drive: &Drive, nonce: u8) { - use crate::drive::RootTree; - use grovedb::Element; - use grovedb_costs::CostContext; - use grovedb_path::SubtreePath; - let misc: [&[u8]; 1] = [Into::<&[u8; 1]>::into(RootTree::Misc)]; - let CostContext { value, cost: _ } = drive.grove.insert( - SubtreePath::from(misc.as_ref()), - &[b'c', b'h', b'u', b'r', b'n', nonce], - Element::new_item(vec![nonce]), - None, - None, - &platform_version().drive.grove_version, - ); - value.expect("expected to commit the unrelated churn item"); - } - - /// A commit landing inside a branched read's window is detected by - /// the root-hash bracket and the whole union is retried against the - /// new committed state — a served page can never mix two states. + /// The `IN` union is served from ONE committed state: a `None` read + /// runs under a grovedb snapshot read transaction internally, and + /// the same primitive is exercised here explicitly — a branched read + /// under a snapshot transaction taken before a commit returns the + /// pre-commit union (the new branch still absent, the changed page + /// unchanged), while a fresh committed read returns the post-commit + /// union. #[test] - fn a_branched_read_retries_across_a_concurrent_commit() { - let (drive, _contract) = setup_grades_compound_ranked(); - let grove_version = &platform_version().drive.grove_version; - let mut window = 0u8; - let result = super::super::branches::read_branches_at_one_root( - &drive.grove, - None, - grove_version, - || { - window += 1; - if window == 1 { - // A "block commit" lands mid-window. - commit_unrelated_churn(&drive, window); - } - Ok(window) - }, - ); + fn a_branched_read_is_pinned_to_one_committed_state() { + let (drive, contract) = setup_grades_compound_ranked(); + let pv = platform_version(); + insert_grades(&drive, &contract, &[(IDENTITY_X, "math", 80)]); + + let snapshot_transaction = drive.grove.start_snapshot_read_transaction(); + + // A "block commit" lands after the snapshot: Y's branch springs + // into existence and X gains a class. Documents are built with + // distinct seeds so they cannot collide with `insert_grades`'. + let document_type = contract + .document_type_for_name(DOCUMENT_TYPE) + .expect("grade doctype exists"); + for (seed, identity, class, grade) in [ + (9100u64, IDENTITY_Y, "science", 95i64), + (9101u64, IDENTITY_X, "art", 90i64), + ] { + let mut doc: Document = document_type + .random_document(Some(seed), pv) + .expect("random document"); + let mut props = BTreeMap::new(); + props.insert(PREFIX_PROPERTY.to_string(), Value::Identifier(identity)); + props.insert(CLASS_PROPERTY.to_string(), Value::Text(class.to_string())); + props.insert("grade".to_string(), Value::I64(grade)); + doc.set_properties(props); + drive + .add_document_for_contract( + DocumentAndContractInfo { + owned_document_info: OwnedDocumentInfo { + document_info: DocumentRefInfo((&doc, None)), + owner_id: None, + }, + contract: &contract, + document_type, + }, + false, + BlockInfo::default(), + true, + None, + pv, + None, + ) + .expect("expected to commit the post-snapshot grade"); + } + + let pins = in_pin(&[IDENTITY_X, IDENTITY_Y]); + let group_by = vec![CLASS_PROPERTY.to_string()]; + let order_by = vec![OrderClause { + field: "grade".to_string(), + ascending: false, + }]; + let request = || DocumentRankedRequest { + contract: &contract, + document_type, + group_by: &group_by, + select: SelectProjection::avg("grade"), + having: &[], + order_by: &order_by, + where_clauses: &pins, + limit: Some(4), + offset: None, + has_start_at: false, + prove: false, + }; + + // Under the pre-commit snapshot the union is the pre-commit + // state: Y's branch is still absent (empty, not an error) and X + // has only math. + let pinned = match drive + .execute_document_ranked_request(request(), Some(&snapshot_transaction), pv) + .expect("the snapshot branched read serves") + { + DocumentRankedResponse::Entries(page) => page, + DocumentRankedResponse::Proof(_) => panic!("expected entries"), + }; assert_eq!( - result.expect("the second, untorn window serves"), - 2, - "the torn first window must be discarded and retried" + pinned.entries.len(), + 1, + "the snapshot union is the pre-commit state" ); - } + assert_eq!(pinned.entries[0].key, b"math".to_vec()); - /// Persistent churn exhausts the retry budget and fails closed with - /// a retryable error instead of serving a page that may mix - /// committed states — or looping unboundedly. - #[test] - fn a_branched_read_racing_every_window_fails_closed() { - let (drive, _contract) = setup_grades_compound_ranked(); - let grove_version = &platform_version().drive.grove_version; - let mut window = 0u8; - let error = super::super::branches::read_branches_at_one_root( - &drive.grove, - None, - grove_version, - || { - window += 1; - commit_unrelated_churn(&drive, window); - Ok(window) - }, - ) - .expect_err("every window is torn, so the read must fail closed"); + // A fresh committed read sees the post-commit union. + let fresh = match run(&drive, &contract, &pins, 4, false) + .expect("the committed branched read serves") + { + DocumentRankedResponse::Entries(page) => page, + DocumentRankedResponse::Proof(_) => panic!("expected entries"), + }; assert_eq!( - window as usize, - super::super::branches::BRANCHED_READ_ATTEMPTS, - "exactly the retry budget is spent" - ); - assert!( - matches!( - &error, - Error::Drive(crate::error::drive::DriveError::ConcurrentStateChurn(message)) - if message.contains("retry") - ), - "expected the concurrent-churn rejection, got {error:?}" + fresh.entries.len(), + 3, + "the committed union reflects the commit" ); } } diff --git a/packages/rs-platform-version/Cargo.toml b/packages/rs-platform-version/Cargo.toml index 4ee353c872e..68a3f150fc8 100644 --- a/packages/rs-platform-version/Cargo.toml +++ b/packages/rs-platform-version/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" thiserror = { version = "2.0.12" } bincode = { version = "=2.0.1" } versioned-feature-core = { git = "https://github.com/dashpay/versioned-feature-core", version = "1.0.0" } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "753a11f14c9a4bc72bf2d5302751dd43d174621e" } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034" } [features] mock-versions = [] diff --git a/packages/rs-platform-wallet/Cargo.toml b/packages/rs-platform-wallet/Cargo.toml index 755f7bb110b..97bf468c610 100644 --- a/packages/rs-platform-wallet/Cargo.toml +++ b/packages/rs-platform-wallet/Cargo.toml @@ -69,7 +69,7 @@ zeroize = "1" log = "0.4" # Shielded pool (optional, behind `shielded` feature) -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "753a11f14c9a4bc72bf2d5302751dd43d174621e", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034", optional = true } # Direct `rusqlite` access so `FileBackedShieldedStore::open_path` can set # WAL + synchronous=NORMAL pragmas before handing the connection to # `ClientPersistentCommitmentTree`. Version locked to match the rev grovedb diff --git a/packages/rs-sdk/Cargo.toml b/packages/rs-sdk/Cargo.toml index ee1bfca14ae..a3975930164 100644 --- a/packages/rs-sdk/Cargo.toml +++ b/packages/rs-sdk/Cargo.toml @@ -18,7 +18,7 @@ drive = { path = "../rs-drive", default-features = false, features = [ ] } drive-proof-verifier = { path = "../rs-drive-proof-verifier", default-features = false } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "753a11f14c9a4bc72bf2d5302751dd43d174621e", features = [ +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034", features = [ "client", "sqlite", ], optional = true } From bb6aebe1ebec065851418e19404eaf661e52ca4d Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 25 Aug 2026 18:22:19 +0200 Subject: [PATCH 13/25] refactor(drive): one shared branched-read sequence; envelope wording Per review: the ranked and having-range executors duplicated the whole branched-read sequence (path decomposition, branched PathQuery, snapshot-transaction selection, run-shape and branch-set checks, per-branch cap, merge), differing only in the AxisQuery and the cap. Proof/read equivalence depends on the copies staying identical, so the sequence now lives once in branches::read_branched_union and both executors call it. Also per review: the last "branch container" wording in both test suites now names the branched PathQuery envelope, and the book row no longer states the non-zero-OFFSET rejection twice. Co-Authored-By: Claude Fable 5 --- book/src/drive/document-ranked-trees.md | 2 +- .../execute_range.rs | 80 +++------------- .../drive_document_having_query/tests.rs | 4 +- .../drive_document_ranked_query/branches.rs | 92 ++++++++++++++++++- .../execute_top_k.rs | 90 ++++-------------- .../drive_document_ranked_query/tests.rs | 6 +- 6 files changed, 130 insertions(+), 144 deletions(-) diff --git a/book/src/drive/document-ranked-trees.md b/book/src/drive/document-ranked-trees.md index 41f97f23c62..8a0343ceb40 100644 --- a/book/src/drive/document-ranked-trees.md +++ b/book/src/drive/document-ranked-trees.md @@ -341,7 +341,7 @@ Note that the fixture puts each shape on its **own document type**. That's not a | Top / bottom K groups by sum of a property | `rankedSummable: true` on an index with `summable: ""` + `rangeSummable: true` | | Top / bottom K groups by average of a property | `rankedAverageable: true` on an index with `averageable: ""` + `rangeAverageable: true` (or the count+sum longhand) | | Two rankings on one index (e.g. by count *and* by average) | Both keywords. The tree is a PCPSIT carrying both axes in its TLV; you pay one secondary Merk per axis on every write. | -| A ranking filtered by another property (`top 5 restaurants in London`) | A **compound ranked index** with the filter property leading: `[city, restaurantId]` with the ranked flags. Each city gets its own secondary; the query pins the prefix with an equality `where` (`WHERE city == "London" GROUP BY restaurantId ORDER BY DESC LIMIT 5`). Equality pins select one prefix; at most one pin may be an `IN` (2..=10 distinct elements; a never-written element — or one whose deeper pinned path was never written — contributes an empty branch), which walks one secondary per element and merges by `(aggregate, encoded prefix, group key)`, proved in one branched `PathQuery` envelope (shared ancestors proved once, per-element authenticated absence) — entries then carry `in_key`. A single-element `IN` normalizes to the equality pin; a `null` pin stays legal on its own but cannot combine with an `IN` (null addresses its prefix through an empty path segment the branched proof cannot express); a non-zero `OFFSET` is rejected together with `IN`; and branched proofs are generated from committed state only. A range operator on the prefix stays rejected, a non-zero `OFFSET` is rejected together with `IN`, and there is still no global cross-prefix ordering beyond that merge. | +| A ranking filtered by another property (`top 5 restaurants in London`) | A **compound ranked index** with the filter property leading: `[city, restaurantId]` with the ranked flags. Each city gets its own secondary; the query pins the prefix with an equality `where` (`WHERE city == "London" GROUP BY restaurantId ORDER BY DESC LIMIT 5`). Equality pins select one prefix; at most one pin may be an `IN` (2..=10 distinct elements; a never-written element — or one whose deeper pinned path was never written — contributes an empty branch), which walks one secondary per element and merges by `(aggregate, encoded prefix, group key)`, proved in one branched `PathQuery` envelope (shared ancestors proved once, per-element authenticated absence) — entries then carry `in_key`. A single-element `IN` normalizes to the equality pin; a `null` pin stays legal on its own but cannot combine with an `IN` (null addresses its prefix through an empty path segment the branched proof cannot express); a non-zero `OFFSET` is rejected together with `IN`; and branched proofs are generated from committed state only. A range operator on the prefix stays rejected, and there is still no global cross-prefix ordering beyond that merge. | | A ranking on a unique or contested index | Not available, and not meaningful: every group holds at most one document. | | Range aggregates without ranking (the 4.0 surface) | Just the `range*` flags. Ranking is strictly additive — adding it never changes what a range query returns. | | Nothing ranking-aware (default) | Don't set any `ranked*` flag. The terminal property-name tree keeps the type its range flags give it. | diff --git a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs index 044af3a28dc..e26e1657f42 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs @@ -12,7 +12,7 @@ //! `pub mod execute_range;` declaration. use super::super::drive_document_ranked_query::branches::{ - axis_keys_to_ranked, decompose_branch_paths, merge_branch_pages, + decompose_branch_paths, read_branched_union, }; use super::super::drive_document_ranked_query::{RankedAxis, RankedEntry, RankedEntryValue}; use super::{AxisRangeBounds, DriveDocumentHavingQuery}; @@ -20,9 +20,8 @@ use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; use dpp::version::PlatformVersion; -use grovedb::query_result_type::QueryResultType; +use grovedb::PathQuery; use grovedb::TransactionArg; -use grovedb::{PathQuery, PathQueryRun}; use grovedb_costs::CostContext; use grovedb_query::AxisQuery; @@ -54,75 +53,26 @@ impl DriveDocumentHavingQuery<'_> { platform_version: &PlatformVersion, ) -> Result, Error> { if self.prefix_branches.len() > 1 { - // One grovedb call for the whole union, pinned to ONE - // committed state — a `None` read runs under a grovedb - // snapshot read transaction, a caller transaction is used - // as-is; same contract as the ranked executor (see its - // comment). Absence at any depth is the branched reader's - // empty branch. - let grove_version = &platform_version.drive.grove_version; + // ONE grovedb call for the whole union, pinned to one + // committed state — the entire sequence lives in the ranked + // surface's `branches::read_branched_union`, shared with the + // ranked executor so the two cannot drift. let paths = (0..self.prefix_branches.len()) .map(|branch| self.indexed_property_name_tree_path(branch)) .collect::, Error>>()?; - let (prefix, keys, suffix) = decompose_branch_paths(&paths)?; let axis = self.bounds.axis(); let (lo, hi) = self.bounds.inclusive_bounds_i128(); - let path_query = PathQuery::new_branched_axis( - prefix, - keys.clone(), - suffix, - AxisQuery::bounded(axis.into(), lo, hi, self.limit, self.descending).keys_only(), - ); - let snapshot_transaction = if transaction.is_none() { - Some(drive.grove.start_snapshot_read_transaction()) - } else { - None - }; - let read_transaction = snapshot_transaction.as_ref().or(transaction); - let CostContext { value, cost: _ } = drive.grove.run_path_query( - &path_query, - true, - true, - true, - QueryResultType::QueryKeyElementPairResultType, - read_transaction, - grove_version, - ); - let run = value.map_err(|e| Error::GroveDB(Box::new(e)))?; - let PathQueryRun::BranchedAxisKeys(branches) = run else { - return Err(Error::Drive(DriveError::CorruptedDriveState( - "a branched keys-only having read returned a non-branched shape".to_string(), - ))); - }; - if branches.len() != keys.len() || branches.iter().map(|(key, _)| key).ne(keys.iter()) { - return Err(Error::Drive(DriveError::CorruptedDriveState( - "a branched having read returned a different branch set than the request \ - resolved" - .to_string(), - ))); - } - let per_branch = branches - .into_iter() - .map(|(_key, page)| { - let entries = match page { - None => Vec::new(), - Some(page) => axis_keys_to_ranked(axis, page)?, - }; - if entries.len() > self.limit as usize { - return Err(Error::Drive(DriveError::CorruptedDriveState(format!( - "a branch of a having read returned {} entries for limit = {}", - entries.len(), - self.limit - )))); - } - Ok(entries) - }) - .collect::, Error>>()?; - return merge_branch_pages( - per_branch, + return read_branched_union( + &drive.grove, + "having", &self.prefix_branches, - self.descending, + &paths, + axis, + AxisQuery::bounded(axis.into(), lo, hi, self.limit, self.descending), self.limit as usize, + self.descending, + transaction, + &platform_version.drive.grove_version, ); } self.execute_range_no_proof_branch(0, drive, transaction, platform_version) diff --git a/packages/rs-drive/src/query/drive_document_having_query/tests.rs b/packages/rs-drive/src/query/drive_document_having_query/tests.rs index a489fa4a25c..03d51dfbac9 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/tests.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/tests.rs @@ -2144,7 +2144,7 @@ mod pinned_prefix { /// A `null` element mixed with a real value in one `IN` pin: the /// null branch is the write path's empty segment, which sorts /// **first** in canonical branch order, and the merged bound covers - /// both subtrees — proved through the branch container. + /// both subtrees — proved through the branched envelope. #[test] fn a_mixed_null_in_pin_bounds_both_prefixes_and_proves() { const TAGGED_DOCTYPE: &str = "taggedGrade"; @@ -2285,7 +2285,7 @@ mod pinned_prefix { ); let (root_hash, verified) = query .verify_having_range_proof(&proof, pv) - .expect("the mixed-null branch container must verify"); + .expect("the mixed-null branched envelope must verify"); assert_eq!(verified, entries); assert_eq!( root_hash, diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs b/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs index 5268b2aecac..77c11a444a3 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs @@ -33,7 +33,15 @@ use crate::error::drive::DriveError; use crate::error::Error; use grovedb::operations::proof::indexed_axis::AxisEntries; #[cfg(feature = "server")] -use grovedb::AxisKeys; +use grovedb::{ + query_result_type::QueryResultType, AxisKeys, GroveDb, PathQuery, PathQueryRun, TransactionArg, +}; +#[cfg(feature = "server")] +use grovedb_costs::CostContext; +#[cfg(feature = "server")] +use grovedb_query::AxisQuery; +#[cfg(feature = "server")] +use grovedb_version::version::GroveVersion; use std::cmp::Ordering; /// The position (index into a branch's segment list) at which the @@ -217,6 +225,88 @@ pub(crate) fn axis_keys_to_ranked( } } +/// The entire unproved branched-read sequence, shared by the ranked and +/// having-range executors so the read/prove contract has exactly ONE +/// implementation: decompose the branch paths, run one branched +/// keys-only grovedb call, validate the run shape and the branch-set +/// identity, translate and cap each branch's page, and merge with the +/// shared comparator. +/// +/// The union is served from **one committed state**: with no caller +/// transaction the call runs under a grovedb snapshot read transaction, +/// so every per-branch absence probe and axis walk inside grovedb's +/// branched arm reads the same RocksDB snapshot and a block commit +/// landing mid-read cannot mix committed states into the merged page. A +/// caller transaction is used as-is — a transactional reader asks for +/// its own writes over its base. +/// +/// `page_cap` is the surface's page bound (`k` for ranked, `limit` for +/// having-range): each branch may return at most that many entries and +/// the merged union is cut at it. `surface` names the caller in the +/// corrupted-state messages. +#[cfg(feature = "server")] +#[allow(clippy::too_many_arguments)] +pub(crate) fn read_branched_union( + grove: &GroveDb, + surface: &'static str, + prefix_branches: &[Vec>], + paths: &[Vec>], + axis: RankedAxis, + axis_query: AxisQuery, + page_cap: usize, + descending: bool, + transaction: TransactionArg, + grove_version: &GroveVersion, +) -> Result, Error> { + let (prefix, keys, suffix) = decompose_branch_paths(paths)?; + let path_query = + PathQuery::new_branched_axis(prefix, keys.clone(), suffix, axis_query.keys_only()); + let snapshot_transaction = if transaction.is_none() { + Some(grove.start_snapshot_read_transaction()) + } else { + None + }; + let read_transaction = snapshot_transaction.as_ref().or(transaction); + let CostContext { value, cost: _ } = grove.run_path_query( + &path_query, + true, + true, + true, + QueryResultType::QueryKeyElementPairResultType, + read_transaction, + grove_version, + ); + let run = value.map_err(|e| Error::GroveDB(Box::new(e)))?; + let PathQueryRun::BranchedAxisKeys(branches) = run else { + return Err(Error::Drive(DriveError::CorruptedDriveState(format!( + "a branched keys-only {surface} read returned a non-branched shape" + )))); + }; + if branches.len() != keys.len() || branches.iter().map(|(key, _)| key).ne(keys.iter()) { + return Err(Error::Drive(DriveError::CorruptedDriveState(format!( + "a branched {surface} read returned a different branch set than the request resolved" + )))); + } + let per_branch = branches + .into_iter() + .map(|(_key, page)| { + let entries = match page { + None => Vec::new(), + Some(page) => axis_keys_to_ranked(axis, page)?, + }; + if entries.len() > page_cap { + return Err(Error::Drive(DriveError::CorruptedDriveState(format!( + "a branch of a {surface} read returned {} entries for a page cap of \ + {page_cap}", + entries.len(), + )))); + } + Ok(entries) + }) + .collect::, Error>>()?; + merge_branch_pages(per_branch, prefix_branches, descending, page_cap) +} + /// Translate one branch's verified [`AxisEntries`] into drive entries /// on the requested axis — the same mapping the single-path verifiers /// perform, shared here so both surfaces' branched verifiers agree. diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs index 9f601df2882..e6eb339a389 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs @@ -10,14 +10,13 @@ //! Whole module is gated `feature = "server"` via the parent's //! `pub mod execute_top_k;` declaration. -use super::branches::{axis_keys_to_ranked, decompose_branch_paths, merge_branch_pages}; +use super::branches::{decompose_branch_paths, read_branched_union}; use super::{DriveDocumentRankedQuery, RankedAxis, RankedEntry, RankedEntryValue, RankedPage}; use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; use dpp::version::PlatformVersion; -use grovedb::query_result_type::QueryResultType; -use grovedb::{IndexedTopKKeysPage, PathQuery, PathQueryRun, TransactionArg}; +use grovedb::{IndexedTopKKeysPage, PathQuery, TransactionArg}; use grovedb_costs::CostContext; use grovedb_query::AxisQuery; @@ -82,84 +81,31 @@ impl DriveDocumentRankedQuery<'_> { platform_version: &PlatformVersion, ) -> Result { if self.prefix_branches.len() > 1 { - // The whole union is read through ONE grovedb call — a branched - // keys-only PathQuery — pinned to ONE committed state: a `None` - // read runs under a grovedb snapshot read transaction, so every - // per-branch absence probe and axis walk inside the branched - // arm reads the same RocksDB snapshot and a block commit - // landing mid-read cannot mix committed states into the merged - // page. A caller transaction is used as-is — a transactional - // reader asks for its own writes over its base. Absence at any - // depth of a branch's chain is the branched reader's empty - // branch, exactly as the proved path authenticates it. `offset` - // is grammar-rejected with `IN`, so `skipped` is always 0 here. - let grove_version = &platform_version.drive.grove_version; + // ONE grovedb call for the whole union, pinned to one + // committed state and merged with the shared comparator — + // the entire sequence lives in + // `branches::read_branched_union`, shared with the + // having-range surface so the two cannot drift. `offset` is + // grammar-rejected with `IN`, so `skipped` is always 0 here. let paths = (0..self.prefix_branches.len()) .map(|branch| self.indexed_property_name_tree_path(branch)) .collect::, Error>>()?; - let (prefix, keys, suffix) = decompose_branch_paths(&paths)?; - let path_query = PathQuery::new_branched_axis( - prefix, - keys.clone(), - suffix, + let entries = read_branched_union( + &drive.grove, + "ranked", + &self.prefix_branches, + &paths, + self.axis, AxisQuery::top_k( self.axis.into(), self.k, self.offset as u64, self.descending, - ) - .keys_only(), - ); - let snapshot_transaction = if transaction.is_none() { - Some(drive.grove.start_snapshot_read_transaction()) - } else { - None - }; - let read_transaction = snapshot_transaction.as_ref().or(transaction); - let CostContext { value, cost: _ } = drive.grove.run_path_query( - &path_query, - true, - true, - true, - QueryResultType::QueryKeyElementPairResultType, - read_transaction, - grove_version, - ); - let run = value.map_err(|e| Error::GroveDB(Box::new(e)))?; - let PathQueryRun::BranchedAxisKeys(branches) = run else { - return Err(Error::Drive(DriveError::CorruptedDriveState( - "a branched keys-only ranked read returned a non-branched shape".to_string(), - ))); - }; - if branches.len() != keys.len() || branches.iter().map(|(key, _)| key).ne(keys.iter()) { - return Err(Error::Drive(DriveError::CorruptedDriveState( - "a branched ranked read returned a different branch set than the request \ - resolved" - .to_string(), - ))); - } - let per_branch = branches - .into_iter() - .map(|(_key, page)| { - let entries = match page { - None => Vec::new(), - Some(page) => axis_keys_to_ranked(self.axis, page)?, - }; - if entries.len() > self.k as usize { - return Err(Error::Drive(DriveError::CorruptedDriveState(format!( - "a branch of a ranked read returned {} entries for k = {}", - entries.len(), - self.k - )))); - } - Ok(entries) - }) - .collect::, Error>>()?; - let entries = merge_branch_pages( - per_branch, - &self.prefix_branches, - self.descending, + ), self.k as usize, + self.descending, + transaction, + &platform_version.drive.grove_version, )?; return Ok(RankedPage { skipped: 0, diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs index c759da5398c..067da3f0ba6 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs @@ -2577,8 +2577,8 @@ mod pinned_prefix { /// branch's `in_key`, and a **cross-prefix aggregate tie** breaking /// by encoded prefix ascending (X's 32 `1`-bytes before Y's `2`s) — /// the comparator's middle term, observable only here. The proof is - /// a branch container, round-tripped through the shared resolver - /// against the live root hash. + /// one branched `PathQuery` envelope, round-tripped through the + /// shared resolver against the live root hash. #[test] fn in_pinned_top_k_merges_branches_and_proves() { let (drive, contract) = setup_grades_compound_ranked(); @@ -2634,7 +2634,7 @@ mod pinned_prefix { assert_eq!(query.prefix_branches.len(), 2, "two branches resolved"); let (root_hash, verified) = query .verify_ranked_top_k_proof(&proof, platform_version()) - .expect("the branch container must verify"); + .expect("the branched envelope must verify"); assert_eq!(verified.entries, page.entries); assert_eq!( root_hash, From 8ac1d7ecb982b4ac983b349c912dd3c5bf48afee Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 25 Aug 2026 20:56:26 +0200 Subject: [PATCH 14/25] fix(drive)!: one snapshot per branched proof; reject ordinary transactions on branched reads Two snapshot-consistency gaps from review, closed at their sources: Proof generation: grovedb's recursive provers opened a fresh ordinary transaction per layer, so a commit landing mid-generation combined layers from states that never coexisted - the hash chain then fails every verifier, making proof generation spuriously fail under normal block traffic. grovedb (pin cbcb3b59, dashpay/grovedb#831) now begins ONE snapshot read transaction at each prove_query entry (V0 and V1) and threads it through the entire recursion, so every layer - shared ancestors, the branching level, each branch's axis descent - reads one committed state. Proof bytes for any single state are unchanged. Unproved branched reads: a caller-supplied ordinary transaction reads the latest committed state on every operation, so forwarding it through the multi-operation branched arm could tear the union - and an ordinary transaction cannot be told apart from a snapshot-pinned one at this boundary. Branched reads under a caller transaction now fail closed (mirroring the branched provers); per-element reads keep the transactional capability exactly (a single-pin read is one grovedb operation), pinned by the rewritten rejection test. The one-committed-state regression now exercises the production None path itself: a test-only seam fires after the executor takes its internal snapshot, a scoped writer thread lands the commit inside the window, and the None read must return the pre-commit union - deleting the automatic snapshot selection fails the test. Also per review: DocumentRankedMode/DocumentHavingMode prefix_pins docs and both resolvers now describe PrefixPins (one value normally, several for the single branching IN) instead of equality pairs. Co-Authored-By: Claude Fable 5 --- Cargo.lock | 32 +-- packages/rs-dpp/Cargo.toml | 2 +- packages/rs-drive-abci/Cargo.toml | 8 +- packages/rs-drive/Cargo.toml | 14 +- .../query/drive_document_having_query/mod.rs | 20 +- .../drive_document_ranked_query/branches.rs | 62 +++-- .../execute_top_k.rs | 18 +- .../index_picker.rs | 5 +- .../query/drive_document_ranked_query/mod.rs | 18 +- .../drive_document_ranked_query/tests.rs | 224 +++++++++++------- packages/rs-platform-version/Cargo.toml | 2 +- packages/rs-platform-wallet/Cargo.toml | 2 +- packages/rs-sdk/Cargo.toml | 2 +- 13 files changed, 250 insertions(+), 159 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d8f1c123d06..735972ab485 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2974,7 +2974,7 @@ dependencies = [ [[package]] name = "grovedb" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" +source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" dependencies = [ "axum 0.8.9", "bincode", @@ -3013,7 +3013,7 @@ dependencies = [ [[package]] name = "grovedb-bulk-append-tree" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" +source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" dependencies = [ "bincode", "blake3", @@ -3031,7 +3031,7 @@ dependencies = [ [[package]] name = "grovedb-commitment-tree" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" +source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" dependencies = [ "blake3", "grovedb-bulk-append-tree", @@ -3048,7 +3048,7 @@ dependencies = [ [[package]] name = "grovedb-costs" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" +source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" dependencies = [ "integer-encoding", "intmap", @@ -3058,7 +3058,7 @@ dependencies = [ [[package]] name = "grovedb-dense-fixed-sized-merkle-tree" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" +source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" dependencies = [ "bincode", "blake3", @@ -3072,7 +3072,7 @@ dependencies = [ [[package]] name = "grovedb-element" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" +source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" dependencies = [ "bincode", "bincode_derive", @@ -3088,7 +3088,7 @@ dependencies = [ [[package]] name = "grovedb-epoch-based-storage-flags" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" +source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" dependencies = [ "grovedb-costs", "hex", @@ -3100,7 +3100,7 @@ dependencies = [ [[package]] name = "grovedb-merk" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" +source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" dependencies = [ "bincode", "bincode_derive", @@ -3126,7 +3126,7 @@ dependencies = [ [[package]] name = "grovedb-merkle-mountain-range" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" +source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" dependencies = [ "bincode", "blake3", @@ -3139,7 +3139,7 @@ dependencies = [ [[package]] name = "grovedb-path" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" +source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" dependencies = [ "hex", ] @@ -3147,7 +3147,7 @@ dependencies = [ [[package]] name = "grovedb-private-document-store" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" +source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" dependencies = [ "blake3", "grovedb-bulk-append-tree", @@ -3160,7 +3160,7 @@ dependencies = [ [[package]] name = "grovedb-query" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" +source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" dependencies = [ "bincode", "byteorder", @@ -3176,7 +3176,7 @@ dependencies = [ [[package]] name = "grovedb-storage" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" +source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" dependencies = [ "blake3", "grovedb-costs", @@ -3195,7 +3195,7 @@ dependencies = [ [[package]] name = "grovedb-version" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" +source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" dependencies = [ "thiserror 2.0.18", "versioned-feature-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3204,7 +3204,7 @@ dependencies = [ [[package]] name = "grovedb-visualize" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" +source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" dependencies = [ "hex", "itertools 0.14.0", @@ -3213,7 +3213,7 @@ dependencies = [ [[package]] name = "grovedbg-types" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=ad012ded0d36d43a91fdab5d26e71ffd055b1034#ad012ded0d36d43a91fdab5d26e71ffd055b1034" +source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" dependencies = [ "serde", "serde_with 3.21.0", diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index f20fb672e10..7f011c36835 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -71,7 +71,7 @@ strum = { version = "0.26", features = ["derive"] } json-schema-compatibility-validator = { path = '../rs-json-schema-compatibility-validator', optional = true } once_cell = "1.19.0" tracing = { version = "0.1.41" } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41", optional = true } [dev-dependencies] tokio = { version = "1.40", features = ["full"] } diff --git a/packages/rs-drive-abci/Cargo.toml b/packages/rs-drive-abci/Cargo.toml index 7c443ce937b..8ff7c8c54b5 100644 --- a/packages/rs-drive-abci/Cargo.toml +++ b/packages/rs-drive-abci/Cargo.toml @@ -82,7 +82,7 @@ derive_more = { version = "1.0", features = ["from", "deref", "deref_mut"] } async-trait = "0.1.77" console-subscriber = { version = "0.4", optional = true } bls-signatures = { git = "https://github.com/dashpay/bls-signatures", rev = "0842b17583888e8f46c252a4ee84cdfd58e0546f", optional = true } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034" } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41" } nonempty = "0.11" # Shielded-pool snapshot needs raw RocksDB SstFileWriter + ingest_external_file_cf # bindings, and blake3 for the snapshot-file checksum. @@ -107,7 +107,7 @@ dpp = { path = "../rs-dpp", default-features = false, features = [ drive = { path = "../rs-drive", features = ["fixtures-and-mocks"] } drive-proof-verifier = { path = "../rs-drive-proof-verifier" } strategy-tests = { path = "../strategy-tests" } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034", features = ["client"] } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41", features = ["client"] } assert_matches = "1.5.0" drive-abci = { path = ".", features = ["testing-config", "mocks", "shielded_test_data"] } bls-signatures = { git = "https://github.com/dashpay/bls-signatures", rev = "0842b17583888e8f46c252a4ee84cdfd58e0546f" } @@ -121,8 +121,8 @@ integer-encoding = { version = "4.0.0" } # For dump_only_default_and_aux_cfs_under_shielded_subtree_prefix — same # subtree-prefix algorithm grovedb uses internally. -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034" } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41" } [features] default = ["bls-signatures"] diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index e99f9aa6c33..540cbe83cfd 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -52,13 +52,13 @@ enum-map = { version = "2.0.3", optional = true } intmap = { version = "3.0.1", features = ["serde"], optional = true } chrono = { version = "0.4.35", optional = true } itertools = { version = "0.13", optional = true } -grovedb = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034", optional = true, default-features = false } -grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034", optional = true } -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034", optional = true } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034" } -grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034" } -grovedb-query = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034" } +grovedb = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41", optional = true, default-features = false } +grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41", optional = true } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41", optional = true } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41" } +grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41" } +grovedb-query = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41" } [dev-dependencies] criterion = "0.5" diff --git a/packages/rs-drive/src/query/drive_document_having_query/mod.rs b/packages/rs-drive/src/query/drive_document_having_query/mod.rs index 291ccb762f3..07388d7d905 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/mod.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/mod.rs @@ -235,7 +235,7 @@ impl AxisRangeBounds { /// Produced by [`mode_detection::detect_having_mode`]. Parallels /// [`super::drive_document_ranked_query::DocumentRankedMode`]. /// -/// Not `Eq`: the equality pins carry [`Value`]s, whose float variant +/// Not `Eq`: the prefix pins carry [`Value`]s, whose float variant /// keeps the type at `PartialEq`. #[derive(Debug, Clone, PartialEq)] #[cfg(any(feature = "server", feature = "verify"))] @@ -255,12 +255,13 @@ pub struct DocumentHavingMode { /// The field the aggregate applies to. Empty for `COUNT(*)`; the /// index's `summable` property for `SUM` / `AVG`. pub aggregate_field: String, - /// The equality `where` pins, `(property, value)` per clause — - /// exactly one per leading property of the covering compound index, - /// in request order (the resolver re-orders them into index order - /// when it encodes the path). Empty for the single-property form. - /// At most one pin carries several values (the `IN` pin); see - /// [`PrefixPin`]. + /// The `where` prefix pins — one [`PrefixPin`] per clause, exactly + /// one per leading property of the covering compound index, in + /// request order (the resolver re-orders them into index order when + /// it encodes the path). A pin normally carries one value (an `==` + /// clause); at most one pin carries several (the single permitted + /// branching `IN`, whose elements fan the bound out across one + /// prefix branch each). Empty for the single-property form. pub prefix_pins: Vec, } @@ -336,8 +337,9 @@ impl DriveDocumentHavingQuery<'_> { /// Resolve a validated [`DocumentHavingMode`] against a document type's /// indexes into the executable [`DriveDocumentHavingQuery`]: pick the /// covering index (shared with the ranked surface — both read the same -/// indexed tree), encode the equality pins into prefix-value path -/// segments, and assemble the query. +/// indexed tree), encode the prefix pins into prefix **branches** (one +/// branch for all-`==` pins, one branch per element of the single +/// permitted `IN`), and assemble the query. /// /// The **one** resolution path for the having surface, mirroring /// [`super::drive_document_ranked_query::index_picker::resolve_ranked_query_for_mode`]: diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs b/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs index 77c11a444a3..06b1a64cb03 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs @@ -136,6 +136,20 @@ pub fn merge_branch_pages( Ok(merged) } +/// Test-only seam for [`read_branched_union`]: invoked after the +/// internal snapshot transaction is taken and before the branched call +/// runs, so a test can land a commit deterministically inside the +/// window and prove the production `None` path's automatic snapshot +/// selection end-to-end. +#[cfg(test)] +pub(crate) mod test_hooks { + use std::cell::RefCell; + thread_local! { + pub(crate) static AFTER_BRANCHED_SNAPSHOT: RefCell>> = + const { RefCell::new(None) }; + } +} + /// The `(shared prefix, branch keys, shared suffix)` decomposition of a /// branch path set — the triple `PathQuery::new_branched_axis` takes. pub type BranchPathDecomposition = (Vec>, Vec>, Vec>); @@ -232,13 +246,20 @@ pub(crate) fn axis_keys_to_ranked( /// identity, translate and cap each branch's page, and merge with the /// shared comparator. /// -/// The union is served from **one committed state**: with no caller -/// transaction the call runs under a grovedb snapshot read transaction, -/// so every per-branch absence probe and axis walk inside grovedb's -/// branched arm reads the same RocksDB snapshot and a block commit -/// landing mid-read cannot mix committed states into the merged page. A -/// caller transaction is used as-is — a transactional reader asks for -/// its own writes over its base. +/// The union is served from **one committed state**: the call always +/// runs under a grovedb snapshot read transaction taken here, so every +/// per-branch absence probe and axis walk inside grovedb's branched arm +/// reads the same RocksDB snapshot and a block commit landing mid-read +/// cannot mix committed states into the merged page. +/// +/// A caller-supplied transaction is **rejected**, mirroring the +/// branched provers: an ordinary grovedb transaction reads the latest +/// committed state on every operation, so forwarding it would reopen +/// the exact tear the snapshot closes — and there is no way to tell an +/// ordinary transaction from a snapshot-pinned one at this boundary. +/// Transactional callers read per prefix element (each single-branch +/// read is one grovedb operation and honors the transaction exactly), +/// or commit first. /// /// `page_cap` is the surface's page bound (`k` for ranked, `limit` for /// having-range): each branch may return at most that many entries and @@ -258,22 +279,35 @@ pub(crate) fn read_branched_union( transaction: TransactionArg, grove_version: &GroveVersion, ) -> Result, Error> { + if transaction.is_some() { + return Err(Error::Drive(DriveError::NotSupported( + "an IN-pinned (branched) unproved read under a caller transaction is not \ + supported: an ordinary grovedb transaction reads the latest committed state on \ + every operation, so a concurrent commit could tear the union across branches — \ + read per prefix element under the transaction, or pass no transaction (the read \ + then runs under an internal snapshot)", + ))); + } let (prefix, keys, suffix) = decompose_branch_paths(paths)?; let path_query = PathQuery::new_branched_axis(prefix, keys.clone(), suffix, axis_query.keys_only()); - let snapshot_transaction = if transaction.is_none() { - Some(grove.start_snapshot_read_transaction()) - } else { - None - }; - let read_transaction = snapshot_transaction.as_ref().or(transaction); + let snapshot_transaction = grove.start_snapshot_read_transaction(); + // Test-only seam: lets a regression test land a commit deterministically + // INSIDE the window — after the snapshot is taken, before the read runs — + // proving the automatic snapshot selection on the production `None` path. + #[cfg(test)] + test_hooks::AFTER_BRANCHED_SNAPSHOT.with(|hook| { + if let Some(hook) = hook.borrow_mut().as_mut() { + hook(); + } + }); let CostContext { value, cost: _ } = grove.run_path_query( &path_query, true, true, true, QueryResultType::QueryKeyElementPairResultType, - read_transaction, + Some(&snapshot_transaction), grove_version, ); let run = value.map_err(|e| Error::GroveDB(Box::new(e)))?; diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs index e6eb339a389..f6bd4c5f25d 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs @@ -32,10 +32,12 @@ impl DriveDocumentRankedQuery<'_> { /// the branch key itself, or any deeper pinned segment under a /// *present* key — contributes an **empty branch** (union /// semantics), exactly as the proved envelope authenticates it, and - /// the union is served from **one committed state**: a `None` read - /// runs under a grovedb snapshot read transaction, so every - /// per-branch probe and walk reads the same RocksDB snapshot. A - /// missing path under a single `==` pin *is* an error rather than + /// the union is served from **one committed state**: the branched + /// read always runs under a grovedb snapshot read transaction, so + /// every per-branch probe and walk reads the same RocksDB snapshot + /// (a caller transaction is rejected on this shape, mirroring the + /// branched prover — read per prefix element under a transaction). + /// A missing path under a single `==` pin *is* an error rather than /// an empty result: the indexed property-name tree is created when /// the contract is registered, so its absence means the /// contract-level state is not what the request claims, not that @@ -265,10 +267,12 @@ impl DriveDocumentRankedQuery<'_> { ) -> Result, Error> { if self.prefix_branches.len() > 1 { // grovedb's unified `prove_query` proves COMMITTED state only — - // it opens its own transaction internally and cannot see the - // caller's. Serving a proof for a different snapshot than the + // it takes one internal snapshot of committed state and threads + // it through every proof layer, and cannot see the caller's + // transaction. Serving a proof for a different snapshot than the // unproved read would silently desynchronize the two paths, so a - // transactional branched prove fails closed instead. + // transactional branched prove fails closed instead — exactly + // like the branched unproved read. if transaction.is_some() { return Err(Error::Drive(DriveError::NotSupported( "an IN-pinned (branched) ranked proof is generated from committed state \ diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs b/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs index c23aa59002c..5d120758377 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/index_picker.rs @@ -118,8 +118,9 @@ pub fn find_ranked_index_for_mode<'b>( /// Resolve a validated [`DocumentRankedMode`] against a document type's /// indexes into the executable [`DriveDocumentRankedQuery`]: pick the -/// covering index, encode the equality pins into prefix-value path -/// segments, and assemble the query. +/// covering index, encode the prefix pins into prefix **branches** (one +/// branch for all-`==` pins, one branch per element of the single +/// permitted `IN`), and assemble the query. /// /// This is the **one** resolution path — the server's executors and the /// SDK's proof helpers both call it, which is what guarantees a proof diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs b/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs index dd3f9e31007..27b4ca76fa0 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs @@ -436,7 +436,7 @@ pub struct RankedPaginationInputs { /// one executor pair (no-proof / proof) and all of its variation is in /// these values. /// -/// Not `Eq`: the equality pins carry [`Value`]s, whose float variant +/// Not `Eq`: the prefix pins carry [`Value`]s, whose float variant /// keeps the type at `PartialEq`. #[derive(Debug, Clone, PartialEq)] #[cfg(any(feature = "server", feature = "verify"))] @@ -456,13 +456,15 @@ pub struct DocumentRankedMode { /// [`RankedAxis::Count`] (`COUNT(*)`); the index's `summable` /// property for [`RankedAxis::Sum`] / [`RankedAxis::Avg`]. pub aggregate_field: String, - /// The `where` pins — exactly one per leading property of the - /// covering compound index, in whatever order the request supplied - /// them (the resolver re-orders them into index-property order when - /// it encodes the path). Empty for the single-property form. - /// Shape-validated only: the index-aware checks (does a compound - /// index exist whose leading properties these pin?) live in - /// [`index_picker`]. + /// The `where` prefix pins — one [`PrefixPin`] per clause, exactly + /// one per leading property of the covering compound index, in + /// whatever order the request supplied them (the resolver re-orders + /// them into index-property order when it encodes the path). A pin + /// normally carries one value (an `==` clause); at most one carries + /// several (the single permitted branching `IN`). Empty for the + /// single-property form. Shape-validated only: the index-aware + /// checks (does a compound index exist whose leading properties + /// these pin?) live in [`index_picker`]. pub prefix_pins: Vec, } diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs index 067da3f0ba6..999a69ff92a 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs @@ -3186,12 +3186,15 @@ mod pinned_prefix { ); } - /// The branched unproved read is ONE grovedb call executed under the - /// caller's transaction — every absence decision and branch page from - /// one snapshot. A branch written only inside the transaction is - /// visible through it and invisible without it. + /// A branched unproved read REJECTS a caller transaction, mirroring + /// the branched prover: an ordinary grovedb transaction reads the + /// latest committed state on every operation, so forwarding it could + /// tear the union across branches. Per-element reads keep the + /// transactional capability exactly — a single-pin read is one + /// grovedb operation and serves the transaction's own writes — and + /// the committed (`None`) branched read is unaffected. #[test] - fn a_branched_unproved_read_honors_the_transaction() { + fn a_branched_unproved_read_rejects_an_ordinary_transaction() { let (drive, contract) = setup_grades_compound_ranked(); let pv = platform_version(); insert_grades(&drive, &contract, &[(IDENTITY_X, "math", 80)]); @@ -3250,9 +3253,38 @@ mod pinned_prefix { prove: false, }; - let with_tx = match drive + let error = drive .execute_document_ranked_request(request(), Some(&transaction), pv) - .expect("the transactional branched read serves") + .expect_err("a transactional branched read must fail closed"); + assert!( + matches!( + &error, + Error::Drive(crate::error::drive::DriveError::NotSupported(message)) + if message.contains("tear the union") + ), + "expected the caller-transaction rejection, got {error:?}" + ); + + // Per-element under the SAME transaction: the single-pin read is + // one grovedb operation and serves the branch written only + // inside it. + let y_pin = pin(IDENTITY_Y); + let y_request = DocumentRankedRequest { + contract: &contract, + document_type, + group_by: &group_by, + select: SelectProjection::avg("grade"), + having: &[], + order_by: &order_by, + where_clauses: &y_pin, + limit: Some(4), + offset: None, + has_start_at: false, + prove: false, + }; + let with_tx = match drive + .execute_document_ranked_request(y_request, Some(&transaction), pv) + .expect("the transactional single-pin read serves") { DocumentRankedResponse::Entries(page) => page, DocumentRankedResponse::Proof(_) => panic!("expected entries"), @@ -3261,10 +3293,10 @@ mod pinned_prefix { with_tx .entries .iter() - .map(|e| e.in_key.clone()) + .map(|e| (e.key.clone(), e.in_key.clone())) .collect::>(), - vec![Some(IDENTITY_Y.to_vec()), Some(IDENTITY_X.to_vec())], - "under the transaction both branches contribute (Y's 95 outranks X's 80)" + vec![(b"science".to_vec(), None)], + "the single-pin read serves the transaction's own branch" ); let without_tx = match drive @@ -3331,8 +3363,9 @@ mod pinned_prefix { ); // The unproved read is unaffected by the failed prove; it serves - // committed state (the transactional case is pinned by - // `a_branched_unproved_read_honors_the_transaction`). + // committed state (a transactional branched read is likewise + // rejected — pinned by + // `a_branched_unproved_read_rejects_an_ordinary_transaction`). let page = match run(&drive, &contract, &pins, 2, false) .expect("the committed unproved read is unaffected") { @@ -3374,95 +3407,110 @@ mod pinned_prefix { ); } - /// The `IN` union is served from ONE committed state: a `None` read - /// runs under a grovedb snapshot read transaction internally, and - /// the same primitive is exercised here explicitly — a branched read - /// under a snapshot transaction taken before a commit returns the - /// pre-commit union (the new branch still absent, the changed page - /// unchanged), while a fresh committed read returns the post-commit - /// union. + /// The `IN` union is served from ONE committed state through the + /// production `None` path itself: the executor takes a snapshot + /// read transaction internally, and the test-only seam + /// (`branches::test_hooks::AFTER_BRANCHED_SNAPSHOT`) lands a commit + /// deterministically INSIDE the window — after that snapshot is + /// taken, before the branched call runs. The `None` read must + /// return the pre-commit union (the new branch still absent, the + /// changed page unchanged); a second `None` read after the window + /// returns the post-commit union. Removing the executor's internal + /// snapshot selection fails this test. #[test] fn a_branched_read_is_pinned_to_one_committed_state() { + use std::time::Duration; + let (drive, contract) = setup_grades_compound_ranked(); let pv = platform_version(); insert_grades(&drive, &contract, &[(IDENTITY_X, "math", 80)]); - let snapshot_transaction = drive.grove.start_snapshot_read_transaction(); - - // A "block commit" lands after the snapshot: Y's branch springs - // into existence and X gains a class. Documents are built with + // The mid-window "block commit": Y's branch springs into + // existence and X gains a class. Documents are built with // distinct seeds so they cannot collide with `insert_grades`'. - let document_type = contract - .document_type_for_name(DOCUMENT_TYPE) - .expect("grade doctype exists"); - for (seed, identity, class, grade) in [ - (9100u64, IDENTITY_Y, "science", 95i64), - (9101u64, IDENTITY_X, "art", 90i64), - ] { - let mut doc: Document = document_type - .random_document(Some(seed), pv) - .expect("random document"); - let mut props = BTreeMap::new(); - props.insert(PREFIX_PROPERTY.to_string(), Value::Identifier(identity)); - props.insert(CLASS_PROPERTY.to_string(), Value::Text(class.to_string())); - props.insert("grade".to_string(), Value::I64(grade)); - doc.set_properties(props); - drive - .add_document_for_contract( - DocumentAndContractInfo { - owned_document_info: OwnedDocumentInfo { - document_info: DocumentRefInfo((&doc, None)), - owner_id: None, + let commit_post_snapshot_rows = || { + let document_type = contract + .document_type_for_name(DOCUMENT_TYPE) + .expect("grade doctype exists"); + for (seed, identity, class, grade) in [ + (9100u64, IDENTITY_Y, "science", 95i64), + (9101u64, IDENTITY_X, "art", 90i64), + ] { + let mut doc: Document = document_type + .random_document(Some(seed), pv) + .expect("random document"); + let mut props = BTreeMap::new(); + props.insert(PREFIX_PROPERTY.to_string(), Value::Identifier(identity)); + props.insert(CLASS_PROPERTY.to_string(), Value::Text(class.to_string())); + props.insert("grade".to_string(), Value::I64(grade)); + doc.set_properties(props); + drive + .add_document_for_contract( + DocumentAndContractInfo { + owned_document_info: OwnedDocumentInfo { + document_info: DocumentRefInfo((&doc, None)), + owner_id: None, + }, + contract: &contract, + document_type, }, - contract: &contract, - document_type, - }, - false, - BlockInfo::default(), - true, - None, - pv, - None, - ) - .expect("expected to commit the post-snapshot grade"); - } + false, + BlockInfo::default(), + true, + None, + pv, + None, + ) + .expect("expected to commit the mid-window grade"); + } + }; + + // Rendezvous: when the executor's hook fires (snapshot taken, + // read not yet run), wake the writer, wait for its commit to + // land, then let the read proceed. + let (enter_window_tx, enter_window_rx) = std::sync::mpsc::channel::<()>(); + let (commit_done_tx, commit_done_rx) = std::sync::mpsc::channel::<()>(); + super::super::branches::test_hooks::AFTER_BRANCHED_SNAPSHOT.with(|hook| { + *hook.borrow_mut() = Some(Box::new(move || { + let _ = enter_window_tx.send(()); + let _ = commit_done_rx.recv_timeout(Duration::from_secs(20)); + })); + }); let pins = in_pin(&[IDENTITY_X, IDENTITY_Y]); - let group_by = vec![CLASS_PROPERTY.to_string()]; - let order_by = vec![OrderClause { - field: "grade".to_string(), - ascending: false, - }]; - let request = || DocumentRankedRequest { - contract: &contract, - document_type, - group_by: &group_by, - select: SelectProjection::avg("grade"), - having: &[], - order_by: &order_by, - where_clauses: &pins, - limit: Some(4), - offset: None, - has_start_at: false, - prove: false, - }; + let pinned = std::thread::scope(|scope| { + scope.spawn(move || { + if enter_window_rx + .recv_timeout(Duration::from_secs(20)) + .is_ok() + { + commit_post_snapshot_rows(); + let _ = commit_done_tx.send(()); + } + }); + match run(&drive, &contract, &pins, 4, false) + .expect("the branched read serves across the mid-window commit") + { + DocumentRankedResponse::Entries(page) => page, + DocumentRankedResponse::Proof(_) => panic!("expected entries"), + } + }); + super::super::branches::test_hooks::AFTER_BRANCHED_SNAPSHOT.with(|hook| { + *hook.borrow_mut() = None; + }); - // Under the pre-commit snapshot the union is the pre-commit - // state: Y's branch is still absent (empty, not an error) and X - // has only math. - let pinned = match drive - .execute_document_ranked_request(request(), Some(&snapshot_transaction), pv) - .expect("the snapshot branched read serves") - { - DocumentRankedResponse::Entries(page) => page, - DocumentRankedResponse::Proof(_) => panic!("expected entries"), - }; + // The `None` read observed the pre-commit state even though the + // commit landed inside its window: Y's branch still absent + // (empty, not an error), X still math-only. assert_eq!( - pinned.entries.len(), - 1, - "the snapshot union is the pre-commit state" + pinned + .entries + .iter() + .map(|e| e.key.clone()) + .collect::>(), + vec![b"math".to_vec()], + "the automatic snapshot pins the union to the pre-commit state" ); - assert_eq!(pinned.entries[0].key, b"math".to_vec()); // A fresh committed read sees the post-commit union. let fresh = match run(&drive, &contract, &pins, 4, false) diff --git a/packages/rs-platform-version/Cargo.toml b/packages/rs-platform-version/Cargo.toml index 68a3f150fc8..3bc4ecf2702 100644 --- a/packages/rs-platform-version/Cargo.toml +++ b/packages/rs-platform-version/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" thiserror = { version = "2.0.12" } bincode = { version = "=2.0.1" } versioned-feature-core = { git = "https://github.com/dashpay/versioned-feature-core", version = "1.0.0" } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034" } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41" } [features] mock-versions = [] diff --git a/packages/rs-platform-wallet/Cargo.toml b/packages/rs-platform-wallet/Cargo.toml index 97bf468c610..b2d329cff36 100644 --- a/packages/rs-platform-wallet/Cargo.toml +++ b/packages/rs-platform-wallet/Cargo.toml @@ -69,7 +69,7 @@ zeroize = "1" log = "0.4" # Shielded pool (optional, behind `shielded` feature) -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41", optional = true } # Direct `rusqlite` access so `FileBackedShieldedStore::open_path` can set # WAL + synchronous=NORMAL pragmas before handing the connection to # `ClientPersistentCommitmentTree`. Version locked to match the rev grovedb diff --git a/packages/rs-sdk/Cargo.toml b/packages/rs-sdk/Cargo.toml index a3975930164..b495a6f1eab 100644 --- a/packages/rs-sdk/Cargo.toml +++ b/packages/rs-sdk/Cargo.toml @@ -18,7 +18,7 @@ drive = { path = "../rs-drive", default-features = false, features = [ ] } drive-proof-verifier = { path = "../rs-drive-proof-verifier", default-features = false } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "ad012ded0d36d43a91fdab5d26e71ffd055b1034", features = [ +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41", features = [ "client", "sqlite", ], optional = true } From d66779e23bcc6266f6710a57111a83679559b9cb Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 25 Aug 2026 21:03:50 +0200 Subject: [PATCH 15/25] fix(drive): move the test-hook module to the end of branches.rs clippy 1.92's items_after_test_module (a warning locally, an error under CI's -D warnings) forbids items after a #[cfg(test)] module; the test_hooks seam now sits last in the file. Co-Authored-By: Claude Fable 5 --- .../drive_document_ranked_query/branches.rs | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs b/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs index 06b1a64cb03..8f7e9c24bcb 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/branches.rs @@ -136,20 +136,6 @@ pub fn merge_branch_pages( Ok(merged) } -/// Test-only seam for [`read_branched_union`]: invoked after the -/// internal snapshot transaction is taken and before the branched call -/// runs, so a test can land a commit deterministically inside the -/// window and prove the production `None` path's automatic snapshot -/// selection end-to-end. -#[cfg(test)] -pub(crate) mod test_hooks { - use std::cell::RefCell; - thread_local! { - pub(crate) static AFTER_BRANCHED_SNAPSHOT: RefCell>> = - const { RefCell::new(None) }; - } -} - /// The `(shared prefix, branch keys, shared suffix)` decomposition of a /// branch path set — the triple `PathQuery::new_branched_axis` takes. pub type BranchPathDecomposition = (Vec>, Vec>, Vec>); @@ -382,3 +368,19 @@ pub fn axis_entries_to_ranked( )))), } } + +/// Test-only seam for [`read_branched_union`]: invoked after the +/// internal snapshot transaction is taken and before the branched call +/// runs, so a test can land a commit deterministically inside the +/// window and prove the production `None` path's automatic snapshot +/// selection end-to-end. Last in the file: clippy's +/// `items_after_test_module` forbids items after a `#[cfg(test)]` +/// module. +#[cfg(test)] +pub(crate) mod test_hooks { + use std::cell::RefCell; + thread_local! { + pub(crate) static AFTER_BRANCHED_SNAPSHOT: RefCell>> = + const { RefCell::new(None) }; + } +} From ca50ce6aed843563b42c17ec6f8b2fa552945a28 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 25 Aug 2026 23:13:25 +0200 Subject: [PATCH 16/25] chore(drive): pin grovedb at the proof-snapshot regression test Per review: the snapshot threading through recursive proof generation had no concurrency regression - a regression back to per-layer views would have stayed green. grovedb fa6c85cf adds a test-only seam fired in both prove_query entries right after the generation snapshot is taken, and a test that lands a commit inside that window through a scoped writer thread and requires the branched envelope to still verify against the PRE-commit root with the pre-commit content, absence included - the common primitive both IN-pinned surfaces prove through. Test-only grovedb delta; no platform code change. Co-Authored-By: Claude Fable 5 --- Cargo.lock | 32 ++++++++++++------------- packages/rs-dpp/Cargo.toml | 2 +- packages/rs-drive-abci/Cargo.toml | 8 +++---- packages/rs-drive/Cargo.toml | 14 +++++------ packages/rs-platform-version/Cargo.toml | 2 +- packages/rs-platform-wallet/Cargo.toml | 2 +- packages/rs-sdk/Cargo.toml | 2 +- 7 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 735972ab485..d4127078cfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2974,7 +2974,7 @@ dependencies = [ [[package]] name = "grovedb" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" +source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" dependencies = [ "axum 0.8.9", "bincode", @@ -3013,7 +3013,7 @@ dependencies = [ [[package]] name = "grovedb-bulk-append-tree" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" +source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" dependencies = [ "bincode", "blake3", @@ -3031,7 +3031,7 @@ dependencies = [ [[package]] name = "grovedb-commitment-tree" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" +source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" dependencies = [ "blake3", "grovedb-bulk-append-tree", @@ -3048,7 +3048,7 @@ dependencies = [ [[package]] name = "grovedb-costs" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" +source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" dependencies = [ "integer-encoding", "intmap", @@ -3058,7 +3058,7 @@ dependencies = [ [[package]] name = "grovedb-dense-fixed-sized-merkle-tree" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" +source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" dependencies = [ "bincode", "blake3", @@ -3072,7 +3072,7 @@ dependencies = [ [[package]] name = "grovedb-element" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" +source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" dependencies = [ "bincode", "bincode_derive", @@ -3088,7 +3088,7 @@ dependencies = [ [[package]] name = "grovedb-epoch-based-storage-flags" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" +source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" dependencies = [ "grovedb-costs", "hex", @@ -3100,7 +3100,7 @@ dependencies = [ [[package]] name = "grovedb-merk" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" +source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" dependencies = [ "bincode", "bincode_derive", @@ -3126,7 +3126,7 @@ dependencies = [ [[package]] name = "grovedb-merkle-mountain-range" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" +source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" dependencies = [ "bincode", "blake3", @@ -3139,7 +3139,7 @@ dependencies = [ [[package]] name = "grovedb-path" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" +source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" dependencies = [ "hex", ] @@ -3147,7 +3147,7 @@ dependencies = [ [[package]] name = "grovedb-private-document-store" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" +source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" dependencies = [ "blake3", "grovedb-bulk-append-tree", @@ -3160,7 +3160,7 @@ dependencies = [ [[package]] name = "grovedb-query" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" +source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" dependencies = [ "bincode", "byteorder", @@ -3176,7 +3176,7 @@ dependencies = [ [[package]] name = "grovedb-storage" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" +source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" dependencies = [ "blake3", "grovedb-costs", @@ -3195,7 +3195,7 @@ dependencies = [ [[package]] name = "grovedb-version" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" +source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" dependencies = [ "thiserror 2.0.18", "versioned-feature-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3204,7 +3204,7 @@ dependencies = [ [[package]] name = "grovedb-visualize" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" +source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" dependencies = [ "hex", "itertools 0.14.0", @@ -3213,7 +3213,7 @@ dependencies = [ [[package]] name = "grovedbg-types" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=cbcb3b594d649b24670041bbbdb3b2522573eb41#cbcb3b594d649b24670041bbbdb3b2522573eb41" +source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" dependencies = [ "serde", "serde_with 3.21.0", diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index 7f011c36835..88ebfdf8c25 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -71,7 +71,7 @@ strum = { version = "0.26", features = ["derive"] } json-schema-compatibility-validator = { path = '../rs-json-schema-compatibility-validator', optional = true } once_cell = "1.19.0" tracing = { version = "0.1.41" } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08", optional = true } [dev-dependencies] tokio = { version = "1.40", features = ["full"] } diff --git a/packages/rs-drive-abci/Cargo.toml b/packages/rs-drive-abci/Cargo.toml index 8ff7c8c54b5..1cd478e58f0 100644 --- a/packages/rs-drive-abci/Cargo.toml +++ b/packages/rs-drive-abci/Cargo.toml @@ -82,7 +82,7 @@ derive_more = { version = "1.0", features = ["from", "deref", "deref_mut"] } async-trait = "0.1.77" console-subscriber = { version = "0.4", optional = true } bls-signatures = { git = "https://github.com/dashpay/bls-signatures", rev = "0842b17583888e8f46c252a4ee84cdfd58e0546f", optional = true } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41" } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08" } nonempty = "0.11" # Shielded-pool snapshot needs raw RocksDB SstFileWriter + ingest_external_file_cf # bindings, and blake3 for the snapshot-file checksum. @@ -107,7 +107,7 @@ dpp = { path = "../rs-dpp", default-features = false, features = [ drive = { path = "../rs-drive", features = ["fixtures-and-mocks"] } drive-proof-verifier = { path = "../rs-drive-proof-verifier" } strategy-tests = { path = "../strategy-tests" } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41", features = ["client"] } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08", features = ["client"] } assert_matches = "1.5.0" drive-abci = { path = ".", features = ["testing-config", "mocks", "shielded_test_data"] } bls-signatures = { git = "https://github.com/dashpay/bls-signatures", rev = "0842b17583888e8f46c252a4ee84cdfd58e0546f" } @@ -121,8 +121,8 @@ integer-encoding = { version = "4.0.0" } # For dump_only_default_and_aux_cfs_under_shielded_subtree_prefix — same # subtree-prefix algorithm grovedb uses internally. -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41" } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08" } [features] default = ["bls-signatures"] diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index 540cbe83cfd..7bc4239e2cb 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -52,13 +52,13 @@ enum-map = { version = "2.0.3", optional = true } intmap = { version = "3.0.1", features = ["serde"], optional = true } chrono = { version = "0.4.35", optional = true } itertools = { version = "0.13", optional = true } -grovedb = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41", optional = true, default-features = false } -grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41", optional = true } -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41", optional = true } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41" } -grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41" } -grovedb-query = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41" } +grovedb = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08", optional = true, default-features = false } +grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08", optional = true } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08", optional = true } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08" } +grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08" } +grovedb-query = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08" } [dev-dependencies] criterion = "0.5" diff --git a/packages/rs-platform-version/Cargo.toml b/packages/rs-platform-version/Cargo.toml index 3bc4ecf2702..c142541aa30 100644 --- a/packages/rs-platform-version/Cargo.toml +++ b/packages/rs-platform-version/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" thiserror = { version = "2.0.12" } bincode = { version = "=2.0.1" } versioned-feature-core = { git = "https://github.com/dashpay/versioned-feature-core", version = "1.0.0" } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41" } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08" } [features] mock-versions = [] diff --git a/packages/rs-platform-wallet/Cargo.toml b/packages/rs-platform-wallet/Cargo.toml index b2d329cff36..5c3a61b3839 100644 --- a/packages/rs-platform-wallet/Cargo.toml +++ b/packages/rs-platform-wallet/Cargo.toml @@ -69,7 +69,7 @@ zeroize = "1" log = "0.4" # Shielded pool (optional, behind `shielded` feature) -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08", optional = true } # Direct `rusqlite` access so `FileBackedShieldedStore::open_path` can set # WAL + synchronous=NORMAL pragmas before handing the connection to # `ClientPersistentCommitmentTree`. Version locked to match the rev grovedb diff --git a/packages/rs-sdk/Cargo.toml b/packages/rs-sdk/Cargo.toml index b495a6f1eab..0b9d36cb94f 100644 --- a/packages/rs-sdk/Cargo.toml +++ b/packages/rs-sdk/Cargo.toml @@ -18,7 +18,7 @@ drive = { path = "../rs-drive", default-features = false, features = [ ] } drive-proof-verifier = { path = "../rs-drive-proof-verifier", default-features = false } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "cbcb3b594d649b24670041bbbdb3b2522573eb41", features = [ +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08", features = [ "client", "sqlite", ], optional = true } From 49b700733149ccf0aba9f8483b9c9058e590f609 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 26 Aug 2026 12:54:55 +0200 Subject: [PATCH 17/25] chore(drive): pin grovedb at develop after #831 landed Pure hash normalization: dashpay/grovedb#831 (snapshot-pinned read transactions) squash-merged into develop as f7e9d1b9, so the interim pin at the PR-branch head fa6c85cf moves to the develop commit carrying identical content. No code change; ranked/having suites (112), workspace check and fmt green at the new pin. Co-Authored-By: Claude Fable 5 --- Cargo.lock | 32 ++++++++++++------------- packages/rs-dpp/Cargo.toml | 2 +- packages/rs-drive-abci/Cargo.toml | 8 +++---- packages/rs-drive/Cargo.toml | 14 +++++------ packages/rs-platform-version/Cargo.toml | 2 +- packages/rs-platform-wallet/Cargo.toml | 2 +- packages/rs-sdk/Cargo.toml | 2 +- 7 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4127078cfb..676135af4ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2974,7 +2974,7 @@ dependencies = [ [[package]] name = "grovedb" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" +source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" dependencies = [ "axum 0.8.9", "bincode", @@ -3013,7 +3013,7 @@ dependencies = [ [[package]] name = "grovedb-bulk-append-tree" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" +source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" dependencies = [ "bincode", "blake3", @@ -3031,7 +3031,7 @@ dependencies = [ [[package]] name = "grovedb-commitment-tree" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" +source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" dependencies = [ "blake3", "grovedb-bulk-append-tree", @@ -3048,7 +3048,7 @@ dependencies = [ [[package]] name = "grovedb-costs" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" +source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" dependencies = [ "integer-encoding", "intmap", @@ -3058,7 +3058,7 @@ dependencies = [ [[package]] name = "grovedb-dense-fixed-sized-merkle-tree" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" +source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" dependencies = [ "bincode", "blake3", @@ -3072,7 +3072,7 @@ dependencies = [ [[package]] name = "grovedb-element" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" +source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" dependencies = [ "bincode", "bincode_derive", @@ -3088,7 +3088,7 @@ dependencies = [ [[package]] name = "grovedb-epoch-based-storage-flags" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" +source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" dependencies = [ "grovedb-costs", "hex", @@ -3100,7 +3100,7 @@ dependencies = [ [[package]] name = "grovedb-merk" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" +source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" dependencies = [ "bincode", "bincode_derive", @@ -3126,7 +3126,7 @@ dependencies = [ [[package]] name = "grovedb-merkle-mountain-range" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" +source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" dependencies = [ "bincode", "blake3", @@ -3139,7 +3139,7 @@ dependencies = [ [[package]] name = "grovedb-path" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" +source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" dependencies = [ "hex", ] @@ -3147,7 +3147,7 @@ dependencies = [ [[package]] name = "grovedb-private-document-store" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" +source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" dependencies = [ "blake3", "grovedb-bulk-append-tree", @@ -3160,7 +3160,7 @@ dependencies = [ [[package]] name = "grovedb-query" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" +source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" dependencies = [ "bincode", "byteorder", @@ -3176,7 +3176,7 @@ dependencies = [ [[package]] name = "grovedb-storage" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" +source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" dependencies = [ "blake3", "grovedb-costs", @@ -3195,7 +3195,7 @@ dependencies = [ [[package]] name = "grovedb-version" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" +source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" dependencies = [ "thiserror 2.0.18", "versioned-feature-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3204,7 +3204,7 @@ dependencies = [ [[package]] name = "grovedb-visualize" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" +source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" dependencies = [ "hex", "itertools 0.14.0", @@ -3213,7 +3213,7 @@ dependencies = [ [[package]] name = "grovedbg-types" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=fa6c85cf24194e5e07635f871c8540ea6fb4ec08#fa6c85cf24194e5e07635f871c8540ea6fb4ec08" +source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" dependencies = [ "serde", "serde_with 3.21.0", diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index 88ebfdf8c25..ba37d246cc3 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -71,7 +71,7 @@ strum = { version = "0.26", features = ["derive"] } json-schema-compatibility-validator = { path = '../rs-json-schema-compatibility-validator', optional = true } once_cell = "1.19.0" tracing = { version = "0.1.41" } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d", optional = true } [dev-dependencies] tokio = { version = "1.40", features = ["full"] } diff --git a/packages/rs-drive-abci/Cargo.toml b/packages/rs-drive-abci/Cargo.toml index 1cd478e58f0..77e8e481c8c 100644 --- a/packages/rs-drive-abci/Cargo.toml +++ b/packages/rs-drive-abci/Cargo.toml @@ -82,7 +82,7 @@ derive_more = { version = "1.0", features = ["from", "deref", "deref_mut"] } async-trait = "0.1.77" console-subscriber = { version = "0.4", optional = true } bls-signatures = { git = "https://github.com/dashpay/bls-signatures", rev = "0842b17583888e8f46c252a4ee84cdfd58e0546f", optional = true } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08" } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d" } nonempty = "0.11" # Shielded-pool snapshot needs raw RocksDB SstFileWriter + ingest_external_file_cf # bindings, and blake3 for the snapshot-file checksum. @@ -107,7 +107,7 @@ dpp = { path = "../rs-dpp", default-features = false, features = [ drive = { path = "../rs-drive", features = ["fixtures-and-mocks"] } drive-proof-verifier = { path = "../rs-drive-proof-verifier" } strategy-tests = { path = "../strategy-tests" } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08", features = ["client"] } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d", features = ["client"] } assert_matches = "1.5.0" drive-abci = { path = ".", features = ["testing-config", "mocks", "shielded_test_data"] } bls-signatures = { git = "https://github.com/dashpay/bls-signatures", rev = "0842b17583888e8f46c252a4ee84cdfd58e0546f" } @@ -121,8 +121,8 @@ integer-encoding = { version = "4.0.0" } # For dump_only_default_and_aux_cfs_under_shielded_subtree_prefix — same # subtree-prefix algorithm grovedb uses internally. -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08" } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d" } [features] default = ["bls-signatures"] diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index 7bc4239e2cb..cb277d15567 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -52,13 +52,13 @@ enum-map = { version = "2.0.3", optional = true } intmap = { version = "3.0.1", features = ["serde"], optional = true } chrono = { version = "0.4.35", optional = true } itertools = { version = "0.13", optional = true } -grovedb = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08", optional = true, default-features = false } -grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08", optional = true } -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08", optional = true } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08" } -grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08" } -grovedb-query = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08" } +grovedb = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d", optional = true, default-features = false } +grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d", optional = true } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d", optional = true } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d" } +grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d" } +grovedb-query = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d" } [dev-dependencies] criterion = "0.5" diff --git a/packages/rs-platform-version/Cargo.toml b/packages/rs-platform-version/Cargo.toml index c142541aa30..8111477a9e4 100644 --- a/packages/rs-platform-version/Cargo.toml +++ b/packages/rs-platform-version/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" thiserror = { version = "2.0.12" } bincode = { version = "=2.0.1" } versioned-feature-core = { git = "https://github.com/dashpay/versioned-feature-core", version = "1.0.0" } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08" } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d" } [features] mock-versions = [] diff --git a/packages/rs-platform-wallet/Cargo.toml b/packages/rs-platform-wallet/Cargo.toml index 5c3a61b3839..ac7713e2f2f 100644 --- a/packages/rs-platform-wallet/Cargo.toml +++ b/packages/rs-platform-wallet/Cargo.toml @@ -69,7 +69,7 @@ zeroize = "1" log = "0.4" # Shielded pool (optional, behind `shielded` feature) -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d", optional = true } # Direct `rusqlite` access so `FileBackedShieldedStore::open_path` can set # WAL + synchronous=NORMAL pragmas before handing the connection to # `ClientPersistentCommitmentTree`. Version locked to match the rev grovedb diff --git a/packages/rs-sdk/Cargo.toml b/packages/rs-sdk/Cargo.toml index 0b9d36cb94f..c565876223a 100644 --- a/packages/rs-sdk/Cargo.toml +++ b/packages/rs-sdk/Cargo.toml @@ -18,7 +18,7 @@ drive = { path = "../rs-drive", default-features = false, features = [ ] } drive-proof-verifier = { path = "../rs-drive-proof-verifier", default-features = false } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "fa6c85cf24194e5e07635f871c8540ea6fb4ec08", features = [ +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d", features = [ "client", "sqlite", ], optional = true } From b7fd5b9f8e2b1256ee69cf29819599fe7c24bb2a Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 26 Aug 2026 15:15:29 +0200 Subject: [PATCH 18/25] chore(drive)!: pin grovedb at develop after #833; adapt savepoint error wraps; SDK docs for branching IN The grovedb #833 hardening (snapshot read transactions refuse writes with a typed error, expose their age, and route every read through the snapshot-injecting funnel) squash-merged into develop; the pin moves to that head. Its transaction wrapper returns the storage `Error` from `rollback_to_savepoint`, so the three call sites that hand-wrapped the raw rocksdb error (prepare_proposal, process_proposal, and the per-transition rollback in process_raw_state_transitions) now map the storage error directly. Also closes the final validation's last suggestion: the SDK-facing request-shape docs and error guidance in dash-platform-queries (ranked and having entry docs, both proof helpers' error strings) now describe the single bounded branching `IN`, singleton-`IN` normalization, the `in_key` branch discriminator on merged pages, and the non-zero-offset and null-pin exclusions, instead of calling compound prefixes equality-only. Co-Authored-By: Claude Fable 5 --- Cargo.lock | 32 +++++++++---------- .../src/documents/document_having_entries.rs | 15 ++++++--- .../src/documents/document_ranked_entries.rs | 20 ++++++++---- .../src/documents/having_proof_helpers.rs | 9 ++++-- .../src/documents/ranked_proof_helpers.rs | 9 ++++-- packages/rs-dpp/Cargo.toml | 2 +- packages/rs-drive-abci/Cargo.toml | 8 ++--- .../src/abci/handler/prepare_proposal.rs | 3 +- .../src/abci/handler/process_proposal.rs | 3 +- .../process_raw_state_transitions/v0/mod.rs | 7 ++-- packages/rs-drive/Cargo.toml | 14 ++++---- packages/rs-platform-version/Cargo.toml | 2 +- packages/rs-platform-wallet/Cargo.toml | 2 +- packages/rs-sdk/Cargo.toml | 2 +- 14 files changed, 72 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 676135af4ae..e08b3411c73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2974,7 +2974,7 @@ dependencies = [ [[package]] name = "grovedb" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" +source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" dependencies = [ "axum 0.8.9", "bincode", @@ -3013,7 +3013,7 @@ dependencies = [ [[package]] name = "grovedb-bulk-append-tree" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" +source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" dependencies = [ "bincode", "blake3", @@ -3031,7 +3031,7 @@ dependencies = [ [[package]] name = "grovedb-commitment-tree" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" +source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" dependencies = [ "blake3", "grovedb-bulk-append-tree", @@ -3048,7 +3048,7 @@ dependencies = [ [[package]] name = "grovedb-costs" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" +source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" dependencies = [ "integer-encoding", "intmap", @@ -3058,7 +3058,7 @@ dependencies = [ [[package]] name = "grovedb-dense-fixed-sized-merkle-tree" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" +source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" dependencies = [ "bincode", "blake3", @@ -3072,7 +3072,7 @@ dependencies = [ [[package]] name = "grovedb-element" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" +source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" dependencies = [ "bincode", "bincode_derive", @@ -3088,7 +3088,7 @@ dependencies = [ [[package]] name = "grovedb-epoch-based-storage-flags" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" +source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" dependencies = [ "grovedb-costs", "hex", @@ -3100,7 +3100,7 @@ dependencies = [ [[package]] name = "grovedb-merk" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" +source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" dependencies = [ "bincode", "bincode_derive", @@ -3126,7 +3126,7 @@ dependencies = [ [[package]] name = "grovedb-merkle-mountain-range" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" +source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" dependencies = [ "bincode", "blake3", @@ -3139,7 +3139,7 @@ dependencies = [ [[package]] name = "grovedb-path" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" +source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" dependencies = [ "hex", ] @@ -3147,7 +3147,7 @@ dependencies = [ [[package]] name = "grovedb-private-document-store" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" +source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" dependencies = [ "blake3", "grovedb-bulk-append-tree", @@ -3160,7 +3160,7 @@ dependencies = [ [[package]] name = "grovedb-query" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" +source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" dependencies = [ "bincode", "byteorder", @@ -3176,7 +3176,7 @@ dependencies = [ [[package]] name = "grovedb-storage" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" +source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" dependencies = [ "blake3", "grovedb-costs", @@ -3195,7 +3195,7 @@ dependencies = [ [[package]] name = "grovedb-version" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" +source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" dependencies = [ "thiserror 2.0.18", "versioned-feature-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3204,7 +3204,7 @@ dependencies = [ [[package]] name = "grovedb-visualize" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" +source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" dependencies = [ "hex", "itertools 0.14.0", @@ -3213,7 +3213,7 @@ dependencies = [ [[package]] name = "grovedbg-types" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=f7e9d1b903a0348d3de856244abf5774f201fb0d#f7e9d1b903a0348d3de856244abf5774f201fb0d" +source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" dependencies = [ "serde", "serde_with 3.21.0", diff --git a/packages/dash-platform-queries/src/documents/document_having_entries.rs b/packages/dash-platform-queries/src/documents/document_having_entries.rs index 33b4997895f..2468d6801e0 100644 --- a/packages/dash-platform-queries/src/documents/document_having_entries.rs +++ b/packages/dash-platform-queries/src/documents/document_having_entries.rs @@ -22,10 +22,16 @@ //! a contiguous-range operator (`=`, `>`, `>=`, `<`, `<=`, `BETWEEN*` — //! `!=` and `IN` are rejected), and a `LIMIT`. `ORDER BY` is optional: //! omitted means ascending by the aggregate; naming the selected -//! aggregate sets the direction. `where` clauses are equality pins on a -//! covering compound ranked index's leading properties (one per leading +//! aggregate sets the direction. `where` clauses are pins on a covering +//! compound ranked index's leading properties (one per leading //! property, selecting which prefix's groups the bound reads) — absent -//! for a single-property index. No `offset`, no `start_at`. +//! for a single-property index. Each pin is an equality, except that +//! **at most one** may be an `IN` of 2..=10 distinct elements: the +//! bound fans out across one prefix branch per element and merges, +//! entries carrying the encoded branch segment in `in_key` (unset on +//! single-branch responses; a single-element `IN` normalizes to the +//! equality pin; a `null` pin on another property cannot combine with +//! the `IN`). No `offset`, no `start_at`. //! //! ## Contract prerequisites //! @@ -33,7 +39,8 @@ //! `rankedCountable` / `rankedSummable` / `rankedAverageable` //! (meta-schema v3, **protocol version 14+**). The index may be //! single-property (`group_by` its property, no `where`) or compound -//! (`group_by` its trailing property, equality-pin every leading one). +//! (`group_by` its trailing property, pin every leading one — equality +//! pins, at most one of them an `IN`). //! Against a pre-v14 node the request is refused with "HAVING clause //! is not yet implemented" — the intended activation gate. //! diff --git a/packages/dash-platform-queries/src/documents/document_ranked_entries.rs b/packages/dash-platform-queries/src/documents/document_ranked_entries.rs index 1f351f2c4b9..31aa47d488b 100644 --- a/packages/dash-platform-queries/src/documents/document_ranked_entries.rs +++ b/packages/dash-platform-queries/src/documents/document_ranked_entries.rs @@ -18,13 +18,19 @@ //! //! Exactly one aggregate `select`, exactly one `group_by` property, //! exactly one `ORDER BY` clause naming that select's aggregate, and a -//! `LIMIT` — plus an optional `OFFSET`. `where` clauses are equality -//! pins on a covering compound ranked index's leading properties (one -//! per leading property, selecting which prefix's own ranking the walk -//! reads) — absent for a single-property index. No `having`, no -//! `start_at`: each of those is rejected rather than ignored, on both -//! sides, because a ranked walk cannot honour them and silently -//! answering a different question is worse than an error. +//! `LIMIT` — plus an optional `OFFSET`. `where` clauses are pins on a +//! covering compound ranked index's leading properties (one per leading +//! property, selecting which prefix's own ranking the walk reads) — +//! absent for a single-property index. Each pin is an equality, except +//! that **at most one** may be an `IN` of 2..=10 distinct elements: one +//! walk per element, merged by `(aggregate, encoded pin, group key)`, +//! with each merged entry carrying the encoded branch segment in +//! `in_key` (unset on single-branch responses; a single-element `IN` +//! normalizes to the equality pin). A non-zero `OFFSET` cannot combine +//! with the `IN`, nor can a `null` pin on another property. No +//! `having`, no `start_at`: each of those is rejected rather than +//! ignored, on both sides, because a ranked walk cannot honour them and +//! silently answering a different question is worse than an error. //! //! [`DocumentQuery::order_by_selected_aggregate`] builds the ordering //! clause, deriving the ordered field from the `select` through diff --git a/packages/dash-platform-queries/src/documents/having_proof_helpers.rs b/packages/dash-platform-queries/src/documents/having_proof_helpers.rs index c751e30ca09..2b0bd03b805 100644 --- a/packages/dash-platform-queries/src/documents/having_proof_helpers.rs +++ b/packages/dash-platform-queries/src/documents/having_proof_helpers.rs @@ -71,8 +71,10 @@ pub(super) fn assert_having_shape( `.with_having()` and `.with_limit(n)`, optionally \ `.order_by_selected_aggregate()`, with no offset and no start_at; \ - where clauses, when present, must be equality pins on the covering compound \ - index's leading properties." + where clauses, when present, pin the covering compound index's leading \ + properties — one equality pin per property, of which at most one may instead \ + be an `IN` of 2..=10 elements (merged entries then carry `in_key`; a null pin \ + on another property is rejected with `IN`)." ), }) } @@ -128,7 +130,8 @@ pub(super) fn verify_having_query( "document type `{}` cannot serve this having-range query: {e}. Ranked indexes \ are opt-in contract grammar (meta-schema v3, protocol version 14+); a pinned \ (compound-index) bound additionally needs every leading index property pinned \ - by an equality where clause.", + by a where clause — equality pins, of which at most one may be an `IN` of \ + 2..=10 elements.", request.document_type_name, ), })?; diff --git a/packages/dash-platform-queries/src/documents/ranked_proof_helpers.rs b/packages/dash-platform-queries/src/documents/ranked_proof_helpers.rs index f442842fb4c..ec774957b58 100644 --- a/packages/dash-platform-queries/src/documents/ranked_proof_helpers.rs +++ b/packages/dash-platform-queries/src/documents/ranked_proof_helpers.rs @@ -88,8 +88,10 @@ pub(super) fn assert_ranked_shape( `.with_select()`, `.with_group_by()`, \ `.order_by_selected_aggregate()` and `.with_limit(n)`, \ optionally `.with_offset(m)`, with no having and no start_at; where clauses, \ - when present, must be equality pins on the covering compound index's leading \ - properties." + when present, pin the covering compound index's leading properties — one \ + equality pin per property, of which at most one may instead be an `IN` of \ + 2..=10 elements (merged entries then carry `in_key`; a non-zero offset and a \ + null pin on another property are rejected with `IN`)." ), }) } @@ -159,7 +161,8 @@ pub(super) fn verify_ranked_query( "document type `{}` cannot serve this ranked query: {e}. Ranked indexes are \ opt-in contract grammar (meta-schema v3, protocol version 14+); a pinned \ (compound-index) ranking additionally needs every leading index property \ - pinned by an equality where clause.", + pinned by a where clause — equality pins, of which at most one may be an \ + `IN` of 2..=10 elements.", request.document_type_name, ), })?; diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index ba37d246cc3..e504bb2a594 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -71,7 +71,7 @@ strum = { version = "0.26", features = ["derive"] } json-schema-compatibility-validator = { path = '../rs-json-schema-compatibility-validator', optional = true } once_cell = "1.19.0" tracing = { version = "0.1.41" } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e", optional = true } [dev-dependencies] tokio = { version = "1.40", features = ["full"] } diff --git a/packages/rs-drive-abci/Cargo.toml b/packages/rs-drive-abci/Cargo.toml index 77e8e481c8c..5f2f3429e6f 100644 --- a/packages/rs-drive-abci/Cargo.toml +++ b/packages/rs-drive-abci/Cargo.toml @@ -82,7 +82,7 @@ derive_more = { version = "1.0", features = ["from", "deref", "deref_mut"] } async-trait = "0.1.77" console-subscriber = { version = "0.4", optional = true } bls-signatures = { git = "https://github.com/dashpay/bls-signatures", rev = "0842b17583888e8f46c252a4ee84cdfd58e0546f", optional = true } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d" } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e" } nonempty = "0.11" # Shielded-pool snapshot needs raw RocksDB SstFileWriter + ingest_external_file_cf # bindings, and blake3 for the snapshot-file checksum. @@ -107,7 +107,7 @@ dpp = { path = "../rs-dpp", default-features = false, features = [ drive = { path = "../rs-drive", features = ["fixtures-and-mocks"] } drive-proof-verifier = { path = "../rs-drive-proof-verifier" } strategy-tests = { path = "../strategy-tests" } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d", features = ["client"] } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e", features = ["client"] } assert_matches = "1.5.0" drive-abci = { path = ".", features = ["testing-config", "mocks", "shielded_test_data"] } bls-signatures = { git = "https://github.com/dashpay/bls-signatures", rev = "0842b17583888e8f46c252a4ee84cdfd58e0546f" } @@ -121,8 +121,8 @@ integer-encoding = { version = "4.0.0" } # For dump_only_default_and_aux_cfs_under_shielded_subtree_prefix — same # subtree-prefix algorithm grovedb uses internally. -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d" } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e" } [features] default = ["bls-signatures"] diff --git a/packages/rs-drive-abci/src/abci/handler/prepare_proposal.rs b/packages/rs-drive-abci/src/abci/handler/prepare_proposal.rs index 10a4bd95631..e85bd7dc285 100644 --- a/packages/rs-drive-abci/src/abci/handler/prepare_proposal.rs +++ b/packages/rs-drive-abci/src/abci/handler/prepare_proposal.rs @@ -13,7 +13,6 @@ use crate::rpc::core::CoreRPCLike; use dpp::dashcore::hashes::Hash; use dpp::dashcore::Network; use dpp::version::TryIntoPlatformVersioned; -use drive::grovedb_storage::Error::RocksDBError; use tenderdash_abci::proto::abci as proto; use tenderdash_abci::proto::abci::tx_record::TxAction; use tenderdash_abci::proto::abci::{ExecTxResult, TxRecord}; @@ -142,7 +141,7 @@ where ); if let Some(tx) = transaction_guard.as_ref() { tx.rollback_to_savepoint() - .map_err(|e| drive::grovedb::error::Error::StorageError(RocksDBError(e)))?; + .map_err(drive::grovedb::error::Error::StorageError)?; tx.set_savepoint(); } transaction_guard diff --git a/packages/rs-drive-abci/src/abci/handler/process_proposal.rs b/packages/rs-drive-abci/src/abci/handler/process_proposal.rs index a64aba5013b..69bae1c5fcd 100644 --- a/packages/rs-drive-abci/src/abci/handler/process_proposal.rs +++ b/packages/rs-drive-abci/src/abci/handler/process_proposal.rs @@ -14,7 +14,6 @@ use crate::platform_types::state_transitions_processing_result::StateTransitionE use crate::rpc::core::CoreRPCLike; use dpp::dashcore::Network; use dpp::version::TryIntoPlatformVersioned; -use drive::grovedb_storage::Error::RocksDBError; use tenderdash_abci::proto::abci as proto; use tenderdash_abci::proto::abci::tx_record::TxAction; @@ -173,7 +172,7 @@ where ); if let Some(tx) = transaction_guard.as_ref() { tx.rollback_to_savepoint() - .map_err(|e| drive::grovedb::error::Error::StorageError(RocksDBError(e)))?; + .map_err(drive::grovedb::error::Error::StorageError)?; tx.set_savepoint(); } transaction_guard diff --git a/packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/process_raw_state_transitions/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/process_raw_state_transitions/v0/mod.rs index 4e19577b925..bca8b67d3aa 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/process_raw_state_transitions/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/process_raw_state_transitions/v0/mod.rs @@ -17,7 +17,6 @@ use crate::platform_types::state_transitions_processing_result::{ use dpp::util::hash::hash_single; use dpp::version::PlatformVersion; use drive::grovedb::Transaction; -use drive::grovedb_storage::Error::RocksDBError; use std::time::Instant; use super::super::StateTransitionAwareError; @@ -223,9 +222,9 @@ where // in the state the app hash is computed over. A rollback // failure means the proposal can no longer match the // block — fail it rather than continue on leaked state. - transaction.rollback_to_savepoint().map_err(|e| { - drive::grovedb::error::Error::StorageError(RocksDBError(e)) - })?; + transaction + .rollback_to_savepoint() + .map_err(drive::grovedb::error::Error::StorageError)?; } StateTransitionExecutionResult::SuccessfulExecution { .. } | StateTransitionExecutionResult::PaidConsensusError { .. } => { diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index cb277d15567..999980bc559 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -52,13 +52,13 @@ enum-map = { version = "2.0.3", optional = true } intmap = { version = "3.0.1", features = ["serde"], optional = true } chrono = { version = "0.4.35", optional = true } itertools = { version = "0.13", optional = true } -grovedb = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d", optional = true, default-features = false } -grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d", optional = true } -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d", optional = true } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d" } -grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d" } -grovedb-query = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d" } +grovedb = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e", optional = true, default-features = false } +grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e", optional = true } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e", optional = true } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e" } +grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e" } +grovedb-query = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e" } [dev-dependencies] criterion = "0.5" diff --git a/packages/rs-platform-version/Cargo.toml b/packages/rs-platform-version/Cargo.toml index 8111477a9e4..2f03f1f6664 100644 --- a/packages/rs-platform-version/Cargo.toml +++ b/packages/rs-platform-version/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" thiserror = { version = "2.0.12" } bincode = { version = "=2.0.1" } versioned-feature-core = { git = "https://github.com/dashpay/versioned-feature-core", version = "1.0.0" } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d" } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e" } [features] mock-versions = [] diff --git a/packages/rs-platform-wallet/Cargo.toml b/packages/rs-platform-wallet/Cargo.toml index ac7713e2f2f..4ffc2e7a5a1 100644 --- a/packages/rs-platform-wallet/Cargo.toml +++ b/packages/rs-platform-wallet/Cargo.toml @@ -69,7 +69,7 @@ zeroize = "1" log = "0.4" # Shielded pool (optional, behind `shielded` feature) -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e", optional = true } # Direct `rusqlite` access so `FileBackedShieldedStore::open_path` can set # WAL + synchronous=NORMAL pragmas before handing the connection to # `ClientPersistentCommitmentTree`. Version locked to match the rev grovedb diff --git a/packages/rs-sdk/Cargo.toml b/packages/rs-sdk/Cargo.toml index c565876223a..8ef0fd0f9ef 100644 --- a/packages/rs-sdk/Cargo.toml +++ b/packages/rs-sdk/Cargo.toml @@ -18,7 +18,7 @@ drive = { path = "../rs-drive", default-features = false, features = [ ] } drive-proof-verifier = { path = "../rs-drive-proof-verifier", default-features = false } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "f7e9d1b903a0348d3de856244abf5774f201fb0d", features = [ +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e", features = [ "client", "sqlite", ], optional = true } From 468b296ee99509303a22cb91fa70a1998182430e Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 26 Aug 2026 17:35:34 +0200 Subject: [PATCH 19/25] docs(sdk): ranked contract-prerequisite line allows the bounded prefix IN The request-shape section documents one bounded branching IN across the leading pins, but the contract-prerequisites paragraph still told SDK users to equality-pin every leading property. Mirror the having surface's wording: pin every leading one, equality pins with at most one IN. Co-Authored-By: Claude Fable 5 --- .../src/documents/document_ranked_entries.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/dash-platform-queries/src/documents/document_ranked_entries.rs b/packages/dash-platform-queries/src/documents/document_ranked_entries.rs index 31aa47d488b..298ddf46b62 100644 --- a/packages/dash-platform-queries/src/documents/document_ranked_entries.rs +++ b/packages/dash-platform-queries/src/documents/document_ranked_entries.rs @@ -43,8 +43,9 @@ //! The index must opt in with `rankedCountable` / `rankedSummable` / //! `rankedAverageable` (meta-schema v3, **protocol version 14+**). The //! index may be single-property (`group_by` its property, no `where`) -//! or compound (`group_by` its trailing property, equality-pin every -//! leading one). Against a protocol-version-13 +//! or compound (`group_by` its trailing property, pin every leading one +//! — equality pins, at most one of them an `IN`, as above). Against a +//! protocol-version-13 //! node the request is refused — v13's query table has no ranked path //! and rejects the ordering as `Unsupported`. That is the intended //! activation gate, not a bug: a v13 node and a v14 node must disagree From a4316eb9d00bf19b742d9932d7f4d3713725369e Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 26 Aug 2026 18:38:42 +0200 Subject: [PATCH 20/25] fix(drive): group_by diagnostics steer to the pinned-prefix form with the bounded IN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both GROUP BY rejection messages (ranked and having-range) still told callers to equality-pin every leading index property, steering anyone holding a supported bounded IN toward removing it. Reword the errors and their guiding comments to one pin per leading property — equality, except at most one bounded IN — and align the rs-dpp per-prefix semantics note. Test assertions follow the new wording. Co-Authored-By: Claude Fable 5 --- .../src/data_contract/document_type/index/mod.rs | 3 ++- .../rs-drive-abci/src/query/document_query/v1/tests.rs | 2 +- .../mode_detection/v0/mod.rs | 8 +++++--- .../src/query/drive_document_having_query/tests.rs | 2 +- .../mode_detection/v0/mod.rs | 10 ++++++---- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/rs-dpp/src/data_contract/document_type/index/mod.rs b/packages/rs-dpp/src/data_contract/document_type/index/mod.rs index 2f099c93014..2133d544abb 100644 --- a/packages/rs-dpp/src/data_contract/document_type/index/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/index/mod.rs @@ -1188,7 +1188,8 @@ impl Index { // `identityId` value — each ordering that identity's `class` groups // by the group aggregate. There is deliberately no global // cross-prefix ordering; the query surfaces require every leading - // property to be pinned by an equality `where` clause. + // property to be pinned by a `where` clause (`==`, at most one of + // them a bounded `IN` whose branches are merged client-visibly). // // One compound shape stays structurally impossible and is rejected // per document type (all indexes are needed to see it): a compound diff --git a/packages/rs-drive-abci/src/query/document_query/v1/tests.rs b/packages/rs-drive-abci/src/query/document_query/v1/tests.rs index 8949e97450c..b52ef993a3a 100644 --- a/packages/rs-drive-abci/src/query/document_query/v1/tests.rs +++ b/packages/rs-drive-abci/src/query/document_query/v1/tests.rs @@ -3425,7 +3425,7 @@ mod having_range_tests { QueryError::Query(QuerySyntaxError::InvalidParameter(message)) => { assert!( message.contains("exactly one `group_by` property") - && message.contains("equality `where` clause"), + && message.contains("pin every leading index property"), "the rejection must steer to the pinned-prefix form, got: {message}" ); } diff --git a/packages/rs-drive/src/query/drive_document_having_query/mode_detection/v0/mod.rs b/packages/rs-drive/src/query/drive_document_having_query/mode_detection/v0/mod.rs index e690a4ab5f9..18c377f2990 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/mode_detection/v0/mod.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/mode_detection/v0/mod.rs @@ -81,13 +81,15 @@ pub fn detect_having_mode_v0( // covering index's LAST property, whose distinct values are the // secondary's group keys. A compound ranked index filters each // prefix's groups separately — its leading properties are pinned by - // equality `where` clauses, never grouped over. + // `where` clauses (`==`, at most one of them a bounded `IN`), never + // grouped over. if group_by.len() != 1 { return Err(Error::Query(QuerySyntaxError::InvalidParameter(format!( "having-range queries require exactly one `group_by` property (the covering \ ranked index's trailing property); got {}. A compound ranked index bounds \ - each prefix's groups separately — pin every leading index property with an \ - equality `where` clause and `group_by` the trailing property.", + each prefix's groups separately — pin every leading index property with a \ + `where` clause (`==`, or a bounded `IN` on at most one of them) and \ + `group_by` the trailing property.", group_by.len() )))); } diff --git a/packages/rs-drive/src/query/drive_document_having_query/tests.rs b/packages/rs-drive/src/query/drive_document_having_query/tests.rs index 03d51dfbac9..c01de0974ee 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/tests.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/tests.rs @@ -336,7 +336,7 @@ mod grammar { let message = format!("{error}"); assert!( message.contains("exactly one `group_by` property") - && message.contains("equality `where` clause"), + && message.contains("pin every leading index property"), "the rejection must steer to the pinned-prefix form, got: {error}" ); } diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/v0/mod.rs b/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/v0/mod.rs index 003bb7e745b..5fbc9797ae8 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/v0/mod.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/v0/mod.rs @@ -191,14 +191,16 @@ pub fn detect_ranked_mode_v0( // Zero group_by would ask to rank a single global aggregate against // itself. Two or more is rejected because a compound ranked index // ranks per prefix, not across a compound grouping: its leading - // properties are pinned by equality `where` clauses, and only the - // trailing property is grouped over. + // properties are pinned by `where` clauses (`==`, at most one of + // them a bounded `IN`), and only the trailing property is grouped + // over. if group_by.len() != 1 { return Err(Error::Query(QuerySyntaxError::InvalidParameter(format!( "ranked queries require exactly one `group_by` property (the covering ranked \ index's trailing property); got {}. A compound ranked index ranks each \ - prefix's groups separately — pin every leading index property with an \ - equality `where` clause and `group_by` the trailing property.", + prefix's groups separately — pin every leading index property with a \ + `where` clause (`==`, or a bounded `IN` on at most one of them) and \ + `group_by` the trailing property.", group_by.len() )))); } From c1902df5e73a0f5cbd3469ee678f0bc8db098b2a Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 26 Aug 2026 19:51:09 +0200 Subject: [PATCH 21/25] fix(drive): make the resolvers the only public constructors of the resolved query types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review: `prefix_branches` is crate-private on both resolved query structs (read-only public accessor), so the validated resolvers — which run `encode_prefix_branches` — are the only way external code can obtain one, and the encoder's invariants (nonempty, canonical order, distinct keys, one varying position, the fan-out ceiling) hold on every externally obtainable value by construction. A hand-built oversized branch set can no longer buy per-branch allocation or proof work past the advertised limit. The one external literal constructor (an ABCI wire test) now goes through `resolve_having_query_for_mode`. Also aligns the two remaining WHERE validation comments (ranked and having mode detection) with the enforced grammar: one pin per leading property, `==` except at most one bounded branching `IN`, singleton `IN` normalizing to equality. Co-Authored-By: Claude Fable 5 --- .../src/query/document_query/v1/tests.rs | 29 +++++++------------ .../query/drive_document_having_query/mod.rs | 18 ++++++++++-- .../mode_detection/v0/mod.rs | 11 +++---- .../query/drive_document_ranked_query/mod.rs | 18 ++++++++++-- .../mode_detection/v0/mod.rs | 13 +++++---- 5 files changed, 55 insertions(+), 34 deletions(-) diff --git a/packages/rs-drive-abci/src/query/document_query/v1/tests.rs b/packages/rs-drive-abci/src/query/document_query/v1/tests.rs index b52ef993a3a..8a4433d5afd 100644 --- a/packages/rs-drive-abci/src/query/document_query/v1/tests.rs +++ b/packages/rs-drive-abci/src/query/document_query/v1/tests.rs @@ -3911,7 +3911,7 @@ mod having_trust_boundary { DocumentHavingRequest, DocumentHavingResponse, }; use drive::query::drive_document_having_query::mode_detection::detect_having_mode; - use drive::query::drive_document_ranked_query::index_picker::find_ranked_index_for_axis; + use drive::query::drive_document_having_query::resolve_having_query_for_mode; use drive::query::having::{ HavingAggregate, HavingAggregateFunction, HavingClause, HavingOperator, HavingRightOperand, }; @@ -4073,30 +4073,21 @@ mod having_trust_boundary { platform_version(), ) .expect("the case is well-formed"); - let index = find_ranked_index_for_axis( + resolve_having_query_for_mode( + contract.id_ref().to_buffer(), + contract + .document_type_for_name("grade") + .expect("grade doctype exists"), + "grade".to_string(), contract .document_types() .get("grade") .expect("grade doctype exists") .indexes(), - &mode.group_by_property, - &[], - mode.bounds.axis(), - &mode.aggregate_field, + &mode, + PlatformVersion::latest(), ) - .expect("the fixture declares the avg axis"); - DriveDocumentHavingQuery { - document_type: contract - .document_type_for_name("grade") - .expect("grade doctype exists"), - contract_id: contract.id_ref().to_buffer(), - document_type_name: "grade".to_string(), - index, - bounds: mode.bounds, - prefix_branches: vec![Vec::new()], - descending: mode.descending, - limit: mode.limit, - } + .expect("the fixture declares the avg axis") } /// Prove the having request against the live Drive and return diff --git a/packages/rs-drive/src/query/drive_document_having_query/mod.rs b/packages/rs-drive/src/query/drive_document_having_query/mod.rs index 07388d7d905..b998a5688e4 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/mod.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/mod.rs @@ -289,8 +289,11 @@ pub struct DriveDocumentHavingQuery<'a> { /// least one branch, several exactly when the request carried a /// multi-element `IN` pin. Part of the prover/verifier agreement /// exactly as on the ranked surface. Produced by - /// [`super::drive_document_ranked_query::index_picker::encode_prefix_branches`]. - pub prefix_branches: Vec>>, + /// [`super::drive_document_ranked_query::index_picker::encode_prefix_branches`] + /// — crate-private so the resolver is the only public constructor + /// and the encoder's invariants hold on every externally obtainable + /// value. + pub(crate) prefix_branches: Vec>>, /// Inclusive bounds on the aggregate. Carry the axis; the index must /// declare the matching `ranked_*` flag. pub bounds: AxisRangeBounds, @@ -310,6 +313,17 @@ pub struct DriveDocumentHavingQuery<'a> { pub limit: u16, } +#[cfg(any(feature = "server", feature = "verify"))] +impl DriveDocumentHavingQuery<'_> { + /// The resolved prefix branches, in canonical order — one per `IN` + /// element (a single branch without an `IN`). Read-only: the field is + /// crate-private so the resolver's encoder invariants cannot be + /// bypassed by construction or mutation. + pub fn prefix_branches(&self) -> &[Vec>] { + &self.prefix_branches + } +} + #[cfg(any(feature = "server", feature = "verify"))] impl DriveDocumentHavingQuery<'_> { /// Path of one branch's terminal property-name tree — identical to diff --git a/packages/rs-drive/src/query/drive_document_having_query/mode_detection/v0/mod.rs b/packages/rs-drive/src/query/drive_document_having_query/mode_detection/v0/mod.rs index 18c377f2990..7736e51628d 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/mode_detection/v0/mod.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/mode_detection/v0/mod.rs @@ -241,13 +241,14 @@ pub fn detect_having_mode_v0( } }; - // ---- WHERE: equality pins on the compound prefix ------------------ + // ---- WHERE: pins on the compound prefix --------------------------- // // Identical contract to the ranked surface: empty for the - // single-property form; for a compound ranked index, one equality - // pin per leading property selects which prefix's secondary the - // bound reads. Shape-only here; the index picker enforces the - // exact-cover rule. + // single-property form; for a compound ranked index, one pin per + // leading property — `==`, except at most one bounded branching + // `IN` — selects which prefix secondary or secondaries the bound + // reads. Shape-only here; the index picker enforces the exact-cover + // rule. let prefix_pins = prefix_pins_from_where_clauses(where_clauses)?; // ---- LIMIT: required, 1 ..= MAX_HAVING_LIMIT --------------------- diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs b/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs index 27b4ca76fa0..0b5b15ade0c 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs @@ -321,8 +321,11 @@ pub struct DriveDocumentRankedQuery<'a> { /// as much a part of the prover/verifier agreement as the path /// builder itself. Produced by /// [`index_picker::encode_prefix_branches`] from the request's - /// `where` pins. - pub prefix_branches: Vec>>, + /// `where` pins — crate-private so the resolver is the only public + /// constructor and the encoder's invariants (nonempty, canonical + /// order, distinct keys, one varying position, the fan-out ceiling) + /// hold on every externally obtainable value. + pub(crate) prefix_branches: Vec>>, /// Which aggregate the groups are ranked by. Must be covered by /// `index`'s matching `ranked_*` flag. pub axis: RankedAxis, @@ -365,6 +368,17 @@ pub struct DriveDocumentRankedQuery<'a> { pub offset: u32, } +#[cfg(any(feature = "server", feature = "verify"))] +impl DriveDocumentRankedQuery<'_> { + /// The resolved prefix branches, in canonical order — one per `IN` + /// element (a single branch without an `IN`). Read-only: the field is + /// crate-private so the resolver's encoder invariants cannot be + /// bypassed by construction or mutation. + pub fn prefix_branches(&self) -> &[Vec>] { + &self.prefix_branches + } +} + /// A page of a ranked result: the entries, plus how many ranks were /// actually skipped to reach them. /// diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/v0/mod.rs b/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/v0/mod.rs index 5fbc9797ae8..e24d78fd94d 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/v0/mod.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/mode_detection/v0/mod.rs @@ -309,12 +309,13 @@ pub fn detect_ranked_mode_v0( // ---- WHERE: equality pins on the compound prefix ------------------ // // Empty for the single-property form. For a compound ranked index, - // each `where` clause must pin one leading index property with `==` - // — that is what selects which prefix's secondary the walk reads - // (per-prefix semantics: there is no global cross-prefix ordering to - // serve). Anything other than a distinct-property equality is - // rejected loudly here; whether the pinned set matches a covering - // index's leading properties exactly is the index picker's call. + // each `where` clause pins one leading index property with `==`, + // except that at most one may use a bounded branching `IN` (a + // singleton `IN` normalizes to equality). These pins select the + // prefix secondary or secondaries the walk reads (per-prefix + // semantics: the only cross-prefix ordering is the branch merge); + // whether the pinned property set exactly matches a covering + // index's leading properties is the index picker's call. let prefix_pins = prefix_pins_from_where_clauses(where_clauses)?; // ---- LIMIT: required, 1 ..= MAX_RANKED_LIMIT --------------------- From 77ce71e4cac2df052e3964332307d023d7f1ef85 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 26 Aug 2026 20:15:54 +0200 Subject: [PATCH 22/25] =?UTF-8?q?feat(drive)!:=20one=20PathQuery=20path=20?= =?UTF-8?q?for=20everything=20=E2=80=94=20pin=20grovedb=20at=20the=20prove?= =?UTF-8?q?r=20retirement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit grovedb #836 gave the unified unproved read the attested skip (`PathQueryRun::AxisKeys/AxisEntries { .., skipped }`), and #839 retired the standalone indexed-axis provers, verifiers and read entry points outright — PathQuery is grovedb's only public surface for the family. The pin moves to that head and platform completes the unification it was waiting on: - Single-prefix unproved reads (ranked top-k and having-range) are one `run_path_query` over `PathQuery::new_axis` with the keys-only projection; the per-axis triple dispatches are gone, and the rank attestation maps from the new `skipped` field (its absence on a paginated read is corrupted state, not a default). - Single-prefix proofs are `prove_query(new_axis_top_k / new_axis_bounded)`; since transactional proving no longer exists anywhere, the committed-state-only guard covers single-prefix and branched proves alike. - Single-prefix verification matches `VerifiedPathQuery::AxisEntries { root_hash, entries, skipped }` through the same shared mappers as the branched arms. - Every read, proof and verification on both surfaces now builds exactly one PathQuery per external query; rs-drive re-exports `grovedb_query` so downstream crates can construct axis queries. Two semantic upgrades ride the retirement, both now pinned by tests: - A bound matching nothing against an EMPTY secondary proves: the envelope commits the element's empty secondary, authenticating complete absence instead of refusing ("Cannot create proof for empty tree" is gone from this surface; drive-abci's InvalidArgument mapping for that class is vestigial here and kept as dead-defensive). - The limit binds by RECONSTRUCTION, not echo: the verifier re-executes the proof under the queried limit, so an exhausted-walk proof is a complete answer under any cap that admits it (the old echo check rejected that soundly-verifiable case), while the dangerous direction — a proof truncated by a smaller limit verified under a larger cap — is rejected for missing coverage. The tamper test now pins BOTH directions; it previously exercised only the benign one. Co-Authored-By: Claude Fable 5 --- Cargo.lock | 32 ++-- packages/rs-dpp/Cargo.toml | 2 +- packages/rs-drive-abci/Cargo.toml | 8 +- .../tests/document/ranked_group_drain.rs | 35 +++- packages/rs-drive/Cargo.toml | 14 +- .../v0/tests/ranked_index_e2e_tests.rs | 70 ++++---- packages/rs-drive/src/lib.rs | 4 + .../execute_range.rs | 159 ++++++----------- .../drive_document_having_query/tests.rs | 57 ++++--- .../execute_top_k.rs | 161 +++++++----------- .../verify_having_range_proof/v0/mod.rs | 90 +++------- .../verify_ranked_top_k_proof/v0/mod.rs | 84 +++------ packages/rs-platform-version/Cargo.toml | 2 +- packages/rs-platform-wallet/Cargo.toml | 2 +- packages/rs-sdk/Cargo.toml | 2 +- 15 files changed, 290 insertions(+), 432 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e08b3411c73..acefd85bff1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2974,7 +2974,7 @@ dependencies = [ [[package]] name = "grovedb" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" +source = "git+https://github.com/dashpay/grovedb?rev=6c882c3ee7d2c331f1feda2eb4223add9a6f0e45#6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" dependencies = [ "axum 0.8.9", "bincode", @@ -3013,7 +3013,7 @@ dependencies = [ [[package]] name = "grovedb-bulk-append-tree" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" +source = "git+https://github.com/dashpay/grovedb?rev=6c882c3ee7d2c331f1feda2eb4223add9a6f0e45#6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" dependencies = [ "bincode", "blake3", @@ -3031,7 +3031,7 @@ dependencies = [ [[package]] name = "grovedb-commitment-tree" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" +source = "git+https://github.com/dashpay/grovedb?rev=6c882c3ee7d2c331f1feda2eb4223add9a6f0e45#6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" dependencies = [ "blake3", "grovedb-bulk-append-tree", @@ -3048,7 +3048,7 @@ dependencies = [ [[package]] name = "grovedb-costs" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" +source = "git+https://github.com/dashpay/grovedb?rev=6c882c3ee7d2c331f1feda2eb4223add9a6f0e45#6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" dependencies = [ "integer-encoding", "intmap", @@ -3058,7 +3058,7 @@ dependencies = [ [[package]] name = "grovedb-dense-fixed-sized-merkle-tree" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" +source = "git+https://github.com/dashpay/grovedb?rev=6c882c3ee7d2c331f1feda2eb4223add9a6f0e45#6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" dependencies = [ "bincode", "blake3", @@ -3072,7 +3072,7 @@ dependencies = [ [[package]] name = "grovedb-element" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" +source = "git+https://github.com/dashpay/grovedb?rev=6c882c3ee7d2c331f1feda2eb4223add9a6f0e45#6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" dependencies = [ "bincode", "bincode_derive", @@ -3088,7 +3088,7 @@ dependencies = [ [[package]] name = "grovedb-epoch-based-storage-flags" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" +source = "git+https://github.com/dashpay/grovedb?rev=6c882c3ee7d2c331f1feda2eb4223add9a6f0e45#6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" dependencies = [ "grovedb-costs", "hex", @@ -3100,7 +3100,7 @@ dependencies = [ [[package]] name = "grovedb-merk" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" +source = "git+https://github.com/dashpay/grovedb?rev=6c882c3ee7d2c331f1feda2eb4223add9a6f0e45#6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" dependencies = [ "bincode", "bincode_derive", @@ -3126,7 +3126,7 @@ dependencies = [ [[package]] name = "grovedb-merkle-mountain-range" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" +source = "git+https://github.com/dashpay/grovedb?rev=6c882c3ee7d2c331f1feda2eb4223add9a6f0e45#6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" dependencies = [ "bincode", "blake3", @@ -3139,7 +3139,7 @@ dependencies = [ [[package]] name = "grovedb-path" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" +source = "git+https://github.com/dashpay/grovedb?rev=6c882c3ee7d2c331f1feda2eb4223add9a6f0e45#6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" dependencies = [ "hex", ] @@ -3147,7 +3147,7 @@ dependencies = [ [[package]] name = "grovedb-private-document-store" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" +source = "git+https://github.com/dashpay/grovedb?rev=6c882c3ee7d2c331f1feda2eb4223add9a6f0e45#6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" dependencies = [ "blake3", "grovedb-bulk-append-tree", @@ -3160,7 +3160,7 @@ dependencies = [ [[package]] name = "grovedb-query" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" +source = "git+https://github.com/dashpay/grovedb?rev=6c882c3ee7d2c331f1feda2eb4223add9a6f0e45#6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" dependencies = [ "bincode", "byteorder", @@ -3176,7 +3176,7 @@ dependencies = [ [[package]] name = "grovedb-storage" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" +source = "git+https://github.com/dashpay/grovedb?rev=6c882c3ee7d2c331f1feda2eb4223add9a6f0e45#6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" dependencies = [ "blake3", "grovedb-costs", @@ -3195,7 +3195,7 @@ dependencies = [ [[package]] name = "grovedb-version" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" +source = "git+https://github.com/dashpay/grovedb?rev=6c882c3ee7d2c331f1feda2eb4223add9a6f0e45#6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" dependencies = [ "thiserror 2.0.18", "versioned-feature-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3204,7 +3204,7 @@ dependencies = [ [[package]] name = "grovedb-visualize" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" +source = "git+https://github.com/dashpay/grovedb?rev=6c882c3ee7d2c331f1feda2eb4223add9a6f0e45#6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" dependencies = [ "hex", "itertools 0.14.0", @@ -3213,7 +3213,7 @@ dependencies = [ [[package]] name = "grovedbg-types" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=0b85d7e94ec792cac22bfde2f1819318499ba05e#0b85d7e94ec792cac22bfde2f1819318499ba05e" +source = "git+https://github.com/dashpay/grovedb?rev=6c882c3ee7d2c331f1feda2eb4223add9a6f0e45#6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" dependencies = [ "serde", "serde_with 3.21.0", diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index e504bb2a594..e72bbd77413 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -71,7 +71,7 @@ strum = { version = "0.26", features = ["derive"] } json-schema-compatibility-validator = { path = '../rs-json-schema-compatibility-validator', optional = true } once_cell = "1.19.0" tracing = { version = "0.1.41" } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "6c882c3ee7d2c331f1feda2eb4223add9a6f0e45", optional = true } [dev-dependencies] tokio = { version = "1.40", features = ["full"] } diff --git a/packages/rs-drive-abci/Cargo.toml b/packages/rs-drive-abci/Cargo.toml index 5f2f3429e6f..337d3432d05 100644 --- a/packages/rs-drive-abci/Cargo.toml +++ b/packages/rs-drive-abci/Cargo.toml @@ -82,7 +82,7 @@ derive_more = { version = "1.0", features = ["from", "deref", "deref_mut"] } async-trait = "0.1.77" console-subscriber = { version = "0.4", optional = true } bls-signatures = { git = "https://github.com/dashpay/bls-signatures", rev = "0842b17583888e8f46c252a4ee84cdfd58e0546f", optional = true } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e" } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" } nonempty = "0.11" # Shielded-pool snapshot needs raw RocksDB SstFileWriter + ingest_external_file_cf # bindings, and blake3 for the snapshot-file checksum. @@ -107,7 +107,7 @@ dpp = { path = "../rs-dpp", default-features = false, features = [ drive = { path = "../rs-drive", features = ["fixtures-and-mocks"] } drive-proof-verifier = { path = "../rs-drive-proof-verifier" } strategy-tests = { path = "../strategy-tests" } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e", features = ["client"] } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "6c882c3ee7d2c331f1feda2eb4223add9a6f0e45", features = ["client"] } assert_matches = "1.5.0" drive-abci = { path = ".", features = ["testing-config", "mocks", "shielded_test_data"] } bls-signatures = { git = "https://github.com/dashpay/bls-signatures", rev = "0842b17583888e8f46c252a4ee84cdfd58e0546f" } @@ -121,8 +121,8 @@ integer-encoding = { version = "4.0.0" } # For dump_only_default_and_aux_cfs_under_shielded_subtree_prefix — same # subtree-prefix algorithm grovedb uses internally. -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e" } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" } [features] default = ["bls-signatures"] diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/ranked_group_drain.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/ranked_group_drain.rs index 10ada3042f7..fb5226372ea 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/ranked_group_drain.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/ranked_group_drain.rs @@ -75,21 +75,42 @@ fn ranked_count_groups( path: &[Vec], platform_version: &PlatformVersion, ) -> Vec<(u64, String)> { - let path_refs: Vec<&[u8]> = path.iter().map(|v| v.as_slice()).collect(); - platform + use drive::grovedb::query_result_type::QueryResultType; + use drive::grovedb::{AxisKeys, PathQuery, PathQueryRun}; + + let path_query = PathQuery::new_axis( + path.to_vec(), + drive::grovedb_query::AxisQuery::top_k( + drive::grovedb_query::IndexAxis::Count, + 100, + 0, + true, + ) + .keys_only(), + ); + let run = platform .drive .grove - .indexed_count_top_k( - path_refs.as_slice(), - 100, + .run_path_query( + &path_query, + true, + true, true, + QueryResultType::QueryKeyElementPairResultType, None, &platform_version.drive.grove_version, ) .unwrap() - .expect("the ranked count read must succeed") + .expect("the ranked count read must succeed"); + let PathQueryRun::AxisKeys { + keys: AxisKeys::Count(pairs), + .. + } = run + else { + panic!("expected a keys-only count page, got {run:?}"); + }; + pairs .into_iter() - .map(|entry| entry.key_pair()) .map(|(count, key)| { ( count, diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index 999980bc559..81d7ff42627 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -52,13 +52,13 @@ enum-map = { version = "2.0.3", optional = true } intmap = { version = "3.0.1", features = ["serde"], optional = true } chrono = { version = "0.4.35", optional = true } itertools = { version = "0.13", optional = true } -grovedb = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e", optional = true, default-features = false } -grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e", optional = true } -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e", optional = true } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e" } -grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e" } -grovedb-query = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e" } +grovedb = { git = "https://github.com/dashpay/grovedb", rev = "6c882c3ee7d2c331f1feda2eb4223add9a6f0e45", optional = true, default-features = false } +grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "6c882c3ee7d2c331f1feda2eb4223add9a6f0e45", optional = true } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "6c882c3ee7d2c331f1feda2eb4223add9a6f0e45", optional = true } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" } +grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" } +grovedb-query = { git = "https://github.com/dashpay/grovedb", rev = "6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" } [dev-dependencies] criterion = "0.5" diff --git a/packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/ranked_index_e2e_tests.rs b/packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/ranked_index_e2e_tests.rs index 62e65e08bc7..2a772e3688a 100644 --- a/packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/ranked_index_e2e_tests.rs +++ b/packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/ranked_index_e2e_tests.rs @@ -271,49 +271,55 @@ fn group_keys(entries: &[(T, Vec)]) -> Vec { .collect() } -fn avg_top_k(drive: &Drive, path: &[Vec], k: u16, descending: bool) -> Vec<(i128, Vec)> { - let path_refs: Vec<&[u8]> = path.iter().map(|v| v.as_slice()).collect(); - drive +fn axis_top_k_keys( + drive: &Drive, + path: &[Vec], + axis: grovedb_query::IndexAxis, + k: u16, + descending: bool, +) -> grovedb::AxisKeys { + let path_query = grovedb::PathQuery::new_axis( + path.to_vec(), + grovedb_query::AxisQuery::top_k(axis, k, 0, descending).keys_only(), + ); + match drive .grove - .indexed_avg_top_k_keys( - path_refs.as_slice(), - k, - descending, + .run_path_query( + &path_query, + true, + true, + true, + grovedb::query_result_type::QueryResultType::QueryKeyElementPairResultType, None, &platform_version().drive.grove_version, ) .unwrap() - .expect("indexed_avg_top_k_keys must succeed") + .expect("the keys-only axis read must succeed") + { + grovedb::PathQueryRun::AxisKeys { keys, .. } => keys, + other => panic!("expected a keys-only axis page, got {other:?}"), + } +} + +fn avg_top_k(drive: &Drive, path: &[Vec], k: u16, descending: bool) -> Vec<(i128, Vec)> { + match axis_top_k_keys(drive, path, grovedb_query::IndexAxis::Avg, k, descending) { + grovedb::AxisKeys::Avg(pairs) => pairs, + other => panic!("expected avg keys, got {other:?}"), + } } fn count_top_k(drive: &Drive, path: &[Vec], k: u16, descending: bool) -> Vec<(u64, Vec)> { - let path_refs: Vec<&[u8]> = path.iter().map(|v| v.as_slice()).collect(); - drive - .grove - .indexed_count_top_k_keys( - path_refs.as_slice(), - k, - descending, - None, - &platform_version().drive.grove_version, - ) - .unwrap() - .expect("indexed_count_top_k_keys must succeed") + match axis_top_k_keys(drive, path, grovedb_query::IndexAxis::Count, k, descending) { + grovedb::AxisKeys::Count(pairs) => pairs, + other => panic!("expected count keys, got {other:?}"), + } } fn sum_top_k(drive: &Drive, path: &[Vec], k: u16, descending: bool) -> Vec<(i64, Vec)> { - let path_refs: Vec<&[u8]> = path.iter().map(|v| v.as_slice()).collect(); - drive - .grove - .indexed_sum_top_k_keys( - path_refs.as_slice(), - k, - descending, - None, - &platform_version().drive.grove_version, - ) - .unwrap() - .expect("indexed_sum_top_k_keys must succeed") + match axis_top_k_keys(drive, path, grovedb_query::IndexAxis::Sum, k, descending) { + grovedb::AxisKeys::Sum(pairs) => pairs, + other => panic!("expected sum keys, got {other:?}"), + } } // --------------------------------------------------------------------------- diff --git a/packages/rs-drive/src/lib.rs b/packages/rs-drive/src/lib.rs index f5c5426bec3..a53a3216012 100644 --- a/packages/rs-drive/src/lib.rs +++ b/packages/rs-drive/src/lib.rs @@ -30,6 +30,10 @@ pub use dpp; #[cfg(any(feature = "server", feature = "verify"))] pub use grovedb; +/// Re-exported so downstream crates can build `AxisQuery`-shaped +/// [`grovedb::PathQuery`]s without a direct dependency. +pub use grovedb_query; + #[cfg(feature = "server")] pub use grovedb_path; diff --git a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs index e26e1657f42..f0a4c4a4bee 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs @@ -12,16 +12,16 @@ //! `pub mod execute_range;` declaration. use super::super::drive_document_ranked_query::branches::{ - decompose_branch_paths, read_branched_union, + axis_keys_to_ranked, decompose_branch_paths, read_branched_union, }; -use super::super::drive_document_ranked_query::{RankedAxis, RankedEntry, RankedEntryValue}; -use super::{AxisRangeBounds, DriveDocumentHavingQuery}; +use super::super::drive_document_ranked_query::RankedEntry; +use super::DriveDocumentHavingQuery; use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; use dpp::version::PlatformVersion; -use grovedb::PathQuery; -use grovedb::TransactionArg; +use grovedb::query_result_type::QueryResultType; +use grovedb::{PathQuery, PathQueryRun, TransactionArg}; use grovedb_costs::CostContext; use grovedb_query::AxisQuery; @@ -89,81 +89,35 @@ impl DriveDocumentHavingQuery<'_> { ) -> Result, Error> { let grove_version = &platform_version.drive.grove_version; let path = self.indexed_property_name_tree_path(branch)?; - let path_refs: Vec<&[u8]> = path.iter().map(|segment| segment.as_slice()).collect(); + let axis = self.bounds.axis(); + let (lo, hi) = self.bounds.inclusive_bounds_i128(); // Costs are destructured away rather than `.unwrap()`-ed, same // as the ranked executors: `CostContext::unwrap` is infallible // but reads like a panicking unwrap at the call site. - let entries = match self.bounds { - AxisRangeBounds::Count { lo, hi } => { - let CostContext { value, cost: _ } = drive.grove.indexed_count_range_keys( - path_refs.as_slice(), - lo, - hi, - self.descending, - self.limit, - transaction, - grove_version, - ); - value - .map_err(|e| Error::GroveDB(Box::new(e)))? - .into_iter() - .map(|(count, key)| RankedEntry { - in_key: None, - key, - value: RankedEntryValue::Count(count), - }) - .collect::>() - } - AxisRangeBounds::Sum { lo, hi } => { - let CostContext { value, cost: _ } = drive.grove.indexed_sum_range_keys( - path_refs.as_slice(), - lo, - hi, - self.descending, - self.limit, - transaction, - grove_version, - ); - value - .map_err(|e| Error::GroveDB(Box::new(e)))? - .into_iter() - .map(|(sum, key)| RankedEntry { - in_key: None, - key, - value: RankedEntryValue::Sum(sum), - }) - .collect::>() - } - AxisRangeBounds::Avg { lo, hi } => { - let CostContext { value, cost: _ } = drive.grove.indexed_avg_range_keys( - path_refs.as_slice(), - lo, - hi, - self.descending, - self.limit, - transaction, - grove_version, - ); - value - .map_err(|e| Error::GroveDB(Box::new(e)))? - .into_iter() - .map(|(avg, key)| RankedEntry { - in_key: None, - key, - value: RankedEntryValue::AvgFixedPoint(avg), - }) - .collect::>() - } + let path_query = PathQuery::new_axis( + path, + AxisQuery::bounded(axis.into(), lo, hi, self.limit, self.descending).keys_only(), + ); + let CostContext { value, cost: _ } = drive.grove.run_path_query( + &path_query, + true, + true, + true, + QueryResultType::QueryKeyElementPairResultType, + transaction, + grove_version, + ); + let run = value.map_err(|e| Error::GroveDB(Box::new(e)))?; + let PathQueryRun::AxisKeys { keys, skipped: _ } = run else { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "a keys-only having read returned a different result shape".to_string(), + ))); }; - - // The limit is the contract with the caller, and on the prove - // path it is re-checked inside the proof envelope. Asserting it - // here keeps the no-proof and prove responses shape-identical. + let entries = axis_keys_to_ranked(axis, keys)?; if entries.len() > self.limit as usize { return Err(Error::Drive(DriveError::CorruptedDriveState(format!( - "having {:?} range read returned {} entries for limit = {}", - self.bounds.axis(), + "having {axis:?} read returned {} entries for limit = {}", entries.len(), self.limit )))); @@ -193,17 +147,16 @@ impl DriveDocumentHavingQuery<'_> { transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result, Error> { + // Same fail-closed rule as the ranked prover: grovedb's + // `prove_query` proves committed state only and cannot see the + // caller's transaction — single-prefix and branched alike. + if transaction.is_some() { + return Err(Error::Drive(DriveError::NotSupported( + "a having-range proof is generated from committed state only: grovedb's \ + prove_query cannot see the caller's transaction — commit first", + ))); + } if self.prefix_branches.len() > 1 { - // Same fail-closed rule as the ranked prover: grovedb's unified - // `prove_query` proves committed state only and cannot see the - // caller's transaction. - if transaction.is_some() { - return Err(Error::Drive(DriveError::NotSupported( - "an IN-pinned (branched) having-range proof is generated from committed \ - state only: grovedb's unified prove_query cannot see the caller's \ - transaction — prove per prefix element, or commit first", - ))); - } // One grovedb **branched** envelope — see the ranked // executor's multi-branch arm for the shape. let grove_version = &platform_version.drive.grove_version; @@ -228,7 +181,7 @@ impl DriveDocumentHavingQuery<'_> { drive.grove.prove_query(&path_query, None, grove_version); return value.map_err(|e| Error::GroveDB(Box::new(e))); } - self.execute_range_with_proof_branch(0, drive, transaction, platform_version) + self.execute_range_with_proof_branch(0, drive, platform_version) } /// One branch's proof — the entire pre-`IN` prover, parameterized by @@ -237,38 +190,22 @@ impl DriveDocumentHavingQuery<'_> { &self, branch: usize, drive: &Drive, - transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result, Error> { let grove_version = &platform_version.drive.grove_version; let path = self.indexed_property_name_tree_path(branch)?; - let path_refs: Vec<&[u8]> = path.iter().map(|segment| segment.as_slice()).collect(); - let secondary_query = self.bounds.merk_query(self.descending); - + let (lo, hi) = self.bounds.inclusive_bounds_i128(); + let path_query = PathQuery::new_axis_bounded( + path, + self.bounds.axis().into(), + lo, + hi, + self.limit, + self.descending, + ); // Same destructure-don't-unwrap rationale as the no-proof arm. - let CostContext { value, cost: _ } = match self.bounds.axis() { - RankedAxis::Count => drive.grove.prove_indexed_count_query( - path_refs.as_slice(), - secondary_query, - Some(self.limit), - transaction, - grove_version, - ), - RankedAxis::Sum => drive.grove.prove_indexed_sum_query( - path_refs.as_slice(), - secondary_query, - Some(self.limit), - transaction, - grove_version, - ), - RankedAxis::Avg => drive.grove.prove_indexed_avg_query( - path_refs.as_slice(), - secondary_query, - Some(self.limit), - transaction, - grove_version, - ), - }; + let CostContext { value, cost: _ } = + drive.grove.prove_query(&path_query, None, grove_version); value.map_err(|e| Error::GroveDB(Box::new(e))) } } diff --git a/packages/rs-drive/src/query/drive_document_having_query/tests.rs b/packages/rs-drive/src/query/drive_document_having_query/tests.rs index c01de0974ee..9e5e0eb6e70 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/tests.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/tests.rs @@ -1047,25 +1047,19 @@ mod execution { fn an_empty_match_set_reads_empty_and_proves_empty() { let (drive, contract) = setup_restaurants(); - // Empty secondary (no documents at all): the unproven read - // returns the empty list, but grovedb's range prover — unlike - // the ranked surface's paginated prover — has no absence-proof - // shape for a completely empty tree and refuses. drive-abci - // maps this exact failure class onto an `InvalidArgument` - // telling the caller to retry unproved - // (`empty_ranking_proof_rejection`); at the drive level it - // surfaces as the grovedb error asserted here. If a future - // grovedb pin makes empty range proofs work, this arm should - // flip to a round-trip assertion. + // Empty secondary (no documents at all): both the unproven read + // and — since the unified PathQuery prover replaced grovedb's + // standalone range prover — the PROVED read serve the empty + // answer: the envelope commits the element's empty secondary + // (NULL_HASH convention), so complete absence is authenticated + // rather than refused, exactly as on the ranked surface. (The + // retired standalone prover refused with "Cannot create proof + // for empty tree"; drive-abci's InvalidArgument mapping for + // that class is vestigial on this surface now.) let case = HavingCase::count(HavingOperator::GreaterThan, Value::U64(100), 10); let entries = entries_of(run(&drive, &contract, &case, false).expect("read succeeds")); assert!(entries.is_empty()); - let error = run(&drive, &contract, &case, true) - .expect_err("proving against an empty secondary is refused by grovedb"); - assert!( - format!("{error}").contains("Cannot create proof for empty tree"), - "the failure must be the recognized empty-tree class, got: {error}" - ); + assert_proof_round_trips(&drive, &contract, &case, &entries); // Populated secondary, bound above every count: a genuine // absence proof, which works — the tree has content to anchor @@ -1145,11 +1139,36 @@ mod execution { .verify_having_range_proof(&proof, platform_version()) .is_err()); + // The limit is a CAP, not an identity parameter: the unified + // verifier re-executes the proof under the queried limit rather + // than comparing echoes, so a proof whose walk EXHAUSTED the + // bound (one entry here) is a complete, valid answer under any + // cap that admits it — the `limit 10` proof verifies under + // `limit 5` too, and that is sound. tampered_query = client_side_query(&contract, &over_two); tampered_query.limit = 5; - assert!(tampered_query - .verify_having_range_proof(&proof, platform_version()) - .is_err()); + assert!( + tampered_query + .verify_having_range_proof(&proof, platform_version()) + .is_ok(), + "an exhausted-walk proof is a complete answer under a smaller cap too" + ); + + // What must NOT verify is the truncation direction: a proof cut + // by a SMALLER limit (2 of the 3 groups matching `> 0`, with + // more in range) misrepresents completeness under a larger cap, + // and the reconstruction rejects it for missing coverage of the + // rest of the bound. + let truncated = HavingCase::count(HavingOperator::GreaterThan, Value::U64(0), 2); + let truncated_proof = + proof_of(run(&drive, &contract, &truncated, true).expect("prove succeeds")); + let widened = HavingCase::count(HavingOperator::GreaterThan, Value::U64(0), 10); + assert!( + client_side_query(&contract, &widened) + .verify_having_range_proof(&truncated_proof, platform_version()) + .is_err(), + "a limit-truncated proof must not verify under a larger cap" + ); } /// A `having` on an axis no index declares is refused with the diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs index f6bd4c5f25d..ba9ef078777 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs @@ -10,13 +10,14 @@ //! Whole module is gated `feature = "server"` via the parent's //! `pub mod execute_top_k;` declaration. -use super::branches::{decompose_branch_paths, read_branched_union}; -use super::{DriveDocumentRankedQuery, RankedAxis, RankedEntry, RankedEntryValue, RankedPage}; +use super::branches::{axis_keys_to_ranked, decompose_branch_paths, read_branched_union}; +use super::{DriveDocumentRankedQuery, RankedPage}; use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; use dpp::version::PlatformVersion; -use grovedb::{IndexedTopKKeysPage, PathQuery, TransactionArg}; +use grovedb::query_result_type::QueryResultType; +use grovedb::{PathQuery, PathQueryRun, TransactionArg}; use grovedb_costs::CostContext; use grovedb_query::AxisQuery; @@ -128,89 +129,46 @@ impl DriveDocumentRankedQuery<'_> { ) -> Result { let grove_version = &platform_version.drive.grove_version; let path = self.indexed_property_name_tree_path(branch)?; - let path_refs: Vec<&[u8]> = path.iter().map(|segment| segment.as_slice()).collect(); - let offset = self.offset as u64; // The cost is dropped rather than `.unwrap()`-ed: // `CostContext::unwrap` is infallible (it drops the cost field) // but reads like a panicking unwrap at the call site. Dropping it // is all there is to do with it — nothing meters a query on this - // surface: neither this executor's caller nor the dispatcher - // above it accumulates or charges the cost, and no credit is - // debited for a read. grovedb computes the `OperationCost` - // because its API always does, and it ends here. - let (entries, skipped) = match self.axis { - RankedAxis::Count => { - let CostContext { value, cost: _ } = - drive.grove.indexed_count_top_k_paginated_keys( - path_refs.as_slice(), - self.k, - offset, - self.descending, - transaction, - grove_version, - ); - let IndexedTopKKeysPage { entries, skipped } = - value.map_err(|e| Error::GroveDB(Box::new(e)))?; - ( - entries - .into_iter() - .map(|(count, key)| RankedEntry { - in_key: None, - key, - value: RankedEntryValue::Count(count), - }) - .collect::>(), - skipped, - ) - } - RankedAxis::Sum => { - let CostContext { value, cost: _ } = drive.grove.indexed_sum_top_k_paginated_keys( - path_refs.as_slice(), - self.k, - offset, - self.descending, - transaction, - grove_version, - ); - let IndexedTopKKeysPage { entries, skipped } = - value.map_err(|e| Error::GroveDB(Box::new(e)))?; - ( - entries - .into_iter() - .map(|(sum, key)| RankedEntry { - in_key: None, - key, - value: RankedEntryValue::Sum(sum), - }) - .collect::>(), - skipped, - ) - } - RankedAxis::Avg => { - let CostContext { value, cost: _ } = drive.grove.indexed_avg_top_k_paginated_keys( - path_refs.as_slice(), - self.k, - offset, - self.descending, - transaction, - grove_version, - ); - let IndexedTopKKeysPage { entries, skipped } = - value.map_err(|e| Error::GroveDB(Box::new(e)))?; - ( - entries - .into_iter() - .map(|(avg, key)| RankedEntry { - in_key: None, - key, - value: RankedEntryValue::AvgFixedPoint(avg), - }) - .collect::>(), - skipped, - ) - } + // surface. grovedb computes the `OperationCost` because its API + // always does, and it ends here. + let path_query = PathQuery::new_axis( + path, + AxisQuery::top_k( + self.axis.into(), + self.k, + self.offset as u64, + self.descending, + ) + .keys_only(), + ); + let CostContext { value, cost: _ } = drive.grove.run_path_query( + &path_query, + true, + true, + true, + QueryResultType::QueryKeyElementPairResultType, + transaction, + grove_version, + ); + let run = value.map_err(|e| Error::GroveDB(Box::new(e)))?; + let PathQueryRun::AxisKeys { keys, skipped } = run else { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "a keys-only ranked read returned a different result shape".to_string(), + ))); }; + let entries = axis_keys_to_ranked(self.axis, keys)?; + // A `RankedPage` traversal always attests its skip; its absence + // would mean grovedb answered a different traversal than asked. + let skipped = skipped.ok_or_else(|| { + Error::Drive(DriveError::CorruptedDriveState( + "a paginated ranked read carried no skip attestation".to_string(), + )) + })?; // `k` is the contract with the caller, and on the prove path it // is re-checked inside the proof envelope. Asserting it here too @@ -265,21 +223,21 @@ impl DriveDocumentRankedQuery<'_> { transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result, Error> { + // grovedb's `prove_query` — since the indexed-axis prover + // retirement, the only proof surface — proves COMMITTED state + // only: it takes one internal snapshot and threads it through + // every proof layer, and cannot see the caller's transaction. + // Serving a proof for a different snapshot than the unproved + // read would silently desynchronize the two paths, so a + // transactional prove fails closed, single-prefix and branched + // alike. + if transaction.is_some() { + return Err(Error::Drive(DriveError::NotSupported( + "a ranked proof is generated from committed state only: grovedb's \ + prove_query cannot see the caller's transaction — commit first", + ))); + } if self.prefix_branches.len() > 1 { - // grovedb's unified `prove_query` proves COMMITTED state only — - // it takes one internal snapshot of committed state and threads - // it through every proof layer, and cannot see the caller's - // transaction. Serving a proof for a different snapshot than the - // unproved read would silently desynchronize the two paths, so a - // transactional branched prove fails closed instead — exactly - // like the branched unproved read. - if transaction.is_some() { - return Err(Error::Drive(DriveError::NotSupported( - "an IN-pinned (branched) ranked proof is generated from committed state \ - only: grovedb's unified prove_query cannot see the caller's transaction \ - — prove per prefix element, or commit first", - ))); - } // One grovedb **branched** envelope: shared ancestor layers // once, one multi-key proof at the branching level, one // secondary proof per branch — a single proof with a single @@ -306,7 +264,7 @@ impl DriveDocumentRankedQuery<'_> { drive.grove.prove_query(&path_query, None, grove_version); return value.map_err(|e| Error::GroveDB(Box::new(e))); } - self.execute_top_k_with_proof_branch(0, drive, transaction, platform_version) + self.execute_top_k_with_proof_branch(0, drive, platform_version) } /// One branch's proof — the entire pre-`IN` prover, parameterized by @@ -315,23 +273,20 @@ impl DriveDocumentRankedQuery<'_> { &self, branch: usize, drive: &Drive, - transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result, Error> { let grove_version = &platform_version.drive.grove_version; let path = self.indexed_property_name_tree_path(branch)?; - let path_refs: Vec<&[u8]> = path.iter().map(|segment| segment.as_slice()).collect(); - - // Same destructure-don't-unwrap rationale as the no-proof arm. - let CostContext { value, cost: _ } = drive.grove.prove_indexed_axis_top_k_paginated( - path_refs.as_slice(), + let path_query = PathQuery::new_axis_top_k( + path, self.axis.into(), self.k, self.offset as u64, self.descending, - transaction, - grove_version, ); + // Same destructure-don't-unwrap rationale as the no-proof arm. + let CostContext { value, cost: _ } = + drive.grove.prove_query(&path_query, None, grove_version); value.map_err(|e| Error::GroveDB(Box::new(e))) } } diff --git a/packages/rs-drive/src/verify/document_having/verify_having_range_proof/v0/mod.rs b/packages/rs-drive/src/verify/document_having/verify_having_range_proof/v0/mod.rs index 38511ab75a6..01953423228 100644 --- a/packages/rs-drive/src/verify/document_having/verify_having_range_proof/v0/mod.rs +++ b/packages/rs-drive/src/verify/document_having/verify_having_range_proof/v0/mod.rs @@ -3,10 +3,9 @@ use crate::error::Error; use crate::query::drive_document_ranked_query::branches::{ axis_entries_to_ranked, decompose_branch_paths, merge_branch_pages, }; -use crate::query::{DriveDocumentHavingQuery, RankedAxis, RankedEntry, RankedEntryValue}; +use crate::query::{DriveDocumentHavingQuery, RankedEntry}; use crate::verify::RootHash; use dpp::version::PlatformVersion; -use grovedb::operations::proof::indexed_axis::AxisEntries; use grovedb::operations::proof::VerifiedPathQuery; use grovedb::GroveDb; use grovedb::PathQuery; @@ -130,80 +129,31 @@ impl DriveDocumentHavingQuery<'_> { platform_version: &PlatformVersion, ) -> Result<(RootHash, Vec), Error> { let path = self.indexed_property_name_tree_path(branch)?; - let path_refs: Vec<&[u8]> = path.iter().map(|segment| segment.as_slice()).collect(); - let secondary_query = self.bounds.merk_query(self.descending); - - let result = match self.bounds.axis() { - RankedAxis::Count => GroveDb::verify_indexed_count_query( - proof, - path_refs.as_slice(), - secondary_query, - Some(self.limit), - &platform_version.drive.grove_version, - ), - RankedAxis::Sum => GroveDb::verify_indexed_sum_query( - proof, - path_refs.as_slice(), - secondary_query, - Some(self.limit), - &platform_version.drive.grove_version, - ), - RankedAxis::Avg => GroveDb::verify_indexed_avg_query( - proof, - path_refs.as_slice(), - secondary_query, - Some(self.limit), - &platform_version.drive.grove_version, - ), - } - .map_err(|e| Error::GroveDB(Box::new(e)))?; - - let entries = match (self.bounds.axis(), result.entries) { - (RankedAxis::Count, AxisEntries::Count(entries)) => entries - .into_iter() - .map(|entry| entry.key_pair()) - .map(|(count, key)| RankedEntry { - in_key: None, - key, - value: RankedEntryValue::Count(count), - }) - .collect::>(), - (RankedAxis::Sum, AxisEntries::Sum(entries)) => entries - .into_iter() - .map(|entry| entry.key_pair()) - .map(|(sum, key)| RankedEntry { - in_key: None, - key, - value: RankedEntryValue::Sum(sum), - }) - .collect::>(), - (RankedAxis::Avg, AxisEntries::Avg(entries)) => entries - .into_iter() - .map(|entry| entry.key_pair()) - .map(|(avg, key)| RankedEntry { - in_key: None, - key, - value: RankedEntryValue::AvgFixedPoint(avg), - }) - .collect::>(), - (axis, other) => { - return Err(Error::Drive(DriveError::CorruptedDriveState(format!( - "having range proof for the {axis:?} axis verified to {} entries of a \ - different axis shape", - other.len() - )))); - } + let axis = self.bounds.axis(); + let (lo, hi) = self.bounds.inclusive_bounds_i128(); + let path_query = + PathQuery::new_axis_bounded(path, axis.into(), lo, hi, self.limit, self.descending); + let verified = + GroveDb::verify_path_query(proof, &path_query, &platform_version.drive.grove_version) + .map_err(|e| Error::GroveDB(Box::new(e)))?; + let VerifiedPathQuery::AxisEntries { + root_hash, + entries, + skipped: _, + } = verified + else { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "a having range proof verified to a different shape".to_string(), + ))); }; - + let entries = axis_entries_to_ranked(axis, entries)?; if entries.len() > self.limit as usize { return Err(Error::Drive(DriveError::CorruptedDriveState(format!( - "having range proof for the {:?} axis verified to {} entries for limit = {}", - self.bounds.axis(), + "having range proof verified to {} entries for limit = {}", entries.len(), self.limit )))); } - - Ok((result.root_hash, entries)) + Ok((root_hash, entries)) } } diff --git a/packages/rs-drive/src/verify/document_ranked/verify_ranked_top_k_proof/v0/mod.rs b/packages/rs-drive/src/verify/document_ranked/verify_ranked_top_k_proof/v0/mod.rs index 40f1594cc32..2be63e9e1c6 100644 --- a/packages/rs-drive/src/verify/document_ranked/verify_ranked_top_k_proof/v0/mod.rs +++ b/packages/rs-drive/src/verify/document_ranked/verify_ranked_top_k_proof/v0/mod.rs @@ -3,12 +3,9 @@ use crate::error::Error; use crate::query::drive_document_ranked_query::branches::{ axis_entries_to_ranked, decompose_branch_paths, merge_branch_pages, }; -use crate::query::{ - DriveDocumentRankedQuery, RankedAxis, RankedEntry, RankedEntryValue, RankedPage, -}; +use crate::query::{DriveDocumentRankedQuery, RankedPage}; use crate::verify::RootHash; use dpp::version::PlatformVersion; -use grovedb::operations::proof::indexed_axis::AxisEntries; use grovedb::operations::proof::VerifiedPathQuery; use grovedb::GroveDb; use grovedb::PathQuery; @@ -151,71 +148,40 @@ impl DriveDocumentRankedQuery<'_> { platform_version: &PlatformVersion, ) -> Result<(RootHash, RankedPage), Error> { let path = self.indexed_property_name_tree_path(branch)?; - let path_refs: Vec<&[u8]> = path.iter().map(|segment| segment.as_slice()).collect(); - - let result = GroveDb::verify_indexed_axis_top_k_paginated( - proof, - path_refs.as_slice(), + let path_query = PathQuery::new_axis_top_k( + path, self.axis.into(), self.k, self.offset as u64, self.descending, - &platform_version.drive.grove_version, - ) - .map_err(|e| Error::GroveDB(Box::new(e)))?; - - let entries = match (self.axis, result.entries) { - (RankedAxis::Count, AxisEntries::Count(entries)) => entries - .into_iter() - .map(|entry| entry.key_pair()) - .map(|(count, key)| RankedEntry { - in_key: None, - key, - value: RankedEntryValue::Count(count), - }) - .collect::>(), - (RankedAxis::Sum, AxisEntries::Sum(entries)) => entries - .into_iter() - .map(|entry| entry.key_pair()) - .map(|(sum, key)| RankedEntry { - in_key: None, - key, - value: RankedEntryValue::Sum(sum), - }) - .collect::>(), - (RankedAxis::Avg, AxisEntries::Avg(entries)) => entries - .into_iter() - .map(|entry| entry.key_pair()) - .map(|(avg, key)| RankedEntry { - in_key: None, - key, - value: RankedEntryValue::AvgFixedPoint(avg), - }) - .collect::>(), - (axis, other) => { - return Err(Error::Drive(DriveError::CorruptedDriveState(format!( - "ranked top-k proof for the {axis:?} axis verified to {} entries of a \ - different axis shape", - other.len() - )))); - } + ); + let verified = + GroveDb::verify_path_query(proof, &path_query, &platform_version.drive.grove_version) + .map_err(|e| Error::GroveDB(Box::new(e)))?; + let VerifiedPathQuery::AxisEntries { + root_hash, + entries, + skipped, + } = verified + else { + return Err(Error::Drive(DriveError::CorruptedDriveState( + "a ranked top-k proof verified to a different shape".to_string(), + ))); }; - + let entries = axis_entries_to_ranked(self.axis, entries)?; if entries.len() > self.k as usize { return Err(Error::Drive(DriveError::CorruptedDriveState(format!( - "ranked top-k proof for the {:?} axis verified to {} entries for k = {}", - self.axis, + "ranked top-k proof verified to {} entries for k = {}", entries.len(), self.k )))); } - - Ok(( - result.root_hash, - RankedPage { - skipped: result.skipped, - entries, - }, - )) + // A paginated traversal's proof always attests its skip. + let skipped = skipped.ok_or_else(|| { + Error::Drive(DriveError::CorruptedDriveState( + "a paginated ranked proof carried no skip attestation".to_string(), + )) + })?; + Ok((root_hash, RankedPage { skipped, entries })) } } diff --git a/packages/rs-platform-version/Cargo.toml b/packages/rs-platform-version/Cargo.toml index 2f03f1f6664..74faf5deaf6 100644 --- a/packages/rs-platform-version/Cargo.toml +++ b/packages/rs-platform-version/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" thiserror = { version = "2.0.12" } bincode = { version = "=2.0.1" } versioned-feature-core = { git = "https://github.com/dashpay/versioned-feature-core", version = "1.0.0" } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e" } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "6c882c3ee7d2c331f1feda2eb4223add9a6f0e45" } [features] mock-versions = [] diff --git a/packages/rs-platform-wallet/Cargo.toml b/packages/rs-platform-wallet/Cargo.toml index 4ffc2e7a5a1..a46939a40ca 100644 --- a/packages/rs-platform-wallet/Cargo.toml +++ b/packages/rs-platform-wallet/Cargo.toml @@ -69,7 +69,7 @@ zeroize = "1" log = "0.4" # Shielded pool (optional, behind `shielded` feature) -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "6c882c3ee7d2c331f1feda2eb4223add9a6f0e45", optional = true } # Direct `rusqlite` access so `FileBackedShieldedStore::open_path` can set # WAL + synchronous=NORMAL pragmas before handing the connection to # `ClientPersistentCommitmentTree`. Version locked to match the rev grovedb diff --git a/packages/rs-sdk/Cargo.toml b/packages/rs-sdk/Cargo.toml index 8ef0fd0f9ef..95a53f942cf 100644 --- a/packages/rs-sdk/Cargo.toml +++ b/packages/rs-sdk/Cargo.toml @@ -18,7 +18,7 @@ drive = { path = "../rs-drive", default-features = false, features = [ ] } drive-proof-verifier = { path = "../rs-drive-proof-verifier", default-features = false } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "0b85d7e94ec792cac22bfde2f1819318499ba05e", features = [ +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "6c882c3ee7d2c331f1feda2eb4223add9a6f0e45", features = [ "client", "sqlite", ], optional = true } From 6e67eb915b5220686aca592d0aa4303b04f9f3b4 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 26 Aug 2026 21:42:40 +0200 Subject: [PATCH 23/25] fix(drive): enforce offset-with-IN at every boundary; describe reconstruction, not echo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review: the offset x IN exclusion is grammar-enforced, but `offset` is a public field and a mode is publicly constructible, so a resolved query could be steered into the forbidden shape — per-branch skips merged into a page reporting `skipped: 0`, with the verifier reconstructing the same malformed traversal. The resolved query now re-checks the invariant (`reject_offset_with_branches`) at every execution, proving and verification entry, pinned by a test that mutates a resolved query and asserts the typed refusal on all three paths. Also rewrites the eight proof-contract docs that still described the retired indexed-axis API and its exact-echo semantics: verification binds by RECONSTRUCTION — the verifier rebuilds the request's own PathQuery and re-executes the proof against it — with the limit binding as a cap (exhausted-walk proofs verify under any admitting cap; a truncated proof fails a larger one for missing coverage), and the stale "no platform_version argument" claim is gone. Co-Authored-By: Claude Fable 5 --- .../execute_range.rs | 17 +++++---- .../query/drive_document_having_query/mod.rs | 20 +++++----- .../execute_top_k.rs | 16 +++++--- .../query/drive_document_ranked_query/mod.rs | 34 +++++++++++++++-- .../drive_document_ranked_query/tests.rs | 38 +++++++++++++++++++ .../src/verify/document_having/mod.rs | 6 +-- .../verify_having_range_proof/v0/mod.rs | 35 +++++++++-------- .../src/verify/document_ranked/mod.rs | 5 ++- .../verify_ranked_top_k_proof/v0/mod.rs | 24 +++++++----- 9 files changed, 136 insertions(+), 59 deletions(-) diff --git a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs index f0a4c4a4bee..12e732da890 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/execute_range.rs @@ -130,14 +130,15 @@ impl DriveDocumentHavingQuery<'_> { /// The envelope commits the in-range secondary entries, the /// primary's root hash, the sibling axes' root hashes, and a /// per-ancestor attestation chain up to the grovedb root — so the - /// client reconstructs the platform root hash from it. The Merk - /// query (the encoded bounds and walk direction) and the limit are - /// echoed and re-checked by grovedb's verifier against the client's - /// own reconstruction via [`AxisRangeBounds::merk_query`] — which is - /// why the bounds are validated rather than clamped upstream, and - /// why completeness needs no extra machinery: a Merk range proof - /// over a sorted keyspace commits its boundaries, so an in-range - /// group the server omitted fails reconstruction. + /// client reconstructs the platform root hash from it. The bounds, + /// direction and limit bind by RECONSTRUCTION: the verifier rebuilds + /// the same `Bounded` axis `PathQuery` from the request + /// ([`AxisRangeBounds::inclusive_bounds_i128`]) and re-executes the + /// proof against it — which is why the bounds are validated rather + /// than clamped upstream, and why completeness needs no extra + /// machinery: a Merk range proof over a sorted keyspace commits its + /// boundaries, so an in-range group the server omitted fails + /// reconstruction. /// /// Verified by /// [`DriveDocumentHavingQuery::verify_having_range_proof`](crate::query::DriveDocumentHavingQuery::verify_having_range_proof). diff --git a/packages/rs-drive/src/query/drive_document_having_query/mod.rs b/packages/rs-drive/src/query/drive_document_having_query/mod.rs index b998a5688e4..8efc4949d87 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/mod.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/mod.rs @@ -37,13 +37,14 @@ //! rank `offset`); having-range addresses them by **value bound** //! (`aggregate ∈ [lo, hi]`). Three consequences: //! -//! 1. **The bound is part of the proof contract.** The grovedb envelope -//! for a range read echoes the Merk query itself, and the verifier -//! re-builds that query from the request's bounds -//! ([`AxisRangeBounds::merk_query`]) — so prover and verifier must -//! share one bounds-to-query translation, exactly as they share the -//! grove path. Completeness comes from the Merk range proof: the -//! boundary commitments show no in-range group was omitted. +//! 1. **The bound is part of the proof contract.** Prover and verifier +//! build the same `Bounded` axis `PathQuery` from the request's +//! inclusive bounds ([`AxisRangeBounds::inclusive_bounds_i128`]), +//! and grovedb re-executes the proof against that traversal — so the +//! two sides share one bounds-to-query translation, exactly as they +//! share the grove path. Completeness comes from the Merk range +//! proof: the boundary commitments show no in-range group was +//! omitted. //! 2. **No `OFFSET`, no `start_at` — and no full pagination.** The //! range primitives take a limit but no skip, and a request carrying //! either knob is rejected loudly. A page cut at `limit` can only be @@ -109,8 +110,9 @@ mod tests; /// rationale as [`super::drive_document_ranked_query::MAX_RANKED_LIMIT`]: /// the proof commits one secondary entry per returned group, so proof /// bytes grow linearly in the limit, and the ceiling is a hard rejection -/// rather than a clamp because the limit is echoed in the proof envelope -/// and re-checked by the verifier. +/// rather than a clamp because the limit is part of the traversal the +/// verifier re-executes: a server-side clamp would truncate the walk +/// and fail coverage under the client's own reconstruction. #[cfg(any(feature = "server", feature = "verify"))] pub const MAX_HAVING_LIMIT: u16 = 100; diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs index ba9ef078777..b049038174f 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/execute_top_k.rs @@ -83,6 +83,7 @@ impl DriveDocumentRankedQuery<'_> { transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result { + self.reject_offset_with_branches()?; if self.prefix_branches.len() > 1 { // ONE grovedb call for the whole union, pinned to one // committed state and merged with the shared comparator — @@ -193,12 +194,14 @@ impl DriveDocumentRankedQuery<'_> { /// entries skipped to reach them, the primary's root hash, the /// sibling axes' root hashes, and a per-ancestor attestation chain /// up to the grovedb root — so the client reconstructs the platform - /// root hash from it. It also echoes `(axis, k, offset, - /// descending)`, which - /// [`grovedb::GroveDb::verify_indexed_axis_top_k_paginated`] - /// re-checks against what the client asked for; that is why `k` is - /// validated rather than clamped upstream (a clamped `k` would - /// produce a proof the client's own reconstruction rejects). + /// root hash from it. `(axis, k, offset, descending)` bind by + /// RECONSTRUCTION, not echo: the verifier rebuilds the same + /// `PathQuery` from the request and + /// [`grovedb::GroveDb::verify_path_query`] re-executes the proof + /// against that traversal, so a proof for a different ranking or a + /// different page fails to cover it; that is why `k` is validated + /// rather than clamped upstream (a clamped `k` would produce a page + /// the client's reconstruction did not ask for). /// /// The paginated primitive is used unconditionally, with /// `offset = 0` for offset-free requests, so there is exactly one @@ -223,6 +226,7 @@ impl DriveDocumentRankedQuery<'_> { transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result, Error> { + self.reject_offset_with_branches()?; // grovedb's `prove_query` — since the indexed-axis prover // retirement, the only proof surface — proves COMMITTED state // only: it takes one internal snapshot and threads it through diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs b/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs index 0b5b15ade0c..a3b1108eab4 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs @@ -126,10 +126,9 @@ mod tests; /// is rejected with /// [`crate::error::query::QuerySyntaxError::InvalidLimit`] rather than /// silently truncated. Truncation would be especially treacherous here -/// because `k` is echoed inside the proof envelope and re-checked by -/// [`grovedb::GroveDb::verify_indexed_axis_top_k_paginated`] — a -/// server-side clamp would produce a proof the client's own -/// reconstruction rejects. +/// because `k` is part of the traversal the client reconstructs for +/// [`grovedb::GroveDb::verify_path_query`] — a server-side clamp would +/// produce a page the client's reconstruction did not ask for. /// /// There is deliberately **no companion ceiling on `OFFSET`**; see the /// module docs and [`DriveDocumentRankedQuery::offset`]. @@ -377,6 +376,33 @@ impl DriveDocumentRankedQuery<'_> { pub fn prefix_branches(&self) -> &[Vec>] { &self.prefix_branches } + + /// Reject the one cross-field combination the request grammar + /// forbids but public construction can still express: a + /// multi-branch (`IN`) query carrying a non-zero `offset`. + /// Rank-skip is attested from ONE secondary's counted commitments; + /// applied independently per branch it would page each branch + /// separately, merge the independently skipped pages, and report + /// `skipped: 0` — and the verifier, reconstructing the same + /// malformed per-branch traversal, would not reject it. Enforced at + /// every execution, proving and verification boundary, because + /// `offset` is a public field and the mode-detection grammar check + /// can be bypassed by building a mode or mutating a resolved query + /// directly. + pub(crate) fn reject_offset_with_branches(&self) -> Result<(), crate::error::Error> { + if self.prefix_branches.len() > 1 && self.offset != 0 { + return Err(crate::error::Error::Query( + crate::error::query::QuerySyntaxError::InvalidLimit( + "`OFFSET` cannot combine with an `IN` prefix pin: rank-skip is attested \ + from one secondary's counted commitments, and an `IN` merges several \ + secondaries with no counted structure over the union. Page one prefix \ + at a time (`==` pin + `OFFSET`), or drop the offset." + .to_string(), + ), + )); + } + Ok(()) + } } /// A page of a ranked result: the entries, plus how many ranks were diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs index 999a69ff92a..c6557a2bb80 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs @@ -3375,6 +3375,44 @@ mod pinned_prefix { assert_eq!(page.entries.len(), 1); } + /// The offset x `IN` exclusion survives resolution: the grammar + /// rejects the combination, but `offset` is a public field and a + /// mode is publicly constructible, so every execution, proving and + /// verification boundary re-checks it — a resolved query mutated + /// into the forbidden shape is refused, not served as a page-broken + /// merge that reports `skipped: 0`. + #[test] + fn a_mutated_offset_cannot_combine_with_branches() { + let (drive, contract) = setup_grades_compound_ranked(); + insert_grades(&drive, &contract, &[(IDENTITY_X, "math", 80)]); + + let mut query = client_side_query(&contract, &in_pin(&[IDENTITY_X, IDENTITY_Y]), 2); + query.offset = 1; + let pv = platform_version(); + + let read = query + .execute_top_k_no_proof(&drive, None, pv) + .expect_err("a branched read with a mutated offset must be refused"); + assert!( + matches!(&read, Error::Query(QuerySyntaxError::InvalidLimit(m)) if m.contains("cannot combine")), + "expected the offset x IN rejection on the read path, got {read:?}" + ); + let prove = query + .execute_top_k_with_proof(&drive, None, pv) + .expect_err("a branched prove with a mutated offset must be refused"); + assert!( + matches!(&prove, Error::Query(QuerySyntaxError::InvalidLimit(_))), + "expected the offset x IN rejection on the prove path, got {prove:?}" + ); + let verify = query + .verify_ranked_top_k_proof(&[], pv) + .expect_err("verification with a mutated offset must be refused"); + assert!( + matches!(&verify, Error::Query(QuerySyntaxError::InvalidLimit(_))), + "expected the offset x IN rejection on the verify path, got {verify:?}" + ); + } + /// The public encoder enforces the documented branch ceiling itself: /// its callers' grammar checks are not the only line of defense. #[test] diff --git a/packages/rs-drive/src/verify/document_having/mod.rs b/packages/rs-drive/src/verify/document_having/mod.rs index a3ddcec97e1..e21cfc913f7 100644 --- a/packages/rs-drive/src/verify/document_having/mod.rs +++ b/packages/rs-drive/src/verify/document_having/mod.rs @@ -10,9 +10,9 @@ //! //! Only one verifier exists here, for the same reason as on the ranked //! surface: every having-range request — any of the three axes, either -//! direction, any contiguous bound — resolves to one -//! `prove_indexed_axis_query` envelope that differs only in the Merk -//! query (the encoded bounds + direction) and limit it echoes. +//! direction, any contiguous bound — resolves to one `Bounded` axis +//! traversal proved through grovedb's unified `prove_query`, against +//! the same `PathQuery` the verifier reconstructs from the request. /// Indexed-axis range proof verification — returns the groups the proof /// commits to as falling inside the bound, in axis order. diff --git a/packages/rs-drive/src/verify/document_having/verify_having_range_proof/v0/mod.rs b/packages/rs-drive/src/verify/document_having/verify_having_range_proof/v0/mod.rs index 01953423228..0f58f625769 100644 --- a/packages/rs-drive/src/verify/document_having/verify_having_range_proof/v0/mod.rs +++ b/packages/rs-drive/src/verify/document_having/verify_having_range_proof/v0/mod.rs @@ -14,34 +14,33 @@ use grovedb_query::AxisQuery; impl DriveDocumentHavingQuery<'_> { /// v0 of [`Self::verify_having_range_proof`]. /// - /// Rebuilds the proved subtree path with - /// [`Self::indexed_property_name_tree_path`] and the secondary query - /// with - /// [`AxisRangeBounds::merk_query`](crate::query::drive_document_having_query::AxisRangeBounds::merk_query), - /// then hands the proof to the matching - /// `GroveDb::verify_indexed_*_query` — an associated function, no + /// Rebuilds the same `PathQuery` the prover built — the subtree + /// path via [`Self::indexed_property_name_tree_path`], the + /// `Bounded` traversal from the request's inclusive bounds + /// ([`AxisRangeBounds::inclusive_bounds_i128`](crate::query::drive_document_having_query::AxisRangeBounds::inclusive_bounds_i128)), + /// limit and direction — and hands the proof to + /// [`GroveDb::verify_path_query`], an associated function: no /// database handle, so this compiles and runs in a verifier-only /// build. /// /// Three things are checked before the entries are returned: /// - /// 1. **The envelope matches this query.** grovedb re-checks the - /// proof against the reconstructed Merk query (the encoded bounds - /// and walk direction) and the expected limit, so a proof - /// generated for a different bound — or a different direction, or - /// a different limit — is rejected rather than silently - /// reinterpreted. Completeness rides on the same check: a Merk - /// range proof commits its boundaries, so an in-range group the - /// prover omitted fails reconstruction. + /// 1. **The proof covers this query's own traversal.** Binding is by + /// RECONSTRUCTION, not echo comparison: the verifier re-executes + /// the proof against the bounds, direction and limit it rebuilt + /// from the request, so a proof for a different bound or + /// direction fails to cover it. The limit binds as a CAP under + /// re-execution — an exhausted-walk proof is a complete answer + /// under any admitting cap (sound, and pinned by the + /// limit-tamper test), while a proof truncated by a smaller limit + /// fails a larger cap for missing coverage of the rest of the + /// bound. Completeness rides on the Merk range proof itself: its + /// boundary commitments show no in-range group was omitted. /// 2. **The result's axis shape matches the requested axis** — the /// same belt-and-braces check the ranked verifier does. /// 3. **At most `limit` entries.** Fewer is normal — fewer groups /// may match the bound — but more would mean the proof committed /// a longer walk than the request authorized. - /// - /// No `platform_version` argument: the parent dispatcher already - /// consumed it to select this version, and verification derives - /// everything else from the proof bytes plus the query. #[inline(always)] pub(super) fn verify_having_range_proof_v0( &self, diff --git a/packages/rs-drive/src/verify/document_ranked/mod.rs b/packages/rs-drive/src/verify/document_ranked/mod.rs index 0a5a0e0b8a7..e9a4ebbe671 100644 --- a/packages/rs-drive/src/verify/document_ranked/mod.rs +++ b/packages/rs-drive/src/verify/document_ranked/mod.rs @@ -12,8 +12,9 @@ //! single proof shape. Where the count surface has five verifiers because //! five different grovedb primitives can answer a count, every ranked //! request — either direction, at any offset, on any of the three axes — -//! resolves to one `prove_indexed_axis_top_k_paginated` envelope that -//! differs only in the `(axis, k, descending, offset)` tuple it echoes. +//! resolves to one `RankedPage` axis traversal proved through grovedb's +//! unified `prove_query`, against the same `PathQuery` the verifier +//! reconstructs from the request. /// Indexed-axis top-k proof verification — returns the ranked groups the /// proof commits to, in ranking order. diff --git a/packages/rs-drive/src/verify/document_ranked/verify_ranked_top_k_proof/v0/mod.rs b/packages/rs-drive/src/verify/document_ranked/verify_ranked_top_k_proof/v0/mod.rs index 2be63e9e1c6..359f5125092 100644 --- a/packages/rs-drive/src/verify/document_ranked/verify_ranked_top_k_proof/v0/mod.rs +++ b/packages/rs-drive/src/verify/document_ranked/verify_ranked_top_k_proof/v0/mod.rs @@ -14,19 +14,24 @@ use grovedb_query::AxisQuery; impl DriveDocumentRankedQuery<'_> { /// v0 of [`Self::verify_ranked_top_k_proof`]. /// - /// Rebuilds the proved subtree path with - /// [`Self::indexed_property_name_tree_path`] and hands the proof to - /// [`GroveDb::verify_indexed_axis_top_k_paginated`], which is an - /// associated function — no database handle is involved, so this + /// Rebuilds the same `PathQuery` the prover built — the subtree + /// path via [`Self::indexed_property_name_tree_path`], the + /// `RankedPage` traversal from `(axis, k, offset, descending)` — + /// and hands the proof to [`GroveDb::verify_path_query`], an + /// associated function: no database handle is involved, so this /// compiles and runs in a verifier-only build. /// /// Three things are checked before the page is returned: /// - /// 1. **The envelope's `(axis, k, offset, descending)` match this - /// query.** grovedb does this itself: the values are echoed in - /// the proof and compared against the arguments, so a proof - /// generated for a different ranking — or a different page of the - /// same ranking — is rejected rather than silently reinterpreted. + /// 1. **The proof covers this query's own traversal.** Binding is by + /// RECONSTRUCTION, not echo comparison: the verifier re-executes + /// the proof against the traversal it rebuilt from the request, + /// so a proof generated for a different ranking — or a different + /// page of the same ranking — fails to cover it rather than being + /// silently reinterpreted. (Coverage semantics make one benign + /// case verifiable that the retired exact-echo check refused: an + /// exhausted-walk proof is a complete answer under any admitting + /// cap — see the having surface's limit-tamper test for the pin.) /// 2. **The result's axis shape matches the requested axis** — a /// `Count` request must not come back holding `Sum` entries. This /// is belt-and-braces on top of (1) (the tag check already rules @@ -54,6 +59,7 @@ impl DriveDocumentRankedQuery<'_> { proof: &[u8], platform_version: &PlatformVersion, ) -> Result<(RootHash, RankedPage), Error> { + self.reject_offset_with_branches()?; if self.prefix_branches.len() > 1 { // `IN`-pinned request: the proof is one grovedb branched // envelope. The branch set (and its order) comes from From de4bbc7743a4392ed860a78558d546f2139a3c68 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 26 Aug 2026 22:35:37 +0200 Subject: [PATCH 24/25] docs(drive,sdk): finish the reconstruction-language sweep Per review, the remaining sites that still described the retired exact-echo contract: the multi-branch verify comment, path.rs's "there is no PathQuery to agree on" header (both sides now build the same axis PathQuery), the SDK proof helper naming the retired prove_indexed_axis_top_k_paginated primitive, the ranked and having LIMIT diagnostics (required-limit and ceiling messages), the having mode-detection module doc, the proof-verifier wrappers (now spelling out the limit-as-cap coverage semantics), and an SDK test doc. The two remaining "echo" mentions in the tree are accurate: one explicitly denies being an echo, the other names the axis tag genuinely stored in the indexed element's TLV. Co-Authored-By: Claude Fable 5 --- .../src/documents/document_ranked_entries.rs | 6 +++--- .../src/documents/ranked_proof_helpers.rs | 8 ++++---- .../src/proof/document_having.rs | 11 +++++++---- .../src/proof/document_ranked.rs | 6 +++--- .../mode_detection/mod.rs | 5 +++-- .../mode_detection/v0/mod.rs | 15 ++++++++------- .../mode_detection/v0/mod.rs | 14 +++++++------- .../src/query/drive_document_ranked_query/path.rs | 10 +++++----- .../verify_ranked_top_k_proof/v0/mod.rs | 9 +++++---- 9 files changed, 45 insertions(+), 39 deletions(-) diff --git a/packages/dash-platform-queries/src/documents/document_ranked_entries.rs b/packages/dash-platform-queries/src/documents/document_ranked_entries.rs index 298ddf46b62..1b382da45f9 100644 --- a/packages/dash-platform-queries/src/documents/document_ranked_entries.rs +++ b/packages/dash-platform-queries/src/documents/document_ranked_entries.rs @@ -501,9 +501,9 @@ mod tests { } /// The ranking's `n` rides `limit`, and an out-of-range one is - /// rejected rather than clamped: `k` is echoed inside the proof - /// envelope and re-checked by the verifier, so a silent clamp would - /// produce a proof the client's own reconstruction rejects. + /// rejected rather than clamped: `k` is part of the traversal the + /// client rebuilds to verify, so a silent clamp would produce a + /// page the client's reconstruction did not ask for. #[test] fn assert_ranked_shape_rejects_an_out_of_range_limit() { // `0` is `DocumentQuery`'s "unset" sentinel, and a ranking with diff --git a/packages/dash-platform-queries/src/documents/ranked_proof_helpers.rs b/packages/dash-platform-queries/src/documents/ranked_proof_helpers.rs index ec774957b58..b2f5bfc1d7c 100644 --- a/packages/dash-platform-queries/src/documents/ranked_proof_helpers.rs +++ b/packages/dash-platform-queries/src/documents/ranked_proof_helpers.rs @@ -12,10 +12,10 @@ //! `(axis, k, descending, offset)` tuple by construction rather than by //! two copies of a grammar agreeing. //! -//! Unlike the count helper there is no per-shape dispatch: the ranked -//! surface has exactly one proof primitive -//! (`prove_indexed_axis_top_k_paginated`), and all of a request's -//! variation is carried *inside* the query struct. +//! Unlike the count helper there is no per-shape dispatch: every +//! ranked request proves as one `RankedPage` axis traversal through +//! grovedb's unified `prove_query`, and all of a request's variation is +//! carried *inside* the query struct. //! //! [`DocumentRankedEntries`]: drive_proof_verifier::DocumentRankedEntries diff --git a/packages/rs-drive-proof-verifier/src/proof/document_having.rs b/packages/rs-drive-proof-verifier/src/proof/document_having.rs index eeedc8bb581..b8208100071 100644 --- a/packages/rs-drive-proof-verifier/src/proof/document_having.rs +++ b/packages/rs-drive-proof-verifier/src/proof/document_having.rs @@ -151,10 +151,13 @@ impl DocumentHavingEntries { /// (which does the merk-level verification). Both sides derive the /// proved subtree from the same /// `DriveDocumentHavingQuery::indexed_property_name_tree_path` and the -/// secondary query from the same `AxisRangeBounds::merk_query`, so -/// prover and verifier cannot drift on *which bound over which tree* is -/// being checked, and grovedb re-checks the echoed query and limit — a -/// proof of one bound does not verify as another. +/// bounded traversal from the same +/// `AxisRangeBounds::inclusive_bounds_i128`, so prover and verifier +/// cannot drift on *which bound over which tree* is being checked, and +/// grovedb re-executes the proof against that reconstruction — a proof +/// of one bound does not cover another (the limit binds as a cap: an +/// exhausted proof verifies under any admitting cap, a truncated one +/// fails a larger cap for missing coverage). /// /// ## The root hash is the whole point /// diff --git a/packages/rs-drive-proof-verifier/src/proof/document_ranked.rs b/packages/rs-drive-proof-verifier/src/proof/document_ranked.rs index d6dd115bc2f..e75f22a1bec 100644 --- a/packages/rs-drive-proof-verifier/src/proof/document_ranked.rs +++ b/packages/rs-drive-proof-verifier/src/proof/document_ranked.rs @@ -300,9 +300,9 @@ pub(crate) fn ranked_entry_from_proto(entry: &ProtoRankedEntry) -> Result { // envelope. The branch set (and its order) comes from // *this* query's own resolution; grovedb binds each branch // tail to its branch key through the branching-level - // multi-key proof, reconstructs one root hash, and echoes - // `(axis, k, offset, direction)`. The page is then - // re-derived by the shared merge — the client never trusts - // a server-side merge. + // multi-key proof, reconstructs one root hash, and + // re-executes each branch's page against the traversal + // rebuilt from THIS query — coverage, not echo comparison. + // The page is then re-derived by the shared merge — the + // client never trusts a server-side merge. let paths = (0..self.prefix_branches.len()) .map(|branch| self.indexed_property_name_tree_path(branch)) .collect::, Error>>()?; From 017ed542e074eb9d4a32260d87efda8399b3df20 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 26 Aug 2026 23:45:31 +0200 Subject: [PATCH 25/25] fix(drive,queries): adapt prefix-IN surfaces to time-range indexes post-merge - re-graft the client-side time-range normalization guard into verify_ranked_query so IN_TIME_RANGE requests cannot be answered by plain-index proofs (dropped by an --ours conflict resolution) - add the new time_range / resolved_time_ranges fields to this branch's Index and request-struct test literals - finish the doc sweep: describe unified PathQuery reconstruction and proof-coverage semantics instead of retired indexed-axis provers and proof-envelope parameter echoes Co-Authored-By: Claude Fable 5 --- .../src/documents/document_having_entries.rs | 5 ++- .../src/documents/ranked_proof_helpers.rs | 41 +++++++++++++++---- .../src/query/document_query/v1/tests.rs | 4 +- .../v0/tests/ranked_index_e2e_tests.rs | 4 ++ .../drive_document_having_query/tests.rs | 5 +++ .../query/drive_document_ranked_query/mod.rs | 3 +- .../drive_document_ranked_query/tests.rs | 33 +++++++++------ 7 files changed, 70 insertions(+), 25 deletions(-) diff --git a/packages/dash-platform-queries/src/documents/document_having_entries.rs b/packages/dash-platform-queries/src/documents/document_having_entries.rs index 2468d6801e0..e4d88a9f335 100644 --- a/packages/dash-platform-queries/src/documents/document_having_entries.rs +++ b/packages/dash-platform-queries/src/documents/document_having_entries.rs @@ -313,8 +313,9 @@ mod tests { /// HAVING limits are a hard inclusive range, `1..=100`: `0` (the /// unset sentinel) and anything above `MAX_HAVING_LIMIT` are - /// rejected client side rather than clamped, because the limit is - /// echoed in the proof envelope and re-checked by the verifier. + /// rejected client side rather than clamped, because the limit + /// bounds the coverage the verifier's rebuilt `PathQuery` demands + /// of the proof. #[test] fn limit_is_required_and_capped_client_side() { for limit in [0u32, 101] { diff --git a/packages/dash-platform-queries/src/documents/ranked_proof_helpers.rs b/packages/dash-platform-queries/src/documents/ranked_proof_helpers.rs index b2f5bfc1d7c..754c0f4c50a 100644 --- a/packages/dash-platform-queries/src/documents/ranked_proof_helpers.rs +++ b/packages/dash-platform-queries/src/documents/ranked_proof_helpers.rs @@ -19,6 +19,7 @@ //! //! [`DocumentRankedEntries`]: drive_proof_verifier::DocumentRankedEntries +use crate::documents::document_query::normalize_time_range_clauses_with_metadata_time; use crate::documents::document_query::DocumentQuery; use dapi_grpc::platform::v0::{GetDocumentsResponse, Proof, ResponseMetadata}; use dapi_grpc::platform::VersionedGrpcResponse; @@ -118,11 +119,43 @@ pub(super) fn assert_ranked_shape( /// metadata before returning. There is no path through this helper /// that yields entries without that check having run. pub(super) fn verify_ranked_query( - request: DocumentQuery, + mut request: DocumentQuery, response: GetDocumentsResponse, platform_version: &PlatformVersion, provider: &dyn ContextProvider, ) -> Result<(Option, ResponseMetadata, Proof), drive_proof_verifier::Error> { + let proof = response + .proof() + .or(Err(drive_proof_verifier::Error::NoProofInResult))?; + let mtd = response + .metadata() + .or(Err(drive_proof_verifier::Error::EmptyResponseMetadata))?; + + // Resolve any pending time-range (`IN_TIME_RANGE`) selections through + // the shared normalization helper, then reject the request outright if + // anything resolved — mirroring the server, whose + // `execute_document_ranked_request` refuses non-empty provenance. The + // rejection is load-bearing, not a shape formality: ranked mode accepts + // equality pins on compound ranked indexes, and the ranked picker + // excludes transformed indexes, so a resolved bucket-start equality + // would pin a *plain* ranked index on the same timestamp field. A + // malicious node could then return a valid proof over that subtree — + // documents whose raw timestamp equals the bucket boundary, not the + // requested window — and without this guard the verifier would + // authenticate it even though an honest server rejects the request. + let resolved_time_ranges = + normalize_time_range_clauses_with_metadata_time(&mut request, mtd.time_ms)?; + if !resolved_time_ranges.is_empty() { + return Err(drive_proof_verifier::Error::RequestError { + error: "a ranked query cannot carry a time-range (IN_TIME_RANGE) selection: \ + ranking groups by an index's own property, a document belongs to every \ + bucket containing its timestamp, and the resolved bucket-start equality \ + could only pin a plain index over raw timestamps — the server rejects \ + this request and the verifier must not authenticate a proof for it" + .to_string(), + }); + } + let document_type = request .data_contract .document_type_for_name(&request.document_type_name) @@ -132,12 +165,6 @@ pub(super) fn verify_ranked_query( request.document_type_name, e ), })?; - let proof = response - .proof() - .or(Err(drive_proof_verifier::Error::NoProofInResult))?; - let mtd = response - .metadata() - .or(Err(drive_proof_verifier::Error::EmptyResponseMetadata))?; let mode = assert_ranked_shape(&request, platform_version)?; diff --git a/packages/rs-drive-abci/src/query/document_query/v1/tests.rs b/packages/rs-drive-abci/src/query/document_query/v1/tests.rs index eb3b76fcc61..fa34310ca4e 100644 --- a/packages/rs-drive-abci/src/query/document_query/v1/tests.rs +++ b/packages/rs-drive-abci/src/query/document_query/v1/tests.rs @@ -2805,8 +2805,8 @@ mod ranked_tests { } /// `limit` is **required** on the ranked path — it is the `k` the - /// proof envelope echoes, so there is no server default a verifying - /// client could reproduce. What this pins is that drive's + /// verifier rebuilds its `PathQuery` around, so there is no server + /// default a verifying client could reproduce. What this pins is that drive's /// `Error::Query` reaches the caller as a query error on the /// validation result, rather than being swallowed into an internal /// error by the dispatcher's `Err(e) => Err(e.into())` arm. diff --git a/packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/ranked_index_e2e_tests.rs b/packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/ranked_index_e2e_tests.rs index 2a772e3688a..9710bbbf70e 100644 --- a/packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/ranked_index_e2e_tests.rs +++ b/packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/ranked_index_e2e_tests.rs @@ -801,6 +801,7 @@ fn compound_ranked_index_resolves_its_terminal_level_to_an_indexed_tree() { ranked_countable: false, ranked_summable: false, ranked_averageable: true, + time_range: None, }; let index_structure = IndexLevel::try_from_indices([&compound_ranked_index], "dish", platform_version()) @@ -1016,6 +1017,7 @@ fn a_null_unsearchable_ranked_level_is_what_makes_a_phantom_group_possible() { ranked_countable: true, ranked_summable: false, ranked_averageable: false, + time_range: None, }; for null_searchable in [false, true] { @@ -1507,6 +1509,7 @@ fn ranked_avg_page( ascending: false, }], where_clauses: &[], + resolved_time_ranges: &[], limit: Some(limit), offset: Some(offset), has_start_at: false, @@ -1553,6 +1556,7 @@ fn verified_ranked_avg_page( ascending: false, }], where_clauses: &[], + resolved_time_ranges: &[], limit: Some(limit), offset: Some(offset), has_start_at: false, diff --git a/packages/rs-drive/src/query/drive_document_having_query/tests.rs b/packages/rs-drive/src/query/drive_document_having_query/tests.rs index 9e5e0eb6e70..7123569eee2 100644 --- a/packages/rs-drive/src/query/drive_document_having_query/tests.rs +++ b/packages/rs-drive/src/query/drive_document_having_query/tests.rs @@ -759,6 +759,7 @@ mod execution { having: &having, order_by: &order_by, where_clauses: &[], + resolved_time_ranges: &[], limit: case.limit, offset: None, has_start_at: false, @@ -1398,6 +1399,7 @@ mod identifier_group_keys { having: &having, order_by, where_clauses: &[], + resolved_time_ranges: &[], limit: Some(10), offset: None, has_start_at: false, @@ -1693,6 +1695,7 @@ mod pinned_prefix { having: &having, order_by, where_clauses, + resolved_time_ranges: &[], limit: Some(10), offset: None, has_start_at: false, @@ -2080,6 +2083,7 @@ mod pinned_prefix { having: &having, order_by: &[], where_clauses: &null_pin, + resolved_time_ranges: &[], limit: Some(10), offset: None, has_start_at: false, @@ -2231,6 +2235,7 @@ mod pinned_prefix { having: &having, order_by: &[], where_clauses: &mixed_pin, + resolved_time_ranges: &[], limit: Some(10), offset: None, has_start_at: false, diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs b/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs index a3b1108eab4..5b54a342116 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/mod.rs @@ -167,7 +167,8 @@ pub const RANKED_COUNT_ORDER_KEY: &str = "$count"; /// Which per-group aggregate the groups are ranked by. /// /// Maps 1:1 onto [`grovedb::element::IndexAxis`], the axis tag stored in -/// an indexed tree's TLV and echoed in the proof envelope. Kept as a +/// an indexed tree's TLV and rebuilt into the `PathQuery` a verifier +/// re-executes proofs against. Kept as a /// separate drive-side type (rather than re-exporting grovedb's) so the /// query surface's error messages and validation can talk about /// `rankedCountable` / `rankedSummable` / `rankedAverageable` — contract diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs index 79006066f10..fe0ce508fb1 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/tests.rs @@ -256,8 +256,9 @@ fn count_star_is_ordered_by_the_dollar_count_sentinel() { } /// `LIMIT 0` selects nothing and `LIMIT > MAX_RANKED_LIMIT` is refused -/// rather than clamped — a clamp would produce a proof whose echoed `k` -/// the client's own reconstruction rejects. The boundary itself is +/// rather than clamped — a clamp would truncate the server's walk below +/// the cap the client's rebuilt `PathQuery` demands coverage for, so the +/// client's own reconstruction rejects the proof. The boundary itself is /// accepted. #[test] fn k_is_bounded_to_one_through_max_ranked_limit() { @@ -280,9 +281,9 @@ fn k_is_bounded_to_one_through_max_ranked_limit() { } /// `LIMIT` is mandatory in ranked mode. There is no server-side default -/// because `k` is echoed inside the proof envelope and re-checked by the -/// verifier: a number the client never chose is a number it cannot -/// reproduce when rebuilding the query to verify. +/// because the verifier re-executes proofs against a `PathQuery` rebuilt +/// from the request: a number the client never chose is a number it +/// cannot reproduce when rebuilding the query to verify. #[test] fn limit_is_required() { let error = detect_avg(false, None, None).expect_err( @@ -1210,8 +1211,9 @@ fn count_axis_ranks_reads_and_proves_consistently() { assert_proof_round_trips(&drive, &contract, &bottom_one, &entries); // A missing LIMIT is refused end to end, not just in the pure - // detector: `k` is echoed in the proof envelope, so there is no - // server-side default a verifying client could reproduce. + // detector: `k` binds verification through the client's rebuilt + // `PathQuery`, so there is no server-side default a verifying + // client could reproduce. let mut no_limit = RankedCase::count(true, None); no_limit.limit = None; let error = run(&drive, &contract, &no_limit, false) @@ -1396,8 +1398,8 @@ fn offset_pages_through_the_ranking_and_the_proof_attests_the_starting_rank() { } /// A proof of one page must not verify as another page of the same -/// ranking. `offset` is echoed in the envelope and re-checked, which is -/// what stops a server from answering "the 5th best" with a proof of +/// ranking. `offset` shifts where the verifier's re-executed walk must +/// start, which is what stops a server from answering "the 5th best" with a proof of /// "the best" — the entries would look perfectly valid, and only the /// offset binding distinguishes them. #[test] @@ -1452,7 +1454,7 @@ fn a_proof_does_not_verify_under_a_different_offset() { /// has no entries", so a freshly registered contract queried with /// `prove = true` got an error until the first document landed. /// -/// `prove_indexed_axis_top_k_paginated` closes that gap — it emits a +/// The unified `PathQuery` prover closes that gap — it emits a /// guaranteed-empty range against the secondary rather than refusing — /// so the two paths now agree on empty state, and this test is the /// tripwire that says so. The attested `skipped` is `0`, which for an @@ -1621,8 +1623,9 @@ fn a_tampered_proof_never_verifies_to_the_honest_root_hash() { /// The verifier must be checking the ranking it was asked about: a proof /// generated for one `(axis, k, descending)` triple must not verify under -/// another. grovedb echoes all three in the envelope and re-checks them, -/// and this pins that drive passes each of them through faithfully — a +/// another. grovedb rebuilds the traversal from all three and re-executes +/// the proof against it, and this pins that drive passes each of them +/// through faithfully — a /// dropped argument here would let a client accept a proof of a /// different question. #[test] @@ -2256,11 +2259,11 @@ mod pinned_prefix { having: &[], order_by: &order_by, where_clauses, + resolved_time_ranges: &[], limit: Some(limit), offset: None, has_start_at: false, prove, - resolved_time_ranges: &[], }, None, platform_version(), @@ -3054,6 +3057,7 @@ mod pinned_prefix { having: &[], order_by: &order_by, where_clauses, + resolved_time_ranges: &[], limit: Some(limit), offset: None, has_start_at: false, @@ -3251,6 +3255,7 @@ mod pinned_prefix { having: &[], order_by: &order_by, where_clauses: &pins, + resolved_time_ranges: &[], limit: Some(4), offset: None, has_start_at: false, @@ -3281,6 +3286,7 @@ mod pinned_prefix { having: &[], order_by: &order_by, where_clauses: &y_pin, + resolved_time_ranges: &[], limit: Some(4), offset: None, has_start_at: false, @@ -3348,6 +3354,7 @@ mod pinned_prefix { having: &[], order_by: &order_by, where_clauses: &pins, + resolved_time_ranges: &[], limit: Some(2), offset: None, has_start_at: false,