diff --git a/Cargo.lock b/Cargo.lock index 6775bdd0c1..3f6a77203b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -576,7 +576,7 @@ dependencies = [ "bitflags 2.13.0", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.13.0", "proc-macro2", "quote", "regex", @@ -1229,7 +1229,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2475,7 +2475,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2536,7 +2536,7 @@ checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", "rustix 1.1.4", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2955,7 +2955,7 @@ dependencies = [ [[package]] name = "grovedb" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +source = "git+https://github.com/dashpay/grovedb?rev=63df14c27c4b9ad47881141a213af6d92ef8d10f#63df14c27c4b9ad47881141a213af6d92ef8d10f" dependencies = [ "axum 0.8.9", "bincode", @@ -2993,7 +2993,7 @@ dependencies = [ [[package]] name = "grovedb-bulk-append-tree" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +source = "git+https://github.com/dashpay/grovedb?rev=63df14c27c4b9ad47881141a213af6d92ef8d10f#63df14c27c4b9ad47881141a213af6d92ef8d10f" dependencies = [ "bincode", "blake3", @@ -3009,7 +3009,7 @@ dependencies = [ [[package]] name = "grovedb-commitment-tree" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +source = "git+https://github.com/dashpay/grovedb?rev=63df14c27c4b9ad47881141a213af6d92ef8d10f#63df14c27c4b9ad47881141a213af6d92ef8d10f" dependencies = [ "blake3", "grovedb-bulk-append-tree", @@ -3025,7 +3025,7 @@ dependencies = [ [[package]] name = "grovedb-costs" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +source = "git+https://github.com/dashpay/grovedb?rev=63df14c27c4b9ad47881141a213af6d92ef8d10f#63df14c27c4b9ad47881141a213af6d92ef8d10f" dependencies = [ "integer-encoding", "intmap", @@ -3035,7 +3035,7 @@ dependencies = [ [[package]] name = "grovedb-dense-fixed-sized-merkle-tree" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +source = "git+https://github.com/dashpay/grovedb?rev=63df14c27c4b9ad47881141a213af6d92ef8d10f#63df14c27c4b9ad47881141a213af6d92ef8d10f" dependencies = [ "bincode", "blake3", @@ -3048,7 +3048,7 @@ dependencies = [ [[package]] name = "grovedb-element" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +source = "git+https://github.com/dashpay/grovedb?rev=63df14c27c4b9ad47881141a213af6d92ef8d10f#63df14c27c4b9ad47881141a213af6d92ef8d10f" dependencies = [ "bincode", "bincode_derive", @@ -3063,7 +3063,7 @@ dependencies = [ [[package]] name = "grovedb-epoch-based-storage-flags" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +source = "git+https://github.com/dashpay/grovedb?rev=63df14c27c4b9ad47881141a213af6d92ef8d10f#63df14c27c4b9ad47881141a213af6d92ef8d10f" dependencies = [ "grovedb-costs", "hex", @@ -3075,7 +3075,7 @@ dependencies = [ [[package]] name = "grovedb-merk" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +source = "git+https://github.com/dashpay/grovedb?rev=63df14c27c4b9ad47881141a213af6d92ef8d10f#63df14c27c4b9ad47881141a213af6d92ef8d10f" dependencies = [ "bincode", "bincode_derive", @@ -3101,7 +3101,7 @@ dependencies = [ [[package]] name = "grovedb-merkle-mountain-range" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +source = "git+https://github.com/dashpay/grovedb?rev=63df14c27c4b9ad47881141a213af6d92ef8d10f#63df14c27c4b9ad47881141a213af6d92ef8d10f" dependencies = [ "bincode", "blake3", @@ -3112,7 +3112,7 @@ dependencies = [ [[package]] name = "grovedb-path" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +source = "git+https://github.com/dashpay/grovedb?rev=63df14c27c4b9ad47881141a213af6d92ef8d10f#63df14c27c4b9ad47881141a213af6d92ef8d10f" dependencies = [ "hex", ] @@ -3120,7 +3120,7 @@ dependencies = [ [[package]] name = "grovedb-query" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +source = "git+https://github.com/dashpay/grovedb?rev=63df14c27c4b9ad47881141a213af6d92ef8d10f#63df14c27c4b9ad47881141a213af6d92ef8d10f" dependencies = [ "bincode", "byteorder", @@ -3136,7 +3136,7 @@ dependencies = [ [[package]] name = "grovedb-storage" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +source = "git+https://github.com/dashpay/grovedb?rev=63df14c27c4b9ad47881141a213af6d92ef8d10f#63df14c27c4b9ad47881141a213af6d92ef8d10f" dependencies = [ "blake3", "grovedb-costs", @@ -3155,7 +3155,7 @@ dependencies = [ [[package]] name = "grovedb-version" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +source = "git+https://github.com/dashpay/grovedb?rev=63df14c27c4b9ad47881141a213af6d92ef8d10f#63df14c27c4b9ad47881141a213af6d92ef8d10f" dependencies = [ "thiserror 2.0.18", "versioned-feature-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3164,7 +3164,7 @@ dependencies = [ [[package]] name = "grovedb-visualize" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +source = "git+https://github.com/dashpay/grovedb?rev=63df14c27c4b9ad47881141a213af6d92ef8d10f#63df14c27c4b9ad47881141a213af6d92ef8d10f" dependencies = [ "hex", "itertools 0.14.0", @@ -3173,7 +3173,7 @@ dependencies = [ [[package]] name = "grovedbg-types" version = "5.0.1" -source = "git+https://github.com/dashpay/grovedb?rev=a2791bbdca756d6a6113024aec48f09f7a33faa9#a2791bbdca756d6a6113024aec48f09f7a33faa9" +source = "git+https://github.com/dashpay/grovedb?rev=63df14c27c4b9ad47881141a213af6d92ef8d10f#63df14c27c4b9ad47881141a213af6d92ef8d10f" dependencies = [ "serde", "serde_with 3.21.0", @@ -3589,7 +3589,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.4", "system-configuration", "tokio", "tower-service", @@ -3840,7 +3840,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5542,7 +5542,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" dependencies = [ "heck 0.4.1", - "itertools 0.10.5", + "itertools 0.13.0", "log", "multimap", "petgraph", @@ -5563,7 +5563,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.13.0", "proc-macro2", "quote", "syn 2.0.117", @@ -5576,7 +5576,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.13.0", "proc-macro2", "quote", "syn 2.0.117", @@ -5712,7 +5712,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.2", "rustls", - "socket2 0.5.10", + "socket2 0.6.4", "thiserror 2.0.18", "tokio", "tracing", @@ -5750,9 +5750,9 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.4", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -6560,7 +6560,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -6573,7 +6573,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -6632,7 +6632,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -7492,7 +7492,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -8941,7 +8941,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/book/src/drive/ranked-index-examples.md b/book/src/drive/ranked-index-examples.md index 2079874180..c702e51fa2 100644 --- a/book/src/drive/ranked-index-examples.md +++ b/book/src/drive/ranked-index-examples.md @@ -321,11 +321,11 @@ The response carries the skip back in `RankedEntries.skipped` (see [The Response Three properties worth stating plainly: -- **The skip is attested, not walked.** grovedb proves the skipped region from the counted subtree commitments (`HashWithCount` / `HashWithCountAndSum`) rather than by traversing it. Both the prover's work and the proof's size stay `O(log n + k)` **at any offset**. -- **There is therefore no offset ceiling.** An offset of 4 and an offset of four billion cost the same, so there is no denial-of-service lever a cap would close — and a cap would only stop honest deep pagination. -- **An offset past the end is a positive answer.** `entries` comes back empty and `skipped` is the ranking's *entire attested population*. "There are only 12 groups" is more information than a bare empty list. +- **The skip is counted, not walked.** grovedb descends the secondary reading each subtree's aggregate count and collapses any subtree that fits entirely inside the remaining offset, instead of stepping through it. Both paths do this: the prover attests the skipped region from the counted subtree commitments (`HashWithCount` / `HashWithCountAndSum`), and the unproven read performs the same counted descent without building a proof. Work and proof size stay `O(log n + k)` **at any offset**. +- **There is therefore no offset ceiling.** An offset of 4 and an offset of four billion cost the same order of work — on either path, the deeper one in fact cheaper, since a tree that fits entirely inside the offset collapses at the root. There is no denial-of-service lever a cap would close, and a cap would only stop honest deep pagination. +- **An offset past the end is a positive answer.** `entries` comes back empty and `skipped` is the ranking's *entire reported population*. "There are only 12 groups" is more information than a bare empty list. -On the **unproven** read there is nothing to attest and grovedb's read API does not report a short walk, so `skipped` simply echoes the requested offset. The proved and unproven paths therefore disagree in exactly one case — an offset past the end, where the unproven read reports the request and the proved one reports the truth. **Callers who need the population must prove.** +Both paths report the same `skipped`: the offset you asked for when the skip succeeded, and the ranking's total population when the walk ran out of groups first. What differs is the warrant, not the value. On the proved path it is cryptographically attested, re-derived by the verifier from the counted commitments. On the unproven path it is an **unverified claim, exactly like the entries beside it** — equal to the attested value on an honest node, with nothing forcing a node to be honest. **Callers who need to trust the population, rather than merely receive it, must still prove.** ## The Response 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 c4e66dadcb..c37388a269 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 @@ -3155,13 +3155,17 @@ GPB_FINAL @interface GetDocumentsRequest_GetDocumentsRequestV1 : GPBMessage * routes to the ranked executor (`group_by` + a single `order_by` * naming the selected aggregate), `offset` skips that many ranks * before the returned page, so `ORDER BY avg(grade) DESC LIMIT 1 - * OFFSET 4` is the 5th-best group. The skip is **count-attested**, - * not walked: grovedb proves it from the counted subtree - * commitments, so the proof stays `O(log n + k)` at any offset and - * the response echoes the attested number in - * `RankedEntries.skipped`. There is deliberately no ceiling — an - * offset of 4 and an offset of four billion cost the same, so - * there is no denial-of-service lever a cap would close. An offset + * OFFSET 4` is the 5th-best group. The skip is **counted, not + * walked**: grovedb descends on each subtree's aggregate count and + * collapses whole subtrees that fit inside the remaining offset, so + * the work stays `O(log n + k)` at any offset and the response + * reports the skip it performed in `RankedEntries.skipped`. On a + * proved request that count is additionally *attested* — committed + * to by the proof and re-derived by the verifier; on an unproved + * one it is the node's own report. See `RankedEntries.skipped`. There is deliberately no ceiling — an + * offset of 4 and an offset of four billion cost the same *order* + * of work — neither walks the region it skips — so there is no + * denial-of-service lever a cap would close. An offset * past the end of the ranking is a provable answer rather than an * error: `entries` comes back empty and `skipped` is the ranking's * whole population. @@ -3842,20 +3846,31 @@ GPB_FINAL @interface GetDocumentsResponse_GetDocumentsResponseV1_RankedEntries : * group rather than the best. * * **When a requested offset exceeds the population**, `entries` - * is empty and `skipped` is the ranking's attested *total* + * is empty and `skipped` is the ranking's *total* reported * population — a positive, useful answer ("there are only 12 * groups") rather than a bare empty list. * - * On the proved path the number is grovedb's cryptographically - * attested count, re-derived by the verifier from the counted - * subtree commitments in the proof bytes rather than trusted - * from this field; a proving client should use the verified - * value. On the unproven read there is nothing to attest and - * grovedb's read API does not report a short walk, so the server - * echoes the requested offset. The two therefore disagree in - * exactly one case — an offset past the end, where the unproven - * read reports the request and the proved one reports the truth. - * Callers who need the population must prove. + * Both paths report the same quantity: the offset you asked for + * when the skip succeeded, and the ranking's total population + * when the walk ran out of groups first. They no longer disagree + * anywhere, including past the end. + * + * What differs is the *warrant*, not the value. On the proved + * path the number is cryptographically attested — re-derived by + * the verifier from the counted subtree commitments in the proof + * bytes rather than trusted from this field — so a proving client + * should use the verified value and ignore this one. On the + * unproven path it is an **unverified claim**, exactly like the + * entries beside it: it equals the attested value on an honest + * node, and nothing forces a node to be honest. Read "the true + * population" as "what this node says the population is". + * Callers who need to trust it, rather than merely receive it, + * must still prove. + * + * Do not assume this field equals the offset you requested. It + * equals the offset only when the skip succeeded; when the walk + * ran out of groups first it is smaller, and that is the answer + * rather than an inconsistency. **/ @property(nonatomic, readwrite) uint64_t skipped; diff --git a/packages/dapi-grpc/protos/platform/v0/platform.proto b/packages/dapi-grpc/protos/platform/v0/platform.proto index 937962817c..0ad3a197fd 100644 --- a/packages/dapi-grpc/protos/platform/v0/platform.proto +++ b/packages/dapi-grpc/protos/platform/v0/platform.proto @@ -1125,13 +1125,17 @@ message GetDocumentsRequest { // routes to the ranked executor (`group_by` + a single `order_by` // naming the selected aggregate), `offset` skips that many ranks // before the returned page, so `ORDER BY avg(grade) DESC LIMIT 1 - // OFFSET 4` is the 5th-best group. The skip is **count-attested**, - // not walked: grovedb proves it from the counted subtree - // commitments, so the proof stays `O(log n + k)` at any offset and - // the response echoes the attested number in - // `RankedEntries.skipped`. There is deliberately no ceiling — an - // offset of 4 and an offset of four billion cost the same, so - // there is no denial-of-service lever a cap would close. An offset + // OFFSET 4` is the 5th-best group. The skip is **counted, not + // walked**: grovedb descends on each subtree's aggregate count and + // collapses whole subtrees that fit inside the remaining offset, so + // the work stays `O(log n + k)` at any offset and the response + // reports the skip it performed in `RankedEntries.skipped`. On a + // proved request that count is additionally *attested* — committed + // to by the proof and re-derived by the verifier; on an unproved + // one it is the node's own report. See `RankedEntries.skipped`. There is deliberately no ceiling — an + // offset of 4 and an offset of four billion cost the same *order* + // of work — neither walks the region it skips — so there is no + // denial-of-service lever a cap would close. An offset // past the end of the ranking is a provable answer rather than an // error: `entries` comes back empty and `skipped` is the ranking's // whole population. @@ -1417,20 +1421,31 @@ message GetDocumentsResponse { // group rather than the best. // // **When a requested offset exceeds the population**, `entries` - // is empty and `skipped` is the ranking's attested *total* + // is empty and `skipped` is the ranking's *total* reported // population — a positive, useful answer ("there are only 12 // groups") rather than a bare empty list. // - // On the proved path the number is grovedb's cryptographically - // attested count, re-derived by the verifier from the counted - // subtree commitments in the proof bytes rather than trusted - // from this field; a proving client should use the verified - // value. On the unproven read there is nothing to attest and - // grovedb's read API does not report a short walk, so the server - // echoes the requested offset. The two therefore disagree in - // exactly one case — an offset past the end, where the unproven - // read reports the request and the proved one reports the truth. - // Callers who need the population must prove. + // Both paths report the same quantity: the offset you asked for + // when the skip succeeded, and the ranking's total population + // when the walk ran out of groups first. They no longer disagree + // anywhere, including past the end. + // + // What differs is the *warrant*, not the value. On the proved + // path the number is cryptographically attested — re-derived by + // the verifier from the counted subtree commitments in the proof + // bytes rather than trusted from this field — so a proving client + // should use the verified value and ignore this one. On the + // unproven path it is an **unverified claim**, exactly like the + // entries beside it: it equals the attested value on an honest + // node, and nothing forces a node to be honest. Read "the true + // population" as "what this node says the population is". + // Callers who need to trust it, rather than merely receive it, + // must still prove. + // + // Do not assume this field equals the offset you requested. It + // equals the offset only when the skip succeeded; when the walk + // ran out of groups first it is smaller, and that is the answer + // rather than an inconsistency. optional uint64 skipped = 2 [jstype = JS_STRING]; } diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index 7a1c3bf85f..1e0748c555 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 = "a2791bbdca756d6a6113024aec48f09f7a33faa9", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "63df14c27c4b9ad47881141a213af6d92ef8d10f", 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 23069f0158..2b3381d596 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 = "a2791bbdca756d6a6113024aec48f09f7a33faa9" } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "63df14c27c4b9ad47881141a213af6d92ef8d10f" } 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 = "a2791bbdca756d6a6113024aec48f09f7a33faa9", features = ["client"] } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "63df14c27c4b9ad47881141a213af6d92ef8d10f", 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 = "a2791bbdca756d6a6113024aec48f09f7a33faa9" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "a2791bbdca756d6a6113024aec48f09f7a33faa9" } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "63df14c27c4b9ad47881141a213af6d92ef8d10f" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "63df14c27c4b9ad47881141a213af6d92ef8d10f" } [features] default = ["bls-signatures"] diff --git a/packages/rs-drive-abci/src/query/document_query/v1/routing.rs b/packages/rs-drive-abci/src/query/document_query/v1/routing.rs index 3677b6bf0c..bc354204f9 100644 --- a/packages/rs-drive-abci/src/query/document_query/v1/routing.rs +++ b/packages/rs-drive-abci/src/query/document_query/v1/routing.rs @@ -280,8 +280,10 @@ pub(super) fn validate_and_route( /// /// Offset pagination exists on exactly one path: the ranked executor, /// where `OFFSET m` is the rank the returned page starts at and costs -/// nothing to prove (grovedb attests the skipped region from counted -/// subtree commitments rather than walking it). Every other v1 shape — +/// nothing to skip on either `prove` setting: grovedb counts the +/// skipped region from the subtree aggregates rather than walking it, +/// and both executors take that descent. Only the proved result +/// additionally *attests* the count. Every other v1 shape — /// documents, and the grouped count / sum / average modes — has no /// offset primitive behind it and keeps the rejection it has always /// had, **message for message**: those callers paginate with 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 475419587a..50fadc2e29 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 @@ -2504,9 +2504,11 @@ mod ranked_tests { assert_eq!(tail.skipped, Some(3)); // A window entirely past the end is an empty page, not an - // error. On this *unproven* path grovedb's read API doesn't - // report the short walk, so `skipped` echoes the request; the - // proved path is where it becomes the attested population. + // error, and `skipped` collapses to the population the walk + // actually reached. That reaches the wire on this *unproven* + // path too: grovedb's counted descent tracks how far the skip + // got and returns it on the page, so the response carries a + // population rather than the offset that was requested. let past_end = ranked_page(&platform, &state, paged(2, 9), version); assert!( past_end.entries.is_empty(), @@ -2514,7 +2516,12 @@ mod ranked_tests { error — got {:?}", group_keys(&past_end.entries) ); - assert_eq!(past_end.skipped, Some(9)); + assert_eq!( + past_end.skipped, + Some(5), + "the response reports the five groups the ranking holds, not the offset that \ + was asked for" + ); // And the same page proves. let result = platform 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 63bd8d1e01..24f06524e7 100644 --- a/packages/rs-drive-proof-verifier/src/proof/document_ranked.rs +++ b/packages/rs-drive-proof-verifier/src/proof/document_ranked.rs @@ -7,9 +7,10 @@ //! the per-axis *secondary* Merk of an indexed tree (grovedb PR #657), //! so it costs `O(log n + k)` and comes with a proof that commits to //! exactly the `k` returned `(aggregate, group key)` pairs — plus the -//! `OFFSET`, which grovedb attests from counted subtree commitments -//! rather than by walking the skipped region, so deep pages cost the -//! same as the first one. +//! `OFFSET`, which grovedb counts from the subtree aggregates rather +//! than by walking the skipped region — and additionally attests, on +//! this proved path — so a deep page costs `O(log n + k)` like any +//! other rather than growing with the offset. //! //! This module holds the client-facing result type //! ([`DocumentRankedEntries`]), the tenderdash-composition wrapper diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index f04f841039..47b47a47ea 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -52,12 +52,12 @@ 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 = "a2791bbdca756d6a6113024aec48f09f7a33faa9", optional = true, default-features = false } -grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "a2791bbdca756d6a6113024aec48f09f7a33faa9", optional = true } -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "a2791bbdca756d6a6113024aec48f09f7a33faa9" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "a2791bbdca756d6a6113024aec48f09f7a33faa9", optional = true } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "a2791bbdca756d6a6113024aec48f09f7a33faa9" } -grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "a2791bbdca756d6a6113024aec48f09f7a33faa9" } +grovedb = { git = "https://github.com/dashpay/grovedb", rev = "63df14c27c4b9ad47881141a213af6d92ef8d10f", optional = true, default-features = false } +grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "63df14c27c4b9ad47881141a213af6d92ef8d10f", optional = true } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "63df14c27c4b9ad47881141a213af6d92ef8d10f" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "63df14c27c4b9ad47881141a213af6d92ef8d10f", optional = true } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "63df14c27c4b9ad47881141a213af6d92ef8d10f" } +grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "63df14c27c4b9ad47881141a213af6d92ef8d10f" } [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 c4e30fd992..a8ae0a604f 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 @@ -1557,10 +1557,10 @@ fn an_offset_window_spanning_the_end_returns_the_short_tail() { /// The page comes back empty and `skipped` collapses below the requested /// offset — and *that shape* is the proof that the ranking holds exactly /// `skipped` groups in total, because the counted commitments cover the whole -/// walk. It is the only way this surface reports a population, and the one -/// place the proved and unproven paths differ: the unproven read cannot see -/// the short walk (grovedb's read API returns an empty vector either way) and -/// reports the requested offset. +/// walk. It is the only way this surface reports a population, and both paths +/// report it: grovedb's counted descent tracks how far the skip got and returns +/// it on the page, so an unproven read reports the population rather than the +/// offset it was asked for. What proving adds is that the number is attested. #[test] fn an_offset_past_the_end_returns_an_empty_page_whose_skip_attests_the_population() { let (drive, contract) = setup_restaurants(); @@ -1572,8 +1572,9 @@ fn an_offset_past_the_end_returns_an_empty_page_whose_skip_attests_the_populatio "there is no rank 12 in a five-group ranking" ); assert_eq!( - page.skipped, 12, - "the unproven read echoes the requested offset — it has nothing to attest with" + page.skipped, 5, + "the unproven read reports the five groups the ranking holds, not the requested \ + offset of 12" ); let verified = verified_ranked_avg_page(&drive, &contract, 3, 12); 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 fb31a6635f..4530b9cfd5 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 @@ -15,7 +15,7 @@ use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; use dpp::version::PlatformVersion; -use grovedb::TransactionArg; +use grovedb::{IndexedTopKPage, TransactionArg}; use grovedb_costs::CostContext; impl DriveDocumentRankedQuery<'_> { @@ -38,12 +38,33 @@ impl DriveDocumentRankedQuery<'_> { /// no-proof and prove paths read the same code path in grovedb and /// cannot drift on the walk's semantics for offset-free queries. /// - /// [`RankedPage::skipped`] on this path is the *requested* offset: - /// grovedb's read API returns an empty vector when the walk runs out - /// during the skip and does not report how far it got, so an - /// unproven read cannot distinguish "skipped exactly `offset`" from - /// "the secondary holds fewer than `offset` groups". Only the proved - /// path attests the true value — see [`RankedPage::skipped`]. + /// # The offset is counted, not walked + /// + /// grovedb descends the secondary reading each subtree's aggregate + /// count off its link, and collapses any subtree that fits entirely + /// inside the remaining offset instead of stepping through it. The + /// skip therefore costs `O(log n)` at any offset rather than one + /// iterator step and one decode per skipped entry, and an offset at + /// or past the population is answered from the root's own count with + /// no descent at all — the cheapest request on this surface rather + /// than the most expensive. `offset = 0` keeps the plain iterator + /// path and never touches the tree, so the common unpaginated + /// request costs exactly what it always did. + /// + /// That is what makes an uncapped `OFFSET` safe rather than merely + /// tolerated. Ranked queries carry no fee, cannot be cancelled once + /// dispatched, and share their rate budget with state transitions + /// rather than having one of their own, so a skip whose cost grew + /// with the offset would be an unmetered lever for any + /// unauthenticated caller. It does not grow. + /// + /// [`RankedPage::skipped`] comes back from grovedb rather than being + /// echoed from the request: it is the requested offset when the skip + /// succeeded, and the secondary's whole population when the walk ran + /// out of groups first. That is the same quantity the proved path + /// attests, so the two no longer disagree — though on this path it is + /// the node's unverified claim rather than an attested value, exactly + /// like the entries beside it. See [`RankedPage::skipped`]. pub fn execute_top_k_no_proof( &self, drive: &Drive, @@ -55,13 +76,15 @@ impl DriveDocumentRankedQuery<'_> { let path_refs: Vec<&[u8]> = path.iter().map(|segment| segment.as_slice()).collect(); let offset = self.offset as u64; - // Costs are destructured away rather than `.unwrap()`-ed: + // 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. The - // dispatcher wraps these executors with its own fee accounting, - // exactly as the count surface's `execute_range_count_no_proof` - // does. - let entries = match self.axis { + // 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( path_refs.as_slice(), @@ -71,14 +94,18 @@ impl DriveDocumentRankedQuery<'_> { transaction, grove_version, ); - value - .map_err(|e| Error::GroveDB(Box::new(e)))? - .into_iter() - .map(|(count, key)| RankedEntry { - key, - value: RankedEntryValue::Count(count), - }) - .collect::>() + let IndexedTopKPage { entries, skipped } = + value.map_err(|e| Error::GroveDB(Box::new(e)))?; + ( + entries + .into_iter() + .map(|(count, key)| RankedEntry { + key, + value: RankedEntryValue::Count(count), + }) + .collect::>(), + skipped, + ) } RankedAxis::Sum => { let CostContext { value, cost: _ } = drive.grove.indexed_sum_top_k_paginated( @@ -89,14 +116,18 @@ impl DriveDocumentRankedQuery<'_> { transaction, grove_version, ); - value - .map_err(|e| Error::GroveDB(Box::new(e)))? - .into_iter() - .map(|(sum, key)| RankedEntry { - key, - value: RankedEntryValue::Sum(sum), - }) - .collect::>() + let IndexedTopKPage { entries, skipped } = + value.map_err(|e| Error::GroveDB(Box::new(e)))?; + ( + entries + .into_iter() + .map(|(sum, key)| RankedEntry { + key, + value: RankedEntryValue::Sum(sum), + }) + .collect::>(), + skipped, + ) } RankedAxis::Avg => { let CostContext { value, cost: _ } = drive.grove.indexed_avg_top_k_paginated( @@ -107,14 +138,18 @@ impl DriveDocumentRankedQuery<'_> { transaction, grove_version, ); - value - .map_err(|e| Error::GroveDB(Box::new(e)))? - .into_iter() - .map(|(avg, key)| RankedEntry { - key, - value: RankedEntryValue::AvgFixedPoint(avg), - }) - .collect::>() + let IndexedTopKPage { entries, skipped } = + value.map_err(|e| Error::GroveDB(Box::new(e)))?; + ( + entries + .into_iter() + .map(|(avg, key)| RankedEntry { + key, + value: RankedEntryValue::AvgFixedPoint(avg), + }) + .collect::>(), + skipped, + ) } }; @@ -131,10 +166,7 @@ impl DriveDocumentRankedQuery<'_> { self.k )))); } - Ok(RankedPage { - skipped: offset, - entries, - }) + Ok(RankedPage { skipped, entries }) } /// Generate the grovedb indexed-axis paginated top-k proof for this diff --git a/packages/rs-drive/src/query/drive_document_ranked_query/executors/top_k_no_proof.rs b/packages/rs-drive/src/query/drive_document_ranked_query/executors/top_k_no_proof.rs index 281fdfe2c2..4345618e5f 100644 --- a/packages/rs-drive/src/query/drive_document_ranked_query/executors/top_k_no_proof.rs +++ b/packages/rs-drive/src/query/drive_document_ranked_query/executors/top_k_no_proof.rs @@ -1,6 +1,11 @@ -//! Ranked executor for `prove = false` — reads one page of `k` groups -//! straight out of the axis secondary, starting at rank `offset`, and -//! returns them in ranking order. +//! Ranked executor for `prove = false` — one page of `k` groups starting +//! at rank `offset`, in ranking order. +//! +//! The page is read from the axis secondary directly, with no proof +//! built. The `offset` is skipped by a counted descent rather than by +//! stepping through the skipped entries, so a deep offset costs +//! `O(log n)` — see +//! [`crate::query::DriveDocumentRankedQuery::execute_top_k_no_proof`]. use super::super::{DocumentRankedMode, RankedPage}; use super::ranked_query_for_mode; 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 fed2b5dffb..a78aa8b964 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 @@ -55,11 +55,12 @@ //! 2. **`limit` is mandatory, `offset` is free, `start_at` is refused.** //! `limit` is the `k` of the walk and the ranked surface has no //! server default for it, so it must be supplied. `offset` is the -//! rank the page starts at and is unbounded above: grovedb's -//! paginated prover is `O(log n + k)` *regardless of offset* (the -//! skipped region is attested by counted subtree commitments, never -//! walked entry by entry), so a large offset is not a cost lever and -//! needs no ceiling. `start_at` / `start_after` name a document id, +//! rank the page starts at and is unbounded above: grovedb counts +//! the skipped region from the subtree aggregates rather than +//! walking it entry by entry, so both executors are `O(log n + k)` +//! *regardless of offset* and a large offset is not a cost lever on +//! either. Only the proved result additionally attests the count. +//! So the offset needs no ceiling. `start_at` / `start_after` name a document id, //! which does not appear anywhere in an aggregate-ordered keyspace. //! 3. **Entry order IS the ranking order.** The executor returns entries //! in the order grovedb walked the secondary; callers must not @@ -312,14 +313,17 @@ pub struct DriveDocumentRankedQuery<'a> { /// How many ranks to skip before the returned page — the request's /// `OFFSET`. `0` for an unpaginated ranking. /// - /// Unbounded above (any `u32`), on purpose. grovedb attests the - /// skipped region through the counted subtree commitments - /// (`HashWithCount` / `HashWithCountAndSum`) rather than by walking - /// it, so both the prover's work and the proof's size stay - /// `O(log n + k)` **at any offset** — an offset of 4 and an offset - /// of four billion cost the same. There is therefore no - /// denial-of-service lever to cap, and capping would only stop - /// honest deep pagination. + /// Unbounded above (any `u32`), on purpose. grovedb skips by + /// counting rather than walking — descending the secondary on each + /// subtree's aggregate count (`HashWithCount` / + /// `HashWithCountAndSum`) and collapsing any subtree that fits + /// inside the remaining offset — so work and proof size stay + /// `O(log n + k)` **at any offset**, and an offset of 4 and an + /// offset of four billion cost the same order of work, the deeper + /// one in fact slightly less. Both executors go through that + /// descent, the unproved one without building a proof, so there is + /// no denial-of-service lever to cap on either path and capping + /// would only stop honest deep pagination. /// /// An offset past the end of the secondary is a provable answer, not /// an error: the page comes back empty and @@ -339,19 +343,28 @@ pub struct DriveDocumentRankedQuery<'a> { pub struct RankedPage { /// Number of secondary entries skipped before this page. /// - /// On the **proved** path this is grovedb's cryptographically - /// attested count, independently re-derived by the verifier from the - /// counted subtree commitments in the proof bytes: it equals the - /// requested offset unless the walk ran out of entries first, in - /// which case `entries` is empty and `skipped` is a proof that the - /// secondary holds exactly `skipped` groups in total. + /// Both paths report the same quantity, and it is never an echo of + /// the request: grovedb's counted descent tracks how far the skip + /// actually got, so this equals the requested offset when the skip + /// succeeded and the secondary's whole population when the walk ran + /// out of groups first (in which case `entries` is empty). /// - /// On the **unproven** read there is nothing to attest and grovedb's - /// read API does not report the short walk, so this is simply the - /// requested offset. The two paths therefore disagree in exactly one - /// case — an offset past the end — where the unproven read reports - /// the requested offset and the proved one reports the true - /// population. Callers that need the population must prove. + /// What differs between the paths is the warrant. On the **proved** + /// path the value is cryptographically attested — independently + /// re-derived by the verifier from the counted subtree commitments + /// in the proof bytes — so a verifying client uses its own + /// reconstruction rather than trusting the server's. On the + /// **unproven** read it is the node's unverified claim, exactly like + /// the entries beside it: equal to the attested value on an honest + /// node, with nothing forcing a node to be honest. + /// + /// One nuance worth knowing on the unproven path: the population is + /// read from the secondary's root aggregate, while grovedb's + /// per-node payload check only fires on nodes the descent visits. In + /// a *corrupt* secondary whose count violation lies outside the + /// visited region, this value can therefore disagree with the true + /// row count where the proved path's would not. On any valid + /// secondary the two are identical by construction. pub skipped: u64, /// The groups on this page, **in ranking order**. Never longer than /// the query's `k`. 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 8a96f033b4..37d87274e4 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 @@ -300,14 +300,20 @@ pub fn detect_ranked_mode_v0( // ---- OFFSET: optional, unbounded -------------------------------- // // No ceiling, and that is a deliberate statement about cost rather - // than an oversight: grovedb's paginated prover attests the skipped - // region from the counted subtree commitments instead of walking - // it, so proving `OFFSET 4` and `OFFSET 4_000_000_000` are the same - // O(log n + k) work and the same proof size. There is no - // denial-of-service lever here to cap, and an arbitrary cap would - // only break honest deep pagination. An offset past the end is a - // provable answer (empty page, `skipped` attesting the population), - // not an error. + // than an oversight. grovedb skips by *counting*, not by walking: + // it descends the secondary reading each subtree's aggregate count + // and collapses any subtree that fits inside the remaining offset, + // so `OFFSET 4` and `OFFSET 4_000_000_000` are the same order of + // O(log n + k) work — the deeper one in fact cheaper, since a tree + // that fits entirely inside the offset collapses at the root. + // + // Both executors get that: the prover attests the skipped region + // from the counted subtree commitments, and the unproved read + // performs the same counted descent without building a proof. So + // there is no denial-of-service lever here for a cap to close on + // either path, and an arbitrary cap would only break honest deep + // pagination. An offset past the end is a real answer (empty page, + // `skipped` reporting the population), not an error. let offset = pagination.offset.unwrap_or(0); // ---- START AT: must be absent ----------------------------------- 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 4a477c9819..0c79345d81 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 @@ -1298,10 +1298,10 @@ fn top_k_larger_than_the_group_count_returns_every_group() { /// page is. /// 3. **A window entirely past the end** — the page is empty *and* /// `skipped` collapses to the secondary's true population, which is -/// the proof's way of saying "there is nothing here, and here is how -/// much there is in total". That is the one case where the proved -/// and unproven paths differ: the unproven read cannot see the short -/// walk and reports the requested offset. +/// the counted walk's way of saying "there is nothing here, and here +/// is how much there is in total". Both paths report it: the counted +/// descent tracks how far the skip got, so an unproven read reports a +/// population rather than the offset it was asked for. #[test] fn offset_pages_through_the_ranking_and_the_proof_attests_the_starting_rank() { let (drive, contract) = setup_restaurants(); @@ -1360,8 +1360,10 @@ fn offset_pages_through_the_ranking_and_the_proof_attests_the_starting_rank() { "there is no rank 9 in a five-group ranking" ); assert_eq!( - page.skipped, 9, - "the unproven read cannot see the short walk, so it echoes the requested offset" + page.skipped, 5, + "the unproven read reports the population it actually reached, not the requested \ + offset of 9: grovedb's counted descent knows how far the walk got and returns it \ + on the page" ); let verified = assert_proof_round_trips(&drive, &contract, &past_end, &page.entries); assert_eq!( diff --git a/packages/rs-platform-version/Cargo.toml b/packages/rs-platform-version/Cargo.toml index 27742bc04d..3dcc7609a4 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 = "a2791bbdca756d6a6113024aec48f09f7a33faa9" } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "63df14c27c4b9ad47881141a213af6d92ef8d10f" } [features] mock-versions = [] diff --git a/packages/rs-platform-wallet/Cargo.toml b/packages/rs-platform-wallet/Cargo.toml index 7d404f6985..3dd76decb5 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 = "a2791bbdca756d6a6113024aec48f09f7a33faa9", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "63df14c27c4b9ad47881141a213af6d92ef8d10f", 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 8f8ba61d9d..d0957e8733 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 = "a2791bbdca756d6a6113024aec48f09f7a33faa9", features = [ +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "63df14c27c4b9ad47881141a213af6d92ef8d10f", features = [ "client", "sqlite", ], optional = true } diff --git a/packages/rs-sdk/src/platform/documents/document_query.rs b/packages/rs-sdk/src/platform/documents/document_query.rs index 679dcd9f2c..7a0ae5ec57 100644 --- a/packages/rs-sdk/src/platform/documents/document_query.rs +++ b/packages/rs-sdk/src/platform/documents/document_query.rs @@ -363,9 +363,15 @@ impl DocumentQuery { /// Only the ranked surface honours it (see /// [`Self::order_by_selected_aggregate`]); on every other path the /// server rejects a set offset with `Unsupported`. There is no - /// ceiling: grovedb attests the skipped region from counted - /// subtree commitments instead of walking it, so a deep offset - /// costs exactly what a shallow one does. + /// ceiling: grovedb counts the skipped region from the subtree + /// aggregates instead of walking it, on both `prove` settings, so + /// the cost of a deep offset does not scale with the offset. It is + /// not identical to a shallow one — `offset = 0` keeps a sequential + /// fast path, a positive offset descends the tree in `O(log n)`, + /// and an offset at or past the population is answered from the + /// root without descending at all — but nothing here grows with how + /// far you page, which is why there is no ceiling. Only a proved + /// response additionally attests the count. /// /// An offset past the end of the ranking is a legitimate answer /// rather than an error — the page comes back empty, and on a diff --git a/packages/rs-sdk/src/platform/documents/document_ranked_entries.rs b/packages/rs-sdk/src/platform/documents/document_ranked_entries.rs index f5a80ee3a5..f3791cac72 100644 --- a/packages/rs-sdk/src/platform/documents/document_ranked_entries.rs +++ b/packages/rs-sdk/src/platform/documents/document_ranked_entries.rs @@ -346,9 +346,11 @@ mod tests { } /// An offset far past any plausible population is **not** capped: - /// grovedb attests the skipped region from counted commitments - /// rather than walking it, so a deep page costs what a shallow one - /// does and there is nothing for a cap to protect. + /// grovedb counts the skipped region from the subtree aggregates + /// rather than walking it, on both `prove` settings, so the cost of + /// a deep page does not grow with the offset — `O(log n)` in the + /// size of the ranking, not in how far you page — and there is + /// nothing for a cap to protect. #[test] fn a_very_deep_offset_is_not_capped() { let query = top_five_by_avg_grade().with_limit(1).with_offset(u32::MAX);