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)" ] ; } diff --git a/src/accountant.rs b/src/accountant.rs index 8c2fb048c8..9a04f6b9d4 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -4,18 +4,14 @@ //! already been signed and verified. use chrono::prelude::*; -use entry::Entry; use event::Event; use hash::Hash; -use historian::Historian; use mint::Mint; -use plan::{Plan, Witness}; -use recorder::Signal; +use plan::{Payment, Plan, Witness}; 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 transaction::Transaction; #[derive(Debug, PartialEq, Eq)] @@ -23,124 +19,82 @@ pub enum AccountingError { InsufficientFunds, InvalidTransfer, InvalidTransferSignature, - SendError, } 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 { - pub historian: Historian, - pub balances: HashMap, - pub first_id: Hash, + balances: HashMap, pending: HashMap, + signatures: HashSet, time_sources: HashSet, last_time: DateTime, } impl Accountant { - /// Create an Accountant using an existing ledger. - pub fn new_from_entries(entries: I, ms_per_tick: Option) -> Self - 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. - 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(), - first_id: start_hash, + /// Create an Accountant using a deposit. + pub fn new_from_deposit(deposit: &Payment) -> Self { + let mut balances = HashMap::new(); + apply_payment(&mut balances, &deposit); + Accountant { + balances, 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(); - - for entry in entries { - for event in entry.events { - acc.process_verified_event(&event, false).unwrap(); - } } - acc } /// 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) - } - - 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 - } - } - - /// 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, false)?; - if let Err(SendError(_)) = self.historian - .sender - .send(Signal::Event(Event::Transaction(tr))) - { - return Err(AccountingError::SendError); - } - - Ok(()) + pub fn new(mint: &Mint) -> Self { + let deposit = Payment { + to: mint.pubkey(), + tokens: mint.tokens, + }; + Self::new_from_deposit(&deposit) } /// 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 { + 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) { + 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); } - 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(); 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); } @@ -152,8 +106,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(); } }; @@ -181,8 +135,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()); } } @@ -195,9 +149,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<()> { + pub 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), } @@ -206,7 +160,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, @@ -214,14 +168,14 @@ 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` /// 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, @@ -230,10 +184,10 @@ 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: &Self, pubkey: &PublicKey) -> Option { + pub fn get_balance(&self, pubkey: &PublicKey) -> Option { self.balances.get(pubkey).cloned() } } @@ -241,63 +195,50 @@ 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)); - acc.transfer(1_000, &alice.keypair(), bob_pubkey, alice.seed()) + let mut acc = Accountant::new(&alice); + 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); - - 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()) + 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) ); 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()); + 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! } assert_eq!( - acc.log_transaction(tr.clone()), + acc.process_transaction(tr.clone()), Err(AccountingError::InvalidTransfer) ); @@ -306,7 +247,7 @@ mod tests { payment.tokens = 0; // <-- whoops! } assert_eq!( - acc.log_transaction(tr.clone()), + acc.process_transaction(tr.clone()), Err(AccountingError::InvalidTransfer) ); } @@ -314,28 +255,22 @@ 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()) + acc.transfer(500, &alice_keypair, bob_pubkey, alice.last_id()) .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(); - 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. @@ -357,14 +292,14 @@ 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(); 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)); @@ -374,11 +309,11 @@ 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(); - 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. @@ -396,4 +331,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); + 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 b8b1226eb7..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; @@ -22,9 +25,10 @@ use transaction::Transaction; use rayon::prelude::*; pub struct AccountantSkel { - pub acc: Accountant, - pub last_id: Hash, + acc: Accountant, + last_id: Hash, writer: W, + historian: Historian, } #[cfg_attr(feature = "cargo-clippy", allow(large_enum_variant))] @@ -32,7 +36,7 @@ pub struct AccountantSkel { pub enum Request { Transaction(Transaction), GetBalance { key: PublicKey }, - GetId { is_last: bool }, + GetLastId, } impl Request { @@ -54,23 +58,23 @@ 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, historian: Historian) -> Self { AccountantSkel { acc, last_id, - writer: w, + 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.historian.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(); } @@ -81,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 } @@ -90,14 +99,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..0c7c528087 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()) } } @@ -92,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}; @@ -107,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, sink()))); + let historian = Historian::new(&alice.last_id(), Some(30)); + let acc = Arc::new(Mutex::new(AccountantSkel::new( + acc, + alice.last_id(), + sink(), + historian, + ))); let _threads = AccountantSkel::serve(acc, addr, exit.clone()).unwrap(); sleep(Duration::from_millis(300)); 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/bin/testnode.rs b/src/bin/testnode.rs index a8daaec4c8..ef6700d309 100644 --- a/src/bin/testnode.rs +++ b/src/bin/testnode.rs @@ -2,6 +2,9 @@ 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}; use std::sync::atomic::AtomicBool; @@ -10,13 +13,43 @@ 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 = Accountant::new_from_entries(entries, Some(1000)); + + // 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(acc, 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 { diff --git a/src/historian.rs b/src/historian.rs index 3bf92668d0..c48fa248c2 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( @@ -66,7 +53,7 @@ impl Historian { #[cfg(test)] mod tests { use super::*; - use ledger::*; + use ledger::Block; use std::thread::sleep; use std::time::Duration; @@ -95,7 +82,7 @@ mod tests { ExitReason::RecvDisconnected ); - assert!(verify_slice(&[entry0, entry1, entry2], &zero)); + assert!([entry0, entry1, entry2].verify(&zero)); } #[test] @@ -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(); 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..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() } @@ -57,7 +61,7 @@ impl Mint { #[cfg(test)] mod tests { use super::*; - use ledger::verify_slice; + use ledger::Block; use plan::Plan; #[test] @@ -74,6 +78,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)); } } 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, } } 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,