Skip to content

Commit 08119c2

Browse files
authored
Merge pull request #44 from CoLearn-Dev/sm-append
Storage Macro Append
2 parents d5ee1e6 + 9755647 commit 08119c2

12 files changed

+304
-118
lines changed

Cargo.toml

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "colink"
3-
version = "0.2.9"
3+
version = "0.2.10"
44
edition = "2021"
55
description = "CoLink Rust SDK"
66
license = "MIT"
@@ -23,13 +23,13 @@ lapin = "2.1"
2323
prost = "0.10"
2424
rand = { version = "0.8", features = ["std_rng"] }
2525
rcgen = { version = "0.10", optional = true }
26-
redis = { version = "0.22", optional = true }
26+
redis = { version = "0.22", features = ["tokio-comp"], optional = true }
2727
reqwest = { version = "0.11", default-features = false, features = ["rustls-tls-native-roots"], optional = true }
2828
secp256k1 = { version = "0.25", features = ["rand-std"] }
2929
serde = { version = "1.0", features = ["derive"] }
3030
serde_json = "1.0"
3131
sha2 = "0.10"
32-
tokio = { version = "1.18", features = ["macros", "rt-multi-thread", "rt", "fs"] }
32+
tokio = { version = "1.24", features = ["macros", "rt-multi-thread", "rt", "fs"] }
3333
tokio-rustls = { version = "0.23", optional = true }
3434
tonic = { version = "0.7", features = ["tls", "tls-roots"] }
3535
tracing = "0.1"

README.md

+5-18
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# CoLink Rust SDK
22

