Skip to content

Commit 1004470

Browse files
committed
Add Vss thin client protobuf generation & build/CI setup
1 parent 3494efa commit 1004470

File tree

8 files changed

+348
-0
lines changed

8 files changed

+348
-0
lines changed

.github/workflows/build.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Continuous Integration Checks
2+
3+
on: [ push, pull_request ]
4+
5+
jobs:
6+
build:
7+
strategy:
8+
matrix:
9+
toolchain: [ stable, beta ]
10+
include:
11+
- toolchain: stable
12+
check-fmt: true
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout source code
16+
uses: actions/checkout@v3
17+
- name: Install Protobuf compiler (protoc)
18+
run: sudo apt-get update && sudo apt-get -y install protobuf-compiler
19+
- name: Install Rust ${{ matrix.toolchain }} toolchain
20+
run: |
21+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ matrix.toolchain }}
22+
rustup override set ${{ matrix.toolchain }}
23+
- name: Build on Rust ${{ matrix.toolchain }}
24+
run: cargo build --verbose --color always
25+
- name: Check formatting
26+
if: matrix.check-fmt
27+
run: rustup component add rustfmt && cargo fmt --all -- --check
28+
- name: Test on Rust ${{ matrix.toolchain }}
29+
run: cargo test
30+
- name: Cargo check release on Rust ${{ matrix.toolchain }}
31+
run: cargo check --release
32+
- name: Cargo check doc on Rust ${{ matrix.toolchain }}
33+
run: cargo doc --release

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/target
2+
/vss-accessor/src/proto/

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[workspace]
2+
members = [
3+
"vss-accessor",
4+
]

rustfmt.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
hard_tabs = true # use tab characters for indentation, spaces for alignment
2+
use_field_init_shorthand = true
3+
max_width = 120
4+
use_small_heuristics = "Max"
5+
chain_width = 80
6+
fn_args_layout = "Compressed"

vss-accessor/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "vss-accessor"
3+
version = "0.1.0"
4+
edition = "2021"
5+
build = "build.rs"
6+
7+
[dependencies]
8+
9+
[dev-dependencies]
10+
11+
[build-dependencies]
12+
prost-build = { version = "0.11.3" }
13+
reqwest = { version = "0.11.13", features = ["blocking"] }
14+
15+
[features]
16+
genproto = []

