Skip to main content

drive/util/batch/drive_op_batch/
document.rs

1use crate::drive::Drive;
2use crate::error::Error;
3use crate::fees::op::LowLevelDriveOperation;
4use crate::util::batch::drive_op_batch::DriveLowLevelOperationConverter;
5use crate::util::object_size_info::DocumentInfo::{DocumentRefAndSerialization, DocumentRefInfo};
6use crate::util::object_size_info::{
7    DataContractInfo, DocumentAndContractInfo, DocumentTypeInfo, OwnedDocumentInfo,
8};
9use crate::util::storage_flags::StorageFlags;
10use dpp::block::block_info::BlockInfo;
11use dpp::data_contract::accessors::v0::DataContractV0Getters;
12use dpp::data_contract::document_type::DocumentTypeRef;
13use dpp::data_contract::DataContract;
14use dpp::document::document_event::DocumentEvent;
15use dpp::document::Document;
16use dpp::prelude::{Identifier, IdentityNonce};
17
18use dpp::system_data_contracts::withdrawals_contract::v1::document_types::withdrawal;
19
20use crate::drive::votes::resolved::vote_polls::contested_document_resource_vote_poll::ContestedDocumentResourceVotePollWithContractInfo;
21use dpp::platform_value::Value;
22use dpp::version::PlatformVersion;
23use dpp::voting::vote_info_storage::contested_document_vote_poll_stored_info::ContestedDocumentVotePollStoredInfo;
24use dpp::ProtocolError;
25use grovedb::batch::KeyInfoPath;
26use grovedb::{EstimatedLayerInformation, TransactionArg};
27use std::borrow::Cow;
28use std::collections::{BTreeMap, HashMap};
29
30/// A wrapper for a document operation
31#[derive(Clone, Debug)]
32#[allow(clippy::large_enum_variant)]
33pub enum DocumentOperation<'a> {
34    /// An add operation
35    AddOperation {
36        /// Document info with maybe the owner id
37        owned_document_info: OwnedDocumentInfo<'a>,
38        /// Should we override the document if one already exists?
39        override_document: bool,
40    },
41    /// An update operation
42    UpdateOperation(UpdateOperationInfo<'a>),
43}
44
45/// Document and contract info
46#[derive(Clone, Debug)]
47pub struct DocumentOperationsForContractDocumentType<'a> {
48    /// Document info
49    pub operations: Vec<DocumentOperation<'a>>,
50    ///DataContract
51    pub contract: &'a DataContract,
52    /// Document type
53    pub document_type: DocumentTypeRef<'a>,
54}
55
56/// Operations on Documents
57#[derive(Clone, Debug)]
58#[allow(clippy::large_enum_variant)]
59pub enum DocumentOperationType<'a> {
60    /// Adds a document to a contract matching the desired info.
61    AddDocument {
62        /// The document and contract info, also may contain the owner_id
63        owned_document_info: OwnedDocumentInfo<'a>,
64        /// Data Contract info to potentially be resolved if needed
65        contract_info: DataContractInfo<'a>,
66        /// Document type
67        document_type_info: DocumentTypeInfo<'a>,
68        /// Should we override the document if one already exists?
69        override_document: bool,
70    },
71    /// Adds a contested document to a contract matching the desired info.
72    /// A contested document is a document that is trying to a acquire a
73    /// unique index that has a conflict resolution mechanism
74    AddContestedDocument {
75        /// The document and contract info, also may contain the owner_id
76        owned_document_info: OwnedDocumentInfo<'a>,
77        /// The vote poll in question that will should be created
78        contested_document_resource_vote_poll: ContestedDocumentResourceVotePollWithContractInfo,
79        /// Data Contract info to potentially be resolved if needed
80        contract_info: DataContractInfo<'a>,
81        /// Document type
82        document_type_info: DocumentTypeInfo<'a>,
83        /// Should we insert without verifying first that the document doesn't already exist
84        insert_without_check: bool,
85        /// Should we also insert the vote poll stored info
86        also_insert_vote_poll_stored_info: Option<ContestedDocumentVotePollStoredInfo>,
87    },
88    /// Updates a document and returns the associated fee.
89    UpdateDocument {
90        /// The document and contract info, also may contain the owner_id
91        owned_document_info: OwnedDocumentInfo<'a>,
92        /// Data Contract info to potentially be resolved if needed
93        contract_info: DataContractInfo<'a>,
94        /// Document type
95        document_type_info: DocumentTypeInfo<'a>,
96    },
97    /// Deletes a document
98    DeleteDocument {
99        /// The document id
100        document_id: Identifier,
101        /// Data Contract info to potentially be resolved if needed
102        contract_info: DataContractInfo<'a>,
103        /// Document type
104        document_type_info: DocumentTypeInfo<'a>,
105    },
106    /// Deletes an indexOnly document from its property values — there is
107    /// no primary-storage row to fetch, so the values (plus the owner)
108    /// are what every index entry is recomputed from. `$createdAt` may
109    /// ride in `data` under its system key when the type indexes it.
110    DeleteIndexOnlyDocument {
111        /// The document id (deterministic; never stored)
112        document_id: Identifier,
113        /// The owner whose entries are being removed
114        owner_id: Identifier,
115        /// The document's property values
116        data: BTreeMap<String, Value>,
117        /// Data Contract info to potentially be resolved if needed
118        contract_info: DataContractInfo<'a>,
119        /// Document type
120        document_type_info: DocumentTypeInfo<'a>,
121    },
122    /// Convenience method to add a withdrawal document.
123    AddWithdrawalDocument {
124        /// The document and contract info, also may contain the owner_id
125        owned_document_info: OwnedDocumentInfo<'a>,
126    },
127    /// Adds a document to a contract.
128    MultipleDocumentOperationsForSameContractDocumentType {
129        /// The document operations
130        document_operations: DocumentOperationsForContractDocumentType<'a>,
131    },
132    /// Adds a historical document to the document history system contract,
133    /// recording a transfer, purchase, or price update of a document whose
134    /// document type subscribed to history.
135    DocumentHistory {
136        /// The data contract of the source document
137        source_data_contract_id: Identifier,
138        /// The document type name of the source document
139        source_document_type_name: String,
140        /// The source document
141        source_document_id: Identifier,
142        /// The identity making the event
143        owner_id: Identifier,
144        /// The nonce
145        nonce: IdentityNonce,
146        /// The document event
147        event: DocumentEvent,
148    },
149}
150
151impl DriveLowLevelOperationConverter for DocumentOperationType<'_> {
152    fn into_low_level_drive_operations(
153        self,
154        drive: &Drive,
155        estimated_costs_only_with_layer_info: &mut Option<
156            HashMap<KeyInfoPath, EstimatedLayerInformation>,
157        >,
158        block_info: &BlockInfo,
159        transaction: TransactionArg,
160        platform_version: &PlatformVersion,
161    ) -> Result<Vec<LowLevelDriveOperation>, Error> {
162        if estimated_costs_only_with_layer_info.is_none() {
163            self.prepare_time_range_ttl(drive, block_info, transaction, platform_version)?;
164        }
165        self.into_low_level_drive_operations_after_ttl_drain(
166            drive,
167            estimated_costs_only_with_layer_info,
168            block_info,
169            transaction,
170            platform_version,
171        )
172    }
173}
174
175impl DocumentOperationType<'_> {
176    /// Run all direct TTL cleanup before any operation in this batch is built.
177    pub(crate) fn prepare_time_range_ttl(
178        &self,
179        drive: &Drive,
180        block_info: &BlockInfo,
181        transaction: TransactionArg,
182        platform_version: &PlatformVersion,
183    ) -> Result<(), Error> {
184        if platform_version
185            .system_limits
186            .min_time_range_ttl_drop_operations_per_write
187            .is_none()
188        {
189            return Ok(());
190        }
191        match self {
192            Self::AddDocument {
193                contract_info,
194                document_type_info,
195                ..
196            }
197            | Self::AddContestedDocument {
198                contract_info,
199                document_type_info,
200                ..
201            }
202            | Self::UpdateDocument {
203                contract_info,
204                document_type_info,
205                ..
206            }
207            | Self::DeleteDocument {
208                contract_info,
209                document_type_info,
210                ..
211            }
212            | Self::DeleteIndexOnlyDocument {
213                contract_info,
214                document_type_info,
215                ..
216            } => {
217                // Preparation reads are unbilled maintenance. Normal conversion
218                // still resolves and bills the contract through its usual path.
219                let resolved = contract_info.clone().resolve(
220                    drive,
221                    block_info,
222                    transaction,
223                    &mut vec![],
224                    platform_version,
225                )?;
226                let contract = resolved.as_ref();
227                let document_type = document_type_info.clone().resolve(contract)?;
228                drive.prepare_document_time_range_ttl(
229                    contract,
230                    document_type,
231                    block_info.time_ms,
232                    transaction,
233                    platform_version,
234                )
235            }
236            Self::MultipleDocumentOperationsForSameContractDocumentType {
237                document_operations,
238            } => {
239                // Each document earns a drainage budget, but all budgets are
240                // spent before the first document's low-level ops are queued.
241                for _ in &document_operations.operations {
242                    drive.prepare_document_time_range_ttl(
243                        document_operations.contract,
244                        document_operations.document_type,
245                        block_info.time_ms,
246                        transaction,
247                        platform_version,
248                    )?;
249                }
250                Ok(())
251            }
252            // These write to system contracts, which have no TTL indexes.
253            Self::AddWithdrawalDocument { .. } | Self::DocumentHistory { .. } => Ok(()),
254        }
255    }
256
257    pub(crate) fn into_low_level_drive_operations_after_ttl_drain(
258        self,
259        drive: &Drive,
260        estimated_costs_only_with_layer_info: &mut Option<
261            HashMap<KeyInfoPath, EstimatedLayerInformation>,
262        >,
263        block_info: &BlockInfo,
264        transaction: TransactionArg,
265        platform_version: &PlatformVersion,
266    ) -> Result<Vec<LowLevelDriveOperation>, Error> {
267        match self {
268            DocumentOperationType::AddDocument {
269                owned_document_info,
270                contract_info,
271                document_type_info,
272                override_document,
273            } => {
274                let mut drive_operations: Vec<LowLevelDriveOperation> = vec![];
275                let contract_resolved_info = contract_info.resolve(
276                    drive,
277                    block_info,
278                    transaction,
279                    &mut drive_operations,
280                    platform_version,
281                )?;
282                let contract = contract_resolved_info.as_ref();
283                let document_type = document_type_info.resolve(contract)?;
284
285                let document_and_contract_info = DocumentAndContractInfo {
286                    owned_document_info,
287                    contract,
288                    document_type,
289                };
290                let mut operations = drive.add_document_for_contract_operations_without_ttl_drain(
291                    document_and_contract_info,
292                    override_document,
293                    block_info,
294                    &mut None,
295                    estimated_costs_only_with_layer_info,
296                    transaction,
297                    platform_version,
298                )?;
299                drive_operations.append(&mut operations);
300                Ok(drive_operations)
301            }
302            DocumentOperationType::AddContestedDocument {
303                owned_document_info,
304                contested_document_resource_vote_poll,
305                contract_info,
306                document_type_info,
307                insert_without_check,
308                also_insert_vote_poll_stored_info,
309            } => {
310                let mut drive_operations: Vec<LowLevelDriveOperation> = vec![];
311                let contract_resolved_info = contract_info.resolve(
312                    drive,
313                    block_info,
314                    transaction,
315                    &mut drive_operations,
316                    platform_version,
317                )?;
318                let contract = contract_resolved_info.as_ref();
319                let document_type = document_type_info.resolve(contract)?;
320
321                let document_and_contract_info = DocumentAndContractInfo {
322                    owned_document_info,
323                    contract,
324                    document_type,
325                };
326                let mut operations = drive.add_contested_document_for_contract_operations(
327                    document_and_contract_info,
328                    contested_document_resource_vote_poll,
329                    insert_without_check,
330                    block_info,
331                    also_insert_vote_poll_stored_info,
332                    &mut None,
333                    estimated_costs_only_with_layer_info,
334                    transaction,
335                    platform_version,
336                )?;
337                drive_operations.append(&mut operations);
338                Ok(drive_operations)
339            }
340            DocumentOperationType::AddWithdrawalDocument {
341                owned_document_info,
342            } => {
343                let contract = drive
344                    .cache
345                    .system_data_contracts
346                    .load_withdrawals(platform_version)?;
347
348                let document_type = contract
349                    .document_type_for_name(withdrawal::NAME)
350                    .map_err(ProtocolError::DataContractError)?;
351
352                let document_and_contract_info = DocumentAndContractInfo {
353                    owned_document_info,
354                    contract: &contract,
355                    document_type,
356                };
357                drive.add_document_for_contract_operations_without_ttl_drain(
358                    document_and_contract_info,
359                    false,
360                    block_info,
361                    &mut None,
362                    estimated_costs_only_with_layer_info,
363                    transaction,
364                    platform_version,
365                )
366            }
367            DocumentOperationType::UpdateDocument {
368                owned_document_info,
369                contract_info,
370                document_type_info,
371            } => {
372                let mut drive_operations = vec![];
373                let contract_resolved_info = contract_info.resolve(
374                    drive,
375                    block_info,
376                    transaction,
377                    &mut drive_operations,
378                    platform_version,
379                )?;
380                let contract = contract_resolved_info.as_ref();
381                let document_type = document_type_info.resolve(contract)?;
382
383                let document_and_contract_info = DocumentAndContractInfo {
384                    owned_document_info,
385                    contract,
386                    document_type,
387                };
388                let mut operations = drive
389                    .update_document_for_contract_operations_without_ttl_drain(
390                        document_and_contract_info,
391                        block_info,
392                        &mut None,
393                        estimated_costs_only_with_layer_info,
394                        transaction,
395                        platform_version,
396                    )?;
397                drive_operations.append(&mut operations);
398                Ok(drive_operations)
399            }
400            DocumentOperationType::DocumentHistory {
401                source_data_contract_id,
402                source_document_type_name,
403                source_document_id,
404                owner_id,
405                nonce,
406                event,
407            } => {
408                let batch_operations = drive.add_document_history_operations(
409                    source_data_contract_id,
410                    source_document_type_name.as_str(),
411                    source_document_id,
412                    owner_id,
413                    nonce,
414                    event,
415                    block_info,
416                    estimated_costs_only_with_layer_info,
417                    transaction,
418                    platform_version,
419                )?;
420                Ok(batch_operations)
421            }
422            DocumentOperationType::DeleteDocument {
423                document_id,
424                contract_info,
425                document_type_info,
426            } => {
427                let mut drive_operations: Vec<LowLevelDriveOperation> = vec![];
428                let contract_resolved_info = contract_info.resolve(
429                    drive,
430                    block_info,
431                    transaction,
432                    &mut drive_operations,
433                    platform_version,
434                )?;
435                let contract = contract_resolved_info.as_ref();
436                let document_type = document_type_info.resolve(contract)?;
437
438                drive.delete_document_for_contract_operations_without_ttl_drain(
439                    document_id,
440                    contract,
441                    document_type,
442                    None,
443                    estimated_costs_only_with_layer_info,
444                    block_info.time_ms,
445                    transaction,
446                    platform_version,
447                )
448            }
449            DocumentOperationType::DeleteIndexOnlyDocument {
450                document_id,
451                owner_id,
452                data,
453                contract_info,
454                document_type_info,
455            } => {
456                let mut drive_operations: Vec<LowLevelDriveOperation> = vec![];
457                let contract_resolved_info = contract_info.resolve(
458                    drive,
459                    block_info,
460                    transaction,
461                    &mut drive_operations,
462                    platform_version,
463                )?;
464                let contract = contract_resolved_info.as_ref();
465                let document_type = document_type_info.resolve(contract)?;
466
467                // Reconstruct the document the entries were written from.
468                let document = Drive::index_only_document_from_values(document_id, owner_id, data)?;
469
470                drive.delete_index_only_document_for_contract_operations_without_ttl_drain(
471                    document,
472                    contract,
473                    document_type,
474                    None,
475                    estimated_costs_only_with_layer_info,
476                    block_info.time_ms,
477                    transaction,
478                    platform_version,
479                )
480            }
481            DocumentOperationType::MultipleDocumentOperationsForSameContractDocumentType {
482                document_operations,
483            } => {
484                let DocumentOperationsForContractDocumentType {
485                    operations,
486                    contract,
487                    document_type,
488                } = document_operations;
489
490                let mut drive_operations = vec![];
491                for document_operation in operations {
492                    match document_operation {
493                        DocumentOperation::AddOperation {
494                            owned_document_info,
495                            override_document,
496                        } => {
497                            let document_and_contract_info = DocumentAndContractInfo {
498                                owned_document_info,
499                                contract,
500                                document_type,
501                            };
502                            let mut operations = drive
503                                .add_document_for_contract_operations_without_ttl_drain(
504                                    document_and_contract_info,
505                                    override_document,
506                                    block_info,
507                                    &mut Some(&mut drive_operations),
508                                    estimated_costs_only_with_layer_info,
509                                    transaction,
510                                    platform_version,
511                                )?;
512                            drive_operations.append(&mut operations);
513                        }
514                        DocumentOperation::UpdateOperation(update_operation) => {
515                            let UpdateOperationInfo {
516                                document,
517                                serialized_document,
518                                owner_id,
519                                storage_flags,
520                            } = update_operation;
521
522                            let document_info =
523                                if let Some(serialized_document) = serialized_document {
524                                    DocumentRefAndSerialization((
525                                        document,
526                                        serialized_document,
527                                        storage_flags,
528                                    ))
529                                } else {
530                                    DocumentRefInfo((document, storage_flags))
531                                };
532                            let document_and_contract_info = DocumentAndContractInfo {
533                                owned_document_info: OwnedDocumentInfo {
534                                    document_info,
535                                    owner_id,
536                                },
537                                contract,
538                                document_type,
539                            };
540                            let mut operations = drive
541                                .update_document_for_contract_operations_without_ttl_drain(
542                                    document_and_contract_info,
543                                    block_info,
544                                    &mut Some(&mut drive_operations),
545                                    estimated_costs_only_with_layer_info,
546                                    transaction,
547                                    platform_version,
548                                )?;
549                            drive_operations.append(&mut operations);
550                        }
551                    }
552                }
553                Ok(drive_operations)
554            }
555        }
556    }
557}
558
559/// A wrapper for an update operation
560#[derive(Clone, Debug)]
561pub struct UpdateOperationInfo<'a> {
562    /// The document to update
563    pub document: &'a Document,
564    /// The document in pre-serialized form
565    pub serialized_document: Option<&'a [u8]>,
566    /// The owner id, if none is specified will try to recover from serialized document
567    pub owner_id: Option<[u8; 32]>,
568    /// Add storage flags (like epoch, owner id, etc)
569    pub storage_flags: Option<Cow<'a, StorageFlags>>,
570}