From 46e8c09bd8c55f10121f2055fe8ac8cbc1484b8c Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 09:30:10 -0600 Subject: [PATCH 01/10] Revoke API access to first_id --- src/accountant.rs | 20 ++++++++++++-------- src/accountant_skel.rs | 24 ++++++++---------------- src/accountant_stub.rs | 27 +++++++++++---------------- src/bin/testnode.rs | 4 ++-- src/recorder.rs | 4 ++-- 5 files changed, 35 insertions(+), 44 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 8c2fb048c8..df2addeef6 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -15,7 +15,7 @@ use signature::{KeyPair, PublicKey, Signature}; use std::collections::hash_map::Entry::Occupied; use std::collections::{HashMap, HashSet}; use std::result; -use std::sync::mpsc::SendError; +use std::sync::mpsc::{Receiver, SendError}; use transaction::Transaction; #[derive(Debug, PartialEq, Eq)] @@ -36,9 +36,8 @@ fn complete_transaction(balances: &mut HashMap, plan: &Plan) { } pub struct Accountant { - pub historian: Historian, - pub balances: HashMap, - pub first_id: Hash, + historian: Historian, + balances: HashMap, pending: HashMap, time_sources: HashSet, last_time: DateTime, @@ -46,7 +45,7 @@ pub struct Accountant { impl Accountant { /// Create an Accountant using an existing ledger. - pub fn new_from_entries(entries: I, ms_per_tick: Option) -> Self + pub fn new_from_entries(entries: I, ms_per_tick: Option) -> (Self, Hash) where I: IntoIterator, { @@ -61,7 +60,6 @@ impl Accountant { let mut acc = Accountant { historian: hist, balances: HashMap::new(), - first_id: start_hash, pending: HashMap::new(), time_sources: HashSet::new(), last_time: Utc.timestamp(0, 0), @@ -73,17 +71,19 @@ impl Accountant { let entry1 = entries.next().unwrap(); acc.process_verified_event(&entry1.events[0], true).unwrap(); + let mut last_id = entry1.id; for entry in entries { + last_id = entry.id; for event in entry.events { acc.process_verified_event(&event, false).unwrap(); } } - acc + (acc, last_id) } /// Create an Accountant with only a Mint. Typically used by unit tests. pub fn new(mint: &Mint, ms_per_tick: Option) -> Self { - Self::new_from_entries(mint.create_entries(), ms_per_tick) + Self::new_from_entries(mint.create_entries(), ms_per_tick).0 } fn is_deposit(allow_deposits: bool, from: &PublicKey, plan: &Plan) -> bool { @@ -94,6 +94,10 @@ impl Accountant { } } + pub fn receiver(&self) -> &Receiver { + &self.historian.receiver + } + /// Process and log the given Transaction. pub fn log_verified_transaction(&mut self, tr: Transaction) -> Result<()> { if self.get_balance(&tr.from).unwrap_or(0) < tr.tokens { diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index b8b1226eb7..26af073993 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -22,8 +22,8 @@ use transaction::Transaction; use rayon::prelude::*; pub struct AccountantSkel { - pub acc: Accountant, - pub last_id: Hash, + acc: Accountant, + last_id: Hash, writer: W, } @@ -32,7 +32,7 @@ pub struct AccountantSkel { pub enum Request { Transaction(Transaction), GetBalance { key: PublicKey }, - GetId { is_last: bool }, + GetLastId, } impl Request { @@ -54,23 +54,22 @@ fn filter_valid_requests(reqs: Vec<(Request, SocketAddr)>) -> Vec<(Request, Sock pub enum Response { Balance { key: PublicKey, val: Option }, Entries { entries: Vec }, - Id { id: Hash, is_last: bool }, + LastId { id: Hash }, } impl AccountantSkel { /// Create a new AccountantSkel that wraps the given Accountant. - pub fn new(acc: Accountant, w: W) -> Self { - let last_id = acc.first_id; + pub fn new(acc: Accountant, last_id: Hash, writer: W) -> Self { AccountantSkel { acc, last_id, - writer: w, + writer, } } /// Process any Entry items that have been published by the Historian. pub fn sync(&mut self) -> Hash { - while let Ok(entry) = self.acc.historian.receiver.try_recv() { + while let Ok(entry) = self.acc.receiver().try_recv() { self.last_id = entry.id; writeln!(self.writer, "{}", serde_json::to_string(&entry).unwrap()).unwrap(); } @@ -90,14 +89,7 @@ impl AccountantSkel { let val = self.acc.get_balance(&key); Some(Response::Balance { key, val }) } - Request::GetId { is_last } => Some(Response::Id { - id: if is_last { - self.sync() - } else { - self.acc.first_id - }, - is_last, - }), + Request::GetLastId => Some(Response::LastId { id: self.sync() }), } } diff --git a/src/accountant_stub.rs b/src/accountant_stub.rs index 1c3c36d2fe..518c08fafd 100644 --- a/src/accountant_stub.rs +++ b/src/accountant_stub.rs @@ -65,26 +65,21 @@ impl AccountantStub { Ok(None) } - /// Request the first or last Entry ID from the server. - fn get_id(&self, is_last: bool) -> io::Result { - let req = Request::GetId { is_last }; - let data = serialize(&req).expect("serialize GetId"); - self.socket.send_to(&data, &self.addr)?; - let mut buf = vec![0u8; 1024]; - self.socket.recv_from(&mut buf)?; - let resp = deserialize(&buf).expect("deserialize Id"); - if let Response::Id { id, .. } = resp { - return Ok(id); - } - Ok(Default::default()) - } - /// Request the last Entry ID from the server. This method blocks /// until the server sends a response. At the time of this writing, /// it also has the side-effect of causing the server to log any /// entries that have been published by the Historian. pub fn get_last_id(&self) -> io::Result { - self.get_id(true) + let req = Request::GetLastId; + let data = serialize(&req).expect("serialize GetId"); + self.socket.send_to(&data, &self.addr)?; + let mut buf = vec![0u8; 1024]; + self.socket.recv_from(&mut buf)?; + let resp = deserialize(&buf).expect("deserialize Id"); + if let Response::LastId { id } = resp { + return Ok(id); + } + Ok(Default::default()) } } @@ -110,7 +105,7 @@ mod tests { let acc = Accountant::new(&alice, Some(30)); let bob_pubkey = KeyPair::new().pubkey(); let exit = Arc::new(AtomicBool::new(false)); - let acc = Arc::new(Mutex::new(AccountantSkel::new(acc, sink()))); + let acc = Arc::new(Mutex::new(AccountantSkel::new(acc, alice.seed(), sink()))); let _threads = AccountantSkel::serve(acc, addr, exit.clone()).unwrap(); sleep(Duration::from_millis(300)); diff --git a/src/bin/testnode.rs b/src/bin/testnode.rs index a8daaec4c8..1e562b74fd 100644 --- a/src/bin/testnode.rs +++ b/src/bin/testnode.rs @@ -14,9 +14,9 @@ fn main() { .lock() .lines() .map(|line| serde_json::from_str(&line.unwrap()).unwrap()); - let acc = Accountant::new_from_entries(entries, Some(1000)); + let (acc, last_id) = Accountant::new_from_entries(entries, Some(1000)); let exit = Arc::new(AtomicBool::new(false)); - let skel = Arc::new(Mutex::new(AccountantSkel::new(acc, stdout()))); + let skel = Arc::new(Mutex::new(AccountantSkel::new(acc, last_id, stdout()))); eprintln!("Listening on {}", addr); let threads = AccountantSkel::serve(skel, addr, exit.clone()).unwrap(); for t in threads { diff --git a/src/recorder.rs b/src/recorder.rs index 40536458c7..faf0759357 100644 --- a/src/recorder.rs +++ b/src/recorder.rs @@ -34,11 +34,11 @@ pub struct Recorder { } impl Recorder { - pub fn new(receiver: Receiver, sender: SyncSender, start_hash: Hash) -> Self { + pub fn new(receiver: Receiver, sender: SyncSender, last_hash: Hash) -> Self { Recorder { receiver, sender, - last_hash: start_hash, + last_hash, events: vec![], num_hashes: 0, num_ticks: 0, From 3abe305a215874c8de19fd1f059c78fc5f98131f Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 09:36:22 -0600 Subject: [PATCH 02/10] Move reserve_signatures into accountant Reasons Transaction signatures need to be unique: 1. guard against duplicates 2. accountant uses them as IDs to link Witness signatures to transactions via the `pending` hash map --- src/accountant.rs | 21 ++++++++++++++++++++- src/historian.rs | 22 ---------------------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index df2addeef6..43e4c8e259 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -39,6 +39,7 @@ pub struct Accountant { historian: Historian, balances: HashMap, pending: HashMap, + signatures: HashSet, time_sources: HashSet, last_time: DateTime, } @@ -61,6 +62,7 @@ impl Accountant { historian: hist, balances: HashMap::new(), pending: HashMap::new(), + signatures: HashSet::new(), time_sources: HashSet::new(), last_time: Utc.timestamp(0, 0), }; @@ -124,13 +126,21 @@ impl Accountant { self.log_verified_transaction(tr) } + fn reserve_signature(&mut self, sig: &Signature) -> bool { + if self.signatures.contains(sig) { + return false; + } + self.signatures.insert(*sig); + true + } + /// Process a Transaction that has already been verified. fn process_verified_transaction( self: &mut Self, tr: &Transaction, allow_deposits: bool, ) -> Result<()> { - if !self.historian.reserve_signature(&tr.sig) { + if !self.reserve_signature(&tr.sig) { return Err(AccountingError::InvalidTransferSignature); } @@ -400,4 +410,13 @@ mod tests { acc.process_verified_sig(alice.pubkey(), sig).unwrap(); // <-- Attack! Attempt to cancel completed transaction. assert_ne!(acc.get_balance(&alice.pubkey()), Some(2)); } + + #[test] + fn test_duplicate_event_signature() { + let alice = Mint::new(1); + let mut acc = Accountant::new(&alice, None); + let sig = Signature::default(); + assert!(acc.reserve_signature(&sig)); + assert!(!acc.reserve_signature(&sig)); + } } diff --git a/src/historian.rs b/src/historian.rs index 3bf92668d0..bc77b64164 100644 --- a/src/historian.rs +++ b/src/historian.rs @@ -4,8 +4,6 @@ use entry::Entry; use hash::Hash; use recorder::{ExitReason, Recorder, Signal}; -use signature::Signature; -use std::collections::HashSet; use std::sync::mpsc::{sync_channel, Receiver, SyncSender}; use std::thread::{spawn, JoinHandle}; use std::time::Instant; @@ -14,7 +12,6 @@ pub struct Historian { pub sender: SyncSender, pub receiver: Receiver, pub thread_hdl: JoinHandle, - pub signatures: HashSet, } impl Historian { @@ -23,23 +20,13 @@ impl Historian { let (entry_sender, receiver) = sync_channel(1000); let thread_hdl = Historian::create_recorder(*start_hash, ms_per_tick, event_receiver, entry_sender); - let signatures = HashSet::new(); Historian { sender, receiver, thread_hdl, - signatures, } } - pub fn reserve_signature(&mut self, sig: &Signature) -> bool { - if self.signatures.contains(sig) { - return false; - } - self.signatures.insert(*sig); - true - } - /// A background thread that will continue tagging received Event messages and /// sending back Entry messages until either the receiver or sender channel is closed. fn create_recorder( @@ -110,15 +97,6 @@ mod tests { ); } - #[test] - fn test_duplicate_event_signature() { - let zero = Hash::default(); - let mut hist = Historian::new(&zero, None); - let sig = Signature::default(); - assert!(hist.reserve_signature(&sig)); - assert!(!hist.reserve_signature(&sig)); - } - #[test] fn test_ticking_historian() { let zero = Hash::default(); From da2b4962a993953327d9aac89557dc05225080d0 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 11:36:51 -0600 Subject: [PATCH 03/10] Move verify_slice() into a trait --- src/bin/historian-demo.rs | 4 ++-- src/historian.rs | 4 ++-- src/ledger.rs | 32 +++++++++++++++++++------------- src/mint.rs | 4 ++-- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/bin/historian-demo.rs b/src/bin/historian-demo.rs index 89d377c986..306b8ffbaa 100644 --- a/src/bin/historian-demo.rs +++ b/src/bin/historian-demo.rs @@ -4,7 +4,7 @@ use solana::entry::Entry; use solana::event::Event; use solana::hash::Hash; use solana::historian::Historian; -use solana::ledger::verify_slice; +use solana::ledger::Block; use solana::recorder::Signal; use solana::signature::{KeyPair, KeyPairUtil}; use solana::transaction::Transaction; @@ -33,5 +33,5 @@ fn main() { } // Proof-of-History: Verify the historian learned about the events // in the same order they appear in the vector. - assert!(verify_slice(&entries, &seed)); + assert!(entries[..].verify(&seed)); } diff --git a/src/historian.rs b/src/historian.rs index bc77b64164..c48fa248c2 100644 --- a/src/historian.rs +++ b/src/historian.rs @@ -53,7 +53,7 @@ impl Historian { #[cfg(test)] mod tests { use super::*; - use ledger::*; + use ledger::Block; use std::thread::sleep; use std::time::Duration; @@ -82,7 +82,7 @@ mod tests { ExitReason::RecvDisconnected ); - assert!(verify_slice(&[entry0, entry1, entry2], &zero)); + assert!([entry0, entry1, entry2].verify(&zero)); } #[test] diff --git a/src/ledger.rs b/src/ledger.rs index 50657b828c..0056bd54e2 100644 --- a/src/ledger.rs +++ b/src/ledger.rs @@ -5,11 +5,17 @@ use entry::{next_tick, Entry}; use hash::Hash; use rayon::prelude::*; -/// Verifies the hashes and counts of a slice of events are all consistent. -pub fn verify_slice(entries: &[Entry], start_hash: &Hash) -> bool { - let genesis = [Entry::new_tick(Default::default(), start_hash)]; - let entry_pairs = genesis.par_iter().chain(entries).zip(entries); - entry_pairs.all(|(x0, x1)| x1.verify(&x0.id)) +pub trait Block { + /// Verifies the hashes and counts of a slice of events are all consistent. + fn verify(&self, start_hash: &Hash) -> bool; +} + +impl Block for [Entry] { + fn verify(&self, start_hash: &Hash) -> bool { + let genesis = [Entry::new_tick(0, start_hash)]; + let entry_pairs = genesis.par_iter().chain(self).zip(self); + entry_pairs.all(|(x0, x1)| x1.verify(&x0.id)) + } } /// Create a vector of Ticks of length `len` from `start_hash` hash and `num_hashes`. @@ -33,14 +39,14 @@ mod tests { fn test_verify_slice() { let zero = Hash::default(); let one = hash(&zero); - assert!(verify_slice(&vec![], &zero)); // base case - assert!(verify_slice(&vec![Entry::new_tick(0, &zero)], &zero)); // singleton case 1 - assert!(!verify_slice(&vec![Entry::new_tick(0, &zero)], &one)); // singleton case 2, bad - assert!(verify_slice(&next_ticks(&zero, 0, 2), &zero)); // inductive step + assert!(vec![][..].verify(&zero)); // base case + assert!(vec![Entry::new_tick(0, &zero)][..].verify(&zero)); // singleton case 1 + assert!(!vec![Entry::new_tick(0, &zero)][..].verify(&one)); // singleton case 2, bad + assert!(next_ticks(&zero, 0, 2)[..].verify(&zero)); // inductive step let mut bad_ticks = next_ticks(&zero, 0, 2); bad_ticks[1].id = one; - assert!(!verify_slice(&bad_ticks, &zero)); // inductive step, bad + assert!(!bad_ticks.verify(&zero)); // inductive step, bad } } @@ -52,10 +58,10 @@ mod bench { #[bench] fn event_bench(bencher: &mut Bencher) { - let start_hash = Default::default(); - let events = next_ticks(&start_hash, 10_000, 8); + let start_hash = Hash::default(); + let entries = next_ticks(&start_hash, 10_000, 8); bencher.iter(|| { - assert!(verify_slice(&events, &start_hash)); + assert!(entries.verify(&start_hash)); }); } } diff --git a/src/mint.rs b/src/mint.rs index 7f0339b546..fb9a1a2a81 100644 --- a/src/mint.rs +++ b/src/mint.rs @@ -57,7 +57,7 @@ impl Mint { #[cfg(test)] mod tests { use super::*; - use ledger::verify_slice; + use ledger::Block; use plan::Plan; #[test] @@ -74,6 +74,6 @@ mod tests { #[test] fn test_verify_entries() { let entries = Mint::new(100).create_entries(); - assert!(verify_slice(&entries, &entries[0].id)); + assert!(entries[..].verify(&entries[0].id)); } } From fc540395f9e0a1d1d63be997d9f4015902b86dc2 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 11:51:56 -0600 Subject: [PATCH 04/10] Update docs --- doc/historian.md | 6 +++--- doc/historian.msc | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/historian.md b/doc/historian.md index 0064d7f64b..f2cb23c581 100644 --- a/doc/historian.md +++ b/doc/historian.md @@ -11,7 +11,7 @@ with by verifying each entry's hash can be generated from the hash in the previo extern crate solana; use solana::historian::Historian; -use solana::ledger::{verify_slice, Entry, Hash}; +use solana::ledger::{Block, Entry, Hash}; use solana::event::{generate_keypair, get_pubkey, sign_claim_data, Event}; use std::thread::sleep; use std::time::Duration; @@ -38,7 +38,7 @@ fn main() { } // Proof-of-History: Verify the historian learned about the events // in the same order they appear in the vector. - assert!(verify_slice(&entries, &seed)); + assert!(entries[..].verify(&seed)); } ``` @@ -56,7 +56,7 @@ Proof-of-History Take note of the last line: ```rust -assert!(verify_slice(&entries, &seed)); +assert!(entries[..].verify(&seed)); ``` [It's a proof!](https://en.wikipedia.org/wiki/Curry–Howard_correspondence) For each entry returned by the diff --git a/doc/historian.msc b/doc/historian.msc index ace614b40c..b9bc84bd49 100644 --- a/doc/historian.msc +++ b/doc/historian.msc @@ -14,5 +14,5 @@ msc { recorder=>historian [ label = "e2 = Entry{id: h6, n: 3, event: Tick}" ] ; client=>historian [ label = "collect()" ] ; historian=>client [ label = "entries = [e0, e1, e2]" ] ; - client=>client [ label = "verify_slice(entries, h0)" ] ; + client=>client [ label = "entries.verify(h0)" ] ; } From 17de6876bb2814820abf7b16acda7f114618d87c Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 13:51:44 -0600 Subject: [PATCH 05/10] Add simpler accountant constructor --- src/accountant.rs | 71 +++++++++++++++++++++++++++++------------------ src/plan.rs | 8 +++--- 2 files changed, 48 insertions(+), 31 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 43e4c8e259..0fd889a2cd 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -9,7 +9,7 @@ use event::Event; use hash::Hash; use historian::Historian; use mint::Mint; -use plan::{Plan, Witness}; +use plan::{Payment, Plan, Witness}; use recorder::Signal; use signature::{KeyPair, PublicKey, Signature}; use std::collections::hash_map::Entry::Occupied; @@ -29,10 +29,8 @@ pub enum AccountingError { pub type Result = result::Result; /// Commit funds to the 'to' party. -fn complete_transaction(balances: &mut HashMap, plan: &Plan) { - if let Plan::Pay(ref payment) = *plan { - *balances.entry(payment.to).or_insert(0) += payment.tokens; - } +fn apply_payment(balances: &mut HashMap, payment: &Payment) { + *balances.entry(payment.to).or_insert(0) += payment.tokens; } pub struct Accountant { @@ -45,6 +43,34 @@ pub struct Accountant { } impl Accountant { + /// Create an Accountant using a deposit. + pub fn new_from_deposit( + start_hash: &Hash, + deposit: &Payment, + ms_per_tick: Option, + ) -> Self { + let mut balances = HashMap::new(); + apply_payment(&mut balances, &deposit); + let historian = Historian::new(&start_hash, ms_per_tick); + Accountant { + historian, + balances, + pending: HashMap::new(), + signatures: HashSet::new(), + time_sources: HashSet::new(), + last_time: Utc.timestamp(0, 0), + } + } + + /// Create an Accountant with only a Mint. Typically used by unit tests. + pub fn new(mint: &Mint, ms_per_tick: Option) -> Self { + let deposit = Payment { + to: mint.pubkey(), + tokens: mint.tokens, + }; + Self::new_from_deposit(&mint.seed(), &deposit, ms_per_tick) + } + /// Create an Accountant using an existing ledger. pub fn new_from_entries(entries: I, ms_per_tick: Option) -> (Self, Hash) where @@ -57,21 +83,17 @@ impl Accountant { let entry0 = entries.next().unwrap(); let start_hash = entry0.id; - let hist = Historian::new(&start_hash, ms_per_tick); - let mut acc = Accountant { - historian: hist, - balances: HashMap::new(), - pending: HashMap::new(), - signatures: HashSet::new(), - time_sources: HashSet::new(), - last_time: Utc.timestamp(0, 0), - }; - // The second item in the ledger is a special transaction where the to and from // fields are the same. That entry should be treated as a deposit, not a // transfer to oneself. let entry1 = entries.next().unwrap(); - acc.process_verified_event(&entry1.events[0], true).unwrap(); + let deposit = if let Event::Transaction(ref tr) = entry1.events[0] { + tr.plan.final_payment() + } else { + None + }; + + let mut acc = Self::new_from_deposit(&start_hash, &deposit.unwrap(), ms_per_tick); let mut last_id = entry1.id; for entry in entries { @@ -83,11 +105,6 @@ impl Accountant { (acc, last_id) } - /// Create an Accountant with only a Mint. Typically used by unit tests. - pub fn new(mint: &Mint, ms_per_tick: Option) -> Self { - Self::new_from_entries(mint.create_entries(), ms_per_tick).0 - } - fn is_deposit(allow_deposits: bool, from: &PublicKey, plan: &Plan) -> bool { if let Plan::Pay(ref payment) = *plan { allow_deposits && *from == payment.to @@ -153,8 +170,8 @@ impl Accountant { let mut plan = tr.plan.clone(); plan.apply_witness(&Witness::Timestamp(self.last_time)); - if plan.is_complete() { - complete_transaction(&mut self.balances, &plan); + if let Some(ref payment) = plan.final_payment() { + apply_payment(&mut self.balances, payment); } else { self.pending.insert(tr.sig, plan); } @@ -166,8 +183,8 @@ impl Accountant { fn process_verified_sig(&mut self, from: PublicKey, tx_sig: Signature) -> Result<()> { if let Occupied(mut e) = self.pending.entry(tx_sig) { e.get_mut().apply_witness(&Witness::Signature(from)); - if e.get().is_complete() { - complete_transaction(&mut self.balances, e.get()); + if let Some(ref payment) = e.get().final_payment() { + apply_payment(&mut self.balances, payment); e.remove_entry(); } }; @@ -195,8 +212,8 @@ impl Accountant { let mut completed = vec![]; for (key, plan) in &mut self.pending { plan.apply_witness(&Witness::Timestamp(self.last_time)); - if plan.is_complete() { - complete_transaction(&mut self.balances, plan); + if let Some(ref payment) = plan.final_payment() { + apply_payment(&mut self.balances, payment); completed.push(key.clone()); } } diff --git a/src/plan.rs b/src/plan.rs index adefe61332..d1bc40745e 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -72,11 +72,11 @@ impl Plan { ) } - /// Return true if the spending plan requires no additional Witnesses. - pub fn is_complete(&self) -> bool { + /// Return Payment if the spending plan requires no additional Witnesses. + pub fn final_payment(&self) -> Option { match *self { - Plan::Pay(_) => true, - _ => false, + Plan::Pay(ref payment) => Some(payment.clone()), + _ => None, } } From d63506f98ce0df10d92e8253cfdf7a57ce14aba2 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 14:00:42 -0600 Subject: [PATCH 06/10] No longer allow deposits outside the constructor --- src/accountant.rs | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 0fd889a2cd..7f7422686b 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -99,20 +99,12 @@ impl Accountant { for entry in entries { last_id = entry.id; for event in entry.events { - acc.process_verified_event(&event, false).unwrap(); + acc.process_verified_event(&event).unwrap(); } } (acc, last_id) } - fn is_deposit(allow_deposits: bool, from: &PublicKey, plan: &Plan) -> bool { - if let Plan::Pay(ref payment) = *plan { - allow_deposits && *from == payment.to - } else { - false - } - } - pub fn receiver(&self) -> &Receiver { &self.historian.receiver } @@ -123,7 +115,7 @@ impl Accountant { return Err(AccountingError::InsufficientFunds); } - self.process_verified_transaction(&tr, false)?; + self.process_verified_transaction(&tr)?; if let Err(SendError(_)) = self.historian .sender .send(Signal::Event(Event::Transaction(tr))) @@ -152,19 +144,13 @@ impl Accountant { } /// Process a Transaction that has already been verified. - fn process_verified_transaction( - self: &mut Self, - tr: &Transaction, - allow_deposits: bool, - ) -> Result<()> { + fn process_verified_transaction(&mut self, tr: &Transaction) -> Result<()> { if !self.reserve_signature(&tr.sig) { return Err(AccountingError::InvalidTransferSignature); } - if !Self::is_deposit(allow_deposits, &tr.from, &tr.plan) { - if let Some(x) = self.balances.get_mut(&tr.from) { - *x -= tr.tokens; - } + if let Some(x) = self.balances.get_mut(&tr.from) { + *x -= tr.tokens; } let mut plan = tr.plan.clone(); @@ -226,9 +212,9 @@ impl Accountant { } /// Process an Transaction or Witness that has already been verified. - fn process_verified_event(self: &mut Self, event: &Event, allow_deposits: bool) -> Result<()> { + fn process_verified_event(&mut self, event: &Event) -> Result<()> { match *event { - Event::Transaction(ref tr) => self.process_verified_transaction(tr, allow_deposits), + Event::Transaction(ref tr) => self.process_verified_transaction(tr), Event::Signature { from, tx_sig, .. } => self.process_verified_sig(from, tx_sig), Event::Timestamp { from, dt, .. } => self.process_verified_timestamp(from, dt), } @@ -237,7 +223,7 @@ impl Accountant { /// Create, sign, and process a Transaction from `keypair` to `to` of /// `n` tokens where `last_id` is the last Entry ID observed by the client. pub fn transfer( - self: &mut Self, + &mut self, n: i64, keypair: &KeyPair, to: PublicKey, @@ -252,7 +238,7 @@ impl Accountant { /// to `to` of `n` tokens on `dt` where `last_id` is the last Entry ID /// observed by the client. pub fn transfer_on_date( - self: &mut Self, + &mut self, n: i64, keypair: &KeyPair, to: PublicKey, @@ -264,7 +250,7 @@ impl Accountant { self.log_transaction(tr).map(|_| sig) } - pub fn get_balance(self: &Self, pubkey: &PublicKey) -> Option { + pub fn get_balance(&self, pubkey: &PublicKey) -> Option { self.balances.get(pubkey).cloned() } } From 90cd9bd5338141b9b9c30178971b574ebf4b9a05 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 14:14:49 -0600 Subject: [PATCH 07/10] Move balance check so that log_* methods are only used to add logging --- src/accountant.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 7f7422686b..d459dc3322 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -111,10 +111,6 @@ impl Accountant { /// Process and log the given Transaction. pub fn log_verified_transaction(&mut self, tr: Transaction) -> Result<()> { - if self.get_balance(&tr.from).unwrap_or(0) < tr.tokens { - return Err(AccountingError::InsufficientFunds); - } - self.process_verified_transaction(&tr)?; if let Err(SendError(_)) = self.historian .sender @@ -144,7 +140,11 @@ impl Accountant { } /// Process a Transaction that has already been verified. - fn process_verified_transaction(&mut self, tr: &Transaction) -> Result<()> { + pub fn process_verified_transaction(&mut self, tr: &Transaction) -> Result<()> { + if self.get_balance(&tr.from).unwrap_or(0) < tr.tokens { + return Err(AccountingError::InsufficientFunds); + } + if !self.reserve_signature(&tr.sig) { return Err(AccountingError::InvalidTransferSignature); } From 2b788d06b744c2fcb69732b4c23d722623ad00aa Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 14:41:07 -0600 Subject: [PATCH 08/10] Move the historian up to accountant_skel --- src/accountant.rs | 88 ++++++++++-------------------------------- src/accountant_skel.rs | 18 +++++++-- src/accountant_stub.rs | 11 +++++- src/bin/testnode.rs | 11 +++++- 4 files changed, 52 insertions(+), 76 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index d459dc3322..561b317c7f 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -7,15 +7,12 @@ use chrono::prelude::*; use entry::Entry; use event::Event; use hash::Hash; -use historian::Historian; use mint::Mint; use plan::{Payment, Plan, Witness}; -use recorder::Signal; use signature::{KeyPair, PublicKey, Signature}; use std::collections::hash_map::Entry::Occupied; use std::collections::{HashMap, HashSet}; use std::result; -use std::sync::mpsc::{Receiver, SendError}; use transaction::Transaction; #[derive(Debug, PartialEq, Eq)] @@ -23,7 +20,6 @@ pub enum AccountingError { InsufficientFunds, InvalidTransfer, InvalidTransferSignature, - SendError, } pub type Result = result::Result; @@ -34,7 +30,6 @@ fn apply_payment(balances: &mut HashMap, payment: &Payment) { } pub struct Accountant { - historian: Historian, balances: HashMap, pending: HashMap, signatures: HashSet, @@ -44,16 +39,10 @@ pub struct Accountant { impl Accountant { /// Create an Accountant using a deposit. - pub fn new_from_deposit( - start_hash: &Hash, - deposit: &Payment, - ms_per_tick: Option, - ) -> Self { + pub fn new_from_deposit(deposit: &Payment) -> Self { let mut balances = HashMap::new(); apply_payment(&mut balances, &deposit); - let historian = Historian::new(&start_hash, ms_per_tick); Accountant { - historian, balances, pending: HashMap::new(), signatures: HashSet::new(), @@ -63,16 +52,16 @@ impl Accountant { } /// Create an Accountant with only a Mint. Typically used by unit tests. - pub fn new(mint: &Mint, ms_per_tick: Option) -> Self { + pub fn new(mint: &Mint) -> Self { let deposit = Payment { to: mint.pubkey(), tokens: mint.tokens, }; - Self::new_from_deposit(&mint.seed(), &deposit, ms_per_tick) + Self::new_from_deposit(&deposit) } /// Create an Accountant using an existing ledger. - pub fn new_from_entries(entries: I, ms_per_tick: Option) -> (Self, Hash) + pub fn new_from_entries(entries: I) -> (Self, Hash) where I: IntoIterator, { @@ -80,8 +69,7 @@ impl Accountant { // The first item in the ledger is required to be an entry with zero num_hashes, // which implies its id can be used as the ledger's seed. - let entry0 = entries.next().unwrap(); - let start_hash = entry0.id; + entries.next().unwrap(); // The second item in the ledger is a special transaction where the to and from // fields are the same. That entry should be treated as a deposit, not a @@ -93,7 +81,7 @@ impl Accountant { None }; - let mut acc = Self::new_from_deposit(&start_hash, &deposit.unwrap(), ms_per_tick); + let mut acc = Self::new_from_deposit(&deposit.unwrap()); let mut last_id = entry1.id; for entry in entries { @@ -105,30 +93,13 @@ impl Accountant { (acc, last_id) } - pub fn receiver(&self) -> &Receiver { - &self.historian.receiver - } - - /// Process and log the given Transaction. - pub fn log_verified_transaction(&mut self, tr: Transaction) -> Result<()> { - self.process_verified_transaction(&tr)?; - if let Err(SendError(_)) = self.historian - .sender - .send(Signal::Event(Event::Transaction(tr))) - { - return Err(AccountingError::SendError); - } - - Ok(()) - } - /// Verify and process the given Transaction. - pub fn log_transaction(&mut self, tr: Transaction) -> Result<()> { + pub fn process_transaction(&mut self, tr: Transaction) -> Result<()> { if !tr.verify() { return Err(AccountingError::InvalidTransfer); } - self.log_verified_transaction(tr) + self.process_verified_transaction(&tr) } fn reserve_signature(&mut self, sig: &Signature) -> bool { @@ -231,7 +202,7 @@ impl Accountant { ) -> Result { let tr = Transaction::new(keypair, to, n, last_id); let sig = tr.sig; - self.log_transaction(tr).map(|_| sig) + self.process_transaction(tr).map(|_| sig) } /// Create, sign, and process a postdated Transaction from `keypair` @@ -247,7 +218,7 @@ impl Accountant { ) -> Result { let tr = Transaction::new_on_date(keypair, to, dt, n, last_id); let sig = tr.sig; - self.log_transaction(tr).map(|_| sig) + self.process_transaction(tr).map(|_| sig) } pub fn get_balance(&self, pubkey: &PublicKey) -> Option { @@ -258,14 +229,13 @@ impl Accountant { #[cfg(test)] mod tests { use super::*; - use recorder::ExitReason; use signature::KeyPairUtil; #[test] fn test_accountant() { let alice = Mint::new(10_000); let bob_pubkey = KeyPair::new().pubkey(); - let mut acc = Accountant::new(&alice, Some(2)); + let mut acc = Accountant::new(&alice); acc.transfer(1_000, &alice.keypair(), bob_pubkey, alice.seed()) .unwrap(); assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_000); @@ -273,18 +243,12 @@ mod tests { acc.transfer(500, &alice.keypair(), bob_pubkey, alice.seed()) .unwrap(); assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_500); - - drop(acc.historian.sender); - assert_eq!( - acc.historian.thread_hdl.join().unwrap(), - ExitReason::RecvDisconnected - ); } #[test] fn test_invalid_transfer() { let alice = Mint::new(11_000); - let mut acc = Accountant::new(&alice, Some(2)); + let mut acc = Accountant::new(&alice); let bob_pubkey = KeyPair::new().pubkey(); acc.transfer(1_000, &alice.keypair(), bob_pubkey, alice.seed()) .unwrap(); @@ -296,25 +260,19 @@ mod tests { let alice_pubkey = alice.keypair().pubkey(); assert_eq!(acc.get_balance(&alice_pubkey).unwrap(), 10_000); assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_000); - - drop(acc.historian.sender); - assert_eq!( - acc.historian.thread_hdl.join().unwrap(), - ExitReason::RecvDisconnected - ); } #[test] fn test_overspend_attack() { let alice = Mint::new(1); - let mut acc = Accountant::new(&alice, None); + let mut acc = Accountant::new(&alice); let bob_pubkey = KeyPair::new().pubkey(); let mut tr = Transaction::new(&alice.keypair(), bob_pubkey, 1, alice.seed()); if let Plan::Pay(ref mut payment) = tr.plan { payment.tokens = 2; // <-- attack! } assert_eq!( - acc.log_transaction(tr.clone()), + acc.process_transaction(tr.clone()), Err(AccountingError::InvalidTransfer) ); @@ -323,7 +281,7 @@ mod tests { payment.tokens = 0; // <-- whoops! } assert_eq!( - acc.log_transaction(tr.clone()), + acc.process_transaction(tr.clone()), Err(AccountingError::InvalidTransfer) ); } @@ -331,24 +289,18 @@ mod tests { #[test] fn test_transfer_to_newb() { let alice = Mint::new(10_000); - let mut acc = Accountant::new(&alice, Some(2)); + let mut acc = Accountant::new(&alice); let alice_keypair = alice.keypair(); let bob_pubkey = KeyPair::new().pubkey(); acc.transfer(500, &alice_keypair, bob_pubkey, alice.seed()) .unwrap(); assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 500); - - drop(acc.historian.sender); - assert_eq!( - acc.historian.thread_hdl.join().unwrap(), - ExitReason::RecvDisconnected - ); } #[test] fn test_transfer_on_date() { let alice = Mint::new(1); - let mut acc = Accountant::new(&alice, Some(2)); + let mut acc = Accountant::new(&alice); let alice_keypair = alice.keypair(); let bob_pubkey = KeyPair::new().pubkey(); let dt = Utc::now(); @@ -374,7 +326,7 @@ mod tests { #[test] fn test_transfer_after_date() { let alice = Mint::new(1); - let mut acc = Accountant::new(&alice, Some(2)); + let mut acc = Accountant::new(&alice); let alice_keypair = alice.keypair(); let bob_pubkey = KeyPair::new().pubkey(); let dt = Utc::now(); @@ -391,7 +343,7 @@ mod tests { #[test] fn test_cancel_transfer() { let alice = Mint::new(1); - let mut acc = Accountant::new(&alice, Some(2)); + let mut acc = Accountant::new(&alice); let alice_keypair = alice.keypair(); let bob_pubkey = KeyPair::new().pubkey(); let dt = Utc::now(); @@ -417,7 +369,7 @@ mod tests { #[test] fn test_duplicate_event_signature() { let alice = Mint::new(1); - let mut acc = Accountant::new(&alice, None); + let mut acc = Accountant::new(&alice); let sig = Signature::default(); assert!(acc.reserve_signature(&sig)); assert!(!acc.reserve_signature(&sig)); diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index 26af073993..8893fcb1f7 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -3,8 +3,11 @@ //! in flux. Clients should use AccountantStub to interact with it. use accountant::Accountant; +use historian::Historian; +use recorder::Signal; use bincode::{deserialize, serialize}; use entry::Entry; +use event::Event; use hash::Hash; use result::Result; use serde_json; @@ -13,7 +16,7 @@ use std::default::Default; use std::io::Write; use std::net::{SocketAddr, UdpSocket}; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc::channel; +use std::sync::mpsc::{channel, SendError}; use std::sync::{Arc, Mutex}; use std::thread::{spawn, JoinHandle}; use std::time::Duration; @@ -25,6 +28,7 @@ pub struct AccountantSkel { acc: Accountant, last_id: Hash, writer: W, + historian: Historian, } #[cfg_attr(feature = "cargo-clippy", allow(large_enum_variant))] @@ -59,17 +63,18 @@ pub enum Response { impl AccountantSkel { /// Create a new AccountantSkel that wraps the given Accountant. - pub fn new(acc: Accountant, last_id: Hash, writer: W) -> Self { + pub fn new(acc: Accountant, last_id: Hash, writer: W, historian: Historian) -> Self { AccountantSkel { acc, last_id, writer, + historian, } } /// Process any Entry items that have been published by the Historian. pub fn sync(&mut self) -> Hash { - while let Ok(entry) = self.acc.receiver().try_recv() { + while let Ok(entry) = self.historian.receiver.try_recv() { self.last_id = entry.id; writeln!(self.writer, "{}", serde_json::to_string(&entry).unwrap()).unwrap(); } @@ -80,8 +85,13 @@ impl AccountantSkel { pub fn log_verified_request(&mut self, msg: Request) -> Option { match msg { Request::Transaction(tr) => { - if let Err(err) = self.acc.log_verified_transaction(tr) { + if let Err(err) = self.acc.process_verified_transaction(&tr) { eprintln!("Transaction error: {:?}", err); + } else if let Err(SendError(_)) = self.historian + .sender + .send(Signal::Event(Event::Transaction(tr))) + { + eprintln!("Channel send error"); } None } diff --git a/src/accountant_stub.rs b/src/accountant_stub.rs index 518c08fafd..cd8b1c87a2 100644 --- a/src/accountant_stub.rs +++ b/src/accountant_stub.rs @@ -87,6 +87,7 @@ impl AccountantStub { mod tests { use super::*; use accountant::Accountant; + use historian::Historian; use accountant_skel::AccountantSkel; use mint::Mint; use signature::{KeyPair, KeyPairUtil}; @@ -102,10 +103,16 @@ mod tests { let addr = "127.0.0.1:9000"; let send_addr = "127.0.0.1:9001"; let alice = Mint::new(10_000); - let acc = Accountant::new(&alice, Some(30)); + let acc = Accountant::new(&alice); let bob_pubkey = KeyPair::new().pubkey(); let exit = Arc::new(AtomicBool::new(false)); - let acc = Arc::new(Mutex::new(AccountantSkel::new(acc, alice.seed(), sink()))); + let historian = Historian::new(&alice.seed(), Some(30)); + let acc = Arc::new(Mutex::new(AccountantSkel::new( + acc, + alice.seed(), + sink(), + historian, + ))); let _threads = AccountantSkel::serve(acc, addr, exit.clone()).unwrap(); sleep(Duration::from_millis(300)); diff --git a/src/bin/testnode.rs b/src/bin/testnode.rs index 1e562b74fd..346da9f70c 100644 --- a/src/bin/testnode.rs +++ b/src/bin/testnode.rs @@ -2,6 +2,7 @@ extern crate serde_json; extern crate solana; use solana::accountant::Accountant; +use solana::historian::Historian; use solana::accountant_skel::AccountantSkel; use std::io::{self, stdout, BufRead}; use std::sync::atomic::AtomicBool; @@ -14,9 +15,15 @@ fn main() { .lock() .lines() .map(|line| serde_json::from_str(&line.unwrap()).unwrap()); - let (acc, last_id) = Accountant::new_from_entries(entries, Some(1000)); + let (acc, last_id) = Accountant::new_from_entries(entries); + let historian = Historian::new(&last_id, Some(1000)); let exit = Arc::new(AtomicBool::new(false)); - let skel = Arc::new(Mutex::new(AccountantSkel::new(acc, last_id, stdout()))); + let skel = Arc::new(Mutex::new(AccountantSkel::new( + acc, + last_id, + stdout(), + historian, + ))); eprintln!("Listening on {}", addr); let threads = AccountantSkel::serve(skel, addr, exit.clone()).unwrap(); for t in threads { From daadae79879cc3a5bde5c03d48e6747881645f2c Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 14:51:38 -0600 Subject: [PATCH 09/10] Move replaying ledger out of accountant --- src/accountant.rs | 36 +----------------------------------- src/bin/testnode.rs | 30 ++++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 37 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 561b317c7f..dc73c6278d 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -4,7 +4,6 @@ //! already been signed and verified. use chrono::prelude::*; -use entry::Entry; use event::Event; use hash::Hash; use mint::Mint; @@ -60,39 +59,6 @@ impl Accountant { Self::new_from_deposit(&deposit) } - /// Create an Accountant using an existing ledger. - pub fn new_from_entries(entries: I) -> (Self, Hash) - where - I: IntoIterator, - { - let mut entries = entries.into_iter(); - - // The first item in the ledger is required to be an entry with zero num_hashes, - // which implies its id can be used as the ledger's seed. - entries.next().unwrap(); - - // The second item in the ledger is a special transaction where the to and from - // fields are the same. That entry should be treated as a deposit, not a - // transfer to oneself. - let entry1 = entries.next().unwrap(); - let deposit = if let Event::Transaction(ref tr) = entry1.events[0] { - tr.plan.final_payment() - } else { - None - }; - - let mut acc = Self::new_from_deposit(&deposit.unwrap()); - - let mut last_id = entry1.id; - for entry in entries { - last_id = entry.id; - for event in entry.events { - acc.process_verified_event(&event).unwrap(); - } - } - (acc, last_id) - } - /// Verify and process the given Transaction. pub fn process_transaction(&mut self, tr: Transaction) -> Result<()> { if !tr.verify() { @@ -183,7 +149,7 @@ impl Accountant { } /// Process an Transaction or Witness that has already been verified. - fn process_verified_event(&mut self, event: &Event) -> Result<()> { + pub fn process_verified_event(&mut self, event: &Event) -> Result<()> { match *event { Event::Transaction(ref tr) => self.process_verified_transaction(tr), Event::Signature { from, tx_sig, .. } => self.process_verified_sig(from, tx_sig), diff --git a/src/bin/testnode.rs b/src/bin/testnode.rs index 346da9f70c..ef6700d309 100644 --- a/src/bin/testnode.rs +++ b/src/bin/testnode.rs @@ -2,6 +2,8 @@ extern crate serde_json; extern crate solana; use solana::accountant::Accountant; +use solana::event::Event; +use solana::entry::Entry; use solana::historian::Historian; use solana::accountant_skel::AccountantSkel; use std::io::{self, stdout, BufRead}; @@ -11,11 +13,35 @@ use std::sync::{Arc, Mutex}; fn main() { let addr = "127.0.0.1:8000"; let stdin = io::stdin(); - let entries = stdin + let mut entries = stdin .lock() .lines() .map(|line| serde_json::from_str(&line.unwrap()).unwrap()); - let (acc, last_id) = Accountant::new_from_entries(entries); + + // The first item in the ledger is required to be an entry with zero num_hashes, + // which implies its id can be used as the ledger's seed. + entries.next().unwrap(); + + // The second item in the ledger is a special transaction where the to and from + // fields are the same. That entry should be treated as a deposit, not a + // transfer to oneself. + let entry1: Entry = entries.next().unwrap(); + let deposit = if let Event::Transaction(ref tr) = entry1.events[0] { + tr.plan.final_payment() + } else { + None + }; + + let mut acc = Accountant::new_from_deposit(&deposit.unwrap()); + + let mut last_id = entry1.id; + for entry in entries { + last_id = entry.id; + for event in entry.events { + acc.process_verified_event(&event).unwrap(); + } + } + let historian = Historian::new(&last_id, Some(1000)); let exit = Arc::new(AtomicBool::new(false)); let skel = Arc::new(Mutex::new(AccountantSkel::new( From 49708e92d3135975bdbbe1307f351fa4487e8533 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 15:02:23 -0600 Subject: [PATCH 10/10] Use last_id instead of seed It doesn't really matter, but was confusing since the seed points to an entry before the mint's deposit. --- src/accountant.rs | 18 +++++++++--------- src/accountant_stub.rs | 4 ++-- src/mint.rs | 4 ++++ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index dc73c6278d..9a04f6b9d4 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -202,11 +202,11 @@ mod tests { let alice = Mint::new(10_000); let bob_pubkey = KeyPair::new().pubkey(); let mut acc = Accountant::new(&alice); - acc.transfer(1_000, &alice.keypair(), bob_pubkey, alice.seed()) + acc.transfer(1_000, &alice.keypair(), bob_pubkey, alice.last_id()) .unwrap(); assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_000); - acc.transfer(500, &alice.keypair(), bob_pubkey, alice.seed()) + acc.transfer(500, &alice.keypair(), bob_pubkey, alice.last_id()) .unwrap(); assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_500); } @@ -216,10 +216,10 @@ mod tests { let alice = Mint::new(11_000); let mut acc = Accountant::new(&alice); let bob_pubkey = KeyPair::new().pubkey(); - acc.transfer(1_000, &alice.keypair(), bob_pubkey, alice.seed()) + acc.transfer(1_000, &alice.keypair(), bob_pubkey, alice.last_id()) .unwrap(); assert_eq!( - acc.transfer(10_001, &alice.keypair(), bob_pubkey, alice.seed()), + acc.transfer(10_001, &alice.keypair(), bob_pubkey, alice.last_id()), Err(AccountingError::InsufficientFunds) ); @@ -233,7 +233,7 @@ mod tests { let alice = Mint::new(1); let mut acc = Accountant::new(&alice); let bob_pubkey = KeyPair::new().pubkey(); - let mut tr = Transaction::new(&alice.keypair(), bob_pubkey, 1, alice.seed()); + let mut tr = Transaction::new(&alice.keypair(), bob_pubkey, 1, alice.last_id()); if let Plan::Pay(ref mut payment) = tr.plan { payment.tokens = 2; // <-- attack! } @@ -258,7 +258,7 @@ mod tests { let mut acc = Accountant::new(&alice); let alice_keypair = alice.keypair(); let bob_pubkey = KeyPair::new().pubkey(); - acc.transfer(500, &alice_keypair, bob_pubkey, alice.seed()) + acc.transfer(500, &alice_keypair, bob_pubkey, alice.last_id()) .unwrap(); assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 500); } @@ -270,7 +270,7 @@ mod tests { let alice_keypair = alice.keypair(); let bob_pubkey = KeyPair::new().pubkey(); let dt = Utc::now(); - acc.transfer_on_date(1, &alice_keypair, bob_pubkey, dt, alice.seed()) + acc.transfer_on_date(1, &alice_keypair, bob_pubkey, dt, alice.last_id()) .unwrap(); // Alice's balance will be zero because all funds are locked up. @@ -299,7 +299,7 @@ mod tests { acc.process_verified_timestamp(alice.pubkey(), dt).unwrap(); // It's now past now, so this transfer should be processed immediately. - acc.transfer_on_date(1, &alice_keypair, bob_pubkey, dt, alice.seed()) + acc.transfer_on_date(1, &alice_keypair, bob_pubkey, dt, alice.last_id()) .unwrap(); assert_eq!(acc.get_balance(&alice.pubkey()), Some(0)); @@ -313,7 +313,7 @@ mod tests { let alice_keypair = alice.keypair(); let bob_pubkey = KeyPair::new().pubkey(); let dt = Utc::now(); - let sig = acc.transfer_on_date(1, &alice_keypair, bob_pubkey, dt, alice.seed()) + let sig = acc.transfer_on_date(1, &alice_keypair, bob_pubkey, dt, alice.last_id()) .unwrap(); // Alice's balance will be zero because all funds are locked up. diff --git a/src/accountant_stub.rs b/src/accountant_stub.rs index cd8b1c87a2..0c7c528087 100644 --- a/src/accountant_stub.rs +++ b/src/accountant_stub.rs @@ -106,10 +106,10 @@ mod tests { let acc = Accountant::new(&alice); let bob_pubkey = KeyPair::new().pubkey(); let exit = Arc::new(AtomicBool::new(false)); - let historian = Historian::new(&alice.seed(), Some(30)); + let historian = Historian::new(&alice.last_id(), Some(30)); let acc = Arc::new(Mutex::new(AccountantSkel::new( acc, - alice.seed(), + alice.last_id(), sink(), historian, ))); diff --git a/src/mint.rs b/src/mint.rs index fb9a1a2a81..5b869c3152 100644 --- a/src/mint.rs +++ b/src/mint.rs @@ -33,6 +33,10 @@ impl Mint { hash(&self.pkcs8) } + pub fn last_id(&self) -> Hash { + self.create_entries()[1].id + } + pub fn keypair(&self) -> KeyPair { KeyPair::from_pkcs8(Input::from(&self.pkcs8)).unwrap() }