vss-accessor/build.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
extern crate prost_build;
2+
3+
use std::fs::File;
4+
use std::path::Path;
5+
use std::{env, fs};
6+
7+
/// To generate updated proto objects:
8+
/// 1. Place `vss.proto` file in `src/proto/`
9+
/// 2. run `cargo build --features=genproto`
10+
fn main() {
11+
#[cfg(feature = "genproto")]
12+
generate_protos();
13+
}
14+
15+
#[cfg(feature = "genproto")]
16+
fn generate_protos() {
17+
download_file(
18+
"https://raw.githubusercontent.com/lightningdevkit/vss-server/ff4b5fc6a079ed8719eb8be7ec35ca1d01c1cc55/app/src/main/proto/vss.proto",
19+
"src/proto/vss.proto",
20+
).unwrap();
21+
22+
prost_build::compile_protos(&["src/proto/vss.proto"], &["src/"]).unwrap();
23+
let from_path = Path::new(&env::var("OUT_DIR").unwrap()).join("org.vss.rs");
24+
fs::copy(from_path, "src/generated-src/org.vss.rs").unwrap();
25+
}
26+
27+
#[cfg(feature = "genproto")]
28+
fn download_file(url: &str, save_to: &str) -> Result<(), Box<dyn std::error::Error>> {
29+
let mut response = reqwest::blocking::get(url)?;
30+
fs::create_dir_all(Path::new(save_to).parent().unwrap())?;
31+
let mut out_file = File::create(save_to)?;
32+
response.copy_to(&mut out_file)?;
33+
Ok(())
34+
}
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
#[allow(clippy::derive_partial_eq_without_eq)]
2+
#[derive(Clone, PartialEq, ::prost::Message)]
3+
pub struct GetObjectRequest {
4+
/// store_id is a keyspace identifier.
5+
/// Ref: <https://en.wikipedia.org/wiki/Keyspace_(distributed_data_store>)
6+
/// All APIs operate within a single store_id.
7+
/// It is up to clients to use single or multiple stores for their use-case.
8+
/// This can be used for client-isolation/ rate-limiting / throttling on the server-side.
9+
/// Authorization and billing can also be performed at the store_id level.
10+
#[prost(string, tag = "1")]
11+
pub store_id: ::prost::alloc::string::String,
12+
/// Key for which the value is to be fetched.
13+
///
14+
/// Consistency Guarantee:
15+
/// Get(read) operations against a key are consistent reads and will reflect all previous writes,
16+
/// since Put/Write provides read-after-write and read-after-update consistency guarantees.
17+
///
18+
/// Read Isolation:
19+
/// Get/Read operations against a key are ensured to have read-committed isolation.
20+
/// Ref: <https://en.wikipedia.org/wiki/Isolation_(database_systems>)#Read_committed
21+
#[prost(string, tag = "2")]
22+
pub key: ::prost::alloc::string::String,
23+
}
24+
#[allow(clippy::derive_partial_eq_without_eq)]
25+
#[derive(Clone, PartialEq, ::prost::Message)]
26+
pub struct GetObjectResponse {
27+
/// Fetched value and version along with the corresponding key in the request.
28+
#[prost(message, optional, tag = "2")]
29+
pub value: ::core::option::Option<KeyValue>,
30+
}
31+
#[allow(clippy::derive_partial_eq_without_eq)]
32+
#[derive(Clone, PartialEq, ::prost::Message)]
33+
pub struct PutObjectRequest {
34+
/// store_id is a keyspace identifier.
35+
/// Ref: <https://en.wikipedia.org/wiki/Keyspace_(distributed_data_store>)
36+
/// All APIs operate within a single store_id.
37+
/// It is up to clients to use single or multiple stores for their use-case.
38+
/// This can be used for client-isolation/ rate-limiting / throttling on the server-side.
39+
/// Authorization and billing can also be performed at the store_id level.
40+
#[prost(string, tag = "1")]
41+
pub store_id: ::prost::alloc::string::String,
42+
/// global_version is a sequence-number/version of the whole store. This can be used for versioning
43+
/// and ensures that multiple updates in case of multiple devices can only be done linearly, even
44+
/// if those updates did not directly conflict with each other based on keys/transaction_items.
45+
///
46+
/// If present, the write will only succeed if the current server-side global_version against
47+
/// the store_id is same as in the request.
48+
/// Clients are expected to store (client-side) the global version against store_id.
49+
/// The request must contain their client-side value of global_version if global versioning and
50+
/// conflict detection is desired.
51+
///
52+
/// For the first write of the store, global version should be '0'. If the write succeeds, clients
53+
/// must increment their global version (client-side) by 1.
54+
/// The server increments global_version (server-side) for every successful write, hence this
55+
/// client-side increment is required to ensure matching versions. This updated global version
56+
/// should be used in subsequent PutObjectRequests for the store.
57+
///
58+
/// Requests with a conflicting version will fail with `CONFLICT_EXCEPTION` as ErrorCode.
59+
#[prost(int64, optional, tag = "2")]
60+
pub global_version: ::core::option::Option<i64>,
61+
/// Items to be written as a result of this PutObjectRequest.
62+
///
63+
/// In an item, each key is supplied with its corresponding value and version.
64+
/// Clients can choose to encrypt the keys client-side in order to obfuscate their usage patterns.
65+
/// If the write is successful, the previous value corresponding to the key will be overwritten.
66+
///
67+
/// Multiple items in transaction_items of a single PutObjectRequest are written in
68+
/// a database-transaction in an all-or-nothing fashion.
69+
/// Items in a single PutObjectRequest must have distinct keys.
70+
///
71+
/// Clients are expected to store a version against every key.
72+
/// The write will succeed if the current DB version against the key is the same as in the request.
73+
/// When initiating a PutObjectRequest, the request should contain their client-side version for
74+
/// that key-value.
75+
///
76+
/// For the first write of any key, the version should be '0'. If the write succeeds, the client
77+
/// must increment their corresponding key versions (client-side) by 1.
78+
/// The server increments key versions (server-side) for every successful write, hence this
79+
/// client-side increment is required to ensure matching versions. These updated key versions should
80+
/// be used in subsequent PutObjectRequests for the keys.
81+
///
82+
/// Requests with a conflicting version will fail with `CONFLICT_EXCEPTION` as ErrorCode.
83+
///
84+
/// Considerations for transactions:
85+
/// Transaction writes of multiple items have a performance overhead, hence it is recommended to use
86+
/// them only if required by the client application to ensure logic/code correctness.
87+
/// That is, transaction_items are not a substitute for batch-write of multiple unrelated items.
88+
/// When a write of multiple unrelated items is desired, it is recommended to use separate
89+
/// PutObjectRequests.
90+
///
91+
/// Consistency guarantee:
92+
/// All PutObjectRequests are strongly consistent i.e. they provide read-after-write and
93+
/// read-after-update consistency guarantees.
94+
#[prost(message, repeated, tag = "3")]
95+
pub transaction_items: ::prost::alloc::vec::Vec<KeyValue>,
96+
}
97+
#[allow(clippy::derive_partial_eq_without_eq)]
98+
#[derive(Clone, PartialEq, ::prost::Message)]
99+
pub struct PutObjectResponse {}
100+
#[allow(clippy::derive_partial_eq_without_eq)]
101+
#[derive(Clone, PartialEq, ::prost::Message)]
102+
pub struct ListKeyVersionsRequest {
103+
/// store_id is a keyspace identifier.
104+
/// Ref: <https://en.wikipedia.org/wiki/Keyspace_(distributed_data_store>)
105+
/// All APIs operate within a single store_id.
106+
/// It is up to clients to use single or multiple stores for their use-case.
107+
/// This can be used for client-isolation/ rate-limiting / throttling on the server-side.
108+
/// Authorization and billing can also be performed at the store_id level.
109+
#[prost(string, tag = "1")]
110+
pub store_id: ::prost::alloc::string::String,
111+
/// A key_prefix is a string of characters at the beginning of the key. Prefixes can be used as
112+
/// a way to organize key-values in a similar way to directories.
113+
///
114+
/// If key_prefix is specified, the response results will be limited to those keys that begin with
115+
/// the specified prefix.
116+
///
117+
/// If no key_prefix is specified or it is empty (""), all the keys are eligible to be returned in
118+
/// the response.
119+
#[prost(string, optional, tag = "2")]
120+
pub key_prefix: ::core::option::Option<::prost::alloc::string::String>,
121+
/// page_size is used by clients to specify the maximum number of results that can be returned by
122+
/// the server.
123+
/// The server may further constrain the maximum number of results returned in a single page.
124+
/// If the page_size is 0 or not set, the server will decide the number of results to be returned.
125+
#[prost(int32, optional, tag = "3")]
126+
pub page_size: ::core::option::Option<i32>,
127+
/// page_token is a pagination token.
128+
///
129+
/// To query for the first page of ListKeyVersions, page_token must not be specified.
130+
///
131+
/// For subsequent pages, use the value that was returned as `next_page_token` in the previous
132+
/// page's ListKeyVersionsResponse.
133+
#[prost(string, optional, tag = "4")]
134+
pub page_token: ::core::option::Option<::prost::alloc::string::String>,
135+
}
136+
#[allow(clippy::derive_partial_eq_without_eq)]
137+
#[derive(Clone, PartialEq, ::prost::Message)]
138+
pub struct ListKeyVersionsResponse {
139+
/// Fetched keys and versions.
140+
/// Even though this API reuses KeyValue struct, the value sub-field will not be set by the server.
141+
#[prost(message, repeated, tag = "1")]
142+
pub key_versions: ::prost::alloc::vec::Vec<KeyValue>,
143+
/// next_page_token is a pagination token, used to retrieve the next page of results.
144+
/// Use this value to query for next_page of paginated ListKeyVersions operation, by specifying
145+
/// this value as the `page_token` in the next request.
146+
///
147+
/// If next_page_token is empty (""), then the "last page" of results has been processed and
148+
/// there is no more data to be retrieved.
149+
///
150+
/// If next_page_token is not empty, it does not necessarily mean that there is more data in the
151+
/// result set. The only way to know when you have reached the end of the result set is when
152+
/// next_page_token is empty.
153+
///
154+
/// Caution: Clients must not assume a specific number of key_versions to be present in a page for
155+
/// paginated response.
156+
#[prost(string, optional, tag = "2")]
157+
pub next_page_token: ::core::option::Option<::prost::alloc::string::String>,
158+
/// global_version is a sequence-number/version of the whole store.
159+
///
160+
/// global_version is only returned in response for the first page of the ListKeyVersionsResponse
161+
/// and is guaranteed to be read before reading any key-versions.
162+
///
163+
/// In case of refreshing the complete key-version view on the client-side, correct usage for
164+
/// the returned global_version is as following:
165+
/// 1. Read global_version from the first page of paginated response and save it as local variable.
166+
/// 2. Update all the key_versions on client-side from all the pages of paginated response.
167+
/// 3. Update global_version on client_side from the local variable saved in step-1.
168+
/// This ensures that on client-side, all current key_versions were stored at global_version or later.
169+
/// This guarantee is helpful for ensuring the versioning correctness if using the global_version
170+
/// in PutObject API and can help avoid the race conditions related to it.
171+
#[prost(int64, optional, tag = "3")]
172+
pub global_version: ::core::option::Option<i64>,
173+
}
174+
/// When HttpStatusCode is not ok (200), the response `content` contains a serialized ErrorResponse
175+
/// with the relevant ErrorCode and message
176+
#[allow(clippy::derive_partial_eq_without_eq)]
177+
#[derive(Clone, PartialEq, ::prost::Message)]
178+
pub struct ErrorResponse {
179+
/// The error code uniquely identifying an error condition.
180+
/// It is meant to be read and understood programmatically by code that detects/handles errors by
181+
/// type.
182+
#[prost(enumeration = "ErrorCode", tag = "1")]
183+
pub error_code: i32,
184+
/// The error message containing a generic description of the error condition in English.
185+
/// It is intended for a human audience only and should not be parsed to extract any information
186+
/// programmatically. Client-side code may use it for logging only.
187+
#[prost(string, tag = "2")]
188+
pub message: ::prost::alloc::string::String,
189+
}
190+
#[allow(clippy::derive_partial_eq_without_eq)]
191+
#[derive(Clone, PartialEq, ::prost::Message)]
192+
pub struct KeyValue {
193+
/// Key against which the value is stored.
194+
#[prost(string, tag = "1")]
195+
pub key: ::prost::alloc::string::String,
196+
/// Version field is used for key-level versioning.
197+
/// For first write of key, version should be '0'. If the write succeeds, clients must increment
198+
/// their corresponding key version (client-side) by 1.
199+
/// The server increments key version (server-side) for every successful write, hence this
200+
/// client-side increment is required to ensure matching versions. These updated key versions should
201+
/// be used in subsequent PutObjectRequests for the keys.
202+
#[prost(int64, tag = "2")]
203+
pub version: i64,
204+
/// Object value in bytes which is stored (in put) and fetched (in get).
205+
/// Clients must encrypt this blob client-side before sending it over the wire to server in order
206+
/// to preserve privacy and security.
207+
#[prost(bytes = "vec", tag = "3")]
208+
pub value: ::prost::alloc::vec::Vec<u8>,
209+
}
210+
/// ErrorCodes to be used in ErrorResponse
211+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
212+
#[repr(i32)]
213+
pub enum ErrorCode {
214+
/// Default protobuf Enum value. Will not be used as ErrorCode by server.
215+
Unknown = 0,
216+
/// CONFLICT_EXCEPTION is used when the request contains mismatched version (either key or global)
217+
/// in PutObjectRequest. For more info refer PutObjectRequest.
218+
ConflictException = 1,
219+
/// INVALID_REQUEST_EXCEPTION is used in the following cases:
220+
/// - The request was missing a required argument.
221+
/// - The specified argument was invalid, incomplete or in the wrong format.
222+
/// - The request body of api cannot be deserialized into corresponding protobuf object.
223+
InvalidRequestException = 2,
224+
/// An internal server error occurred, client is probably at no fault and can safely retry this
225+
/// error with exponential backoff.
226+
InternalServerException = 3,
227+
}
228+
impl ErrorCode {
229+
/// String value of the enum field names used in the ProtoBuf definition.
230+
///
231+
/// The values are not transformed in any way and thus are considered stable
232+
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
233+
pub fn as_str_name(&self) -> &'static str {
234+
match self {
235+
ErrorCode::Unknown => "UNKNOWN",
236+
ErrorCode::ConflictException => "CONFLICT_EXCEPTION",
237+
ErrorCode::InvalidRequestException => "INVALID_REQUEST_EXCEPTION",
238+
ErrorCode::InternalServerException => "INTERNAL_SERVER_EXCEPTION",
239+
}
240+
}
241+
/// Creates an enum from field names used in the ProtoBuf definition.
242+
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
243+
match value {
244+
"UNKNOWN" => Some(Self::Unknown),
245+
"CONFLICT_EXCEPTION" => Some(Self::ConflictException),
246+
"INVALID_REQUEST_EXCEPTION" => Some(Self::InvalidRequestException),
247+
"INTERNAL_SERVER_EXCEPTION" => Some(Self::InternalServerException),
248+
_ => None,
249+
}
250+
}
251+
}

vss-accessor/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#![deny(rustdoc::broken_intra_doc_links)]
2+
#![deny(rustdoc::private_intra_doc_links)]

0 commit comments

Comments
 (0)