Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions packages/dash-platform-queries/src/documents/document_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use dpp::{
prelude::{DataContract, Identifier},
InvalidVectorSizeError, ProtocolError,
};
use drive::config::DEFAULT_QUERY_LIMIT;
use drive::query::drive_document_ranked_query::mode_detection::ranked_order_key;
use drive::query::{
DriveDocumentQuery, HavingAggregate, HavingAggregateFunction, HavingClause, HavingOperator,
Expand Down Expand Up @@ -788,10 +789,28 @@ impl<'a> TryFrom<&'a DocumentQuery> for DriveDocumentQuery<'a> {
)
.map_err(Error::Drive)?;

let limit = if request.limit != 0 {
Some(request.limit as u16)
} else {
None
// Mirror the limit contract of the server's
// `DriveDocumentQuery::from_typed_clauses` exactly: `0` (this
// struct's "unset" sentinel — V0's `limit: 0`, V1's
// `limit: None`) falls back to the server default, and anything
// above `DEFAULT_QUERY_LIMIT` (the `config.default_query_limit`
// every deployed server runs with) is refused with the server's
// own `QuerySyntaxError::InvalidLimit` rather than truncated or
// passed through. A `u16::try_from` alone would not do: limits
// 101..=65535 fit a `u16` but the server refuses them, so a raw
// `DriveDocumentQuery` carrying one would verify a proof no
// honest server could have produced.
let limit = match request.limit {
0 => Some(DEFAULT_QUERY_LIMIT),
limit if limit > u32::from(DEFAULT_QUERY_LIMIT) => {
return Err(Error::Drive(drive::error::Error::Query(
drive::error::query::QuerySyntaxError::InvalidLimit(format!(
"limit {} greater than max limit {}",
limit, DEFAULT_QUERY_LIMIT
)),
)));
}
limit => Some(limit as u16),
};

let (start_at, start_at_included) = match request.start.as_ref() {
Expand Down
66 changes: 66 additions & 0 deletions packages/dash-platform-queries/tests/document_limit_lowering.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//! The `DocumentQuery` → `DriveDocumentQuery` lowering must reproduce
//! the server's limit contract exactly: `SizedQuery::limit` is
//! proof-sensitive, so any divergence lets an untrusted transport pair
//! a request the server would refuse (or answer differently) with a
//! genuine proof produced for another query.

use std::sync::Arc;

use dash_platform_queries::documents::document_query::DocumentQuery;
use dash_platform_queries::Error;
use dpp::prelude::DataContract;
use dpp::tests::fixtures::get_data_contract_fixture;
use dpp::version::PlatformVersion;
use drive::query::DriveDocumentQuery;

fn test_contract() -> Arc<DataContract> {
let platform_version = PlatformVersion::latest();
Arc::new(
get_data_contract_fixture(None, 0, platform_version.protocol_version).data_contract_owned(),
)
}

/// The lowering mirrors `DriveDocumentQuery::from_typed_clauses`'
/// limit contract exactly: `0` = unset → the concrete server default
/// (`Some(DEFAULT_QUERY_LIMIT)`, never `None` — `None` is unbounded in
/// `SizedQuery`, which no honest server produces),
/// `1..=DEFAULT_QUERY_LIMIT` passes through, and anything above the
/// cap — including 101..=65535, which fits a `u16` but is
/// server-invalid, and 65537, which the old `as u16` cast silently
/// truncated to 1 — is refused with the server's `InvalidLimit`.
#[test]
fn limit_cap_mirrors_the_server() {
let contract = test_contract();
let query = |limit: u32| {
DocumentQuery::new(Arc::clone(&contract), "niceDocument")
.expect("document type exists")
.with_limit(limit)
};

let unset_query = query(0);
let unset = DriveDocumentQuery::try_from(&unset_query).expect("limit 0 is the unset sentinel");
assert_eq!(
unset.limit,
Some(100),
"0 must lower to the concrete server default, not unbounded"
);

let at_cap_query = query(100);
let at_cap =
DriveDocumentQuery::try_from(&at_cap_query).expect("the server serves limits up to 100");
assert_eq!(at_cap.limit, Some(100));

for limit in [101u32, 65_535, 65_537, u32::MAX] {
let error = DriveDocumentQuery::try_from(&query(limit))
.expect_err("a limit the server refuses must not reach a DriveDocumentQuery");
assert!(
matches!(
&error,
Error::Drive(drive::error::Error::Query(
drive::error::query::QuerySyntaxError::InvalidLimit(message)
)) if message.contains("greater than max limit 100")
),
"expected the server's InvalidLimit for limit {limit}: {error}"
);
}
}
Loading