From c960e8d35177ea949f5f01e869ab9bcc3d76805d Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Thu, 5 Apr 2018 09:53:58 -0600 Subject: [PATCH 1/2] Reject transactions with a `last_id` that isn't from this ledger Before this patch, a client could put any value into `last_id` and was primarily there to ensure the transaction had a globally unique signature. With this patch, the server can use `last_id` as an indicator of how long its been since the transaction was created. The server may choose to reject sufficiently old transactions so that it can forget about old signatures. --- src/accountant.rs | 21 +++++++++++++++------ src/accountant_skel.rs | 1 + 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 1b1b28178..153ba8786 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -62,7 +62,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 +85,16 @@ impl Accountant { { return Self::reserve_signature(&entry.1, sig); } + 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 sigs = RwLock::new(HashSet::new()); - Self::reserve_signature(&sigs, sig); self.last_ids.write().unwrap().push_back((*last_id, sigs)); - true } /// Process a Transaction that has already been verified. @@ -331,9 +339,8 @@ 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())); } } @@ -362,6 +369,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 From 01326936e606ac72a0917012b525f5e3f72e4e0f Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Thu, 5 Apr 2018 10:26:43 -0600 Subject: [PATCH 2/2] Expire all transactions after some amount of time Reject old transactions so that we can calculate an upper bound for memory usage, and therefore ensure the server won't slow down over time to crash due to memory exhaustion. --- src/accountant.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 153ba8786..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, @@ -93,8 +95,11 @@ impl Accountant { /// 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 sigs = RwLock::new(HashSet::new()); - self.last_ids.write().unwrap().push_back((*last_id, sigs)); + 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. @@ -222,6 +227,8 @@ impl Accountant { mod tests { use super::*; use signature::KeyPairUtil; + use hash::hash; + use bincode::serialize; #[test] fn test_accountant() { @@ -342,6 +349,19 @@ mod tests { 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())); + } } #[cfg(all(feature = "unstable", test))]