Skip to content

Commit b936d4c

Browse files
fix(sdk): index-only document creates and deletes resolve once they land (#5136)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 91db70a commit b936d4c

7 files changed

Lines changed: 86 additions & 15 deletions

File tree

‎packages/js-evo-sdk/src/documents/facade.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ export class DocumentsFacade {
106106
* instance when you later intend to delete an indexOnly document whose
107107
* type requires `$createdAt`. A document of a contested index joins a
108108
* contest: `options.contestFund` is the most, in credits, it pays into it.
109+
* For an indexOnly type the proof shows the document's entry at the proof's
110+
* block, not that this create wrote it: no stronger proof exists for one.
109111
*/
110112
async create(options: wasm.DocumentCreateOptions): Promise<wasm.Document> {
111113
const w = await this.sdk.getWasmSdkConnected();
@@ -117,6 +119,11 @@ export class DocumentsFacade {
117119
return w.documentReplace(options);
118120
}
119121

122+
/**
123+
* Deletes a document and resolves once the proof shows it gone. For an
124+
* indexOnly type the proof shows the document's entry gone at the proof's
125+
* block, not that this delete removed it: no stronger proof exists for one.
126+
*/
120127
async delete(options: wasm.DocumentDeleteOptions): Promise<void> {
121128
const w = await this.sdk.getWasmSdkConnected();
122129
return w.documentDelete(options);

‎packages/rs-sdk/src/platform/documents/transitions/create.rs‎

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::platform::transition::broadcast::BroadcastStateTransition;
33
use crate::platform::transition::put_settings::PutSettings;
44
use crate::{Error, Sdk};
55
use dpp::data_contract::accessors::v0::DataContractV0Getters;
6+
use dpp::data_contract::document_type::accessors::DocumentTypeV2Getters;
67
use dpp::data_contract::document_type::action_fees::agreement::DocumentActionFeeAgreement;
78
use dpp::data_contract::DataContract;
89
use dpp::document::{Document, DocumentV0Getters};
@@ -252,6 +253,9 @@ impl Sdk {
252253
///
253254
/// This method broadcasts a document creation transition to add a new document
254255
/// to the specified data contract. The result contains the created document.
256+
/// For an indexOnly document type the proof shows the document's entry at the
257+
/// proof's block, not that this create wrote it: no stronger proof exists for
258+
/// such a document.
255259
///
256260
/// # Arguments
257261
///
@@ -279,6 +283,11 @@ impl Sdk {
279283
let platform_version = self.version();
280284

281285
let put_settings = create_document_transition_builder.settings;
286+
let index_only = create_document_transition_builder
287+
.data_contract
288+
.document_type_for_name(&create_document_transition_builder.document_type_name)
289+
.map_err(|e| Error::Protocol(e.into()))?
290+
.index_only();
282291

283292
let state_transition = create_document_transition_builder
284293
.sign(self, signing_key, signer, platform_version)
@@ -289,9 +298,22 @@ impl Sdk {
289298
trace!(hex = %hex::encode(state_transition.serialize_to_bytes()?), "document_create: transition bytes");
290299
trace!(transition = ?state_transition, "document_create: transition details");
291300

292-
let proof_result = state_transition
293-
.broadcast_and_wait::<StateTransitionProofResult>(self, put_settings)
294-
.await?;
301+
// An indexOnly document keeps no row: its proof shows the entry the create leaves,
302+
// which an earlier create with the same values would show too, so it cannot prove that
303+
// this create executed. That is the strongest proof such a document has, so it is
304+
// accepted rather than failing a create that landed.
305+
let proof_result = if index_only {
306+
state_transition
307+
.broadcast_and_wait_for_affected_state::<StateTransitionProofResult>(
308+
self,
309+
put_settings,
310+
)
311+
.await?
312+
} else {
313+
state_transition
314+
.broadcast_and_wait::<StateTransitionProofResult>(self, put_settings)
315+
.await?
316+
};
295317

296318
match proof_result {
297319
StateTransitionProofResult::VerifiedDocuments(documents) => {

‎packages/rs-sdk/src/platform/documents/transitions/delete.rs‎

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::platform::transition::put_settings::PutSettings;
33
use crate::platform::Identifier;
44
use crate::{Error, Sdk};
55
use dpp::data_contract::accessors::v0::DataContractV0Getters;
6+
use dpp::data_contract::document_type::accessors::DocumentTypeV2Getters;
67
use dpp::data_contract::document_type::action_fees::agreement::DocumentActionFeeAgreement;
78
use dpp::data_contract::DataContract;
89
use dpp::document::{Document, INITIAL_REVISION};
@@ -195,7 +196,6 @@ impl DocumentDeleteTransitionBuilder {
195196
),
196197
Error,
197198
> {
198-
use dpp::data_contract::document_type::accessors::DocumentTypeV2Getters;
199199
use dpp::document::DocumentV0Getters;
200200

201201
let document_type = self
@@ -320,7 +320,10 @@ impl Sdk {
320320
/// Deletes an existing document from the platform.
321321
///
322322
/// This method broadcasts a document deletion transition to permanently remove
323-
/// a document from the platform. The result confirms the deletion.
323+
/// a document from the platform. The result confirms the deletion. For an
324+
/// indexOnly document type the proof shows the document's entry gone at the
325+
/// proof's block, not that this delete removed it: no stronger proof exists
326+
/// for such a document.
324327
///
325328
/// # Arguments
326329
///
@@ -349,14 +352,32 @@ impl Sdk {
349352
let platform_version = self.version();
350353

351354
let put_settings = delete_document_transition_builder.settings;
355+
let index_only = delete_document_transition_builder
356+
.data_contract
357+
.document_type_for_name(&delete_document_transition_builder.document_type_name)
358+
.map_err(|e| Error::Protocol(e.into()))?
359+
.index_only();
352360

353361
let state_transition = delete_document_transition_builder
354362
.sign(self, signing_key, signer, platform_version)
355363
.await?;
356364

357-
let proof_result = state_transition
358-
.broadcast_and_wait::<StateTransitionProofResult>(self, put_settings)
359-
.await?;
365+
// An indexOnly document keeps no row: its proof shows the entry is gone, which it would
366+
// also be had it never existed, so it cannot prove that this delete executed. That is the
367+
// strongest proof such a document has, so it is accepted rather than failing a delete
368+
// that landed.
369+
let proof_result = if index_only {
370+
state_transition
371+
.broadcast_and_wait_for_affected_state::<StateTransitionProofResult>(
372+
self,
373+
put_settings,
374+
)
375+
.await?
376+
} else {
377+
state_transition
378+
.broadcast_and_wait::<StateTransitionProofResult>(self, put_settings)
379+
.await?
380+
};
360381

361382
match proof_result {
362383
StateTransitionProofResult::VerifiedDocuments(documents) => {

‎packages/rs-sdk/src/platform/transition/broadcast.rs‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ pub trait BroadcastStateTransition {
4545
/// transition executed. For the transition families whose proofs can
4646
/// only authenticate the affected state (balance top-ups, credit
4747
/// transfers and withdrawals, address funds movements, shields,
48-
/// no-history token operations, key limits updates), this returns
48+
/// no-history token operations, key limits updates, contract updates,
49+
/// contract moderation and fee claims, and creates and deletes of
50+
/// indexOnly documents), this returns
4951
/// [`Error::ExecutionNotProved`] — use
5052
/// [`wait_for_affected_state`](Self::wait_for_affected_state) for those
5153
/// flows and treat the result as a height-pinned snapshot.
@@ -395,7 +397,7 @@ pub fn require_execution_proved(
395397
StateTransitionProofGuarantee::ExecutionProved => Ok(result),
396398
StateTransitionProofGuarantee::AffectedState => Err(Error::ExecutionNotProved(
397399
format!(
398-
"received a verified {} snapshot for this transition family; use the *_affected_state wait APIs and treat the result as a height-pinned snapshot",
400+
"received a verified {} snapshot for this transition family; wait with the affected-state APIs instead (wait_for_affected_state in Rust, waitForAffectedState or broadcastAndWaitForAffectedState in JavaScript) and treat the result as a height-pinned snapshot",
399401
result
400402
),
401403
)),

‎packages/rs-sdk/src/platform/transition/put_document.rs‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::platform::transition::put_settings::PutSettings;
66
use crate::{Error, Sdk};
77
use dpp::dashcore::secp256k1::rand::rngs::StdRng;
88
use dpp::dashcore::secp256k1::rand::{Rng, SeedableRng};
9-
use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters;
9+
use dpp::data_contract::document_type::accessors::{DocumentTypeV0Getters, DocumentTypeV2Getters};
1010
use dpp::data_contract::document_type::methods::DocumentTypeV0Methods;
1111
use dpp::data_contract::document_type::DocumentType;
1212
use dpp::document::{Document, DocumentV0Getters, DocumentV0Setters, INITIAL_REVISION};
@@ -36,7 +36,10 @@ pub trait PutDocument<S: Signer<IdentityPublicKey>>: Waitable {
3636
settings: Option<PutSettings>,
3737
) -> Result<StateTransition, Error>;
3838

39-
/// Puts a document on platform and waits for the confirmation proof
39+
/// Puts a document on platform and waits for the confirmation proof. For an
40+
/// indexOnly document type the proof shows the document's entry at the proof's
41+
/// block, not that this put wrote it: no stronger proof exists for such a
42+
/// document.
4043
#[allow(clippy::too_many_arguments)]
4144
async fn put_to_platform_and_wait_for_response(
4245
&self,
@@ -204,6 +207,7 @@ impl<S: Signer<IdentityPublicKey>> PutDocument<S> for Document {
204207
signer: &S,
205208
settings: Option<PutSettings>,
206209
) -> Result<Document, Error> {
210+
let index_only = document_type.index_only();
207211
let state_transition = self
208212
.put_to_platform(
209213
sdk,
@@ -216,6 +220,13 @@ impl<S: Signer<IdentityPublicKey>> PutDocument<S> for Document {
216220
)
217221
.await?;
218222

223+
// An indexOnly document keeps no row: its proof shows the entry the create leaves, not
224+
// that this create executed, and no stronger proof exists for it.
225+
if index_only {
226+
return wait_for_document_and_owner_balance(sdk, state_transition, settings)
227+
.await
228+
.map(|(document, _owner_balance)| document);
229+
}
219230
Self::wait_for_response(sdk, state_transition, settings).await
220231
}
221232
}

‎packages/wasm-sdk/src/state_transitions/broadcast.rs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,9 @@ impl WasmSdk {
226226
/// whose proofs cannot be bound to the execution of one specific
227227
/// transition (balance top-ups, credit transfers and withdrawals,
228228
/// address funds movements, shields, no-history token operations, key
229-
/// limits updates). This method accepts those outcomes instead. The result is a verified,
229+
/// limits updates, contract updates, contract moderation and fee claims,
230+
/// and creates and deletes of indexOnly documents). This method accepts
231+
/// those outcomes instead. The result is a verified,
230232
/// height-pinned snapshot of the affected state — NOT evidence that this
231233
/// specific transition executed.
232234
///

‎packages/wasm-sdk/src/state_transitions/document.rs‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,10 @@ impl WasmSdk {
168168
///
169169
/// @returns Promise resolving to the confirmed Document as Platform
170170
/// committed it — its final `id` and the consensus-populated system fields
171-
/// (`$createdAt` and friends) included. Keep THIS instance
171+
/// (`$createdAt` and friends) included. For an indexOnly document type
172+
/// the proof shows the document's entry at the proof's block, not that
173+
/// this create wrote it: no stronger proof exists for such a document.
174+
/// Keep THIS instance
172175
/// when you later intend to delete an indexOnly document
173176
/// whose type requires `$createdAt`: the delete carries the
174177
/// document's values, and the pre-broadcast wrapper never
@@ -447,7 +450,10 @@ impl WasmSdk {
447450
/// 3. Broadcasts and waits for confirmation
448451
///
449452
/// @param options - Delete options including document (or document identifiers), identity key, and signer
450-
/// @returns Promise that resolves when the document is deleted
453+
/// @returns Promise that resolves when the document is deleted. For an indexOnly
454+
/// document type the proof shows the document's entry gone at the proof's
455+
/// block, not that this delete removed it: no stronger proof exists for
456+
/// such a document.
451457
#[wasm_bindgen(js_name = "documentDelete")]
452458
pub async fn document_delete(
453459
&self,

0 commit comments

Comments
 (0)