Skip to main content

drive_proof_verifier/proof/
composite_document.rs

1//! Verified **composite document** results: a page plus the
2//! sub-queries derived from it, answered as ONE merged grovedb proof.
3//!
4//! The server proves the limited page and every sub-query (by-id
5//! joins, indexed lookups, grouped counts, siblings) as one merged
6//! path query. The verifier
7//! ([`DriveDocumentQuery::verify_composite_documents_proof`])
8//! bootstraps the page from the proof with a subset pass, re-derives
9//! every sub-query's `IN` clause from the PROVEN page (or the proven
10//! earlier sub-query it binds), rebuilds the same merged query, verifies
11//! it in one authoritative pass, and routes the proved entries back to
12//! their components — refusing unclaimed entries, dangling joins and
13//! derivation divergence. This module's [`FromProof`] impl composes
14//! that with the tenderdash signature binding of the single root.
15//!
16//! There is deliberately **no unproven decoder with verification
17//! semantics** here: an unproven composite response is free to
18//! fabricate any sub-result, which is precisely what the surface exists
19//! to prevent. [`CompositeDocuments`] can still be built from a trusted
20//! node's unproven wire by the SDK if it chooses, but the canonical
21//! path proves.
22
23use crate::error::MapGroveDbError;
24use crate::verify::{supported_grovedb_proof_bytes, verify_tenderdash_proof};
25use crate::{ContextProvider, Error, FromProof};
26use dapi_grpc::platform::v0::{GetDocumentsResponse, Proof, ResponseMetadata};
27use dapi_grpc::platform::VersionedGrpcResponse;
28use dpp::dashcore::Network;
29use dpp::document::Document;
30use dpp::version::PlatformVersion;
31use drive::drive::contract::moderation::types::ContractDocumentRemovalEntry;
32use drive::query::{DriveDocumentQuery, SubQueryResult};
33use drive::verify::RootHash;
34
35/// The verified result of a composite document query.
36#[derive(Debug, Clone, PartialEq, Default)]
37pub struct CompositeDocuments {
38    /// The page, exactly as the page query alone would return it.
39    pub page_documents: Vec<Document>,
40    /// One result per sub-query, in request order: a by-id join's
41    /// documents in first-appearance order of their ids among the
42    /// source documents, a lookup's or sibling's in query order, or one
43    /// count per derived value that has a count tree (a value without
44    /// an entry counts zero).
45    pub sub_results: Vec<SubQueryResult>,
46    /// One list per sub-query, in request order: for a by-id join off a
47    /// `deletableDocument` property, the derived ids that have NO
48    /// document, in first-appearance order, each one a PROVEN absence;
49    /// empty for every other sub-query.
50    pub sub_result_missing_ids: Vec<Vec<dpp::identifier::Identifier>>,
51    /// One list per sub-query, in request order: for a by-id join off a
52    /// `moderatedDocument` property, the derived ids whose document the
53    /// contract's moderators removed, each with its PROVEN removal record,
54    /// in first-appearance order; empty for every other sub-query. There a
55    /// derived id with neither a document nor a record is an invalid proof.
56    pub sub_result_removals: Vec<Vec<ContractDocumentRemovalEntry>>,
57}
58
59/// Verify a composite query's single merged proof and bind its root
60/// hash to the quorum signature.
61///
62/// The merk-level composition (bootstrap subset pass on the page,
63/// re-derivation of every sub-query, authoritative full verification,
64/// routing with the completeness checks) lives in rs-drive's
65/// [`DriveDocumentQuery::verify_composite_documents_proof`];
66/// this wrapper adds the [`verify_tenderdash_proof`] binding — the root
67/// hash the proof commits to is only an attested fact once it is tied
68/// to the quorum-signed app hash, and this function exists so the
69/// composition can never be skipped by accident.
70pub fn verify_composite_documents_proof(
71    query: &DriveDocumentQuery,
72    proof: &Proof,
73    mtd: &ResponseMetadata,
74    platform_version: &PlatformVersion,
75    provider: &dyn ContextProvider,
76) -> Result<(RootHash, CompositeDocuments), Error> {
77    let (root_hash, result) = query
78        .verify_composite_documents_proof(supported_grovedb_proof_bytes(proof)?, platform_version)
79        .map_drive_error(proof, mtd)?;
80
81    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
82
83    Ok((
84        root_hash,
85        CompositeDocuments {
86            page_documents: result.page_documents,
87            sub_results: result.sub_results,
88            sub_result_missing_ids: result.sub_result_missing_ids,
89            sub_result_removals: result.sub_result_removals,
90        },
91    ))
92}
93
94impl<'dq, Q> FromProof<Q> for CompositeDocuments
95where
96    Q: TryInto<DriveDocumentQuery<'dq>> + Clone + 'dq,
97    Q::Error: std::fmt::Display,
98{
99    type Request = Q;
100    type Response = GetDocumentsResponse;
101
102    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
103        request: I,
104        response: O,
105        _network: Network,
106        platform_version: &PlatformVersion,
107        provider: &'a dyn ContextProvider,
108    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
109    where
110        Self: 'a,
111    {
112        let request: Self::Request = request.into();
113        let response: Self::Response = response.into();
114
115        let query: DriveDocumentQuery<'dq> =
116            request
117                .clone()
118                .try_into()
119                .map_err(|e: Q::Error| Error::RequestError {
120                    error: e.to_string(),
121                })?;
122
123        // The standard envelope carries the single MERGED proof, and
124        // the proof alone is enough: the verifier bootstraps the page
125        // from it via a subset pass and re-derives the rest.
126        let proof = response.proof().or(Err(Error::NoProofInResult))?;
127        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
128
129        let (_root_hash, composite) =
130            verify_composite_documents_proof(&query, proof, mtd, platform_version, provider)?;
131
132        // An empty page is a valid, proven "nothing here" — surface it
133        // as Some(empty) rather than None so callers can tell it apart
134        // from a missing object.
135        Ok((Some(composite), mtd.clone(), proof.clone()))
136    }
137}