use std::{collections::BTreeMap, sync::Arc};
use async_trait::async_trait;
use bytes::Bytes;
use melstructs::{BlockHeight, CoinID, CoinValue, Header, NetID, TxHash};
use mini_moka::sync::Cache;
use once_cell::sync::Lazy;
use parking_lot::RwLock;
use stdcode::StdcodeSerializeExt;
use tmelcrypt::{Ed25519PK, HashVal};
use crate::Substate;
pub(crate) static GLOBAL_CACHE: Lazy<RwLock<Arc<dyn StateCache>>> =
Lazy::new(|| RwLock::new(Arc::new(InMemoryStateCache::new(100_000_000))));
pub fn set_global_cache(cache: impl StateCache) {
*GLOBAL_CACHE.write() = Arc::new(cache);
}
pub struct InMemoryStateCache {
inner: Cache<Bytes, Bytes>,
}
impl InMemoryStateCache {
pub fn new(max_bytes: usize) -> Self {
Self {
inner: Cache::builder()
.max_capacity(max_bytes as u64)
.weigher(|k: &Bytes, v: &Bytes| (k.len() + v.len() + 10) as u32)
.build(),
}
}
}
#[async_trait]
impl StateCache for InMemoryStateCache {
async fn get_blob(&self, key: &[u8]) -> Option<Bytes> {
let key: Bytes = key.to_vec().into();
let res = self.inner.get(&key);
log::debug!("memcache: {:?} hit? {}", key, res.is_some());
res
}
async fn insert_blob(&self, key: &[u8], value: &[u8]) {
self.inner
.insert(Bytes::copy_from_slice(key), Bytes::copy_from_slice(value));
}
}
#[async_trait]
pub trait StateCache: Send + Sync + 'static {
async fn get_blob(&self, key: &[u8]) -> Option<Bytes>;
async fn insert_blob(&self, key: &[u8], value: &[u8]);
async fn get_header(&self, network: NetID, height: BlockHeight) -> Option<Header> {
stdcode::deserialize(
&self
.get_blob(&("header", network, height).stdcode())
.await?,
)
.ok()
}
async fn get_staker_votes(&self, epoch: u64) -> Option<BTreeMap<Ed25519PK, CoinValue>> {
stdcode::deserialize(&self.get_blob(&("staker_votes", epoch).stdcode()).await?).ok()
}
async fn insert_staker_votes(&self, epoch: u64, votes: BTreeMap<Ed25519PK, CoinValue>) {
self.insert_blob(&("staker_votes", epoch).stdcode(), &votes.stdcode())
.await;
}
async fn get_spend_location(&self, coin: CoinID) -> Option<(TxHash, BlockHeight)> {
stdcode::deserialize(&self.get_blob(&("spend_location", coin).stdcode()).await?).ok()
}
async fn insert_spend_location(&self, coin: CoinID, txhash: TxHash, height: BlockHeight) {
self.insert_blob(
&("spend_location", coin).stdcode(),
&(txhash, height).stdcode(),
)
.await;
}
async fn insert_header(&self, network: NetID, height: BlockHeight, header: Header) {
self.insert_blob(&("header", network, height).stdcode(), &header.stdcode())
.await;
}
async fn get_smt_branch(
&self,
header_hash: HashVal,
tree: Substate,
branch: HashVal,
) -> Option<Bytes> {
self.get_blob(&("smt", header_hash, tree, branch).stdcode())
.await
}
async fn insert_smt_branch(
&self,
header_hash: HashVal,
tree: Substate,
branch: HashVal,
value: &[u8],
) {
self.insert_blob(&("smt", header_hash, tree, branch).stdcode(), value)
.await
}
}