Skip to content

Commit 64cb9b1

Browse files
committed
Add VSS Store Implementation
1 parent 422514e commit 64cb9b1

File tree

2 files changed

+180
-0
lines changed

2 files changed

+180
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ tokio = { version = "1", default-features = false, features = [ "rt-multi-thread
7878
esplora-client = { version = "0.4", default-features = false }
7979
libc = "0.2"
8080
uniffi = { version = "0.23.0", features = ["build"], optional = true }
81+
vss-client = "0.0.1-alpha"
8182

8283
[target.'cfg(windows)'.dependencies]
8384
winapi = { version = "0.3", features = ["winbase"] }

src/io/vss_store.rs

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
use std::io::Cursor;
2+
use std::io::Read;
3+
use std::sync::Arc;
4+
use std::{error::Error, io};
5+
6+
use super::*;
7+
use crate::KVStore;
8+
use lightning::util::persist::KVStorePersister;
9+
use lightning::util::ser::Writeable;
10+
use tokio::runtime::Runtime;
11+
use vss_client::client::VssClient;
12+
use vss_client::error::VssError;
13+
use vss_client::types::{
14+
DeleteObjectRequest, GetObjectRequest, KeyValue, ListKeyVersionsRequest, PutObjectRequest,
15+
};
16+
17+
/// Implements [KVStore] and [KVStorePersister] for VSS using [VssClient].
18+
///
19+
/// A [`KVStore`] implementation that writes to and reads from VSS using [VssClient].
20+
///
21+
/// Learn more about Versioned Storage Service (VSS) [here](https://github.com/lightningdevkit/vss-server/blob/main/README.md).
22+
pub struct VssKVStore {
23+
client: VssClient,
24+
store_id: String,
25+
runtime: Arc<Runtime>,
26+
}
27+
28+
impl VssKVStore {
29+
pub(crate) fn new(base_url: &str, store_id: String) -> Self {
30+
let client = VssClient::new(base_url);
31+
let runtime =
32+
Arc::new(tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap());
33+
Self { client, store_id, runtime }
34+
}
35+
36+
fn build_key(&self, namespace: &str, key: &str) -> io::Result<String> {
37+
if key.is_empty() {
38+
return Err(io::Error::new(io::ErrorKind::Other, "Empty key is not allowed"));
39+
}
40+
if namespace.is_empty() {
41+
Ok(key.to_string())
42+
} else {
43+
Ok(format!("{}/{}", namespace, key))
44+
}
45+
}
46+
47+
fn split_key(&self, key: &str) -> (String, String) {
48+
get_namespace_and_key_from_prefixed(key).unwrap()
49+
}
50+
51+
async fn list_all_keys(&self, namespace: &str) -> Result<Vec<String>, VssError> {
52+
let mut page_token = None;
53+
let mut keys = vec![];
54+
55+
while page_token != Some("".to_string()) {
56+
let request = ListKeyVersionsRequest {
57+
store_id: self.store_id.to_string(),
58+
key_prefix: Some(namespace.to_string()),
59+
page_token,
60+
page_size: None,
61+
};
62+
63+
let response = self.client.list_key_versions(&request).await?;
64+
65+
for kv in response.key_versions {
66+
keys.push(self.split_key(&kv.key).1);
67+
}
68+
page_token = response.next_page_token;
69+
}
70+
Ok(keys)
71+
}
72+
}
73+
74+
impl KVStore for VssKVStore {
75+
type Reader = Cursor<Vec<u8>>;
76+
77+
fn read(&self, namespace: &str, key: &str) -> io::Result<Self::Reader> {
78+
let request = GetObjectRequest {
79+
store_id: self.store_id.to_string(),
80+
key: self.build_key(namespace, key)?,
81+
};
82+
83+
let resp =
84+
tokio::task::block_in_place(|| self.runtime.block_on(self.client.get_object(&request)))
85+
.map_err(|e| {
86+
let msg = format!("Failed to read from key {}/{}: {}", namespace, key, e);
87+
io::Error::new(io::ErrorKind::Other, msg)
88+
})?;
89+
90+
let value = resp.value.unwrap().value;
91+
if value.is_empty() {
92+
let msg = format!("Failed to read as key could not be found: {}/{}", namespace, key);
93+
Err(io::Error::new(io::ErrorKind::NotFound, msg))
94+
} else {
95+
Ok(Cursor::new(value))
96+
}
97+
}
98+
99+
fn write(&self, namespace: &str, key: &str, buf: &[u8]) -> io::Result<()> {
100+
let request = PutObjectRequest {
101+
store_id: self.store_id.to_string(),
102+
global_version: None,
103+
transaction_items: vec![KeyValue {
104+
key: self.build_key(namespace, key)?,
105+
version: -1,
106+
value: buf.to_vec(),
107+
}],
108+
delete_items: vec![],
109+
};
110+
111+
tokio::task::block_in_place(|| self.runtime.block_on(self.client.put_object(&request)))
112+
.map_err(|e| {
113+
let msg = format!("Failed to write to key {}/{}: {}", namespace, key, e);
114+
io::Error::new(io::ErrorKind::Other, msg)
115+
})?;
116+
117+
Ok(())
118+
}
119+
120+
fn remove(&self, namespace: &str, key: &str) -> io::Result<()> {
121+
let request = DeleteObjectRequest {
122+
store_id: self.store_id.to_string(),
123+
key_value: Some(KeyValue {
124+
key: self.build_key(namespace, key)?,
125+
version: -1,
126+
value: vec![],
127+
}),
128+
};
129+
130+
tokio::task::block_in_place(|| self.runtime.block_on(self.client.delete_object(&request)))
131+
.map_err(|e| {
132+
let msg = format!("Failed to delete key {}/{}: {}", namespace, key, e);
133+
io::Error::new(io::ErrorKind::Other, msg)
134+
})?;
135+
Ok(())
136+
}
137+
138+
fn list(&self, namespace: &str) -> io::Result<Vec<String>> {
139+
let keys =
140+
tokio::task::block_in_place(|| self.runtime.block_on(self.list_all_keys(namespace)))
141+
.map_err(|e| {
142+
let msg =
143+
format!("Failed to retrieve keys in namespace: {} : {}", namespace, e);
144+
io::Error::new(io::ErrorKind::Other, msg)
145+
})?;
146+
147+
Ok(keys)
148+
}
149+
}
150+
151+
impl KVStorePersister for VssKVStore {
152+
fn persist<W: Writeable>(&self, prefixed_key: &str, object: &W) -> io::Result<()> {
153+
self.write("", &prefixed_key, &object.encode())?;
154+
Ok(())
155+
}
156+
}
157+
158+
#[cfg(test)]
159+
mod tests {
160+
use super::*;
161+
use crate::test::utils::random_storage_path;
162+
163+
use proptest::prelude::*;
164+
proptest! {
165+
#[test]
166+
fn read_write_remove_list_persist(data in any::<[u8; 32]>()) {
167+
let vss_base_url = std::env::var("TEST_VSS_BASE_URL");
168+
if vss_base_url.is_ok()
169+
{
170+
let rand_store_id = random_storage_path();
171+
let vss_store = VssKVStore::new(&vss_base_url.unwrap(), rand_store_id);
172+
173+
do_read_write_remove_list_persist(&data, &vss_store);
174+
}else{
175+
eprintln!("** SKIPPING `VssKVStore` test-suite since environment variable `TEST_VSS_BASE_URL` is not set **");
176+
}
177+
}
178+
}
179+
}

0 commit comments

Comments
 (0)