aboutsummaryrefslogtreecommitdiffstats
path: root/src/schema.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/schema.rs')
-rw-r--r--src/schema.rs377
1 files changed, 0 insertions, 377 deletions
diff --git a/src/schema.rs b/src/schema.rs
deleted file mode 100644
index bbd4628..0000000
--- a/src/schema.rs
+++ /dev/null
@@ -1,377 +0,0 @@
1//! # Data Representations
2//!
3//! We need persistence for [`Block`]s and [`User`]s, not so much for [`Transaction`]s
4//!
5//! There are around 30 students, a full fledged database would be an overkill (for next year?)
6//!
7//! Pending transactions are held in memory, these are cleared with every new block
8//! Only the last block is held in memory, every block is written to a file
9//! Users are held in memory and they're also backed up to text files
10use chrono::{NaiveDate, NaiveDateTime};
11use lazy_static::lazy_static;
12use log::debug;
13use parking_lot::RwLock;
14use serde::{Deserialize, Serialize};
15use std::collections::{HashMap, HashSet};
16use std::fmt;
17use std::fs;
18use std::io;
19use std::path::PathBuf;
20use std::string::String;
21use std::sync::Arc;
22use std::vec::Vec;
23
24pub type Fingerprint = String;
25pub type Id = String;
26
27fn block_parser(path: String) -> u64 {
28 let end_pos = path.find(".block").unwrap();
29 let block_str = path[9..end_pos].to_string();
30 let block_u64: u64 = block_str.parse().unwrap();
31 block_u64
32}
33
34fn last_block_content() -> Option<String> {
35 let blocks = read_block_name().unwrap();
36
37 if blocks.is_empty() {
38 return None;
39 }
40
41 let last_block = blocks[0].to_str().unwrap();
42 let mut last_block = block_parser(last_block.to_string());
43 let mut last_block_index = 0;
44
45 for (index, block) in blocks.iter().enumerate() {
46 let block = block.to_str().unwrap();
47 let block = block_parser(block.to_string());
48 if block > last_block {
49 last_block = block;
50 last_block_index = index;
51 }
52 }
53 return Some(blocks[last_block_index].to_str().unwrap().parse().unwrap());
54}
55
56fn read_block_name() -> io::Result<Vec<PathBuf>> {
57 let entries = fs::read_dir("./blocks")?
58 .map(|res| res.map(|e| e.path()))
59 .collect::<Result<Vec<_>, io::Error>>()?;
60
61 Ok(entries)
62}
63
64fn read_users() -> io::Result<Vec<PathBuf>> {
65 let entries = fs::read_dir("./users")?
66 .map(|res| res.map(|e| e.path()))
67 .collect::<Result<Vec<_>, io::Error>>()?;
68
69 Ok(entries)
70}
71
72fn populate_db_with_last_block(db: &mut Db, path: String) -> &mut Db {
73 debug!("Populating db with last block {}", path);
74 let file = fs::read(path).unwrap();
75 let json = std::str::from_utf8(&file).unwrap();
76 let block: Block = serde_json::from_str(json).unwrap();
77 *db.blockchain.write() = block;
78
79 db
80}
81
82#[derive(Debug, Serialize, Deserialize, PartialEq)]
83pub struct UserAtRest {
84 pub fingerprint: Fingerprint,
85 pub user: User,
86}
87
88fn populate_db_with_users(db: &mut Db, files: Vec<PathBuf>) -> &mut Db {
89 for fs in files {
90 if let Ok(file_content) = fs::read(fs) {
91 let json =
92 String::from_utf8(file_content).expect("we have written a malformed user file");
93 let user_at_rest: UserAtRest = serde_json::from_str(&json).unwrap();
94
95 debug!("Populating db with user: {:?}", user_at_rest);
96 db.users
97 .write()
98 .insert(user_at_rest.fingerprint, user_at_rest.user);
99 }
100 }
101
102 db
103}
104
105/// Creates a new database, uses the previous last block if one exists and attempts the populate
106/// the users
107pub fn create_database() -> Db {
108 fs::create_dir_all("blocks").unwrap();
109 fs::create_dir_all("users").unwrap();
110 let mut db = Db::new();
111 if let Some(block_path) = last_block_content() {
112 populate_db_with_last_block(&mut db, block_path);
113 }
114
115 if let Ok(users_path) = read_users() {
116 populate_db_with_users(&mut db, users_path);
117 }
118
119 db
120}
121
122/// A JWT Payload/Claims representation
123///
124/// https://tools.ietf.org/html/rfc7519#section-4.1
125///
126/// - `tha`: Transaction Hash, String (custom field)
127/// - `iat`: Issued At, Unix Time, epoch
128/// - `exp`: Expiration Time, epoch
129#[derive(Debug, Serialize, Deserialize, PartialEq)]
130pub struct Claims {
131 pub tha: String,
132 pub iat: usize,
133 pub exp: usize,
134}
135
136/// Global Database representation
137///
138/// [`Db::blockchain`] is just the last block that was mined. All the blocks are written to disk as text
139/// files whenever they are accepted.
140///
141/// [`Db::pending_transactions`] is the in memory representation of the waiting transactions. Every
142/// user can have only one outstanding transaction at any given time.
143///
144/// [`Db::users`] is the in memory representation of the users, with their public keys, metu_ids and
145/// gradecoin balances.
146#[derive(Debug, Clone)]
147pub struct Db {
148 pub blockchain: Arc<RwLock<Block>>,
149 pub pending_transactions: Arc<RwLock<HashMap<Id, Transaction>>>,
150 pub users: Arc<RwLock<HashMap<Fingerprint, User>>>,
151}
152
153impl Db {
154 pub fn new() -> Self {
155 let mut users: HashMap<Fingerprint, User> = HashMap::new();
156
157 let friendly_1 = MetuId::new("friend_1".to_owned(), "not_used".to_owned()).unwrap();
158
159 users.insert(
160 "cde48537ca2c28084ff560826d0e6388b7c57a51497a6cb56f397289e52ff41b".to_owned(),
161 User {
162 user_id: friendly_1,
163 public_key: "not_used".to_owned(),
164 balance: 70,
165 is_bot: true,
166 },
167 );
168
169 let friendly_2 = MetuId::new("friend_2".to_owned(), "not_used".to_owned()).unwrap();
170
171 users.insert(
172 "a1a38b5bae5866d7d998a9834229ec2f9db7a4fc8fb6f58b1115a96a446875ff".to_owned(),
173 User {
174 user_id: friendly_2,
175 public_key: "not_used".to_owned(),
176 balance: 20,
177 is_bot: true,
178 },
179 );
180
181 let friendly_3 = MetuId::new("friend_4".to_owned(), "not_used".to_owned()).unwrap();
182
183 users.insert(
184 "4e048fd2a62f1307866086e803e9be43f78a702d5df10831fbf434e7663ae0e7".to_owned(),
185 User {
186 user_id: friendly_3,
187 public_key: "not_used".to_owned(),
188 balance: 120,
189 is_bot: true,
190 },
191 );
192
193 let friendly_4 = MetuId::new("friend_4".to_owned(), "not_used".to_owned()).unwrap();
194
195 users.insert(
196 "60e77101e76950a9b1830fa107fd2f8fc545255b3e0f14b6a7797cf9ee005f07".to_owned(),
197 User {
198 user_id: friendly_4,
199 public_key: "not_used".to_owned(),
200 balance: 40,
201 is_bot: true,
202 },
203 );
204
205 Db {
206 blockchain: Arc::new(RwLock::new(Block::new())),
207 pending_transactions: Arc::new(RwLock::new(HashMap::new())),
208 users: Arc::new(RwLock::new(users)),
209 }
210 }
211}
212
213impl Default for Db {
214 fn default() -> Self {
215 Self::new()
216 }
217}
218
219/// A transaction between `source` and `target` that moves `amount`
220#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
221pub struct Transaction {
222 pub source: Fingerprint,
223 pub target: Fingerprint,
224 pub amount: u16,
225 pub timestamp: NaiveDateTime,
226}
227
228/// A block that was proposed with `transaction_list` and `nonce` that made `hash` valid, 6 zeroes
229/// at the left hand side of the hash (24 bytes)
230///
231/// We are mining using blake2s algorithm, which produces 256 bit hashes. Hash/second is roughly
232/// 20x10^3.
233///
234/// https://serde.rs/container-attrs.html might be valuable to normalize the serialize/deserialize
235/// conventions as these will be hashed
236///
237#[derive(Serialize, Deserialize, Debug, PartialEq)]
238pub struct Block {
239 #[serde(skip_serializing_if = "Vec::is_empty")]
240 pub transaction_list: Vec<Fingerprint>,
241 pub nonce: u32,
242 pub timestamp: NaiveDateTime,
243 pub hash: String,
244}
245
246/// For prototyping and letting serde handle everything json
247#[derive(Serialize, Deserialize, Debug, PartialEq)]
248pub struct NakedBlock {
249 #[serde(skip_serializing_if = "Vec::is_empty", default)]
250 pub transaction_list: Vec<Fingerprint>,
251 pub nonce: u32,
252 pub timestamp: NaiveDateTime,
253}
254
255impl Block {
256 /// Genesis block
257 pub fn new() -> Block {
258 Block {
259 transaction_list: vec!["gradecoin_bank".to_owned()],
260 nonce: 0,
261 timestamp: NaiveDate::from_ymd(2021, 4, 11).and_hms(20, 45, 00),
262 hash: String::from("not_actually_mined"),
263 }
264 }
265}
266
267impl Default for Block {
268 fn default() -> Self {
269 Self::new()
270 }
271}
272
273/// A Student
274///
275/// * [`user_id`]: Can only be one of the repopulated
276/// * [`public_key`]: A PEM format public key "---- BEGIN" and all
277/// * [`balance`]: User's current Gradecoin amount
278///
279/// This should ideally include the fingerprint as well?
280#[derive(Serialize, Deserialize, Debug, PartialEq)]
281pub struct User {
282 pub user_id: MetuId,
283 pub public_key: String,
284 pub balance: u16,
285 #[serde(skip, default = "bool::default")]
286 pub is_bot: bool,
287}
288
289/// The values are hard coded in [`OUR_STUDENTS`] so MetuId::new() can accept/reject values based on that
290#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
291pub struct MetuId {
292 id: String,
293 passwd: String,
294}
295
296impl MetuId {
297 pub fn quick_equal(&self, other: &str) -> bool {
298 self.id == other
299 }
300}
301
302/// The plaintext of the initial user authentication request
303#[derive(Serialize, Deserialize, Debug, PartialEq)]
304pub struct AuthRequest {
305 pub student_id: String,
306 pub passwd: String,
307 pub public_key: String,
308}
309
310/// Ciphertext of the initial authentication request, or what we will receive
311#[derive(Serialize, Deserialize, Debug)]
312pub struct InitialAuthRequest {
313 pub c: String,
314 pub iv: String,
315 pub key: String,
316}
317
318// Students who are authorized to have Gradecoin accounts
319lazy_static! {
320 static ref OUR_STUDENTS: HashSet<(&'static str, &'static str)> = {
321 [
322 ("e254275", "DtNX1qk4YF4saRH"),
323 ("e223687", "cvFEs4XLjuGBD1v"),
324 ("e211024", "voQAcxiKJmEXYRT"),
325 ("e209888", "O75dli6AQtz2tUi"),
326 ("e223725", "xXuTD3Y4tyrv2Jz"),
327 ("e209362", "N7wGm5XU5zVWOWu"),
328 ("e209898", "aKBFfB8fZMq8pVn"),
329 ("e230995", "TgcHGlqeFhQGx42"),
330 ("e223743", "YVWVSWuIHplJk9C"),
331 ("e223747", "8LAeHrsjnwXh59Q"),
332 ("e223749", "HMFeJqVOzwCPHbc"),
333 ("e223751", "NjMsxmtmy2VOwMW"),
334 ("e188126", "QibuPdV2gXfsVJW"),
335 ("e209913", "kMxJvl2vHSWCy4A"),
336 ("e203608", "mfkkR0MWurk6Rp1"),
337 ("e233013", "GCqHxdOaDj2pWXx"),
338 ("e216982", "2Z0xmgCStnj5qg5"),
339 ("e217185", "BcaZNlzlhPph7A3"),
340 ("e223780", "2KvVxKUQaA9H4sn"),
341 ("e194931", "hsC0Wb8PQ5vzwdQ"),
342 ("e223783", "ETUJA3kt1QYvJai"),
343 ("e254550", "rPRjX0A4NefvKWi"),
344 ("e217203", "lN3IWhGyCrGfkk5"),
345 ("e217477", "O9xlMaa7LanC82w"),
346 ("e223786", "UxI6czykJfp9T9N"),
347 ("e231060", "VJgziofQQPCoisH"),
348 ("e223795", "pmcTCKox99NFsqp"),
349 ("e223715", "1H5QuOYI1b2r9ET"),
350 ("e181932", "THANKYOUHAVEFUN"),
351 ("bank", "P7oxDm30g1jeIId"),
352 ("friend_1", "not_used"),
353 ("friend_2", "not_used"),
354 ("friend_3", "not_used"),
355 ("friend_4", "not_used"),
356 ]
357 .iter()
358 .cloned()
359 .collect()
360 };
361}
362
363impl fmt::Display for MetuId {
364 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365 write!(f, "{}", self.id)
366 }
367}
368
369impl MetuId {
370 pub fn new(id: String, pwd: String) -> Option<Self> {
371 if OUR_STUDENTS.contains(&(&*id, &*pwd)) {
372 Some(MetuId { id, passwd: pwd })
373 } else {
374 None
375 }
376 }
377}