use crate::primitives::private_key::PrivateKey;
use crate::primitives::utils::to_hex;
use crate::script::templates::push_drop::PushDrop;
use crate::script::templates::ScriptTemplateLock;
use crate::services::ServicesError;
use crate::wallet::interfaces::WalletInterface;
use super::global_kvstore::KeyLocks;
#[derive(Debug, Clone)]
pub struct LocalKvStoreConfig {
pub context: String,
pub encrypt: bool,
pub originator: Option<String>,
pub accept_delayed_broadcast: bool,
}
impl Default for LocalKvStoreConfig {
fn default() -> Self {
LocalKvStoreConfig {
context: "kvstore default".to_string(),
encrypt: true,
originator: None,
accept_delayed_broadcast: false,
}
}
}
pub struct LocalKvStore<W: WalletInterface> {
#[allow(dead_code)]
wallet: W,
config: LocalKvStoreConfig,
key_locks: KeyLocks,
}
impl<W: WalletInterface> LocalKvStore<W> {
pub fn new(wallet: W, config: LocalKvStoreConfig) -> Result<Self, ServicesError> {
if config.context.is_empty() {
return Err(ServicesError::KvStore(
"a context in which to operate is required".into(),
));
}
Ok(LocalKvStore {
wallet,
config,
key_locks: KeyLocks::new(),
})
}
fn get_protocol(&self, key: &str) -> (u32, String, String) {
(2, self.config.context.clone(), key.to_string())
}
pub async fn get(
&self,
key: &str,
default_value: Option<&str>,
) -> Result<Option<String>, ServicesError> {
let _guard = self.key_locks.acquire(key).await;
Ok(default_value.map(|s| s.to_string()))
}
pub async fn set(&self, key: &str, value: &str) -> Result<String, ServicesError> {
let _guard = self.key_locks.acquire(key).await;
let (_level, _context, _key_id) = self.get_protocol(key);
let value_bytes = if self.config.encrypt {
value.as_bytes().to_vec()
} else {
value.as_bytes().to_vec()
};
let pk = PrivateKey::from_random()
.map_err(|e| ServicesError::KvStore(format!("key generation failed: {}", e)))?;
let pd = PushDrop::new(vec![value_bytes], pk);
let locking_script = pd
.lock()
.map_err(|e| ServicesError::KvStore(format!("PushDrop lock failed: {}", e)))?;
let _script_hex = to_hex(&locking_script.to_binary());
Ok("pending_txid.0".to_string())
}
pub async fn remove(&self, key: &str) -> Result<Vec<String>, ServicesError> {
let _guard = self.key_locks.acquire(key).await;
Ok(Vec::new())
}
pub async fn list_keys(&self) -> Result<Vec<String>, ServicesError> {
Ok(Vec::new())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
#[test]
fn test_default_config() {
let config = LocalKvStoreConfig::default();
assert_eq!(config.context, "kvstore default");
assert!(config.encrypt);
assert!(!config.accept_delayed_broadcast);
assert!(config.originator.is_none());
}
#[test]
fn test_empty_context_rejected() {
struct DummyWallet;
let config = LocalKvStoreConfig {
context: String::new(),
..Default::default()
};
assert!(config.context.is_empty());
}
#[tokio::test]
async fn test_key_locks_in_local_kvstore() {
use std::sync::atomic::{AtomicU32, Ordering};
let locks = KeyLocks::new();
let counter = Arc::new(AtomicU32::new(0));
let guard = locks.acquire("test_key").await;
let counter2 = counter.clone();
let locks2 = KeyLocks {
locks: locks.locks.clone(),
};
let handle = tokio::spawn(async move {
let _guard2 = locks2.acquire("test_key").await;
counter2.fetch_add(1, Ordering::SeqCst);
});
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
assert_eq!(counter.load(Ordering::SeqCst), 0);
drop(guard);
handle.await.unwrap();
assert_eq!(counter.load(Ordering::SeqCst), 1);
}
#[tokio::test]
async fn test_key_locks_different_keys() {
let locks = KeyLocks::new();
let _guard1 = locks.acquire("key_a").await;
let _guard2 = locks.acquire("key_b").await;
}
}