diff --git a/src/accountant.rs b/src/accountant.rs index 1b1b28178..efe80df37 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -15,6 +15,8 @@ use std::result; use std::sync::RwLock; use transaction::Transaction; +const MAX_ENTRY_IDS: usize = 1024 * 4; + #[derive(Debug, PartialEq, Eq)] pub enum AccountingError { InsufficientFunds, @@ -62,7 +64,9 @@ impl Accountant { to: mint.pubkey(), tokens: mint.tokens, }; - Self::new_from_deposit(&deposit) + let acc = Self::new_from_deposit(&deposit); + acc.register_entry_id(&mint.last_id()); + acc } fn reserve_signature(signatures: &RwLock>, sig: &Signature) -> bool { @@ -83,10 +87,19 @@ impl Accountant { { return Self::reserve_signature(&entry.1, sig); } - let sigs = RwLock::new(HashSet::new()); - Self::reserve_signature(&sigs, sig); - self.last_ids.write().unwrap().push_back((*last_id, sigs)); - true + false + } + + /// Tell the accountant which Entry IDs exist on the ledger. This function + /// assumes subsequent calls correspond to later entries, and will boot + /// the oldest ones once its internal cache is full. Once boot, the + /// accountant will reject transactions using that `last_id`. + pub fn register_entry_id(&self, last_id: &Hash) { + let mut last_ids = self.last_ids.write().unwrap(); + if last_ids.len() >= MAX_ENTRY_IDS { + last_ids.pop_front(); + } + last_ids.push_back((*last_id, RwLock::new(HashSet::new()))); } /// Process a Transaction that has already been verified. @@ -214,6 +227,8 @@ impl Accountant { mod tests { use super::*; use signature::KeyPairUtil; + use hash::hash; + use bincode::serialize; #[test] fn test_accountant() { @@ -331,9 +346,21 @@ mod tests { let alice = Mint::new(1); let acc = Accountant::new(&alice); let sig = Signature::default(); - let last_id = Hash::default(); - assert!(acc.reserve_signature_with_last_id(&sig, &last_id)); - assert!(!acc.reserve_signature_with_last_id(&sig, &last_id)); + assert!(acc.reserve_signature_with_last_id(&sig, &alice.last_id())); + assert!(!acc.reserve_signature_with_last_id(&sig, &alice.last_id())); + } + + #[test] + fn test_max_entry_ids() { + let alice = Mint::new(1); + let acc = Accountant::new(&alice); + let sig = Signature::default(); + for i in 0..MAX_ENTRY_IDS { + let last_id = hash(&serialize(&i).unwrap()); // Unique hash + acc.register_entry_id(&last_id); + } + // Assert we're no longer able to use the oldest entry ID. + assert!(!acc.reserve_signature_with_last_id(&sig, &alice.last_id())); } } @@ -362,6 +389,8 @@ mod bench { // Seed the 'to' account and a cell for its signature. let last_id = hash(&serialize(&i).unwrap()); // Unique hash + acc.register_entry_id(&last_id); + let rando1 = KeyPair::new(); let tr = Transaction::new(&rando0, rando1.pubkey(), 1, last_id); acc.process_verified_transaction(&tr).unwrap(); diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index dae08cd71..bba0ab740 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -77,6 +77,7 @@ impl AccountantSkel { pub fn sync(&mut self) -> Hash { while let Ok(entry) = self.historian.receiver.try_recv() { self.last_id = entry.id; + self.acc.register_entry_id(&self.last_id); writeln!(self.writer, "{}", serde_json::to_string(&entry).unwrap()).unwrap(); } self.last_id