iota_storage/
package_object_cache.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{num::NonZeroUsize, sync::Arc};
6
7use iota_types::{
8    base_types::ObjectID,
9    error::{IotaError, IotaResult, UserInputError},
10    storage::{ObjectStore, PackageObject},
11};
12use lru::LruCache;
13use parking_lot::RwLock;
14
15pub struct PackageObjectCache {
16    cache: RwLock<LruCache<ObjectID, PackageObject>>,
17}
18
19const CACHE_CAP: usize = 1024 * 1024;
20
21impl PackageObjectCache {
22    pub fn new() -> Arc<Self> {
23        Arc::new(Self {
24            cache: RwLock::new(LruCache::new(NonZeroUsize::new(CACHE_CAP).unwrap())),
25        })
26    }
27
28    pub fn get_package_object(
29        &self,
30        package_id: &ObjectID,
31        store: &impl ObjectStore,
32    ) -> IotaResult<Option<PackageObject>> {
33        // TODO: Here the use of `peek` doesn't update the internal use record,
34        // and hence the LRU is really used as a capped map here.
35        // This is OK because we won't typically have too many entries.
36        // We cannot use `get` here because it requires a mut reference and that would
37        // require unnecessary lock contention on the mutex, which defeats the purpose.
38        if let Some(p) = self.cache.read().peek(package_id) {
39            #[cfg(debug_assertions)]
40            {
41                assert_eq!(
42                    store.get_object(package_id).unwrap().unwrap().digest(),
43                    p.object().digest(),
44                    "Package object cache is inconsistent for package {:?}",
45                    package_id
46                )
47            }
48            return Ok(Some(p.clone()));
49        }
50        if let Some(p) = store.get_object(package_id)? {
51            if p.is_package() {
52                let p = PackageObject::new(p);
53                self.cache.write().push(*package_id, p.clone());
54                Ok(Some(p))
55            } else {
56                Err(IotaError::UserInput {
57                    error: UserInputError::MoveObjectAsPackage {
58                        object_id: *package_id,
59                    },
60                })
61            }
62        } else {
63            Ok(None)
64        }
65    }
66
67    pub fn force_reload_system_packages(
68        &self,
69        system_package_ids: impl IntoIterator<Item = ObjectID>,
70        store: &impl ObjectStore,
71    ) {
72        for package_id in system_package_ids {
73            if let Some(p) = store
74                .get_object(&package_id)
75                .expect("Failed to update system packages")
76            {
77                assert!(p.is_package());
78                self.cache.write().push(package_id, PackageObject::new(p));
79            }
80            // It's possible that a package is not found if it's newly added
81            // system package ID that hasn't got created yet. This
82            // should be very very rare though.
83        }
84    }
85}