1#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
9pub struct Transaction {
10 pub from_user: String,
12 pub to_user: String,
14 pub amount: f64,
16 pub lamport_time: i64,
18 pub source_node: String,
20 pub optional_msg: Option<String>,
22 pub vector_clock: std::collections::HashMap<String, i64>,
24}
25
26#[allow(unused_imports)]
27use clap::Parser;
28#[cfg(feature = "server")]
29lazy_static::lazy_static! {
30 pub static ref DB_CONN: std::sync::Mutex<rusqlite::Connection> =
31 std::sync::Mutex::new(rusqlite::Connection::open(format!("peillute_{}.db", super::Args::parse().cli_db_id)).unwrap());
32}
33
34#[cfg(feature = "server")]
35const NULL: &str = "NULL";
37
38#[cfg(feature = "server")]
39pub fn init_db() -> rusqlite::Result<()> {
41 {
42 let conn = DB_CONN.lock().unwrap();
43
44 conn.execute(
46 "CREATE TABLE IF NOT EXISTS VectorClock (
47 id INTEGER PRIMARY KEY AUTOINCREMENT
48 );",
49 [],
50 )?;
51
52 conn.execute(
54 "CREATE TABLE IF NOT EXISTS VectorClockEntry (
55 vector_clock_id INTEGER,
56 site_id TEXT,
57 value INTEGER NOT NULL,
58 PRIMARY KEY(vector_clock_id, site_id),
59 FOREIGN KEY(vector_clock_id) REFERENCES VectorClock(id) ON DELETE CASCADE
60 );
61 ",
62 [],
63 )?;
64
65 conn.execute(
67 "CREATE TABLE IF NOT EXISTS User (
68 unique_name TEXT PRIMARY KEY,
69 solde FLOAT NOT NULL
70 )",
71 [],
72 )?;
73
74 conn.execute(
76 "CREATE TABLE IF NOT EXISTS Transactions (
77 from_user TEXT,
78 to_user TEXT NOT NULL,
79 amount FLOAT NOT NULL,
80 lamport_time INTEGER NOT NULL,
81 vector_clock_id INTEGER NOT NULL,
82 source_node TEXT NOT NULL,
83 optional_msg TEXT,
84 FOREIGN KEY(from_user) REFERENCES User(unique_name),
85 FOREIGN KEY(to_user) REFERENCES User(unique_name),
86 FOREIGN KEY(vector_clock_id) REFERENCES VectorClock(id),
87 PRIMARY KEY(lamport_time, source_node)
88 );",
89 [],
90 )?;
91
92 conn.execute(
93 "CREATE TABLE IF NOT EXISTS LocalState (
94 site_id TEXT PRIMARY KEY,
95 lamport_time INTEGER NOT NULL,
96 vector_clock_id INTEGER NOT NULL,
97 FOREIGN KEY(vector_clock_id) REFERENCES VectorClock(id)
98 );",
99 [],
100 )?;
101 }
102
103 log::debug!("Database initialized successfully.");
104 Ok(())
105}
106
107#[cfg(feature = "server")]
108pub fn update_local_state(site_id: &str, clock: crate::clock::Clock) -> rusqlite::Result<()> {
110 use rusqlite::params;
111
112 let lamport_time = clock.get_lamport();
113 let vc_clock = clock.get_vector_clock_map();
114
115 let conn = DB_CONN.lock().unwrap();
116 conn.execute("INSERT INTO VectorClock DEFAULT VALUES", [])?;
117 let vector_clock_id = conn.last_insert_rowid();
118
119 let mut stmt = conn.prepare(
120 "INSERT INTO VectorClockEntry (vector_clock_id, site_id, value) VALUES (?1, ?2, ?3)",
121 )?;
122 for (site, value) in vc_clock.iter() {
123 stmt.execute(params![vector_clock_id, site, value])?;
124 }
125 conn.execute(
126 "INSERT OR REPLACE INTO LocalState (site_id, lamport_time, vector_clock_id)
127 VALUES (?1, ?2, ?3)",
128 params![site_id, lamport_time, vector_clock_id],
129 )?;
130 Ok(())
131}
132
133#[cfg(feature = "server")]
134pub fn update_db_with_snapshot(
136 snapshot: &crate::snapshot::GlobalSnapshot,
137 vector_clock: &std::collections::HashMap<String, i64>,
138) {
139 log::info!("Applying snapshot to database");
140
141 if snapshot.missing.is_empty() {
142 log::info!("No missing transactions, nothing to do");
143 return;
144 }
145
146 let mut sorted_txs: Vec<_> = snapshot
148 .missing
149 .values()
150 .flat_map(|txs| txs.iter())
151 .collect();
152 sorted_txs.sort_by_key(|tx| tx.lamport_time);
153
154 for tx in sorted_txs {
155 let optional_msg = "";
156
157 let _ = crate::db::create_transaction(
158 &tx.from_user,
159 &tx.to_user,
160 (tx.amount_in_cent as f64) / 100.0,
161 &tx.lamport_time,
162 &tx.source_node,
163 &optional_msg,
164 vector_clock,
165 );
166 }
167}
168
169#[cfg(feature = "server")]
170pub fn get_local_state() -> rusqlite::Result<(String, crate::clock::Clock)> {
172 use rusqlite::params;
173 let conn = DB_CONN.lock().unwrap();
174 let mut stmt =
175 conn.prepare("SELECT site_id, lamport_time, vector_clock_id FROM LocalState LIMIT 1")?;
176
177 let row = stmt.query_row([], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)));
178
179 let (site_id, lamport_time, vector_clock_id): (String, i64, i64) = match row {
180 Ok(data) => data,
181 Err(rusqlite::Error::QueryReturnedNoRows) => {
182 return Err(rusqlite::Error::QueryReturnedNoRows);
183 }
184 Err(e) => return Err(e),
185 };
186
187 let mut clock_map = std::collections::HashMap::new();
188 let mut vc_stmt =
189 conn.prepare("SELECT site_id, value FROM VectorClockEntry WHERE vector_clock_id = ?1")?;
190 let mut rows = vc_stmt.query(params![vector_clock_id])?;
191 while let Some(vc_row) = rows.next()? {
192 let site: String = vc_row.get(0)?;
193 let value: i64 = vc_row.get(1)?;
194 clock_map.insert(site, value);
195 }
196
197 let c = crate::clock::Clock::from_parts(lamport_time, clock_map);
198
199 Ok((site_id, c))
200}
201
202#[cfg(feature = "server")]
203pub fn is_database_initialized() -> rusqlite::Result<bool> {
205 {
206 let conn = DB_CONN.lock().unwrap();
207 let mut stmt = conn.prepare(
208 "SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'Transactions')",
209 )?;
210 let exists: bool = stmt.query_row([], |row| row.get(0))?;
211 Ok(exists)
212 }
213}
214
215#[cfg(feature = "server")]
216pub fn transaction_exists(lamport_time: i64, source_node: &str) -> rusqlite::Result<bool> {
218 use rusqlite::params;
219 {
220 let conn = DB_CONN.lock().unwrap();
221 let mut stmt = conn.prepare(
222 "SELECT EXISTS(SELECT 1 FROM Transactions WHERE lamport_time = ?1 AND source_node = ?2)",
223 )?;
224 let exists: bool = stmt.query_row(params![lamport_time, source_node], |row| row.get(0))?;
225 Ok(exists)
226 }
227}
228
229#[cfg(feature = "server")]
230pub fn user_exists(name: &str) -> rusqlite::Result<bool> {
232 {
233 use rusqlite::params;
234 let conn = DB_CONN.lock().unwrap();
235 let mut stmt = conn.prepare("SELECT EXISTS(SELECT 1 FROM User WHERE unique_name = ?1)")?;
236 let exists: bool = stmt.query_row(params![name], |row| row.get(0))?;
237 Ok(exists)
238 }
239}
240
241#[cfg(feature = "server")]
242pub fn create_user(unique_name: &str) -> rusqlite::Result<()> {
244 use rusqlite::params;
245 if user_exists(unique_name)? {
246 log::warn!("User '{}' already exists.", unique_name);
247 return Ok(());
248 }
249
250 {
251 log::debug!("Ajout de l'utilisateur {}", unique_name);
252 let conn = DB_CONN.lock().unwrap();
253 conn.execute(
254 "INSERT INTO User (unique_name, solde) VALUES (?1, 0)",
255 params![unique_name],
256 )?;
257 Ok(())
258 }
259}
260
261#[cfg(feature = "server")]
262pub fn delete_user(name: &str) -> rusqlite::Result<()> {
264 use rusqlite::params;
265 if !user_exists(name)? {
266 let err = rusqlite::Error::SqliteFailure(
267 rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
268 Some(format!("User '{}' does not exist.", name).into()),
269 );
270
271 log::error!("User '{}' does not exist.", name);
272 return Err(err);
273 }
274 {
275 let conn = DB_CONN.lock().unwrap();
276 conn.execute("DELETE FROM User WHERE unique_name = ?1", params![name])?;
277 Ok(())
278 }
279}
280
281#[cfg(feature = "server")]
282pub fn calculate_solde(name: &str) -> rusqlite::Result<f64> {
284 {
285 use rusqlite::params;
286 let conn = DB_CONN.lock().unwrap();
287 let mut stmt = conn.prepare(
288 "SELECT
289 IFNULL((SELECT SUM(amount) FROM Transactions WHERE to_user = ?1), 0) -
290 IFNULL((SELECT SUM(amount) FROM Transactions WHERE from_user = ?1), 0)
291 AS balance",
292 )?;
293 stmt.query_row(params![name], |row| row.get(0))
294 }
295}
296
297#[cfg(feature = "server")]
298pub fn update_solde(name: &str) -> rusqlite::Result<()> {
300 use rusqlite::params;
301
302 if !user_exists(name)? {
303 let err = rusqlite::Error::SqliteFailure(
304 rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
305 Some(format!("User '{}' does not exist.", name).into()),
306 );
307
308 return Err(err);
309 }
310 let solde = calculate_solde(name)?;
311 {
312 let conn = DB_CONN.lock().unwrap();
313 conn.execute(
314 "UPDATE User SET solde = ?1 WHERE unique_name = ?2",
315 params![solde, name],
316 )?;
317 log::debug!("Updated solde for {} to {}", name, solde);
318 Ok(())
319 }
320}
321
322#[cfg(feature = "server")]
323pub fn ensure_user(name: &str) -> rusqlite::Result<()> {
325 if name != NULL && !user_exists(name)? {
326 create_user(name)?;
327 }
328 Ok(())
329}
330
331#[cfg(feature = "server")]
332pub fn create_transaction(
334 from_user: &str,
335 to_user: &str,
336 amount: f64,
337 lamport_time: &i64,
338 source_node: &str,
339 optional_msg: &str,
340 vector_clock: &std::collections::HashMap<String, i64>,
341) -> rusqlite::Result<()> {
342 use rusqlite::params;
343 if from_user != NULL && calculate_solde(from_user)? < amount {
344 let err = rusqlite::Error::SqliteFailure(
345 rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
346 Some(
347 format!(
348 "Insufficient funds: '{}' has less than {}.",
349 from_user, amount
350 )
351 .into(),
352 ),
353 );
354
355 log::error!(
356 "Insufficient funds: '{}' has less than {}.",
357 from_user,
358 amount
359 );
360 return Err(err);
361 }
362
363 ensure_user(from_user)?;
364 ensure_user(to_user)?;
365
366 log::debug!(
367 "Creating transaction from {} to {} with amount {}",
368 from_user,
369 to_user,
370 amount
371 );
372
373 {
374 let conn = DB_CONN.lock().unwrap();
375 conn.execute("INSERT INTO VectorClock DEFAULT VALUES", [])?;
376 let vector_clock_id = conn.last_insert_rowid();
377
378 let mut stmt = conn.prepare(
379 "INSERT INTO VectorClockEntry (vector_clock_id, site_id, value) VALUES (?1, ?2, ?3)",
380 )?;
381 for (site_id, value) in vector_clock.iter() {
382 stmt.execute(params![vector_clock_id, site_id, value])?;
383 }
384
385 conn.execute(
386 "INSERT INTO Transactions (from_user, to_user, amount, lamport_time, vector_clock_id, source_node, optional_msg)
387 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
388 params![
389 from_user,
390 to_user,
391 amount,
392 lamport_time,
393 vector_clock_id,
394 source_node,
395 optional_msg
396 ],
397 )?;
398 }
399
400 if from_user != NULL {
401 update_solde(from_user)?;
402 }
403 if to_user != NULL {
404 update_solde(to_user)?;
405 }
406
407 Ok(())
408}
409
410#[cfg(feature = "server")]
411pub fn deposit(
412 user: &str,
413 amount: f64,
414 lamport_time: &i64,
415 source_node: &str,
416 vector_clock: &std::collections::HashMap<String, i64>,
417) -> rusqlite::Result<()> {
418 if !user_exists(user)? {
419 let err = rusqlite::Error::SqliteFailure(
420 rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
421 Some(format!("Unknown User: {}", user).into()),
422 );
423
424 log::error!("Unknown User: {}", user);
425 return Err(err);
426 }
427
428 if amount < 0.0 {
429 let err = rusqlite::Error::SqliteFailure(
430 rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
431 Some(format!("Negative deposit amount: {}", amount).into()),
432 );
433
434 log::error!("Negative deposit amount: {}", amount);
435 return Err(err);
436 }
437
438 log::debug!("Depositing {} to {}", amount, user);
439
440 create_transaction(
441 NULL,
442 user,
443 amount,
444 lamport_time,
445 source_node,
446 "Deposit",
447 vector_clock,
448 )
449}
450
451#[cfg(feature = "server")]
452pub fn withdraw(
453 user: &str,
454 amount: f64,
455 lamport_time: &i64,
456 source_node: &str,
457 vector_clock: &std::collections::HashMap<String, i64>,
458) -> rusqlite::Result<()> {
459 if amount < 0.0 {
460 let err = rusqlite::Error::SqliteFailure(
461 rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
462 Some(format!("Negative withdrawal amount: {}", amount).into()),
463 );
464 log::error!("Negative withdrawal amount: {}", amount);
465 return Err(err);
466 }
467 if !user_exists(user)? {
468 let err = rusqlite::Error::SqliteFailure(
469 rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
470 Some(format!("Unknown user: {}", user).into()),
471 );
472 log::error!("Unknown user: {}", user);
473 return Err(err);
474 }
475 if calculate_solde(user)? < amount {
476 let err = rusqlite::Error::SqliteFailure(
477 rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
478 Some(format!("User {} not enough money", user).into()),
479 );
480 log::error!("User {} not enough money", user);
481 return Err(err);
482 }
483
484 log::debug!("Withdrawing {} from {}", amount, user);
485
486 create_transaction(
487 user,
488 NULL,
489 amount,
490 lamport_time,
491 source_node,
492 "Withdraw",
493 vector_clock,
494 )
495}
496
497#[cfg(feature = "server")]
498pub fn has_been_refunded(transac_time: i64, node: &str) -> rusqlite::Result<bool> {
499 use rusqlite::params;
500 {
501 let conn = DB_CONN.lock().unwrap();
502 let mut stmt =
503 conn.prepare("SELECT EXISTS(SELECT 1 FROM Transactions WHERE optional_msg = ?1)")?;
504
505 let optional_msg = format!("Refund transaction {}-{}", node, transac_time);
506 let exists: bool = stmt.query_row(params![optional_msg], |row| row.get(0))?;
507
508 Ok(exists)
509 }
510}
511
512#[cfg(feature = "server")]
513pub fn refund_transaction(
514 transac_time: i64,
515 node: &str,
516 lamport_time: &i64,
517 source_node: &str,
518 vector_clock: &std::collections::HashMap<String, i64>,
519) -> rusqlite::Result<()> {
520 if let Some(tx) = get_transaction(transac_time, node)? {
521 if calculate_solde(&tx.to_user)? < tx.amount {
522 let err = rusqlite::Error::SqliteFailure(
523 rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
524 Some(format!("User {} has not enough money to give back", &tx.to_user).into()),
525 );
526 log::error!("User {} has not enough money to give back", &tx.to_user);
527 return Err(err);
528 }
529
530 if tx.optional_msg.is_some() && tx.optional_msg.unwrap().starts_with("Refund transaction") {
531 let err = rusqlite::Error::SqliteFailure(
532 rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
533 Some(
534 format!(
535 "Transaction {}-{} is a refund transaction",
536 node, transac_time
537 )
538 .into(),
539 ),
540 );
541 log::error!(
542 "Transaction {}-{} is a refund transaction",
543 node,
544 transac_time
545 );
546 return Err(err);
547 }
548
549 if has_been_refunded(transac_time, node)? {
550 let err = rusqlite::Error::SqliteFailure(
551 rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
552 Some(format!("Transaction {}-{} already refunded", node, transac_time).into()),
553 );
554 log::error!("Transaction {}-{} already refunded", node, transac_time);
555 return Err(err);
556 }
557
558 create_transaction(
559 &tx.to_user,
560 &tx.from_user,
561 tx.amount,
562 lamport_time,
563 source_node,
564 &format!("Refund transaction {}-{}", node, transac_time),
565 vector_clock,
566 )?;
567 } else {
568 let err = rusqlite::Error::SqliteFailure(
569 rusqlite::ffi::Error::new(rusqlite::ffi::ErrorCode::Unknown as i32),
570 Some(
571 format!(
572 "No transaction found at time {} from node {}",
573 transac_time, node
574 )
575 .into(),
576 ),
577 );
578
579 log::error!(
580 "No transaction found at time {} from node {}",
581 transac_time,
582 node
583 );
584 return Err(err);
585 }
586 Ok(())
587}
588
589#[cfg(feature = "server")]
590pub fn get_transaction(transac_time: i64, node: &str) -> rusqlite::Result<Option<Transaction>> {
591 use rusqlite::params;
592 {
593 let conn = DB_CONN.lock().unwrap();
594 let mut stmt = conn.prepare(
595 "SELECT from_user, to_user, amount, lamport_time, source_node, optional_msg, vector_clock_id
596 FROM Transactions WHERE lamport_time = ?1 AND source_node = ?2",
597 )?;
598
599 match stmt.query_row(params![transac_time, node], |row| {
600 let from_user: String = row.get(0)?;
601 let to_user: String = row.get(1)?;
602 let amount: f64 = row.get(2)?;
603 let lamport_time: i64 = row.get(3)?;
604 let source_node: String = row.get(4)?;
605 let optional_msg: Option<String> = row.get(5)?;
606 let vector_clock_id: i64 = row.get(6)?;
607
608 let mut clock_map = std::collections::HashMap::new();
609 let mut vc_stmt = conn.prepare(
610 "SELECT site_id, value FROM VectorClockEntry WHERE vector_clock_id = ?1",
611 )?;
612 let mut rows = vc_stmt.query(params![vector_clock_id])?;
613 while let Some(vc_row) = rows.next()? {
614 let site_id: String = vc_row.get(0)?;
615 let value: i64 = vc_row.get(1)?;
616 clock_map.insert(site_id, value);
617 }
618
619 Ok(Transaction {
620 from_user,
621 to_user,
622 amount,
623 lamport_time,
624 source_node,
625 optional_msg,
626 vector_clock: clock_map,
627 })
628 }) {
629 Ok(tx) => Ok(Some(tx)),
630 Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
631 Err(e) => Err(e),
632 }
633 }
634}
635
636#[cfg(feature = "server")]
637pub fn print_users() -> rusqlite::Result<()> {
638 {
639 let conn = DB_CONN.lock().unwrap();
640 let mut stmt = conn.prepare("SELECT unique_name, solde FROM User")?;
641 let users = stmt.query_map([], |row| {
642 Ok((row.get::<_, String>(0)?, row.get::<_, f64>(1)?))
643 })?;
644
645 println!("-- Users --");
646 for user in users {
647 let (name, solde) = user?;
648 println!("{}: {:.2}", name, solde);
649 }
650 Ok(())
651 }
652}
653
654#[cfg(feature = "server")]
655pub fn get_users() -> rusqlite::Result<Vec<String>> {
656 {
657 let conn = DB_CONN.lock().unwrap();
658 let mut stmt = conn.prepare("SELECT unique_name FROM User")?;
659 let users = stmt.query_map([], |row| Ok(row.get::<_, String>(0)?))?;
660 let mut users_vec = Vec::new();
661 for user in users {
662 users_vec.push(user?);
663 }
664 Ok(users_vec)
665 }
666}
667
668#[cfg(feature = "server")]
669pub fn print_transactions() -> rusqlite::Result<()> {
670 {
671 let conn = DB_CONN.lock().unwrap();
672 let mut stmt = conn.prepare(
673 "SELECT from_user, to_user, amount, lamport_time, source_node, optional_msg, vector_clock_id FROM Transactions",
674 )?;
675 let txs = stmt.query_map([], |row| {
676 Ok((
677 row.get::<_, String>(0)?,
678 row.get::<_, String>(1)?,
679 row.get::<_, f64>(2)?,
680 row.get::<_, i64>(3)?,
681 row.get::<_, String>(4)?,
682 row.get::<_, Option<String>>(5)?,
683 row.get::<_, i64>(6)?,
684 ))
685 })?;
686
687 println!("📜 -- Transactions --");
688 println!(
689 "┌─────────────────┬─────────────────┬────────────┬────────────┬─────────────────┬──────────────────────┬──────────────────────┐"
690 );
691 println!(
692 "│ {:<15} │ {:<15} │ {:<10} │ {:<10} │ {:<15} │ {:<20} │ {:<20} │",
693 "From", "To", "Amount", "Time", "Node", "Message", "Vector Clock"
694 );
695 println!(
696 "├─────────────────┼─────────────────┼────────────┼────────────┼─────────────────┼──────────────────────┼──────────────────────┤"
697 );
698
699 for tx in txs {
700 let (from, to, amount, time, node, msg, vector_clock_id) = tx?;
701 let mut clock_map = std::collections::HashMap::new();
702 let mut vc_stmt = conn.prepare(
703 "SELECT site_id, value FROM VectorClockEntry WHERE vector_clock_id = ?1",
704 )?;
705 let mut rows = vc_stmt.query(rusqlite::params![vector_clock_id])?;
706 while let Some(vc_row) = rows.next()? {
707 let site_id: String = vc_row.get(0)?;
708 let value: i64 = vc_row.get(1)?;
709 clock_map.insert(site_id, value);
710 }
711
712 println!(
713 "│ {:<15} │ {:<15} │ {:<10.2} │ {:<10} │ {:<15} │ {:<20} │ {:<20?} │",
714 from,
715 to,
716 amount,
717 time,
718 node,
719 msg.unwrap_or_default(),
720 clock_map
721 );
722 }
723
724 println!(
725 "└─────────────────┴─────────────────┴────────────┴────────────┴─────────────────┴──────────────────────┴──────────────────────┘"
726 );
727 Ok(())
728 }
729}
730
731#[cfg(feature = "server")]
732pub fn print_transaction_for_user(name: &str) -> rusqlite::Result<()> {
733 use rusqlite::params;
734 {
735 let conn = DB_CONN.lock().unwrap();
736 let mut stmt = conn.prepare(
737 "SELECT from_user, to_user, amount, lamport_time, source_node, optional_msg, vector_clock_id
738 FROM Transactions WHERE from_user = ?1 OR to_user = ?1",
739 )?;
740
741 let txs = stmt.query_map(params![name], |row| {
742 Ok((
743 row.get::<_, String>(0)?,
744 row.get::<_, String>(1)?,
745 row.get::<_, f64>(2)?,
746 row.get::<_, i64>(3)?,
747 row.get::<_, String>(4)?,
748 row.get::<_, Option<String>>(5)?,
749 row.get::<_, i64>(6)?,
750 ))
751 })?;
752
753 println!("📜 -- Transactions for user {} --", name);
754 println!(
755 "┌─────────────────┬─────────────────┬────────────┬────────────┬─────────────────┬──────────────────────┬──────────────────────┐"
756 );
757 println!(
758 "│ {:<15} │ {:<15} │ {:<10} │ {:<10} │ {:<15} │ {:<20} │ {:<20} │",
759 "From", "To", "Amount", "Time", "Node", "Message", "Vector Clock"
760 );
761 println!(
762 "├─────────────────┼─────────────────┼────────────┼────────────┼─────────────────┼──────────────────────┼──────────────────────┤"
763 );
764
765 for tx in txs {
766 let (from, to, amount, time, node, msg, vector_clock_id) = tx?;
767 let mut clock_map = std::collections::HashMap::new();
768 let mut vc_stmt = conn.prepare(
769 "SELECT site_id, value FROM VectorClockEntry WHERE vector_clock_id = ?1",
770 )?;
771 let mut rows = vc_stmt.query(rusqlite::params![vector_clock_id])?;
772 while let Some(vc_row) = rows.next()? {
773 let site_id: String = vc_row.get(0)?;
774 let value: i64 = vc_row.get(1)?;
775 clock_map.insert(site_id, value);
776 }
777 println!(
778 "│ {:<15} │ {:<15} │ {:<10.2} │ {:<10} │ {:<15} │ {:<20} │ {:<20?} │",
779 from,
780 to,
781 amount,
782 time,
783 node,
784 msg.unwrap_or_default(),
785 clock_map
786 );
787 }
788
789 println!(
790 "└─────────────────┴─────────────────┴────────────┴────────────┴─────────────────┴──────────────────────┴──────────────────────┘"
791 );
792 Ok(())
793 }
794}
795
796#[cfg(feature = "server")]
797pub fn get_transactions_for_user(name: &str) -> rusqlite::Result<Vec<Transaction>> {
798 use rusqlite::params;
799 {
800 let conn = DB_CONN.lock().unwrap();
801 let mut stmt = conn.prepare(
802 "SELECT from_user, to_user, amount, lamport_time, source_node, optional_msg, vector_clock_id
803 FROM Transactions WHERE from_user = ?1 OR to_user = ?1",
804 )?;
805
806 let txs = stmt.query_map(params![name], |row| {
807 Ok((
808 row.get::<_, String>(0)?,
809 row.get::<_, String>(1)?,
810 row.get::<_, f64>(2)?,
811 row.get::<_, i64>(3)?,
812 row.get::<_, String>(4)?,
813 row.get::<_, Option<String>>(5)?,
814 row.get::<_, i64>(6)?,
815 ))
816 })?;
817
818 let mut txs_vec = Vec::new();
819 for tx in txs {
820 let (from, to, amount, time, node, msg, vector_clock_id) = tx?;
821 let mut clock_map = std::collections::HashMap::new();
822 let mut vc_stmt = conn.prepare(
823 "SELECT site_id, value FROM VectorClockEntry WHERE vector_clock_id = ?1",
824 )?;
825 let mut rows = vc_stmt.query(rusqlite::params![vector_clock_id])?;
826 while let Some(vc_row) = rows.next()? {
827 let site_id: String = vc_row.get(0)?;
828 let value: i64 = vc_row.get(1)?;
829 clock_map.insert(site_id, value);
830 }
831
832 txs_vec.push(Transaction {
833 from_user: from,
834 to_user: to,
835 amount: amount,
836 lamport_time: time,
837 source_node: node,
838 optional_msg: msg,
839 vector_clock: clock_map,
840 });
841 }
842 Ok(txs_vec)
843 }
844}
845
846#[cfg(feature = "server")]
847pub fn get_local_transaction_log() -> rusqlite::Result<Vec<Transaction>> {
848 let conn = DB_CONN.lock().unwrap();
849 let mut stmt = conn.prepare(
850 "SELECT from_user, to_user, amount, lamport_time, source_node, optional_msg, vector_clock_id
851 FROM Transactions")?;
852 let rows = stmt.query_map([], |row| {
853 Ok(Transaction {
854 from_user: row.get(0)?,
855 to_user: row.get(1)?,
856 amount: row.get(2)?,
857 lamport_time: row.get(3)?,
858 source_node: row.get(4)?,
859 optional_msg: row.get(5)?,
860 vector_clock: std::collections::HashMap::new(),
861 })
862 })?;
863
864 let mut out = Vec::new();
865 for r in rows {
866 out.push(r?);
867 }
868 Ok(out)
869}