diff --git a/src/accountant.rs b/src/accountant.rs new file mode 100644 index 0000000000..d3a1054c1f --- /dev/null +++ b/src/accountant.rs @@ -0,0 +1,129 @@ +//! The `accountant` is a client of the `historian`. It uses the historian's +//! event log to record transactions. Its users can deposit funds and +//! transfer funds to other users. + +use log::{verify_entry, Event, PublicKey, Sha256Hash}; +use historian::Historian; +use ring::signature::Ed25519KeyPair; +use std::sync::mpsc::{RecvError, SendError}; +use std::collections::HashMap; + +pub struct Accountant { + pub historian: Historian, + pub balances: HashMap, + pub end_hash: Sha256Hash, +} + +impl Accountant { + pub fn new(start_hash: &Sha256Hash, ms_per_tick: Option) -> Self { + let hist = Historian::::new(start_hash, ms_per_tick); + Accountant { + historian: hist, + balances: HashMap::new(), + end_hash: *start_hash, + } + } + + pub fn process_event(self: &mut Self, event: Event) { + match event { + Event::Claim { key, data, .. } => { + if self.balances.contains_key(&key) { + if let Some(x) = self.balances.get_mut(&key) { + *x += data; + } + } else { + self.balances.insert(key, data); + } + } + Event::Transaction { from, to, data, .. } => { + if let Some(x) = self.balances.get_mut(&from) { + *x -= data; + } + if self.balances.contains_key(&to) { + if let Some(x) = self.balances.get_mut(&to) { + *x += data; + } + } else { + self.balances.insert(to, data); + } + } + _ => (), + } + } + + pub fn sync(self: &mut Self) { + while let Ok(entry) = self.historian.receiver.try_recv() { + assert!(verify_entry(&entry, &self.end_hash)); + self.end_hash = entry.end_hash; + + self.process_event(entry.event); + } + } + + pub fn deposit( + self: &Self, + n: u64, + keypair: &Ed25519KeyPair, + ) -> Result<(), SendError>> { + use log::sign_hash; + let event = sign_hash(n, &keypair); + self.historian.sender.send(event) + } + + pub fn transfer( + self: &mut Self, + n: u64, + keypair: &Ed25519KeyPair, + pubkey: PublicKey, + ) -> Result<(), SendError>> { + use log::transfer_hash; + use generic_array::GenericArray; + + let sender_pubkey = GenericArray::clone_from_slice(keypair.public_key_bytes()); + if self.get_balance(&sender_pubkey).unwrap() >= n { + let event = transfer_hash(n, keypair, pubkey); + return self.historian.sender.send(event); + } + Ok(()) + } + + pub fn get_balance(self: &mut Self, pubkey: &PublicKey) -> Result { + self.sync(); + Ok(*self.balances.get(pubkey).unwrap_or(&0)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::thread::sleep; + use std::time::Duration; + use log::generate_keypair; + use historian::ExitReason; + use generic_array::GenericArray; + + #[test] + fn test_accountant() { + let zero = Sha256Hash::default(); + + let mut acc = Accountant::new(&zero, Some(2)); + + let alice_keypair = generate_keypair(); + let bob_keypair = generate_keypair(); + acc.deposit(10_000, &alice_keypair).unwrap(); + acc.deposit(1_000, &bob_keypair).unwrap(); + + let bob_pubkey = GenericArray::clone_from_slice(bob_keypair.public_key_bytes()); + acc.transfer(500, &alice_keypair, bob_pubkey).unwrap(); + + sleep(Duration::new(0, 1_000_000)); + + assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_500); + + drop(acc.historian.sender); + assert_eq!( + acc.historian.thread_hdl.join().unwrap().1, + ExitReason::RecvDisconnected + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index 82be8c1d28..5aef0ab234 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #![cfg_attr(feature = "unstable", feature(test))] pub mod log; pub mod historian; +pub mod accountant; extern crate bincode; extern crate generic_array; extern crate rayon;