Skip to main content

iota_types/storage/
mod.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5pub mod error;
6mod object_store_trait;
7mod read_store;
8mod shared_in_memory_store;
9mod write_store;
10
11use std::{
12    cell::RefCell,
13    collections::BTreeMap,
14    fmt::{Display, Formatter},
15    rc::Rc,
16    sync::Arc,
17};
18
19use iota_sdk_types::{
20    ObjectId, ObjectReference, SenderSignedTransaction, TransactionDigest, TransactionEffects,
21    Version, move_package::MovePackage,
22};
23use itertools::Itertools;
24use move_binary_format::CompiledModule;
25use move_core_types::language_storage::ModuleId;
26pub use object_store_trait::ObjectStore;
27pub use read_store::{
28    AccountOwnedObjectInfo, CoinInfo, DynamicFieldIteratorItem, DynamicFieldKey, EpochInfo,
29    EpochInfoV1Entry, EpochInfoV2, OwnedObjectCursor, OwnedObjectIteratorItem, PackageVersionInfo,
30    PackageVersionIteratorItem, PackageVersionKey, ReadStore, TransactionInfo,
31};
32use serde::{Deserialize, Serialize};
33use serde_with::serde_as;
34pub use shared_in_memory_store::{SharedInMemoryStore, SingleCheckpointSharedInMemoryStore};
35pub use write_store::WriteStore;
36
37use crate::{
38    auth_context::AuthContext,
39    base_types::VersionNumber,
40    committee::EpochId,
41    effects::{TransactionEffectsAPI, TransactionEffectsExt},
42    error::{ExecutionError, IotaError, IotaResult},
43    execution::{DynamicallyLoadedObjectMetadata, ExecutionResults},
44    iota_sdk_types_conversions::identifier_core_to_sdk,
45    object::Object,
46    storage::error::Error as StorageError,
47    transaction::{SenderSignedTransactionAPI, TransactionAPI},
48};
49
50/// A potential input to a transaction.
51#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
52pub enum InputKey {
53    VersionedObject { id: ObjectId, version: Version },
54    Package { id: ObjectId },
55}
56
57impl InputKey {
58    pub fn id(&self) -> ObjectId {
59        match self {
60            InputKey::VersionedObject { id, .. } => *id,
61            InputKey::Package { id } => *id,
62        }
63    }
64
65    pub fn version(&self) -> Option<Version> {
66        match self {
67            InputKey::VersionedObject { version, .. } => Some(*version),
68            InputKey::Package { .. } => None,
69        }
70    }
71
72    pub fn is_cancelled(&self) -> bool {
73        match self {
74            InputKey::VersionedObject { version, .. } => version.is_canceled(),
75            InputKey::Package { .. } => false,
76        }
77    }
78}
79
80impl From<&Object> for InputKey {
81    fn from(obj: &Object) -> Self {
82        if obj.is_package() {
83            InputKey::Package { id: obj.id() }
84        } else {
85            InputKey::VersionedObject {
86                id: obj.id(),
87                version: obj.version(),
88            }
89        }
90    }
91}
92
93#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
94pub enum WriteKind {
95    /// The object was in storage already but has been modified
96    Mutate,
97    /// The object was created in this transaction
98    Create,
99    /// The object was previously wrapped in another object, but has been
100    /// restored to storage
101    Unwrap,
102}
103
104#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
105pub enum DeleteKind {
106    /// An object is provided in the call input, and gets deleted.
107    Normal,
108    /// An object is not provided in the call input, but gets unwrapped
109    /// from another object, and then gets deleted.
110    UnwrapThenDelete,
111    /// An object is provided in the call input, and gets wrapped into another
112    /// object.
113    Wrap,
114}
115
116#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
117pub enum MarkerValue {
118    /// An object was received at the given version in the transaction and is no
119    /// longer able to be received at that version in subsequent
120    /// transactions.
121    Received,
122    /// An owned object was deleted (or wrapped) at the given version, and is no
123    /// longer able to be accessed or used in subsequent transactions.
124    OwnedDeleted,
125    /// A shared object was deleted by the transaction and is no longer able to
126    /// be accessed or used in subsequent transactions.
127    SharedDeleted(TransactionDigest),
128}
129
130/// DeleteKind together with the old sequence number prior to the deletion, if
131/// available. For normal deletion and wrap, we always will consult the object
132/// store to obtain the old sequence number. For UnwrapThenDelete however,
133/// we will not consult the object store,
134/// and hence won't have the old sequence number.
135#[derive(Debug)]
136pub enum DeleteKindWithOldVersion {
137    Normal(Version),
138    UnwrapThenDelete,
139    Wrap(Version),
140}
141
142impl DeleteKindWithOldVersion {
143    pub fn old_version(&self) -> Option<Version> {
144        match self {
145            DeleteKindWithOldVersion::Normal(version) | DeleteKindWithOldVersion::Wrap(version) => {
146                Some(*version)
147            }
148            DeleteKindWithOldVersion::UnwrapThenDelete => None,
149        }
150    }
151
152    pub fn to_delete_kind(&self) -> DeleteKind {
153        match self {
154            DeleteKindWithOldVersion::Normal(_) => DeleteKind::Normal,
155            DeleteKindWithOldVersion::UnwrapThenDelete => DeleteKind::UnwrapThenDelete,
156            DeleteKindWithOldVersion::Wrap(_) => DeleteKind::Wrap,
157        }
158    }
159}
160
161#[derive(Debug)]
162pub enum ObjectChange {
163    Write(Object, WriteKind),
164    // DeleteKind together with the old sequence number prior to the deletion, if available.
165    Delete(DeleteKindWithOldVersion),
166}
167
168pub trait StorageView: Storage + ChildObjectResolver {}
169impl<T: Storage + ChildObjectResolver> StorageView for T {}
170
171/// An abstraction of the (possibly distributed) store for objects. This
172/// API only allows for the retrieval of objects, not any state changes
173pub trait ChildObjectResolver {
174    /// `child` must have an `Object` ownership equal to `parent`.
175    fn read_child_object(
176        &self,
177        parent: &ObjectId,
178        child: &ObjectId,
179        child_version_upper_bound: Version,
180    ) -> IotaResult<Option<Object>>;
181
182    /// `receiving_object_id` must have an `Address` ownership equal to
183    /// `owner`. `get_object_received_at_version` must be the exact version
184    /// at which the object will be received, and it cannot have been
185    /// previously received at that version. NB: An object not existing at
186    /// that version, and not having valid access to the object will be treated
187    /// exactly the same and `Ok(None)` must be returned.
188    fn get_object_received_at_version(
189        &self,
190        owner: &ObjectId,
191        receiving_object_id: &ObjectId,
192        receive_object_at_version: Version,
193        epoch_id: EpochId,
194    ) -> IotaResult<Option<Object>>;
195}
196
197pub struct DenyListResult {
198    /// Ok if all regulated coin owners are allowed.
199    /// Err if any regulated coin owner is denied (returning the error for first
200    /// one denied).
201    pub result: Result<(), ExecutionError>,
202    /// The number of non-gas-coin owners in the transaction results
203    pub num_non_gas_coin_owners: u64,
204}
205
206/// An abstraction of the (possibly distributed) store for objects, and (soon)
207/// events and transactions
208pub trait Storage {
209    fn reset(&mut self);
210
211    fn read_object(&self, id: &ObjectId) -> Option<&Object>;
212
213    fn record_execution_results(&mut self, results: ExecutionResults);
214
215    fn save_loaded_runtime_objects(
216        &mut self,
217        loaded_runtime_objects: BTreeMap<ObjectId, DynamicallyLoadedObjectMetadata>,
218    );
219
220    fn save_wrapped_object_containers(
221        &mut self,
222        wrapped_object_containers: BTreeMap<ObjectId, ObjectId>,
223    );
224
225    /// Check coin denylist during execution,
226    /// and the number of non-gas-coin owners.
227    fn check_coin_deny_list(&self, written_objects: &BTreeMap<ObjectId, Object>) -> DenyListResult;
228
229    fn read_auth_context(&self) -> Option<Rc<RefCell<AuthContext>>>;
230}
231
232pub type PackageFetchResults<Package> = Result<Vec<Package>, Vec<ObjectId>>;
233
234#[derive(Clone, Debug)]
235pub struct PackageObject {
236    package_object: Object,
237}
238
239impl PackageObject {
240    pub fn new(package_object: Object) -> Self {
241        assert!(package_object.is_package());
242        Self { package_object }
243    }
244
245    pub fn object(&self) -> &Object {
246        &self.package_object
247    }
248
249    pub fn move_package(&self) -> &MovePackage {
250        self.package_object.data.as_opt_package().unwrap()
251    }
252}
253
254impl From<PackageObject> for Object {
255    fn from(package_object_arc: PackageObject) -> Self {
256        package_object_arc.package_object
257    }
258}
259
260pub trait BackingPackageStore {
261    fn get_package_object(&self, package_id: &ObjectId) -> IotaResult<Option<PackageObject>>;
262}
263
264impl<S: ?Sized + BackingPackageStore> BackingPackageStore for Box<S> {
265    fn get_package_object(&self, package_id: &ObjectId) -> IotaResult<Option<PackageObject>> {
266        BackingPackageStore::get_package_object(self.as_ref(), package_id)
267    }
268}
269
270impl<S: ?Sized + BackingPackageStore> BackingPackageStore for Arc<S> {
271    fn get_package_object(&self, package_id: &ObjectId) -> IotaResult<Option<PackageObject>> {
272        BackingPackageStore::get_package_object(self.as_ref(), package_id)
273    }
274}
275
276impl<S: ?Sized + BackingPackageStore> BackingPackageStore for &S {
277    fn get_package_object(&self, package_id: &ObjectId) -> IotaResult<Option<PackageObject>> {
278        BackingPackageStore::get_package_object(*self, package_id)
279    }
280}
281
282impl<S: ?Sized + BackingPackageStore> BackingPackageStore for &mut S {
283    fn get_package_object(&self, package_id: &ObjectId) -> IotaResult<Option<PackageObject>> {
284        BackingPackageStore::get_package_object(*self, package_id)
285    }
286}
287
288pub fn load_package_object_from_object_store(
289    store: &impl ObjectStore,
290    package_id: &ObjectId,
291) -> IotaResult<Option<PackageObject>> {
292    let package = store.try_get_object(package_id)?;
293    if let Some(obj) = &package {
294        fp_ensure!(
295            obj.is_package(),
296            IotaError::BadObjectType {
297                error: format!("Package expected, Move object found: {package_id}"),
298            }
299        );
300    }
301    Ok(package.map(PackageObject::new))
302}
303
304/// Returns Ok(<package object for each package id in `package_ids`>) if all
305/// package IDs in `package_id` were found. If any package in `package_ids` was
306/// not found it returns a list of any package ids that are unable to be
307/// found>).
308pub fn get_package_objects<'a>(
309    store: &impl BackingPackageStore,
310    package_ids: impl IntoIterator<Item = &'a ObjectId>,
311) -> IotaResult<PackageFetchResults<PackageObject>> {
312    let packages: Vec<Result<_, _>> = package_ids
313        .into_iter()
314        .map(|id| match store.get_package_object(id) {
315            Ok(None) => Ok(Err(*id)),
316            Ok(Some(o)) => Ok(Ok(o)),
317            Err(x) => Err(x),
318        })
319        .collect::<IotaResult<_>>()?;
320
321    let (fetched, failed_to_fetch): (Vec<_>, Vec<_>) = packages.into_iter().partition_result();
322    if !failed_to_fetch.is_empty() {
323        Ok(Err(failed_to_fetch))
324    } else {
325        Ok(Ok(fetched))
326    }
327}
328
329pub fn get_module(
330    store: impl BackingPackageStore,
331    module_id: &ModuleId,
332) -> Result<Option<Vec<u8>>, IotaError> {
333    Ok(store
334        .get_package_object(&ObjectId::new(module_id.address().into_bytes()))?
335        .and_then(|package| {
336            package
337                .move_package()
338                .serialized_module_map()
339                .get(&identifier_core_to_sdk(module_id.name()))
340                .cloned()
341        }))
342}
343
344pub fn get_module_by_id<S: BackingPackageStore>(
345    store: &S,
346    id: &ModuleId,
347) -> anyhow::Result<Option<CompiledModule>, IotaError> {
348    Ok(get_module(store, id)?
349        .map(|bytes| CompiledModule::deserialize_with_defaults(&bytes).unwrap()))
350}
351
352/// A `BackingPackageStore` that resolves packages from a backing store, but
353/// also includes any packages that were published in the current transaction
354/// execution. This can be used to resolve Move modules right after transaction
355/// execution, but newly published packages have not yet been committed to the
356/// backing store on a fullnode.
357pub struct PostExecutionPackageResolver {
358    backing_store: Arc<dyn BackingPackageStore>,
359    new_packages: BTreeMap<ObjectId, PackageObject>,
360}
361
362impl PostExecutionPackageResolver {
363    pub fn new(
364        backing_store: Arc<dyn BackingPackageStore>,
365        output_objects: &Option<Vec<Object>>,
366    ) -> Self {
367        let new_packages = output_objects
368            .iter()
369            .flatten()
370            .filter_map(|o| {
371                if o.is_package() {
372                    Some((o.id(), PackageObject::new(o.clone())))
373                } else {
374                    None
375                }
376            })
377            .collect();
378        Self {
379            backing_store,
380            new_packages,
381        }
382    }
383}
384
385impl BackingPackageStore for PostExecutionPackageResolver {
386    fn get_package_object(&self, package_id: &ObjectId) -> IotaResult<Option<PackageObject>> {
387        if let Some(package) = self.new_packages.get(package_id) {
388            Ok(Some(package.clone()))
389        } else {
390            self.backing_store.get_package_object(package_id)
391        }
392    }
393}
394
395impl<S: ChildObjectResolver> ChildObjectResolver for std::sync::Arc<S> {
396    fn read_child_object(
397        &self,
398        parent: &ObjectId,
399        child: &ObjectId,
400        child_version_upper_bound: Version,
401    ) -> IotaResult<Option<Object>> {
402        ChildObjectResolver::read_child_object(
403            self.as_ref(),
404            parent,
405            child,
406            child_version_upper_bound,
407        )
408    }
409    fn get_object_received_at_version(
410        &self,
411        owner: &ObjectId,
412        receiving_object_id: &ObjectId,
413        receive_object_at_version: Version,
414        epoch_id: EpochId,
415    ) -> IotaResult<Option<Object>> {
416        ChildObjectResolver::get_object_received_at_version(
417            self.as_ref(),
418            owner,
419            receiving_object_id,
420            receive_object_at_version,
421            epoch_id,
422        )
423    }
424}
425
426impl<S: ChildObjectResolver> ChildObjectResolver for &S {
427    fn read_child_object(
428        &self,
429        parent: &ObjectId,
430        child: &ObjectId,
431        child_version_upper_bound: Version,
432    ) -> IotaResult<Option<Object>> {
433        ChildObjectResolver::read_child_object(*self, parent, child, child_version_upper_bound)
434    }
435    fn get_object_received_at_version(
436        &self,
437        owner: &ObjectId,
438        receiving_object_id: &ObjectId,
439        receive_object_at_version: Version,
440        epoch_id: EpochId,
441    ) -> IotaResult<Option<Object>> {
442        ChildObjectResolver::get_object_received_at_version(
443            *self,
444            owner,
445            receiving_object_id,
446            receive_object_at_version,
447            epoch_id,
448        )
449    }
450}
451
452impl<S: ChildObjectResolver> ChildObjectResolver for &mut S {
453    fn read_child_object(
454        &self,
455        parent: &ObjectId,
456        child: &ObjectId,
457        child_version_upper_bound: Version,
458    ) -> IotaResult<Option<Object>> {
459        ChildObjectResolver::read_child_object(*self, parent, child, child_version_upper_bound)
460    }
461    fn get_object_received_at_version(
462        &self,
463        owner: &ObjectId,
464        receiving_object_id: &ObjectId,
465        receive_object_at_version: Version,
466        epoch_id: EpochId,
467    ) -> IotaResult<Option<Object>> {
468        ChildObjectResolver::get_object_received_at_version(
469            *self,
470            owner,
471            receiving_object_id,
472            receive_object_at_version,
473            epoch_id,
474        )
475    }
476}
477
478// The primary key type for object storage.
479#[serde_as]
480#[derive(Eq, PartialEq, Clone, Copy, PartialOrd, Ord, Hash, Serialize, Deserialize, Debug)]
481pub struct ObjectKey(pub ObjectId, pub VersionNumber);
482
483impl ObjectKey {
484    pub const ZERO: ObjectKey = ObjectKey(ObjectId::ZERO, VersionNumber::MIN_VALID_INCL);
485
486    pub fn max_for_id(id: &ObjectId) -> Self {
487        Self(*id, VersionNumber::MAX_VALID_EXCL)
488    }
489
490    pub fn min_for_id(id: &ObjectId) -> Self {
491        Self(*id, VersionNumber::MIN_VALID_INCL)
492    }
493}
494
495impl From<ObjectReference> for ObjectKey {
496    fn from(object_ref: ObjectReference) -> Self {
497        ObjectKey::from(&object_ref)
498    }
499}
500
501impl From<&ObjectReference> for ObjectKey {
502    fn from(object_ref: &ObjectReference) -> Self {
503        Self(object_ref.object_id, object_ref.version)
504    }
505}
506
507#[derive(Clone)]
508pub enum ObjectOrTombstone {
509    Object(Object),
510    Tombstone(ObjectReference),
511}
512
513impl ObjectOrTombstone {
514    pub fn as_objref(&self) -> ObjectReference {
515        match self {
516            ObjectOrTombstone::Object(obj) => obj.object_ref(),
517            ObjectOrTombstone::Tombstone(obref) => *obref,
518        }
519    }
520}
521
522impl From<Object> for ObjectOrTombstone {
523    fn from(object: Object) -> Self {
524        ObjectOrTombstone::Object(object)
525    }
526}
527
528/// Fetch the `ObjectKey`s (IDs and versions) for non-shared input objects.
529/// Includes owned, and immutable objects as well as the gas objects, but not
530/// move packages or shared objects.
531pub fn transaction_non_shared_input_object_keys(
532    tx: &SenderSignedTransaction,
533) -> IotaResult<Vec<ObjectKey>> {
534    use crate::transaction::InputObjectKind as I;
535    Ok(tx
536        .input_objects()?
537        .into_iter()
538        .filter_map(|object| match object {
539            I::MovePackage(_) | I::SharedMoveObject { .. } => None,
540            I::ImmOrOwnedMoveObject(obj) => Some(obj.into()),
541        })
542        .collect())
543}
544
545pub fn transaction_receiving_object_keys(tx: &SenderSignedTransaction) -> Vec<ObjectKey> {
546    tx.transaction()
547        .receiving_objects()
548        .into_iter()
549        .map(|oref| oref.into())
550        .collect()
551}
552
553impl Display for DeleteKind {
554    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
555        match self {
556            DeleteKind::Wrap => write!(f, "Wrap"),
557            DeleteKind::Normal => write!(f, "Normal"),
558            DeleteKind::UnwrapThenDelete => write!(f, "UnwrapThenDelete"),
559        }
560    }
561}
562
563pub trait BackingStore: BackingPackageStore + ChildObjectResolver + ObjectStore {
564    fn as_object_store(&self) -> &dyn ObjectStore;
565}
566
567impl<T> BackingStore for T
568where
569    T: BackingPackageStore,
570    T: ChildObjectResolver,
571    T: ObjectStore,
572{
573    fn as_object_store(&self) -> &dyn ObjectStore {
574        self
575    }
576}
577
578pub fn get_transaction_input_objects(
579    object_store: &dyn ObjectStore,
580    effects: &TransactionEffects,
581) -> Result<Vec<Object>, StorageError> {
582    let input_object_keys = effects
583        .modified_at_versions()
584        .into_iter()
585        .map(|(object_id, version)| ObjectKey(object_id, version))
586        .collect::<Vec<_>>();
587
588    let input_objects = object_store
589        .multi_get_objects_by_key(&input_object_keys)
590        .into_iter()
591        .enumerate()
592        .map(|(idx, maybe_object)| {
593            maybe_object.ok_or_else(|| {
594                StorageError::missing(format!(
595                    "missing input object key {:?} from tx {}",
596                    input_object_keys[idx],
597                    effects.transaction_digest()
598                ))
599            })
600        })
601        .collect::<Result<Vec<_>, _>>()?;
602    Ok(input_objects)
603}
604
605pub fn get_transaction_output_objects(
606    object_store: &dyn ObjectStore,
607    effects: &TransactionEffects,
608) -> Result<Vec<Object>, StorageError> {
609    let output_object_keys = effects
610        .all_changed_objects()
611        .into_iter()
612        .map(|(object_ref, _owner, _kind)| ObjectKey::from(object_ref))
613        .collect::<Vec<_>>();
614
615    let output_objects = object_store
616        .multi_get_objects_by_key(&output_object_keys)
617        .into_iter()
618        .enumerate()
619        .map(|(idx, maybe_object)| {
620            maybe_object.ok_or_else(|| {
621                StorageError::missing(format!(
622                    "missing output object key {:?} from tx {}",
623                    output_object_keys[idx],
624                    effects.transaction_digest()
625                ))
626            })
627        })
628        .collect::<Result<Vec<_>, _>>()?;
629    Ok(output_objects)
630}
631
632/// Extend a simulation's input objects with the runtime-loaded objects the
633/// effects record as modified, read from `object_store` at their pre-state
634/// versions.
635pub fn extend_input_objects_with_loaded_runtime_objects(
636    input_objects: &mut BTreeMap<ObjectId, Object>,
637    effects: &TransactionEffects,
638    loaded_runtime_objects: &BTreeMap<ObjectId, DynamicallyLoadedObjectMetadata>,
639    object_store: &dyn ObjectStore,
640) {
641    let modified_at: BTreeMap<_, _> = effects.modified_at_versions().into_iter().collect();
642    for (id, metadata) in loaded_runtime_objects {
643        if input_objects.contains_key(id) || modified_at.get(id) != Some(&metadata.version) {
644            continue;
645        }
646        if let Some(object) = object_store.get_object_by_key(id, metadata.version) {
647            input_objects.insert(*id, object);
648        }
649    }
650}