melprot/
cache.rs

1use std::{collections::BTreeMap, sync::Arc};
2
3use async_trait::async_trait;
4use bytes::Bytes;
5
6use melstructs::{BlockHeight, CoinID, CoinValue, Header, NetID, TxHash};
7use mini_moka::sync::Cache;
8use once_cell::sync::Lazy;
9use parking_lot::RwLock;
10
11use stdcode::StdcodeSerializeExt;
12use tmelcrypt::{Ed25519PK, HashVal};
13
14use crate::Substate;
15
16/// Global cache
17pub(crate) static GLOBAL_CACHE: Lazy<RwLock<Arc<dyn StateCache>>> =
18    Lazy::new(|| RwLock::new(Arc::new(InMemoryStateCache::new(100_000_000))));
19
20/// Sets the global state cache. Only affects [crate::Client] instances that are created after this point!
21pub fn set_global_cache(cache: impl StateCache) {
22    *GLOBAL_CACHE.write() = Arc::new(cache);
23}
24
25/// An in-memory state cache.
26pub struct InMemoryStateCache {
27    inner: Cache<Bytes, Bytes>,
28}
29
30impl InMemoryStateCache {
31    /// Creates a new in-memory state cache with the given maximum size, in bytes.
32    pub fn new(max_bytes: usize) -> Self {
33        Self {
34            inner: Cache::builder()
35                .max_capacity(max_bytes as u64)
36                .weigher(|k: &Bytes, v: &Bytes| (k.len() + v.len() + 10) as u32)
37                .build(),
38        }
39    }
40}
41
42#[async_trait]
43impl StateCache for InMemoryStateCache {
44    async fn get_blob(&self, key: &[u8]) -> Option<Bytes> {
45        let key: Bytes = key.to_vec().into();
46        let res = self.inner.get(&key);
47        log::debug!("memcache: {:?} hit? {}", key, res.is_some());
48        res
49    }
50
51    async fn insert_blob(&self, key: &[u8], value: &[u8]) {
52        self.inner
53            .insert(Bytes::copy_from_slice(key), Bytes::copy_from_slice(value));
54    }
55}
56
57/// A trait that abstracts over a key-value cache for verified on-chain information. Only the "blob" methods are mandatory to implement.
58#[async_trait]
59pub trait StateCache: Send + Sync + 'static {
60    /// Gets an arbitrary blob of data from the cache.
61    async fn get_blob(&self, key: &[u8]) -> Option<Bytes>;
62
63    /// Inserts an arbitrary blob of data into the cache.
64    async fn insert_blob(&self, key: &[u8], value: &[u8]);
65
66    /// Gets a historical header from the cache.
67    async fn get_header(&self, network: NetID, height: BlockHeight) -> Option<Header> {
68        stdcode::deserialize(
69            &self
70                .get_blob(&("header", network, height).stdcode())
71                .await?,
72        )
73        .ok()
74    }
75
76    /// Get the *live* staker voting set for a particular epoch.
77    async fn get_staker_votes(&self, epoch: u64) -> Option<BTreeMap<Ed25519PK, CoinValue>> {
78        stdcode::deserialize(&self.get_blob(&("staker_votes", epoch).stdcode()).await?).ok()
79    }
80
81    /// Inserts the *live* staker voting set for a particular epoch.
82    async fn insert_staker_votes(&self, epoch: u64, votes: BTreeMap<Ed25519PK, CoinValue>) {
83        self.insert_blob(&("staker_votes", epoch).stdcode(), &votes.stdcode())
84            .await;
85    }
86
87    /// Gets a coin-spend status, for a *spent* coin, from the cache.
88    async fn get_spend_location(&self, coin: CoinID) -> Option<(TxHash, BlockHeight)> {
89        stdcode::deserialize(&self.get_blob(&("spend_location", coin).stdcode()).await?).ok()
90    }
91
92    /// Inserts a coin-spend status, for a *spent* coin, into the cache.
93    async fn insert_spend_location(&self, coin: CoinID, txhash: TxHash, height: BlockHeight) {
94        self.insert_blob(
95            &("spend_location", coin).stdcode(),
96            &(txhash, height).stdcode(),
97        )
98        .await;
99    }
100
101    /// Inserts a historical header from the cache.
102    async fn insert_header(&self, network: NetID, height: BlockHeight, header: Header) {
103        self.insert_blob(&("header", network, height).stdcode(), &header.stdcode())
104            .await;
105    }
106
107    /// Gets an SMT branch from the cache.
108    async fn get_smt_branch(
109        &self,
110        header_hash: HashVal,
111        tree: Substate,
112        branch: HashVal,
113    ) -> Option<Bytes> {
114        self.get_blob(&("smt", header_hash, tree, branch).stdcode())
115            .await
116    }
117
118    /// Inserts an SMT branch into the cache.
119    async fn insert_smt_branch(
120        &self,
121        header_hash: HashVal,
122        tree: Substate,
123        branch: HashVal,
124        value: &[u8],
125    ) {
126        self.insert_blob(&("smt", header_hash, tree, branch).stdcode(), value)
127            .await
128    }
129}