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    /// Adds a document and deletes the documents its create consumed: the commitments a
98    /// `refersTo` lookup found for a reference declaring `consume` (protocol version 14),
99    /// each a document of the same contract. One conversion builds the add first and then
100    /// each delete against the operations already built, so a tree the new document writes
101    /// into (an index bucket shared with a consumed document, an expirations tree of the
102    /// same millisecond) is not taken for empty and deleted from under the insert.
103    AddDocumentAndDeleteConsumed {
104        /// The document and contract info, also may contain the owner_id
105        owned_document_info: OwnedDocumentInfo<'a>,
106        /// Data Contract info to potentially be resolved if needed
107        contract_info: DataContractInfo<'a>,
108        /// Document type
109        document_type_info: DocumentTypeInfo<'a>,
110        /// The consumed documents, by id and document type name
111        consumed_documents: Vec<(Identifier, String)>,
112    },
113    /// Deletes a document
114    DeleteDocument {
115        /// The document id
116        document_id: Identifier,
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    /// Deletes a document without consulting `canBeDeleted`, which rules what the document's
123    /// own owner may do. Used for a deletion on behalf of the contract's moderators (the
124    /// caller has checked that the document type sets `moderatorAbilities.delete`), for a
125    /// document a contested create consumes (the consume rules admitted it, protocol version
126    /// 14), and for the platform's deletion of a document whose type declares a `ttl` once it
127    /// has passed (protocol version 14), which also removes the document's expirations tree
128    /// entry. A document type that keeps history is still refused, as both keywords are on
129    /// such a type.
130    ForceDeleteDocument {
131        /// The document id
132        document_id: Identifier,
133        /// Data Contract info to potentially be resolved if needed
134        contract_info: DataContractInfo<'a>,
135        /// Document type
136        document_type_info: DocumentTypeInfo<'a>,
137    },
138    /// Deletes an indexOnly document from its property values — there is
139    /// no primary-storage row to fetch, so the values (plus the owner)
140    /// are what every index entry is recomputed from. `$createdAt` may
141    /// ride in `data` under its system key when the type indexes it.
142    DeleteIndexOnlyDocument {
143        /// The document id (deterministic; never stored)
144        document_id: Identifier,
145        /// The owner whose entries are being removed
146        owner_id: Identifier,
147        /// The document's property values
148        data: BTreeMap<String, Value>,
149        /// Data Contract info to potentially be resolved if needed
150        contract_info: DataContractInfo<'a>,
151        /// Document type
152        document_type_info: DocumentTypeInfo<'a>,
153    },
154    /// Convenience method to add a withdrawal document.
155    AddWithdrawalDocument {
156        /// The document and contract info, also may contain the owner_id
157        owned_document_info: OwnedDocumentInfo<'a>,
158    },
159    /// Adds a document to a contract.
160    MultipleDocumentOperationsForSameContractDocumentType {
161        /// The document operations
162        document_operations: DocumentOperationsForContractDocumentType<'a>,
163    },
164    /// Adds a historical document to the document history system contract,
165    /// recording a transfer, purchase, or price update of a document whose
166    /// document type subscribed to history.
167    DocumentHistory {
168        /// The data contract of the source document
169        source_data_contract_id: Identifier,
170        /// The document type name of the source document
171        source_document_type_name: String,
172        /// The source document
173        source_document_id: Identifier,
174        /// The identity making the event
175        owner_id: Identifier,
176        /// The nonce
177        nonce: IdentityNonce,
178        /// The document event
179        event: DocumentEvent,
180    },
181}
182
183impl DriveLowLevelOperationConverter for DocumentOperationType<'_> {
184    fn into_low_level_drive_operations(
185        self,
186        drive: &Drive,
187        estimated_costs_only_with_layer_info: &mut Option<
188            HashMap<KeyInfoPath, EstimatedLayerInformation>,
189        >,
190        block_info: &BlockInfo,
191        transaction: TransactionArg,
192        platform_version: &PlatformVersion,
193    ) -> Result<Vec<LowLevelDriveOperation>, Error> {
194        if estimated_costs_only_with_layer_info.is_none() {
195            self.prepare_time_range_ttl(drive, block_info, transaction, platform_version)?;
196        }
197        self.into_low_level_drive_operations_after_ttl_drain(
198            drive,
199            estimated_costs_only_with_layer_info,
200            block_info,
201            transaction,
202            platform_version,
203        )
204    }
205}
206
207impl DocumentOperationType<'_> {
208    /// Run all direct TTL cleanup before any operation in this batch is built.
209    pub(crate) fn prepare_time_range_ttl(
210        &self,
211        drive: &Drive,
212        block_info: &BlockInfo,
213        transaction: TransactionArg,
214        platform_version: &PlatformVersion,
215    ) -> Result<(), Error> {
216        if platform_version
217            .system_limits
218            .min_time_range_ttl_drop_operations_per_write
219            .is_none()
220        {
221            return Ok(());
222        }
223        // Each document earns a drainage budget, but all budgets are spent
224        // before the first document's low-level ops are queued.
225        self.for_each_document_type(
226            drive,
227            block_info,
228            transaction,
229            |contract, document_type, _| {
230                drive.prepare_document_time_range_ttl(
231                    contract,
232                    document_type,
233                    block_info.time_ms,
234                    transaction,
235                    platform_version,
236                )
237            },
238            platform_version,
239        )
240    }
241
242    /// Calls `visit` with the contract and document type of every document
243    /// this operation writes or deletes, once per document, the consumed
244    /// documents of an `AddDocumentAndDeleteConsumed` included, and whether
245    /// the write may preallocate referring types' trees: a plain insert, the
246    /// one write that does (a contested insert never does; an overriding insert
247    /// that turns out an update is counted conservatively). Withdrawals and
248    /// document history write system types and are not visited. Resolution reads are unbilled
249    /// maintenance; normal conversion still resolves and bills the contract
250    /// through its usual path.
251    pub(crate) fn for_each_document_type(
252        &self,
253        drive: &Drive,
254        block_info: &BlockInfo,
255        transaction: TransactionArg,
256        mut visit: impl FnMut(&DataContract, DocumentTypeRef, bool) -> Result<(), Error>,
257        platform_version: &PlatformVersion,
258    ) -> Result<(), Error> {
259        match self {
260            Self::AddDocument {
261                contract_info,
262                document_type_info,
263                ..
264            }
265            | Self::AddContestedDocument {
266                contract_info,
267                document_type_info,
268                ..
269            }
270            | Self::UpdateDocument {
271                contract_info,
272                document_type_info,
273                ..
274            }
275            | Self::DeleteDocument {
276                contract_info,
277                document_type_info,
278                ..
279            }
280            | Self::ForceDeleteDocument {
281                contract_info,
282                document_type_info,
283                ..
284            }
285            | Self::DeleteIndexOnlyDocument {
286                contract_info,
287                document_type_info,
288                ..
289            } => {
290                let resolved = contract_info.clone().resolve(
291                    drive,
292                    block_info,
293                    transaction,
294                    &mut vec![],
295                    platform_version,
296                )?;
297                let contract = resolved.as_ref();
298                visit(
299                    contract,
300                    document_type_info.clone().resolve(contract)?,
301                    matches!(self, Self::AddDocument { .. }),
302                )
303            }
304            Self::MultipleDocumentOperationsForSameContractDocumentType {
305                document_operations,
306            } => {
307                for operation in &document_operations.operations {
308                    visit(
309                        document_operations.contract,
310                        document_operations.document_type,
311                        matches!(operation, DocumentOperation::AddOperation { .. }),
312                    )?;
313                }
314                Ok(())
315            }
316            // The new document's type and every consumed document's type
317            Self::AddDocumentAndDeleteConsumed {
318                contract_info,
319                document_type_info,
320                consumed_documents,
321                ..
322            } => {
323                let resolved = contract_info.clone().resolve(
324                    drive,
325                    block_info,
326                    transaction,
327                    &mut vec![],
328                    platform_version,
329                )?;
330                let contract = resolved.as_ref();
331                visit(
332                    contract,
333                    document_type_info.clone().resolve(contract)?,
334                    true,
335                )?;
336                for (_, document_type_name) in consumed_documents {
337                    visit(
338                        contract,
339                        DocumentTypeInfo::DocumentTypeName(document_type_name.clone())
340                            .resolve(contract)?,
341                        false,
342                    )?;
343                }
344                Ok(())
345            }
346            // These write to system contracts.
347            Self::AddWithdrawalDocument { .. } | Self::DocumentHistory { .. } => Ok(()),
348        }
349    }
350
351    pub(crate) fn into_low_level_drive_operations_after_ttl_drain(
352        self,
353        drive: &Drive,
354        estimated_costs_only_with_layer_info: &mut Option<
355            HashMap<KeyInfoPath, EstimatedLayerInformation>,
356        >,
357        block_info: &BlockInfo,
358        transaction: TransactionArg,
359        platform_version: &PlatformVersion,
360    ) -> Result<Vec<LowLevelDriveOperation>, Error> {
361        match self {
362            DocumentOperationType::AddDocument {
363                owned_document_info,
364                contract_info,
365                document_type_info,
366                override_document,
367            } => {
368                let mut drive_operations: Vec<LowLevelDriveOperation> = vec![];
369                let contract_resolved_info = contract_info.resolve(
370                    drive,
371                    block_info,
372                    transaction,
373                    &mut drive_operations,
374                    platform_version,
375                )?;
376                let contract = contract_resolved_info.as_ref();
377                let document_type = document_type_info.resolve(contract)?;
378
379                let document_and_contract_info = DocumentAndContractInfo {
380                    owned_document_info,
381                    contract,
382                    document_type,
383                };
384                let mut operations = drive.add_document_for_contract_operations_without_ttl_drain(
385                    document_and_contract_info,
386                    override_document,
387                    block_info,
388                    &mut None,
389                    estimated_costs_only_with_layer_info,
390                    transaction,
391                    platform_version,
392                )?;
393                drive_operations.append(&mut operations);
394                Ok(drive_operations)
395            }
396            DocumentOperationType::AddContestedDocument {
397                owned_document_info,
398                contested_document_resource_vote_poll,
399                contract_info,
400                document_type_info,
401                insert_without_check,
402                also_insert_vote_poll_stored_info,
403            } => {
404                let mut drive_operations: Vec<LowLevelDriveOperation> = vec![];
405                let contract_resolved_info = contract_info.resolve(
406                    drive,
407                    block_info,
408                    transaction,
409                    &mut drive_operations,
410                    platform_version,
411                )?;
412                let contract = contract_resolved_info.as_ref();
413                let document_type = document_type_info.resolve(contract)?;
414
415                let document_and_contract_info = DocumentAndContractInfo {
416                    owned_document_info,
417                    contract,
418                    document_type,
419                };
420                let mut operations = drive.add_contested_document_for_contract_operations(
421                    document_and_contract_info,
422                    contested_document_resource_vote_poll,
423                    insert_without_check,
424                    block_info,
425                    also_insert_vote_poll_stored_info,
426                    &mut None,
427                    estimated_costs_only_with_layer_info,
428                    transaction,
429                    platform_version,
430                )?;
431                drive_operations.append(&mut operations);
432                Ok(drive_operations)
433            }
434            DocumentOperationType::AddWithdrawalDocument {
435                owned_document_info,
436            } => {
437                let contract = drive
438                    .cache
439                    .system_data_contracts
440                    .load_withdrawals(platform_version)?;
441
442                let document_type = contract
443                    .document_type_for_name(withdrawal::NAME)
444                    .map_err(ProtocolError::DataContractError)?;
445
446                let document_and_contract_info = DocumentAndContractInfo {
447                    owned_document_info,
448                    contract: &contract,
449                    document_type,
450                };
451                drive.add_document_for_contract_operations_without_ttl_drain(
452                    document_and_contract_info,
453                    false,
454                    block_info,
455                    &mut None,
456                    estimated_costs_only_with_layer_info,
457                    transaction,
458                    platform_version,
459                )
460            }
461            DocumentOperationType::UpdateDocument {
462                owned_document_info,
463                contract_info,
464                document_type_info,
465            } => {
466                let mut drive_operations = vec![];
467                let contract_resolved_info = contract_info.resolve(
468                    drive,
469                    block_info,
470                    transaction,
471                    &mut drive_operations,
472                    platform_version,
473                )?;
474                let contract = contract_resolved_info.as_ref();
475                let document_type = document_type_info.resolve(contract)?;
476
477                let document_and_contract_info = DocumentAndContractInfo {
478                    owned_document_info,
479                    contract,
480                    document_type,
481                };
482                let mut operations = drive
483                    .update_document_for_contract_operations_without_ttl_drain(
484                        document_and_contract_info,
485                        block_info,
486                        &mut None,
487                        estimated_costs_only_with_layer_info,
488                        transaction,
489                        platform_version,
490                    )?;
491                drive_operations.append(&mut operations);
492                Ok(drive_operations)
493            }
494            DocumentOperationType::DocumentHistory {
495                source_data_contract_id,
496                source_document_type_name,
497                source_document_id,
498                owner_id,
499                nonce,
500                event,
501            } => {
502                let batch_operations = drive.add_document_history_operations(
503                    source_data_contract_id,
504                    source_document_type_name.as_str(),
505                    source_document_id,
506                    owner_id,
507                    nonce,
508                    event,
509                    block_info,
510                    estimated_costs_only_with_layer_info,
511                    transaction,
512                    platform_version,
513                )?;
514                Ok(batch_operations)
515            }
516            DocumentOperationType::AddDocumentAndDeleteConsumed {
517                owned_document_info,
518                contract_info,
519                document_type_info,
520                consumed_documents,
521            } => {
522                let mut drive_operations: Vec<LowLevelDriveOperation> = vec![];
523                let contract_resolved_info = contract_info.resolve(
524                    drive,
525                    block_info,
526                    transaction,
527                    &mut drive_operations,
528                    platform_version,
529                )?;
530                let contract = contract_resolved_info.as_ref();
531                let document_type = document_type_info.resolve(contract)?;
532
533                let document_and_contract_info = DocumentAndContractInfo {
534                    owned_document_info,
535                    contract,
536                    document_type,
537                };
538                let mut operations = drive.add_document_for_contract_operations_without_ttl_drain(
539                    document_and_contract_info,
540                    false,
541                    block_info,
542                    &mut None,
543                    estimated_costs_only_with_layer_info,
544                    transaction,
545                    platform_version,
546                )?;
547                drive_operations.append(&mut operations);
548                // Each delete sees the add and the earlier deletes as pending: a tree
549                // holding a pending insert is not empty. A consumed document is deleted
550                // without its owner's `canBeDeleted` guard: the consume rules admitted it,
551                // and a type whose documents only a consume deletes
552                // (`canBeDeleted: "onlyWhenConsumed"`) refuses its owner's delete
553                for (document_id, document_type_name) in consumed_documents {
554                    let consumed_document_type =
555                        DocumentTypeInfo::DocumentTypeName(document_type_name).resolve(contract)?;
556                    let mut operations = drive.force_delete_document_for_contract_operations(
557                        document_id,
558                        contract,
559                        consumed_document_type,
560                        Some(&mut drive_operations),
561                        estimated_costs_only_with_layer_info,
562                        block_info.time_ms,
563                        transaction,
564                        platform_version,
565                    )?;
566                    drive_operations.append(&mut operations);
567                }
568                Ok(drive_operations)
569            }
570            DocumentOperationType::DeleteDocument {
571                document_id,
572                contract_info,
573                document_type_info,
574            } => {
575                let mut drive_operations: Vec<LowLevelDriveOperation> = vec![];
576                let contract_resolved_info = contract_info.resolve(
577                    drive,
578                    block_info,
579                    transaction,
580                    &mut drive_operations,
581                    platform_version,
582                )?;
583                let contract = contract_resolved_info.as_ref();
584                let document_type = document_type_info.resolve(contract)?;
585
586                drive.delete_document_for_contract_operations_without_ttl_drain(
587                    document_id,
588                    contract,
589                    document_type,
590                    None,
591                    estimated_costs_only_with_layer_info,
592                    block_info.time_ms,
593                    transaction,
594                    platform_version,
595                )
596            }
597            DocumentOperationType::ForceDeleteDocument {
598                document_id,
599                contract_info,
600                document_type_info,
601            } => {
602                let mut drive_operations: Vec<LowLevelDriveOperation> = vec![];
603                let contract_resolved_info = contract_info.resolve(
604                    drive,
605                    block_info,
606                    transaction,
607                    &mut drive_operations,
608                    platform_version,
609                )?;
610                let contract = contract_resolved_info.as_ref();
611                let document_type = document_type_info.resolve(contract)?;
612
613                // The deletion a document's own owner runs, without its `canBeDeleted` guard;
614                // it removes the expirations tree entry of a type with a `ttl` itself.
615                drive.force_delete_document_for_contract_operations(
616                    document_id,
617                    contract,
618                    document_type,
619                    None,
620                    estimated_costs_only_with_layer_info,
621                    block_info.time_ms,
622                    transaction,
623                    platform_version,
624                )
625            }
626            DocumentOperationType::DeleteIndexOnlyDocument {
627                document_id,
628                owner_id,
629                data,
630                contract_info,
631                document_type_info,
632            } => {
633                let mut drive_operations: Vec<LowLevelDriveOperation> = vec![];
634                let contract_resolved_info = contract_info.resolve(
635                    drive,
636                    block_info,
637                    transaction,
638                    &mut drive_operations,
639                    platform_version,
640                )?;
641                let contract = contract_resolved_info.as_ref();
642                let document_type = document_type_info.resolve(contract)?;
643
644                // Reconstruct the document the entries were written from.
645                let document = Drive::index_only_document_from_values(document_id, owner_id, data)?;
646
647                drive.delete_index_only_document_for_contract_operations_without_ttl_drain(
648                    document,
649                    contract,
650                    document_type,
651                    None,
652                    estimated_costs_only_with_layer_info,
653                    block_info.time_ms,
654                    transaction,
655                    platform_version,
656                )
657            }
658            DocumentOperationType::MultipleDocumentOperationsForSameContractDocumentType {
659                document_operations,
660            } => {
661                let DocumentOperationsForContractDocumentType {
662                    operations,
663                    contract,
664                    document_type,
665                } = document_operations;
666
667                let mut drive_operations = vec![];
668                for document_operation in operations {
669                    match document_operation {
670                        DocumentOperation::AddOperation {
671                            owned_document_info,
672                            override_document,
673                        } => {
674                            let document_and_contract_info = DocumentAndContractInfo {
675                                owned_document_info,
676                                contract,
677                                document_type,
678                            };
679                            let mut operations = drive
680                                .add_document_for_contract_operations_without_ttl_drain(
681                                    document_and_contract_info,
682                                    override_document,
683                                    block_info,
684                                    &mut Some(&mut drive_operations),
685                                    estimated_costs_only_with_layer_info,
686                                    transaction,
687                                    platform_version,
688                                )?;
689                            drive_operations.append(&mut operations);
690                        }
691                        DocumentOperation::UpdateOperation(update_operation) => {
692                            let UpdateOperationInfo {
693                                document,
694                                serialized_document,
695                                owner_id,
696                                storage_flags,
697                            } = update_operation;
698
699                            let document_info =
700                                if let Some(serialized_document) = serialized_document {
701                                    DocumentRefAndSerialization((
702                                        document,
703                                        serialized_document,
704                                        storage_flags,
705                                    ))
706                                } else {
707                                    DocumentRefInfo((document, storage_flags))
708                                };
709                            let document_and_contract_info = DocumentAndContractInfo {
710                                owned_document_info: OwnedDocumentInfo {
711                                    document_info,
712                                    owner_id,
713                                },
714                                contract,
715                                document_type,
716                            };
717                            let mut operations = drive
718                                .update_document_for_contract_operations_without_ttl_drain(
719                                    document_and_contract_info,
720                                    block_info,
721                                    &mut Some(&mut drive_operations),
722                                    estimated_costs_only_with_layer_info,
723                                    transaction,
724                                    platform_version,
725                                )?;
726                            drive_operations.append(&mut operations);
727                        }
728                    }
729                }
730                Ok(drive_operations)
731            }
732        }
733    }
734}
735
736/// A wrapper for an update operation
737#[derive(Clone, Debug)]
738pub struct UpdateOperationInfo<'a> {
739    /// The document to update
740    pub document: &'a Document,
741    /// The document in pre-serialized form
742    pub serialized_document: Option<&'a [u8]>,
743    /// The owner id, if none is specified will try to recover from serialized document
744    pub owner_id: Option<[u8; 32]>,
745    /// Add storage flags (like epoch, owner id, etc)
746    pub storage_flags: Option<Cow<'a, StorageFlags>>,
747}