3-
CoLink SDK helps both application adnd protocol developers access the functionalities provided by [the CoLink server](https://github.com/CoLearn-Dev/colink-server-dev).
3+
CoLink SDK helps both application and protocol developers access the functionalities provided by [the CoLink server](https://github.com/CoLearn-Dev/colink-server-dev).
44

55
- For *application developers*, CoLink SDK allows them to update storage, manage computation requests, and monitor the CoLink server status.
66
- For *protocol developers*, CoLink SDK allows them to write CoLink Extensions that extend the functionality of CoLink to support new protocols.
@@ -9,28 +9,15 @@ CoLink SDK helps both application adnd protocol developers access the functional
99
Add this to your Cargo.toml:
1010
```toml
1111
[dependencies]
12-
colink = "0.2.9"
12+
colink = "0.2.10"
1313
```
1414

1515
## Getting Started
1616
You can use this SDK to run protocols, update storage, developing protocol operators. Here is a tutorial for you about how to start a greetings task between two users.
1717
- Set up CoLink server.
18-
Please refer to [colinkctl](https://github.com/CoLearn-Dev/colinkctl), and run the command below. For the following steps, we assume you are using the default settings in colinkctl.
19-
```bash
20-
colinkctl enable_dev_env
21-
```
22-
- Create two new terminals and start protocol operator for two users separately.
23-
```bash
24-
cargo run --example protocol_greetings -- --addr http://localhost:8080 --jwt $(sed -n "1,1p" ~/.colink/user_token.txt)
25-
```
26-
```bash
27-
cargo run --example protocol_greetings -- --addr http://localhost:8080 --jwt $(sed -n "2,2p" ~/.colink/user_token.txt)
28-
```
29-
- Run task
30-
```bash
31-
cargo run --example user_run_task http://localhost:8080 $(sed -n "1,2p" ~/.colink/user_token.txt)
32-
```
33-
- Check the output in protocol operators' terminals
18+
Please refer to [CoLink Server Setup](https://co-learn.notion.site/CoLink-Server-Setup-aa58e481e36e40cba83a002c1f3bd158)
19+
- Use Rust SDK.
20+
Please refer to [CoLink SDK Examples in Rust](https://co-learn.notion.site/CoLink-SDK-Examples-in-Rust-a9b583ac5d764390aeba7293aa63f39d)
3421

3522
## More examples
3623
### Application

src/extensions/storage_macro.rs

+12-19
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
mod append;
12
mod chunk;
2-
mod db_redis;
3+
mod redis;
34

45
type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
56

67
impl crate::application::CoLink {
7-
fn _parse_macro(&self, key_name: &str) -> (String, String, String) {
8+
pub(crate) fn _parse_macro(&self, key_name: &str) -> (String, String, String) {
89
let split_key = key_name.split(':').collect::<Vec<&str>>();
910
let mut macro_type = String::new();
1011
for s in split_key.iter().rev() {
@@ -33,12 +34,9 @@ impl crate::application::CoLink {
3334
) -> Result<String, Error> {
3435
let (string_before, macro_type, string_after) = self._parse_macro(key_name);
3536
match macro_type.as_str() {
36-
"chunk" => {
37-
self._create_entry_chunk(string_before.as_str(), payload)
38-
.await
39-
}
37+
"chunk" => self._create_entry_chunk(&string_before, payload).await,
4038
"redis" => {
41-
self._create_entry_redis(string_before.as_str(), string_after.as_str(), payload)
39+
self._create_entry_redis(&string_before, &string_after, payload)
4240
.await
4341
}
4442
_ => Err(format!(
@@ -52,11 +50,8 @@ impl crate::application::CoLink {
5250
pub(crate) async fn _sm_read_entry(&self, key_name: &str) -> Result<Vec<u8>, Error> {
5351
let (string_before, macro_type, string_after) = self._parse_macro(key_name);
5452
match macro_type.as_str() {
55-
"chunk" => self._read_entry_chunk(string_before.as_str()).await,
56-
"redis" => {
57-
self._read_entry_redis(string_before.as_str(), string_after.as_str())
58-
.await
59-
}
53+
"chunk" => self._read_entry_chunk(&string_before).await,
54+
"redis" => self._read_entry_redis(&string_before, &string_after).await,
6055
_ => Err(format!(
6156
"invalid storage macro, found {} in key name {}",
6257
macro_type, key_name
@@ -72,14 +67,12 @@ impl crate::application::CoLink {
7267
) -> Result<String, Error> {
7368
let (string_before, macro_type, string_after) = self._parse_macro(key_name);
7469
match macro_type.as_str() {
75-
"chunk" => {
76-
self._update_entry_chunk(string_before.as_str(), payload)
77-
.await
78-
}
70+
"chunk" => self._update_entry_chunk(&string_before, payload).await,
7971
"redis" => {
80-
self._update_entry_redis(string_before.as_str(), string_after.as_str(), payload)
72+
self._update_entry_redis(&string_before, &string_after, payload)
8173
.await
8274
}
75+
"append" => self._update_entry_append(&string_before, payload).await,
8376
_ => Err(format!(
8477
"invalid storage macro, found {} in key name {}",
8578
macro_type, key_name
@@ -91,9 +84,9 @@ impl crate::application::CoLink {
9184
pub(crate) async fn _sm_delete_entry(&self, key_name: &str) -> Result<String, Error> {
9285
let (string_before, macro_type, string_after) = self._parse_macro(key_name);
9386
match macro_type.as_str() {
94-
"chunk" => self._delete_entry_chunk(string_before.as_str()).await,
87+
"chunk" => self._delete_entry_chunk(&string_before).await,
9588
"redis" => {
96-
self._delete_entry_redis(string_before.as_str(), string_after.as_str())
89+
self._delete_entry_redis(&string_before, &string_after)
9790
.await
9891
}
9992
_ => Err(format!(
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use async_recursion::async_recursion;
2+
3+
type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
4+
5+
impl crate::application::CoLink {
6+
#[async_recursion]
7+
pub(crate) async fn _update_entry_append(
8+
&self,
9+
key_name: &str,
10+
payload: &[u8],
11+
) -> Result<String, Error> {
12+
if key_name.contains('$') {
13+
let (string_before, macro_type, string_after) = self._parse_macro(key_name);
14+
match macro_type.as_str() {
15+
"redis" => {
16+
return self
17+
._append_entry_redis(&string_before, &string_after, payload)
18+
.await;
19+
}
20+
"chunk" => {
21+
return self._append_entry_chunk(&string_before, payload).await;
22+
}
23+
_ => {}
24+
}
25+
}
26+
let lock = self.lock(key_name).await?;
27+
// use a closure to prevent locking forever caused by errors
28+
let res = async {
29+
let mut data = self.read_entry(key_name).await?;
30+
data.append(&mut payload.to_vec());
31+
let res = self.update_entry(key_name, &data).await?;
32+
Ok::<String, Error>(res)
33+
}
34+
.await;
35+
self.unlock(lock).await?;
36+
res
37+
}
38+
}

src/extensions/storage_macro/chunk.rs

+87-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use crate::decode_jwt_without_validation;
21
use async_recursion::async_recursion;
32

43
const CHUNK_SIZE: usize = 1024 * 1024; // use 1MB chunks
@@ -30,6 +29,62 @@ impl crate::application::CoLink {
3029
Ok(chunk_paths)
3130
}
3231

32+
#[async_recursion]
33+
async fn _append_chunks(
34+
&self,
35+
chunk_paths: &str,
36+
payload: &[u8],
37+
key_name: &str,
38+
) -> Result<Vec<String>, Error> {
39+
let mut chunk_paths = chunk_paths
40+
.split(';')
41+
.map(|x| x.to_string())
42+
.collect::<Vec<String>>();
43+
let last_chunk_id = chunk_paths.len() - 1;
44+
let last_chunk_timestamp = chunk_paths[last_chunk_id].clone();
45+
let mut last_chunk = self
46+
.read_entry(&format!(
47+
"{}::{}:{}@{}",
48+
self.get_user_id()?,
49+
key_name,
50+
last_chunk_id,
51+
last_chunk_timestamp
52+
))
53+
.await?;
54+
let mut offset = 0;
55+
let mut chunk_id = chunk_paths.len();
56+
if last_chunk.len() < CHUNK_SIZE {
57+
let chunk_size = if payload.len() < CHUNK_SIZE - last_chunk.len() {
58+
payload.len()
59+
} else {
60+
CHUNK_SIZE - last_chunk.len()
61+
};
62+
last_chunk.append(&mut payload[..chunk_size].to_vec());
63+
let response = self
64+
.update_entry(&format!("{}:{}", key_name, last_chunk_id), &last_chunk)
65+
.await?;
66+
chunk_paths[last_chunk_id] = response.split('@').last().unwrap().to_string();
67+
offset = chunk_size;
68+
}
69+
while offset < payload.len() {
70+
let chunk_size = if offset + CHUNK_SIZE > payload.len() {
71+
payload.len() - offset
72+
} else {
73+
CHUNK_SIZE
74+
};
75+
let response = self
76+
.update_entry(
77+
&format!("{}:{}", key_name, chunk_id),
78+
&payload[offset..offset + chunk_size],
79+
)
80+
.await?;
81+
chunk_paths.push(response.split('@').last().unwrap().to_string()); // only store the timestamps
82+
offset += chunk_size;
83+
chunk_id += 1;
84+
}
85+
Ok(chunk_paths)
86+
}
87+
3388
fn _check_chunk_paths_size(&self, chunk_paths: Vec<String>) -> Result<String, Error> {
3489
let chunk_paths_string = chunk_paths.join(";");
3590
if chunk_paths_string.len() > CHUNK_SIZE {
@@ -73,7 +128,7 @@ impl crate::application::CoLink {
73128
let metadata_key = format!("{}:chunk_metadata", key_name);
74129
let metadata_response = self.read_entry(&metadata_key).await?;
75130
let payload_string = String::from_utf8(metadata_response)?;
76-
let user_id = decode_jwt_without_validation(&self.jwt).unwrap().user_id;
131+
let user_id = self.get_user_id()?;
77132

78133
// read the chunks into a single vector
79134
let chunks_paths = payload_string.split(';').collect::<Vec<&str>>();
@@ -113,6 +168,36 @@ impl crate::application::CoLink {
113168
res
114169
}
115170

171+
#[async_recursion]
172+
pub(crate) async fn _append_entry_chunk(
173+
&self,
174+
key_name: &str,
175+
payload: &[u8],
176+
) -> Result<String, Error> {
177+
let metadata_key = format!("{}:chunk_metadata", key_name);
178+
// lock the metadata entry to prevent simultaneous writes
179+
let lock_token = self.lock(&metadata_key).await?;
180+
// use a closure to prevent locking forever caused by errors
181+
let res = async {
182+
// split payload into chunks and update the chunks
183+
let metadata_response = self.read_entry(&metadata_key).await?;
184+
let payload_string = String::from_utf8(metadata_response)?;
185+
let chunk_paths = self
186+
._append_chunks(&payload_string, payload, key_name)
187+
.await?;
188+
// make sure that the chunk paths are smaller than the maximum entry size
189+
let chunk_paths_string = self._check_chunk_paths_size(chunk_paths)?;
190+
// update the metadata entry
191+
let response = self
192+
.update_entry(&metadata_key, &chunk_paths_string.into_bytes())
193+
.await?;
194+
Ok::<String, Error>(response)
195+
}
196+
.await;
197+
self.unlock(lock_token).await?;
198+
res
199+
}
200+
116201
#[async_recursion]
117202
pub(crate) async fn _delete_entry_chunk(&self, key_name: &str) -> Result<String, Error> {
118203
let metadata_key = format!("{}:chunk_metadata", key_name);

src/extensions/storage_macro/db_redis.rs renamed to src/extensions/storage_macro/redis.rs

+24-16
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,16 @@
11
use async_recursion::async_recursion;
2-
use redis::Commands;
2+
use redis::{aio::Connection, AsyncCommands};
33

44
type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
55

66
impl crate::application::CoLink {
7-
fn _get_con_from_address(&self, redis_address: &str) -> Result<redis::Connection, Error> {
8-
let client = redis::Client::open(redis_address)?;
9-
let con = client.get_connection()?;
10-
Ok(con)
11-
}
12-
13-
async fn _get_con_from_stored_credentials(
14-
&self,
15-
key_path: &str,
16-
) -> Result<redis::Connection, Error> {
7+
async fn _get_con_from_stored_credentials(&self, key_path: &str) -> Result<Connection, Error> {
178
let redis_url_key = format!("{}:redis_url", key_path);
189
let redis_url = self.read_entry(redis_url_key.as_str()).await?;
1910
let redis_url_string = String::from_utf8(redis_url)?;
20-
self._get_con_from_address(redis_url_string.as_str())
11+
let client = redis::Client::open(redis_url_string)?;
12+
let con = client.get_async_connection().await?;
13+
Ok(con)
2114
}
2215

2316
#[async_recursion]
@@ -28,7 +21,7 @@ impl crate::application::CoLink {
2821
payload: &[u8],
2922
) -> Result<String, Error> {
3023
let mut con = self._get_con_from_stored_credentials(address).await?;
31-
let response: i32 = con.set_nx(key_name, payload)?;
24+
let response: i32 = con.set_nx(key_name, payload).await?;
3225
if response == 0 {
3326
Err("key already exists.")?
3427
}
@@ -42,7 +35,7 @@ impl crate::application::CoLink {
4235
key_name: &str,
4336
) -> Result<Vec<u8>, Error> {
4437
let mut con = self._get_con_from_stored_credentials(address).await?;
45-
let response: Option<Vec<u8>> = con.get(key_name)?;
38+
let response: Option<Vec<u8>> = con.get(key_name).await?;
4639
match response {
4740
Some(response) => Ok(response),
4841
None => Err("key does not exist.")?,
@@ -57,7 +50,22 @@ impl crate::application::CoLink {
5750
payload: &[u8],
5851
) -> Result<String, Error> {
5952
let mut con = self._get_con_from_stored_credentials(address).await?;
60-
let response = con.set(key_name, payload)?;
53+
let response = con.set(key_name, payload).await?;
54+
Ok(response)
55+
}
56+
57+
#[async_recursion]
58+
pub(crate) async fn _append_entry_redis(
59+
&self,
60+
address: &str,
61+
key_name: &str,
62+
payload: &[u8],
63+
) -> Result<String, Error> {
64+
let mut con = self._get_con_from_stored_credentials(address).await?;
65+
let response = con
66+
.append::<&str, &[u8], i32>(key_name, payload)
67+
.await?
68+
.to_string();
6169
Ok(response)
6270
}
6371

@@ -68,7 +76,7 @@ impl crate::application::CoLink {
6876
key_name: &str,
6977
) -> Result<String, Error> {
7078
let mut con = self._get_con_from_stored_credentials(address).await?;
71-
let response: i32 = con.del(key_name)?;
79+
let response: i32 = con.del(key_name).await?;
7280
if response == 0 {
7381
Err("key does not exist.")?
7482
}

0 commit comments

Comments
 (0)