Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
9de6dac
feat(platform)!: dpns contract v3 checks name registration with schem…
QuantumExplorer Oct 8, 2026
08a5679
fix(platform): a pre-upgrade preorder meets only a one-block minimum age
QuantumExplorer Oct 8, 2026
7fa7b0a
Merge remote-tracking branch 'origin/v5.0-dev' into claude/wizardly-d…
QuantumExplorer Oct 8, 2026
521761b
test(sdk): pin the dpns preorder hash of a mixed-case parent domain name
QuantumExplorer Oct 8, 2026
bdc9d9e
Merge remote-tracking branch 'origin/v5.0-dev' into claude/wizardly-d…
QuantumExplorer Oct 8, 2026
d3518c7
chore(contract): shorten the dpns v3 schema descriptions
QuantumExplorer Oct 9, 2026
d93f498
feat(platform)!: dpns v3 preorders are unique per owner and hash the …
QuantumExplorer Oct 9, 2026
e6c1911
Merge remote-tracking branch 'origin/v5.0-dev' into claude/wizardly-d…
QuantumExplorer Oct 9, 2026
b66668f
fix(sdk): hash DPNS preorders against the contract the network stores
QuantumExplorer Oct 9, 2026
fbd02c9
fix(sdk): take the DPNS contract only from a response proved at the p…
QuantumExplorer Oct 9, 2026
b720af0
fix(drive): seed the block cache with the contract a rebuild stores
QuantumExplorer Oct 9, 2026
51403d3
fix(sdk): skip a DPNS contract response when the SDK version moved me…
QuantumExplorer Oct 9, 2026
4905cf3
Merge remote-tracking branch 'origin/v5.0-dev' into claude/wizardly-d…
QuantumExplorer Oct 9, 2026
1f1a8b0
feat(contract): write the DPNS v3 byte arrays with the type shorthands
QuantumExplorer Oct 9, 2026
45309b9
fix(platform): DPNS v3 review fixes
QuantumExplorer Oct 9, 2026
a9b1ee5
refactor(dpp): read a reference's findBy hash key through one accessor
QuantumExplorer Oct 9, 2026
cec6d0a
perf(drive): delete a consumed document from what its lookup read
QuantumExplorer Oct 9, 2026
0e3ae00
test(sdk): give the records.identity refusal spec two distinct identi…
QuantumExplorer Oct 9, 2026
c2d30d9
docs(book): the findBy function exception for $ownerId; DPNS preorder…
QuantumExplorer Oct 9, 2026
b43fcec
test(drive): run the protocol 13 halves of three tests on DPNS v2
QuantumExplorer Oct 9, 2026
c158a7d
fix(sdk): parse a proof the SDK's version can not read under the vers…
QuantumExplorer Oct 9, 2026
3961808
Merge branch 'v5.0-dev' into claude/wizardly-driscoll-44c981
QuantumExplorer Oct 9, 2026
521bfa2
test(sdk): cover the protocol version fallback through the quorum key…
QuantumExplorer Oct 10, 2026
193c42f
test(sdk): fetch a missing quorum key and read the proof again throug…
QuantumExplorer Oct 10, 2026
35696a2
Merge remote-tracking branch 'origin/v5.0-dev' into claude/wizardly-d…
QuantumExplorer Oct 11, 2026
4be5d8c
Merge remote-tracking branch 'origin/v5.0-dev' into claude/wizardly-d…
QuantumExplorer Oct 11, 2026
759d679
feat(platform)!: dpns v3 preorders hash the owner and the name, witho…
QuantumExplorer Oct 11, 2026
aaa0bb7
fix(sdk): dpns registration decodes under the contract it built again…
QuantumExplorer Oct 11, 2026
b1ae90f
fix(wasm-sdk): the trusted context forwards a registered contract to …
QuantumExplorer Oct 11, 2026
ec975f7
Merge branch 'v5.0-dev' into claude/wizardly-driscoll-44c981
QuantumExplorer Oct 12, 2026
8a35b1a
Merge branch 'v5.0-dev' into claude/wizardly-driscoll-44c981
QuantumExplorer Oct 12, 2026
a7e6bf7
Merge v5.1-dev (with v5.0-dev) into the DPNS v3 branch
QuantumExplorer Oct 13, 2026
1cfe150
feat(platform)!: move DPNS v3 to protocol version 15 and choose the f…
QuantumExplorer Oct 13, 2026
4146766
fix(drive-abci): keep DPNS v3 for its rebuild on an upgrade that skip…
QuantumExplorer Oct 13, 2026
13b2ffb
Merge the DPNS v3 branch, retargeted to protocol version 15
QuantumExplorer Oct 13, 2026
f338eb1
feat(platform)!: delete a consumed document from its lookup's read fr…
QuantumExplorer Oct 13, 2026
f86ba2f
Merge v5.1-dev into the consume single-read branch
QuantumExplorer Oct 28, 2026
a87e37e
fix(drive): move consumed documents instead of copying them, and test…
QuantumExplorer Oct 28, 2026
d21b385
Merge v5.1-dev (yes/no vote polls) into the consume single-read branch
QuantumExplorer Oct 28, 2026
dccfbaf
Merge v5.1-dev into the consume single-read branch
QuantumExplorer Oct 29, 2026
6628b77
refactor(platform)!: give the consume single read its own reference v…
QuantumExplorer Oct 29, 2026
49488c5
Merge v5.1-dev into the consume single-read branch
QuantumExplorer Oct 29, 2026
656c547
refactor(platform): read storage flags only for a consuming lookup
QuantumExplorer Oct 29, 2026
ac18c33
test(platform): pin consuming lookup misses and the consumed read exa…
QuantumExplorer Oct 29, 2026
e319d43
test(platform): pin that a consuming lookup reads the create's own co…
QuantumExplorer Oct 30, 2026
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
2 changes: 1 addition & 1 deletion book/src/contract-keywords/refers-to-lookup.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ WHERE saltedDomainHash = -- findBy
`constraints` (holding `minimumAgeBlocks` and [`chooseFirst`](#choosing-the-first-commitment-choosefirst)) and `consume` sit on the `refersTo`, beside `findBy`: they describe the document `findBy` finds when the referring document is created, so the reference must be judged on the create alone. A function in `findBy` makes it so, and so does a referring type whose documents are never replaced (`documentsMutable: false`): a replace is the one write that checks a reference again. Any other `findBy` declaring either is refused at registration (10231), and so is either beside [`inList`](refers-to-list-element.md), which checks that a list holds the value rather than finding a document by a key.

- **`constraints.minimumAgeBlocks`**: the commitment's `$createdAtBlockHeight` is at least that many blocks below the create's height. The commitment type must list `$createdAtBlockHeight` in `required`.
- **`consume: true`**: the create deletes the commitment, its storage refunded to its owner. Only on a `deletableDocument` reference with the `where` entry `"$ownerId": "$ownerId"`, into a type of the same contract whose owner may delete its documents (`canBeDeleted: true`) or whose documents only a consume deletes (`canBeDeleted: "onlyWhenConsumed"`, see [Deletion](deletion.md#canbedeleted)), that declares no delete token cost or delete action fee and no [`deleteConstraints`](deletion.md#deleteconstraints) (no delete transition charges or judges them), and that requires no stricter signature security level than the revealing type. A contract-bound key signing the create must be allowed to act on the consumed type too (`ContractBoundedKeyOutOfBoundsError`, 20014).
- **`consume: true`**: the create deletes the commitment, its storage refunded to its owner. Only on a `deletableDocument` reference with the `where` entry `"$ownerId": "$ownerId"`, into a type of the same contract whose owner may delete its documents (`canBeDeleted: true`) or whose documents only a consume deletes (`canBeDeleted: "onlyWhenConsumed"`, see [Deletion](deletion.md#canbedeleted)), that declares no delete token cost or delete action fee and no [`deleteConstraints`](deletion.md#deleteconstraints) (no delete transition charges or judges them), and that requires no stricter signature security level than the revealing type. A contract-bound key signing the create must be allowed to act on the consumed type too (`ContractBoundedKeyOutOfBoundsError`, 20014). From protocol version 15 the delete uses the commitment `findBy` read, so the create pays for one read of it; before, the delete read it again by id and that read was charged too.

A create missing a param, or with a value holding its separator, is refused before any read (`DocumentReferencePreimageInvalidError`, 10423). No commitment is `ReferencedEntityNotFoundError` (40120), another identity's `ReferencedDocumentPropertyMismatchError` (40127), and one too young `ReferencedDocumentRequirementNotMetError` (40142). The [Documents chapter](../data-model/documents.md#commit-and-reveal-a-findby-function) has the full rules.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod v0;
pub mod v1;

use std::collections::{BTreeMap, BTreeSet};

Expand All @@ -17,6 +18,7 @@ use crate::error::execution::ExecutionError;
use crate::error::Error;
use crate::execution::types::state_transition_execution_context::StateTransitionExecutionContext;
use crate::execution::validation::state_transition::batch::action_validation::document::document_reference_validation::v0::DocumentReferenceValidationV0;
use crate::execution::validation::state_transition::batch::action_validation::document::document_reference_validation::v1::DocumentReferenceValidationV1;
use crate::platform_types::platform::PlatformStateRef;

/// A document a create consumes: the commitment a `refersTo` lookup with a computed key
Expand Down Expand Up @@ -152,7 +154,8 @@ impl DocumentReferenceValidation for DocumentBaseTransitionAction {
.batch_state_transition
.document_reference_validation
{
0 => self.deletable_document_reference_target_is_gone_v0(
// Generation 1 changes what a lookup keeps of a consumed document only
0 | 1 => self.deletable_document_reference_target_is_gone_v0(
property,
referenced_id,
platform,
Expand All @@ -164,7 +167,7 @@ impl DocumentReferenceValidation for DocumentBaseTransitionAction {
version => Err(Error::Execution(ExecutionError::UnknownVersionMismatch {
method: "DocumentBaseTransitionAction::deletable_document_reference_target_is_gone"
.to_string(),
known_versions: vec![0],
known_versions: vec![0, 1],
received: version,
})),
}
Expand Down Expand Up @@ -210,9 +213,25 @@ impl DocumentReferenceValidation for DocumentBaseTransitionAction {
execution_context,
platform_version,
),
1 => self.validate_document_references_v1(
document_data,
owner_id,
creator_id,
referring_system,
changed_fields,
stored_values,
platform,
block_info,
consumed_documents,
derived_index_values,
referenced_documents,
transaction,
execution_context,
platform_version,
),
version => Err(Error::Execution(ExecutionError::UnknownVersionMismatch {
method: "DocumentBaseTransitionAction::validate_document_references".to_string(),
known_versions: vec![0],
known_versions: vec![0, 1],
received: version,
})),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1665,6 +1665,9 @@ fn validate_reference_target_v0(
document: ConsumedDocument {
document_id: found.id(),
document_type_name: document_type_name.to_string(),
// Inert: the create conversion 0 that deletes it reads it by id;
// only generation 1 (protocol version 15) keeps the lookup's read
read: None,
},
referenced_id: Identifier::from(referenced_id),
reference_target: reference_target.clone(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
//! The `refersTo` lookup of document reference validation 1 (protocol version 15).

use crate::error::Error;
use crate::execution::types::execution_operation::ValidationOperation;
use crate::execution::types::state_transition_execution_context::{
StateTransitionExecutionContext, StateTransitionExecutionContextMethodsV0,
};
use crate::execution::validation::state_transition::batch::state::v0::fetch_documents::{
fetch_document_through_lookup, hash_lookup_key, holds_key_part, BilledLookupKey,
};
use dpp::block::epoch::Epoch;
use dpp::data_contract::document_type::methods::DocumentTypeV0Methods;
use dpp::data_contract::document_type::{DocumentReferenceLookup, DocumentTypeRef};
use dpp::data_contract::DataContract;
use dpp::document::Document;
use dpp::fee::fee_result::FeeResult;
use dpp::platform_value::{Identifier, Value};
use dpp::version::PlatformVersion;
use drive::drive::document::query::QueryDocumentsWithFlagsOutcomeV0Methods;
use drive::drive::Drive;
use drive::grovedb::TransactionArg;
use drive::query::{DriveDocumentQuery, InternalClauses, WhereClause, WhereOperator};
use drive::util::storage_flags::StorageFlags;
use std::collections::BTreeMap;

/// What `fetch_document_through_lookup` (batch state 0) finds. For a lookup declaring `consume`,
/// it also returns the storage flags of the document's stored element. That is the same path
/// query, read through `query_documents_with_flags` instead of `query_documents` and billed the
/// same way, and the create that consumes the document deletes it from this read without
/// reading it again.
///
/// The two GroveDB calls behind those queries part ways only past a layer a query item misses:
/// `query` goes on with the next item, `query_item_value` stops. A lookup fixes every property
/// of a unique index, one key a layer, so a consuming lookup that finds nothing stops at the
/// same layer either way and pays what generation 0's pays
/// (`should_bill_a_consuming_lookup_that_finds_nothing_the_same_at_protocol_versions_14_and_15`).
/// A lookup that does not consume needs no flags, so it calls generation 0's read itself.
#[allow(clippy::too_many_arguments)]
pub(super) fn fetch_document_with_flags_through_lookup(
drive: &Drive,
contract: &DataContract,
document_type: DocumentTypeRef,
declaring_document_type: DocumentTypeRef,
lookup: &DocumentReferenceLookup,
reference_value: &Value,
document_data: &BTreeMap<String, Value>,
owner_id: Identifier,
billed_key: Option<BilledLookupKey>,
epoch: &Epoch,
execution_context: &mut StateTransitionExecutionContext,
transaction: TransactionArg,
platform_version: &PlatformVersion,
) -> Result<Option<(Document, Option<StorageFlags>)>, Error> {
if !lookup.consume {
return Ok(fetch_document_through_lookup(
drive,
contract,
document_type,
declaring_document_type,
lookup,
reference_value,
document_data,
owner_id,
billed_key,
epoch,
execution_context,
transaction,
platform_version,
)?
.map(|document| (document, None)));
}
// Registration resolved the index its `findBy` names, and a type's indexes
// never change, so this only guards a declaration no registration admits:
// one naming no unique index finds no document
if lookup.resolve_index(document_type).is_err() {
return Ok(None);
}
// A function's `"."` param reads an identifier (an element, the writer or
// the creator); a property carrying the reference is named by its path.
// The writer is passed in place, inert before protocol version 14: only
// meta-schema v3 admits a computed key, and the `"$ownerId"` param that
// reads the writer
let reference_id = match reference_value {
Value::Identifier(id) => Some(Identifier::from(*id)),
_ => None,
};
let billed_key = billed_key.or_else(|| {
lookup.hash_key().and_then(|(_, key)| {
hash_lookup_key(
key,
declaring_document_type,
reference_id,
owner_id,
document_data,
execution_context,
)
})
});
let computed_key = billed_key.map(|key| Value::Bytes32(key.digest()));
let Some(key_values) = lookup.key_values(
reference_value,
document_data,
owner_id,
computed_key.as_ref(),
) else {
return Ok(None);
};
// A key part no index entry can hold finds no document. Serializing it is
// what the query does first, and its failure is the value's, not the state's
if key_values.iter().any(|(index_property, value)| {
document_type
.serialize_value_for_key(index_property, value, platform_version)
.is_err()
}) {
return Ok(None);
}
let equal_clauses = key_values
.iter()
.map(|(field, value)| {
(
field.clone(),
WhereClause {
field: field.clone(),
operator: WhereOperator::Equal,
value: value.clone(),
},
)
})
.collect();

let drive_query = DriveDocumentQuery {
contract,
document_type,
internal_clauses: InternalClauses {
primary_key_in_clause: None,
primary_key_equal_clause: None,
in_clauses: Vec::new(),
range_clause: None,
equal_clauses,
},
offset: None,
// The index is unique and every one of its properties is fixed, so at
// most one document matches
limit: Some(1),
order_by: Default::default(),
start_at: None,
start_at_included: false,
block_time_ms: None,
resolved_time_ranges: vec![],
sub_queries: vec![],
};

let documents_outcome = drive.query_documents_with_flags(
drive_query,
Some(epoch),
false,
transaction,
Some(platform_version.protocol_version),
)?;
Comment thread
QuantumExplorer marked this conversation as resolved.
Resolved
Comment thread
QuantumExplorer marked this conversation as resolved.
Resolved
execution_context.add_operation(ValidationOperation::PrecalculatedOperation(FeeResult {
storage_fee: 0,
processing_fee: documents_outcome.cost(),
fee_refunds: Default::default(),
removed_bytes_from_system: 0,
lifetime_storage_fees: Default::default(),
}));

Ok(documents_outcome
.documents_owned()
.into_iter()
.next()
.filter(|(document, _)| {
key_values.iter().all(|(index_property, value)| {
holds_key_part(document_type, document, index_property, value)
})
}))
}
Loading
Loading