Summary
On v4.2-dev at dev.8 (post-#4578), a proved ranked read pinned to a timeRange bucket that no document has ever landed in fails proof generation instead of proving an empty ranking:
query: storage: grovedb: invalid path: a single-path axis read must produce exactly one axis descent at the queried path, but the walk produced 0 — the path does not name an indexed tree carrying that axis
The same query on a bucket that holds ≥1 document serves and verifies correctly. The unproved path is not affected in the same way (it returns an empty page).
For a trending contract this is the steady state at the start of every window: the first read of "top posts today" after 00:00 UTC (before anyone has liked anything) is exactly a never-populated bucket, and it surfaces as an internal error rather than an empty leaderboard.
Reproduction
Contract (registers on dev.8):
"like": {
"type": "object", "indexOnly": true, "documentsMutable": false, "canBeDeleted": true,
"properties": { "postId": { "type": "array", "byteArray": true, "minItems": 32, "maxItems": 32, "contentMediaType": "application/x.dash.dpp.identifier", "position": 0 } },
"required": ["$createdAt", "postId"],
"indices": [
{ "name": "byDayPost", "properties": [{ "$createdAt": "asc" }, { "postId": "asc" }], "terminal": "$ownerId",
"countable": "countable", "rangeCountable": true, "rankedCountable": true,
"timeRange": { "on": "$createdAt", "range": 86400, "step": 86400 } }
]
}
Query (wasm-sdk / js-evo-sdk):
await sdk.documents.ranked({
dataContractId, documentTypeName: 'like',
groupBy: 'postId', aggregate: { type: 'count' }, direction: 'desc', limit: 5,
timeRange: [{ field: '$createdAt', selector: 'newest' }],
});
- Register the contract; write nothing. Run the query → the error above (server-side,
Internal error), on every node. Same for selector: 'oldest' and for byStart naming any bucket with no entries.
- Write one
like (so the newest bucket has one entry). Run the same query → [[<postId>, 1]], proved and verified. ✔
- The non-bucketed twin (
byPost without timeRange, same doctype) on an empty index returns an empty page and proves it — the ranking_an_empty_index_reads_empty_and_proves_empty case. The bucketed index is the odd one out.
Observed live on the moutai devnet (13 nodes on 4.2.0-dev.8) against contract DNNibJtgEEkQkLfDZXh9xkbfdVHWu6CtJcgcpicMAuHZ — both the like axes and a separate beat doctype with a 2-grid $createdAt (k=1 daily + k=4 rolling) behave identically.
Where it comes from
grovedb operations/proof/generate.rs — the guard added for single-path axis reads:
A single-path axis read has exactly one answer — the axis descent at the queried path. The generic walk cannot produce one when the target is missing or is not an indexed tree; it returns Ok with an ordinary (or empty) layer instead … Fail generation here instead.
The ranked prover (execute_top_k_with_proof_branch) builds PathQuery::new_axis_top_k(path, …) where path includes the resolved bucket start as a level segment (path.rs: "the pinned value under a bucketed level is the resolved bucket start"). Buckets are created lazily by the first write that lands in them (add_indices_for_top_index_level… fans out time_range_index_keys and batch_insert_empty_tree_if_not_exists), so a never-written bucket has no subtree at that path — the walk produces 0 descents and the guard refuses.
For a non-bucketed ranked index the terminal property-name tree exists from contract registration (that's what makes the empty-index case provable), which is why only the bucketed shape trips this.
Why it matters / expected behaviour
An absent bucket is a perfectly ordinary, provable statement ("no documents in this window") — the same way an absent pinned prefix would be. Expected: the ranked prover returns a proof of emptiness for a bucket path that does not exist (a Merk absence proof of the bucket key at the grid level, which the verifier already re-derives from the signed time), the way #4574's doc promises for byStart: "an empty (or not-yet-started) window is a provable empty answer".
Two candidate fixes, from smallest to most principled:
- In the ranked prover, when the resolved bucket path is absent, route through the branched axis form with one branch (the guard explicitly excludes branched reads: "an absent branch key legitimately produces no descent, and its absence is what the branching-level Merk proof authenticates"), so absence is proved at the bucket level.
- Or have the bucket-level pin be proved as a key absence in the generic walk and let the verifier accept a zero-descent axis read whose enclosing layer proves the key absent.
Client-side we currently map this specific error string to "empty ranking", which is fine for a devnet but is not something a verifier-trusting client should have to do.
Environment
- platform
v4.2.0-dev.8 (7298474), grovedb 97250247
- moutai devnet, 13/13 masternodes on dev.8; also reproduced against the JS SDK 4.2.0-dev.8 from node
Related: #4578 (ranked below timeRange), #4574 (byStart), the ranking_an_empty_index_reads_empty_and_proves_empty test in rs-drive/src/query/drive_document_ranked_query/tests.rs.
Summary
On
v4.2-devat dev.8 (post-#4578), a proved ranked read pinned to atimeRangebucket that no document has ever landed in fails proof generation instead of proving an empty ranking:The same query on a bucket that holds ≥1 document serves and verifies correctly. The unproved path is not affected in the same way (it returns an empty page).
For a trending contract this is the steady state at the start of every window: the first read of "top posts today" after 00:00 UTC (before anyone has liked anything) is exactly a never-populated bucket, and it surfaces as an internal error rather than an empty leaderboard.
Reproduction
Contract (registers on dev.8):
Query (wasm-sdk / js-evo-sdk):
Internal error), on every node. Same forselector: 'oldest'and forbyStartnaming any bucket with no entries.like(so the newest bucket has one entry). Run the same query →[[<postId>, 1]], proved and verified. ✔byPostwithouttimeRange, same doctype) on an empty index returns an empty page and proves it — theranking_an_empty_index_reads_empty_and_proves_emptycase. The bucketed index is the odd one out.Observed live on the moutai devnet (13 nodes on 4.2.0-dev.8) against contract
DNNibJtgEEkQkLfDZXh9xkbfdVHWu6CtJcgcpicMAuHZ— both thelikeaxes and a separatebeatdoctype with a 2-grid$createdAt(k=1 daily + k=4 rolling) behave identically.Where it comes from
grovedb
operations/proof/generate.rs— the guard added for single-path axis reads:The ranked prover (
execute_top_k_with_proof_branch) buildsPathQuery::new_axis_top_k(path, …)wherepathincludes the resolved bucket start as a level segment (path.rs: "the pinned value under a bucketed level is the resolved bucket start"). Buckets are created lazily by the first write that lands in them (add_indices_for_top_index_level…fans outtime_range_index_keysandbatch_insert_empty_tree_if_not_exists), so a never-written bucket has no subtree at that path — the walk produces 0 descents and the guard refuses.For a non-bucketed ranked index the terminal property-name tree exists from contract registration (that's what makes the empty-index case provable), which is why only the bucketed shape trips this.
Why it matters / expected behaviour
An absent bucket is a perfectly ordinary, provable statement ("no documents in this window") — the same way an absent pinned prefix would be. Expected: the ranked prover returns a proof of emptiness for a bucket path that does not exist (a Merk absence proof of the bucket key at the grid level, which the verifier already re-derives from the signed time), the way
#4574's doc promises forbyStart: "an empty (or not-yet-started) window is a provable empty answer".Two candidate fixes, from smallest to most principled:
Client-side we currently map this specific error string to "empty ranking", which is fine for a devnet but is not something a verifier-trusting client should have to do.
Environment
v4.2.0-dev.8(7298474), grovedb97250247Related: #4578 (ranked below timeRange), #4574 (
byStart), theranking_an_empty_index_reads_empty_and_proves_emptytest inrs-drive/src/query/drive_document_ranked_query/tests.rs.