diff --git a/README.md b/README.md index 3696a7fa26..301538c2be 100644 --- a/README.md +++ b/README.md @@ -21,74 +21,64 @@ corresponding benchmarks are also added that demonstrate real performance boosts feature set here will always be a ways behind the loom repo, but that this is an implementation you can take to the bank, literally. -Usage +Running the demo === -Add the latest [silk package](https://crates.io/crates/silk) to the `[dependencies]` section -of your Cargo.toml. +First, build the demo executables in release mode (optimized for performance): -Create a *Historian* and send it *events* to generate an *event log*, where each log *entry* -is tagged with the historian's latest *hash*. Then ensure the order of events was not tampered -with by verifying each entry's hash can be generated from the hash in the previous entry: - -![historian](https://user-images.githubusercontent.com/55449/36950845-459bdb58-1fb9-11e8-850e-894586f3729b.png) - -```rust -extern crate silk; - -use silk::historian::Historian; -use silk::log::{verify_slice, Entry, Sha256Hash}; -use silk::event::{generate_keypair, get_pubkey, sign_claim_data, Event}; -use std::thread::sleep; -use std::time::Duration; -use std::sync::mpsc::SendError; - -fn create_log(hist: &Historian) -> Result<(), SendError>> { - sleep(Duration::from_millis(15)); - let data = Sha256Hash::default(); - let keypair = generate_keypair(); - let event0 = Event::new_claim(get_pubkey(&keypair), data, sign_claim_data(&data, &keypair)); - hist.sender.send(event0)?; - sleep(Duration::from_millis(10)); - Ok(()) -} - -fn main() { - let seed = Sha256Hash::default(); - let hist = Historian::new(&seed, Some(10)); - create_log(&hist).expect("send error"); - drop(hist.sender); - let entries: Vec> = hist.receiver.iter().collect(); - for entry in &entries { - println!("{:?}", entry); - } - // Proof-of-History: Verify the historian learned about the events - // in the same order they appear in the vector. - assert!(verify_slice(&entries, &seed)); -} +```bash + $ cargo build --release + $ cd target/release ``` -Running the program should produce a log similar to: +The testnode server is initialized with a transaction log from stdin and +generates a log on stdout. To create the input log, we'll need to create +a *genesis* configuration file and then generate a log from it. It's done +in two steps here because the demo-genesis.json file contains a private +key that will be used later in this demo. -```rust -Entry { num_hashes: 0, id: [0, ...], event: Tick } -Entry { num_hashes: 3, id: [67, ...], event: Transaction { data: [37, ...] } } -Entry { num_hashes: 3, id: [123, ...], event: Tick } +```bash + $ ./silk-genesis-file-demo > demo-genesis.jsoc + $ cat demo-genesis.json | ./silk-genesis-block > demo-genesis.log ``` -Proof-of-History ---- +Now you can start the server: -Take note of the last line: - -```rust -assert!(verify_slice(&entries, &seed)); +```bash + $ cat demo-genesis.log | ./silk-testnode > demo-entries0.log ``` -[It's a proof!](https://en.wikipedia.org/wiki/Curry–Howard_correspondence) For each entry returned by the -historian, we can verify that `id` is the result of applying a sha256 hash to the previous `id` -exactly `num_hashes` times, and then hashing then event data on top of that. Because the event data is -included in the hash, the events cannot be reordered without regenerating all the hashes. +Then, in a seperate shell, let's execute some transactions. Note we pass in +the JSON configuration file here, not the genesis log. + +```bash + $ cat demo-genesis.json | ./silk-client-demo +``` + +Now kill the server with Ctrl-C and take a look at the transaction log. You should +see something similar to: + +```json +{"num_hashes":27,"id":[0, ...],"event":"Tick"} +{"num_hashes:"3,"id":[67, ...],"event":{"Transaction":{"data":[37, ...]}}} +{"num_hashes":27,"id":[0, ...],"event":"Tick"} +``` + +Now restart the server from where we left off. Pass it both the genesis log and +the transaction log. + +```bash + $ cat demo-genesis.log demo-entries0.log | ./silk-testnode > demo-entries1.log +``` + +Lastly, run the client demo again and verify that all funds were spent in the +previous round and so no additional transactions are added. + +```bash + $ cat demo-genesis.json | ./silk-client-demo +``` + +Stop the server again and verify there are only Tick entries and no Transaction entries. Developing === diff --git a/doc/historian.md b/doc/historian.md new file mode 100644 index 0000000000..ee647dbf95 --- /dev/null +++ b/doc/historian.md @@ -0,0 +1,65 @@ +The Historian +=== + +Create a *Historian* and send it *events* to generate an *event log*, where each log *entry* +is tagged with the historian's latest *hash*. Then ensure the order of events was not tampered +with by verifying each entry's hash can be generated from the hash in the previous entry: + +![historian](https://user-images.githubusercontent.com/55449/36950845-459bdb58-1fb9-11e8-850e-894586f3729b.png) + +```rust +extern crate silk; + +use silk::historian::Historian; +use silk::log::{verify_slice, Entry, Sha256Hash}; +use silk::event::{generate_keypair, get_pubkey, sign_claim_data, Event}; +use std::thread::sleep; +use std::time::Duration; +use std::sync::mpsc::SendError; + +fn create_log(hist: &Historian) -> Result<(), SendError>> { + sleep(Duration::from_millis(15)); + let data = Sha256Hash::default(); + let keypair = generate_keypair(); + let event0 = Event::new_claim(get_pubkey(&keypair), data, sign_claim_data(&data, &keypair)); + hist.sender.send(event0)?; + sleep(Duration::from_millis(10)); + Ok(()) +} + +fn main() { + let seed = Sha256Hash::default(); + let hist = Historian::new(&seed, Some(10)); + create_log(&hist).expect("send error"); + drop(hist.sender); + let entries: Vec> = hist.receiver.iter().collect(); + for entry in &entries { + println!("{:?}", entry); + } + // Proof-of-History: Verify the historian learned about the events + // in the same order they appear in the vector. + assert!(verify_slice(&entries, &seed)); +} +``` + +Running the program should produce a log similar to: + +```rust +Entry { num_hashes: 0, id: [0, ...], event: Tick } +Entry { num_hashes: 3, id: [67, ...], event: Transaction { data: [37, ...] } } +Entry { num_hashes: 3, id: [123, ...], event: Tick } +``` + +Proof-of-History +--- + +Take note of the last line: + +```rust +assert!(verify_slice(&entries, &seed)); +``` + +[It's a proof!](https://en.wikipedia.org/wiki/Curry–Howard_correspondence) For each entry returned by the +historian, we can verify that `id` is the result of applying a sha256 hash to the previous `id` +exactly `num_hashes` times, and then hashing then event data on top of that. Because the event data is +included in the hash, the events cannot be reordered without regenerating all the hashes. diff --git a/diagrams/historian.msc b/doc/historian.msc similarity index 100% rename from diagrams/historian.msc rename to doc/historian.msc diff --git a/src/accountant.rs b/src/accountant.rs index 74c4922c6b..a531ff005e 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -2,16 +2,14 @@ //! event log to record transactions. Its users can deposit funds and //! transfer funds to other users. -use log::{hash, Entry, Sha256Hash}; -use event::{get_pubkey, sign_transaction_data, Event, PublicKey, Signature}; +use log::{Entry, Sha256Hash}; +use event::{get_pubkey, sign_transaction_data, verify_event, Event, PublicKey, Signature}; use genesis::Genesis; -use historian::Historian; +use historian::{reserve_signature, Historian}; use ring::signature::Ed25519KeyPair; use std::sync::mpsc::SendError; use std::collections::HashMap; use std::result; -use std::thread::sleep; -use std::time::Duration; #[derive(Debug, PartialEq, Eq)] pub enum AccountingError { @@ -25,35 +23,51 @@ pub type Result = result::Result; pub struct Accountant { pub historian: Historian, pub balances: HashMap, + pub first_id: Sha256Hash, pub last_id: Sha256Hash, } impl Accountant { - pub fn new(gen: &Genesis, ms_per_tick: Option) -> Self { - let start_hash = hash(&gen.pkcs8); + 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 log is required to be an entry with zero num_hashes, + // which implies its id can be used as the log'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, last_id: start_hash, }; - for (i, event) in gen.create_events().into_iter().enumerate() { - acc.process_verified_event(event, i < 2).unwrap(); + + // The second item in the log 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.event, true).unwrap(); + + for entry in entries { + acc.process_verified_event(&entry.event, false).unwrap(); } acc } - pub fn sync(self: &mut Self) -> Vec> { - let mut entries = vec![]; + pub fn new(gen: &Genesis, ms_per_tick: Option) -> Self { + Self::new_from_entries(gen.create_entries(), ms_per_tick) + } + + pub fn sync(self: &mut Self) -> Sha256Hash { while let Ok(entry) = self.historian.receiver.try_recv() { - entries.push(entry); + self.last_id = entry.id; } - - if let Some(last_entry) = entries.last() { - self.last_id = last_entry.id; - } - - entries + self.last_id } fn is_deposit(allow_deposits: bool, from: &PublicKey, to: &PublicKey) -> bool { @@ -61,46 +75,49 @@ impl Accountant { } pub fn process_event(self: &mut Self, event: Event) -> Result<()> { - if !self.historian.verify_event(&event) { + if !verify_event(&event) { return Err(AccountingError::InvalidEvent); } - self.process_verified_event(event, false) + + if let Event::Transaction { from, data, .. } = event { + if self.get_balance(&from).unwrap_or(0) < data { + return Err(AccountingError::InsufficientFunds); + } + } + + self.process_verified_event(&event, false)?; + if let Err(SendError(_)) = self.historian.sender.send(event) { + return Err(AccountingError::SendError); + } + + Ok(()) } fn process_verified_event( self: &mut Self, - event: Event, + event: &Event, allow_deposits: bool, ) -> Result<()> { - match event { - Event::Tick => Ok(()), - Event::Transaction { from, to, data, .. } => { - if !Self::is_deposit(allow_deposits, &from, &to) { - if self.get_balance(&from).unwrap_or(0) < data { - return Err(AccountingError::InsufficientFunds); - } - } + if !reserve_signature(&mut self.historian.signatures, event) { + return Err(AccountingError::InvalidEvent); + } - if let Err(SendError(_)) = self.historian.sender.send(event) { - return Err(AccountingError::SendError); + if let Event::Transaction { from, to, data, .. } = *event { + if !Self::is_deposit(allow_deposits, &from, &to) { + if let Some(x) = self.balances.get_mut(&from) { + *x -= data; } + } - if !Self::is_deposit(allow_deposits, &from, &to) { - 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; } - - if self.balances.contains_key(&to) { - if let Some(x) = self.balances.get_mut(&to) { - *x += data; - } - } else { - self.balances.insert(to, data); - } - Ok(()) + } else { + self.balances.insert(to, data); } } + Ok(()) } pub fn transfer( @@ -110,11 +127,13 @@ impl Accountant { to: PublicKey, ) -> Result { let from = get_pubkey(keypair); - let sig = sign_transaction_data(&n, keypair, &to); + let last_id = self.last_id; + let sig = sign_transaction_data(&n, keypair, &to, &last_id); let event = Event::Transaction { from, to, data: n, + last_id, sig, }; self.process_event(event).map(|_| sig) @@ -123,21 +142,6 @@ impl Accountant { pub fn get_balance(self: &Self, pubkey: &PublicKey) -> Option { self.balances.get(pubkey).map(|x| *x) } - - pub fn wait_on_signature(self: &mut Self, wait_sig: &Signature) { - let mut entries = self.sync(); - let mut found = false; - while !found { - found = entries.iter().any(|e| match e.event { - Event::Transaction { sig, .. } => sig == *wait_sig, - _ => false, - }); - if !found { - sleep(Duration::from_millis(30)); - entries = self.sync(); - } - } - } } #[cfg(test)] @@ -146,8 +150,6 @@ mod tests { use event::{generate_keypair, get_pubkey}; use logger::ExitReason; use genesis::Creator; - use std::thread::sleep; - use std::time::Duration; #[test] fn test_accountant() { @@ -156,14 +158,12 @@ mod tests { let alice = Genesis::new(10_000, vec![bob]); let mut acc = Accountant::new(&alice, Some(2)); - let sig = acc.transfer(500, &alice.get_keypair(), bob_pubkey).unwrap(); - acc.wait_on_signature(&sig); - + acc.transfer(500, &alice.get_keypair(), bob_pubkey).unwrap(); assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_500); drop(acc.historian.sender); assert_eq!( - acc.historian.thread_hdl.join().unwrap().1, + acc.historian.thread_hdl.join().unwrap(), ExitReason::RecvDisconnected ); } @@ -174,12 +174,10 @@ mod tests { let bob_pubkey = bob.pubkey; let alice = Genesis::new(11_000, vec![bob]); let mut acc = Accountant::new(&alice, Some(2)); - assert_eq!( acc.transfer(10_001, &alice.get_keypair(), bob_pubkey), Err(AccountingError::InsufficientFunds) ); - sleep(Duration::from_millis(30)); let alice_pubkey = get_pubkey(&alice.get_keypair()); assert_eq!(acc.get_balance(&alice_pubkey).unwrap(), 10_000); @@ -187,7 +185,7 @@ mod tests { drop(acc.historian.sender); assert_eq!( - acc.historian.thread_hdl.join().unwrap().1, + acc.historian.thread_hdl.join().unwrap(), ExitReason::RecvDisconnected ); } @@ -199,13 +197,12 @@ mod tests { let alice_keypair = alice.get_keypair(); let bob_keypair = generate_keypair(); let bob_pubkey = get_pubkey(&bob_keypair); - let sig = acc.transfer(500, &alice_keypair, bob_pubkey).unwrap(); - acc.wait_on_signature(&sig); + acc.transfer(500, &alice_keypair, bob_pubkey).unwrap(); assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 500); drop(acc.historian.sender); assert_eq!( - acc.historian.thread_hdl.join().unwrap().1, + acc.historian.thread_hdl.join().unwrap(), ExitReason::RecvDisconnected ); } diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index af9365ffe1..4666e97edf 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -1,11 +1,12 @@ use std::io; use accountant::Accountant; use event::{Event, PublicKey, Signature}; +use log::{Entry, Sha256Hash}; use std::net::UdpSocket; use bincode::{deserialize, serialize}; pub struct AccountantSkel { - pub obj: Accountant, + pub acc: Accountant, } #[derive(Serialize, Deserialize, Debug)] @@ -14,49 +15,67 @@ pub enum Request { from: PublicKey, to: PublicKey, val: u64, + last_id: Sha256Hash, sig: Signature, }, GetBalance { key: PublicKey, }, - Wait { - sig: Signature, + GetEntries { + last_id: Sha256Hash, + }, + GetId { + is_last: bool, }, } #[derive(Serialize, Deserialize, Debug)] pub enum Response { - Balance { key: PublicKey, val: u64 }, - Confirmed { sig: Signature }, + Balance { key: PublicKey, val: Option }, + Entries { entries: Vec> }, + Id { id: Sha256Hash, is_last: bool }, } impl AccountantSkel { - pub fn new(obj: Accountant) -> Self { - AccountantSkel { obj } + pub fn new(acc: Accountant) -> Self { + AccountantSkel { acc } } pub fn process_request(self: &mut Self, msg: Request) -> Option { match msg { - Request::Transfer { from, to, val, sig } => { + Request::Transfer { + from, + to, + val, + last_id, + sig, + } => { let event = Event::Transaction { from, to, data: val, + last_id, sig, }; - if let Err(err) = self.obj.process_event(event) { - println!("Transfer error: {:?}", err); + if let Err(err) = self.acc.process_event(event) { + eprintln!("Transfer error: {:?}", err); } None } Request::GetBalance { key } => { - let val = self.obj.get_balance(&key).unwrap(); + let val = self.acc.get_balance(&key); Some(Response::Balance { key, val }) } - Request::Wait { sig } => { - self.obj.wait_on_signature(&sig); - Some(Response::Confirmed { sig }) - } + Request::GetEntries { .. } => Some(Response::Entries { entries: vec![] }), + Request::GetId { is_last } => Some(Response::Id { + id: if is_last { + self.acc.sync(); + self.acc.last_id + } else { + self.acc.first_id + }, + is_last, + }), } } diff --git a/src/accountant_stub.rs b/src/accountant_stub.rs index 6d5929f688..8016620a97 100644 --- a/src/accountant_stub.rs +++ b/src/accountant_stub.rs @@ -5,13 +5,15 @@ use std::net::UdpSocket; use std::io; use bincode::{deserialize, serialize}; -use event::{get_pubkey, sign_transaction_data, PublicKey, Signature}; +use event::{get_pubkey, get_signature, sign_transaction_data, PublicKey, Signature}; +use log::{Entry, Sha256Hash}; use ring::signature::Ed25519KeyPair; use accountant_skel::{Request, Response}; pub struct AccountantStub { pub addr: String, pub socket: UdpSocket, + pub last_id: Option, } impl AccountantStub { @@ -19,33 +21,43 @@ impl AccountantStub { AccountantStub { addr: addr.to_string(), socket, + last_id: None, } } pub fn transfer_signed( - self: &Self, + &self, from: PublicKey, to: PublicKey, val: u64, + last_id: Sha256Hash, sig: Signature, ) -> io::Result { - let req = Request::Transfer { from, to, val, sig }; + let req = Request::Transfer { + from, + to, + val, + last_id, + sig, + }; let data = serialize(&req).unwrap(); self.socket.send_to(&data, &self.addr) } pub fn transfer( - self: &Self, + &self, n: u64, keypair: &Ed25519KeyPair, to: PublicKey, + last_id: &Sha256Hash, ) -> io::Result { let from = get_pubkey(keypair); - let sig = sign_transaction_data(&n, keypair, &to); - self.transfer_signed(from, to, n, sig).map(|_| sig) + let sig = sign_transaction_data(&n, keypair, &to, last_id); + self.transfer_signed(from, to, n, *last_id, sig) + .map(|_| sig) } - pub fn get_balance(self: &Self, pubkey: &PublicKey) -> io::Result { + pub fn get_balance(&self, pubkey: &PublicKey) -> io::Result> { let req = Request::GetBalance { key: *pubkey }; let data = serialize(&req).expect("serialize GetBalance"); self.socket.send_to(&data, &self.addr)?; @@ -56,20 +68,55 @@ impl AccountantStub { assert_eq!(key, *pubkey); return Ok(val); } - Ok(0) + Ok(None) } - pub fn wait_on_signature(self: &Self, wait_sig: &Signature) -> io::Result<()> { - let req = Request::Wait { sig: *wait_sig }; + 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()) + } + + pub fn get_last_id(&self) -> io::Result { + self.get_id(true) + } + + pub fn wait_on_signature(&mut self, wait_sig: &Signature) -> io::Result<()> { + let last_id = match self.last_id { + None => { + let first_id = self.get_id(false)?; + self.last_id = Some(first_id); + first_id + } + Some(last_id) => last_id, + }; + + let req = Request::GetEntries { last_id }; let data = serialize(&req).unwrap(); self.socket.send_to(&data, &self.addr).map(|_| ())?; let mut buf = vec![0u8; 1024]; self.socket.recv_from(&mut buf)?; let resp = deserialize(&buf).expect("deserialize signature"); - if let Response::Confirmed { sig } = resp { - assert_eq!(sig, *wait_sig); + if let Response::Entries { entries } = resp { + for Entry { id, event, .. } in entries { + self.last_id = Some(id); + if let Some(sig) = get_signature(&event) { + if sig == *wait_sig { + return Ok(()); + } + } + } } + + // TODO: Loop until we found it. Ok(()) } } @@ -95,9 +142,11 @@ mod tests { sleep(Duration::from_millis(30)); let socket = UdpSocket::bind(send_addr).unwrap(); - let acc = AccountantStub::new(addr, socket); - let sig = acc.transfer(500, &alice.get_keypair(), bob_pubkey).unwrap(); + let mut acc = AccountantStub::new(addr, socket); + let last_id = acc.get_last_id().unwrap(); + let sig = acc.transfer(500, &alice.get_keypair(), bob_pubkey, &last_id) + .unwrap(); acc.wait_on_signature(&sig).unwrap(); - assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_500); + assert_eq!(acc.get_balance(&bob_pubkey).unwrap().unwrap(), 1_500); } } diff --git a/src/bin/client-demo.rs b/src/bin/client-demo.rs index 7cd4220b3a..b54331a4c4 100644 --- a/src/bin/client-demo.rs +++ b/src/bin/client-demo.rs @@ -1,35 +1,28 @@ -//extern crate serde_json; +extern crate serde_json; extern crate silk; use silk::accountant_stub::AccountantStub; -use silk::accountant_skel::AccountantSkel; -use silk::accountant::Accountant; use silk::event::{generate_keypair, get_pubkey, sign_transaction_data, verify_event, Event}; use silk::genesis::Genesis; use std::time::Instant; use std::net::UdpSocket; -use std::thread::{sleep, spawn}; -use std::time::Duration; -//use std::io::stdin; +use std::io::stdin; fn main() { let addr = "127.0.0.1:8000"; let send_addr = "127.0.0.1:8001"; - let txs = 200; - - let gen = Genesis::new(txs, vec![]); - let alice_keypair = generate_keypair(); - //let gen: Genesis = serde_json::from_reader(stdin()).unwrap(); - //let alice_keypair = gen.get_keypair(); - - let acc = Accountant::new(&gen, None); - spawn(move || AccountantSkel::new(acc).serve(addr).unwrap()); - sleep(Duration::from_millis(30)); + let gen: Genesis = serde_json::from_reader(stdin()).unwrap(); + let alice_keypair = gen.get_keypair(); + let alice_pubkey = gen.get_pubkey(); let socket = UdpSocket::bind(send_addr).unwrap(); - let acc = AccountantStub::new(addr, socket); - let alice_pubkey = get_pubkey(&alice_keypair); + let mut acc = AccountantStub::new(addr, socket); + let last_id = acc.get_last_id().unwrap(); + + let txs = acc.get_balance(&alice_pubkey).unwrap().unwrap(); + println!("Alice's Initial Balance {}", txs); + let one = 1; println!("Signing transactions..."); let now = Instant::now(); @@ -37,7 +30,7 @@ fn main() { .map(|_| { let rando_keypair = generate_keypair(); let rando_pubkey = get_pubkey(&rando_keypair); - let sig = sign_transaction_data(&one, &alice_keypair, &rando_pubkey); + let sig = sign_transaction_data(&one, &alice_keypair, &rando_pubkey, &last_id); (rando_pubkey, sig) }) .collect(); @@ -58,6 +51,7 @@ fn main() { from: alice_pubkey, to: k, data: one, + last_id, sig: s, }; assert!(verify_event(&e)); @@ -76,7 +70,8 @@ fn main() { let now = Instant::now(); let mut sig = Default::default(); for (k, s) in sigs { - acc.transfer_signed(alice_pubkey, k, one, s).unwrap(); + acc.transfer_signed(alice_pubkey, k, one, last_id, s) + .unwrap(); sig = s; } println!("Waiting for last transaction to be confirmed...",); @@ -86,7 +81,7 @@ fn main() { let ns = duration.as_secs() * 1_000_000_000 + duration.subsec_nanos() as u64; let tps = (txs * 1_000_000_000) as f64 / ns as f64; println!("Done. {} tps!", tps); - let val = acc.get_balance(&alice_pubkey).unwrap(); + let val = acc.get_balance(&alice_pubkey).unwrap().unwrap(); println!("Alice's Final Balance {}", val); assert_eq!(val, 0); } diff --git a/src/bin/demo.rs b/src/bin/demo.rs index c4b2121692..4f6967e418 100644 --- a/src/bin/demo.rs +++ b/src/bin/demo.rs @@ -7,11 +7,19 @@ use std::thread::sleep; use std::time::Duration; use std::sync::mpsc::SendError; -fn create_log(hist: &Historian) -> Result<(), SendError>> { +fn create_log( + hist: &Historian, + seed: &Sha256Hash, +) -> Result<(), SendError>> { sleep(Duration::from_millis(15)); let data = Sha256Hash::default(); let keypair = generate_keypair(); - let event0 = Event::new_claim(get_pubkey(&keypair), data, sign_claim_data(&data, &keypair)); + let event0 = Event::new_claim( + get_pubkey(&keypair), + data, + *seed, + sign_claim_data(&data, &keypair, seed), + ); hist.sender.send(event0)?; sleep(Duration::from_millis(10)); Ok(()) @@ -20,7 +28,7 @@ fn create_log(hist: &Historian) -> Result<(), SendError> = hist.receiver.iter().collect(); for entry in &entries { diff --git a/src/bin/genesis-block.rs b/src/bin/genesis-block.rs index 5a7f19a1ca..ce3e8e5898 100644 --- a/src/bin/genesis-block.rs +++ b/src/bin/genesis-block.rs @@ -5,20 +5,14 @@ extern crate serde_json; extern crate silk; use silk::genesis::Genesis; -use silk::log::{create_entries, hash, verify_slice_u64}; +use silk::log::verify_slice_u64; use std::io::stdin; fn main() { let gen: Genesis = serde_json::from_reader(stdin()).unwrap(); - let entries = create_entries(&hash(&gen.pkcs8), gen.create_events()); + let entries = gen.create_entries(); verify_slice_u64(&entries, &entries[0].id); - println!("["); - let len = entries.len(); - for (i, x) in entries.iter().enumerate() { - let s = serde_json::to_string(&x).unwrap(); - - let terminator = if i + 1 == len { "" } else { "," }; - println!(" {}{}", s, terminator); + for x in entries { + println!("{}", serde_json::to_string(&x).unwrap()); } - println!("]"); } diff --git a/src/bin/genesis-file-demo.rs b/src/bin/genesis-file-demo.rs index 65f28edaf2..26a47c5e8b 100644 --- a/src/bin/genesis-file-demo.rs +++ b/src/bin/genesis-file-demo.rs @@ -14,6 +14,6 @@ fn main() { pubkey: get_pubkey(&generate_keypair()), }; let creators = vec![alice, bob]; - let gen = Genesis::new(300, creators); + let gen = Genesis::new(500, creators); println!("{}", serde_json::to_string(&gen).unwrap()); } diff --git a/src/bin/testnode.rs b/src/bin/testnode.rs index 141fffb904..22a73e6eb4 100644 --- a/src/bin/testnode.rs +++ b/src/bin/testnode.rs @@ -3,14 +3,17 @@ extern crate silk; use silk::accountant_skel::AccountantSkel; use silk::accountant::Accountant; -use silk::genesis::Genesis; -use std::io::stdin; +use std::io::{self, BufRead}; fn main() { let addr = "127.0.0.1:8000"; - let gen: Genesis = serde_json::from_reader(stdin()).unwrap(); - let acc = Accountant::new(&gen, Some(1000)); + let stdin = io::stdin(); + let entries = stdin + .lock() + .lines() + .map(|line| serde_json::from_str(&line.unwrap()).unwrap()); + let acc = Accountant::new_from_entries(entries, Some(1000)); let mut skel = AccountantSkel::new(acc); - println!("Listening on {}", addr); + eprintln!("Listening on {}", addr); skel.serve(addr).unwrap(); } diff --git a/src/event.rs b/src/event.rs index 7c263e437a..1b1fd2f386 100644 --- a/src/event.rs +++ b/src/event.rs @@ -20,6 +20,7 @@ use ring::{rand, signature}; use untrusted; use serde::Serialize; use bincode::serialize; +use log::Sha256Hash; pub type PublicKey = GenericArray; pub type Signature = GenericArray; @@ -36,16 +37,18 @@ pub enum Event { from: PublicKey, to: PublicKey, data: T, + last_id: Sha256Hash, sig: Signature, }, } impl Event { - pub fn new_claim(to: PublicKey, data: T, sig: Signature) -> Self { + pub fn new_claim(to: PublicKey, data: T, last_id: Sha256Hash, sig: Signature) -> Self { Event::Transaction { from: to, to, data, + last_id, sig, } } @@ -74,14 +77,19 @@ pub fn sign_transaction_data( data: &T, keypair: &Ed25519KeyPair, to: &PublicKey, + last_id: &Sha256Hash, ) -> Signature { let from = &get_pubkey(keypair); - sign_serialized(&(from, to, data), keypair) + sign_serialized(&(from, to, data, last_id), keypair) } /// Return a signature for the given data using the private key from the given keypair. -pub fn sign_claim_data(data: &T, keypair: &Ed25519KeyPair) -> Signature { - sign_transaction_data(data, keypair, &get_pubkey(keypair)) +pub fn sign_claim_data( + data: &T, + keypair: &Ed25519KeyPair, + last_id: &Sha256Hash, +) -> Signature { + sign_transaction_data(data, keypair, &get_pubkey(keypair), last_id) } /// Verify a signed message with the given public key. @@ -104,10 +112,11 @@ pub fn verify_event(event: &Event) -> bool { from, to, ref data, + last_id, sig, } = *event { - let sign_data = serialize(&(&from, &to, &data)).unwrap(); + let sign_data = serialize(&(&from, &to, &data, &last_id)).unwrap(); if !verify_signature(&from, &sign_data, &sig) { return false; } @@ -122,7 +131,12 @@ mod tests { #[test] fn test_serialize_claim() { - let claim0 = Event::new_claim(Default::default(), 0u8, Default::default()); + let claim0 = Event::new_claim( + Default::default(), + 0u8, + Default::default(), + Default::default(), + ); let buf = serialize(&claim0).unwrap(); let claim1: Event = deserialize(&buf).unwrap(); assert_eq!(claim1, claim0); diff --git a/src/genesis.rs b/src/genesis.rs index 7982411796..df331532d2 100644 --- a/src/genesis.rs +++ b/src/genesis.rs @@ -1,6 +1,7 @@ //! A library for generating the chain's genesis block. use event::{generate_keypair, get_pubkey, sign_transaction_data, Event, PublicKey}; +use log::{create_entries, hash, Entry, Sha256Hash}; use ring::rand::SystemRandom; use ring::signature::Ed25519KeyPair; use untrusted::Input; @@ -31,6 +32,7 @@ impl Genesis { pub fn new(tokens: u64, creators: Vec) -> Self { let rnd = SystemRandom::new(); let pkcs8 = Ed25519KeyPair::generate_pkcs8(&rnd).unwrap().to_vec(); + println!("{:?}", pkcs8); Genesis { pkcs8, tokens, @@ -38,6 +40,10 @@ impl Genesis { } } + pub fn get_seed(&self) -> Sha256Hash { + hash(&self.pkcs8) + } + pub fn get_keypair(&self) -> Ed25519KeyPair { Ed25519KeyPair::from_pkcs8(Input::from(&self.pkcs8)).unwrap() } @@ -47,12 +53,14 @@ impl Genesis { } pub fn create_transaction(&self, data: u64, to: &PublicKey) -> Event { + let last_id = self.get_seed(); let from = self.get_pubkey(); - let sig = sign_transaction_data(&data, &self.get_keypair(), to); + let sig = sign_transaction_data(&data, &self.get_keypair(), to, &last_id); Event::Transaction { from, to: *to, data, + last_id, sig, } } @@ -70,11 +78,16 @@ impl Genesis { events } + + pub fn create_entries(&self) -> Vec> { + create_entries(&self.get_seed(), self.create_events()) + } } #[cfg(test)] mod tests { use super::*; + use log::verify_slice_u64; #[test] fn test_create_events() { @@ -97,4 +110,16 @@ mod tests { 3 ); } + + #[test] + fn test_verify_entries() { + let entries = Genesis::new(100, vec![]).create_entries(); + assert!(verify_slice_u64(&entries, &entries[0].id)); + } + + #[test] + fn test_verify_entries_with_transactions() { + let entries = Genesis::new(100, vec![Creator::new(42)]).create_entries(); + assert!(verify_slice_u64(&entries, &entries[0].id)); + } } diff --git a/src/historian.rs b/src/historian.rs index 745416c0aa..6db553b476 100644 --- a/src/historian.rs +++ b/src/historian.rs @@ -1,21 +1,20 @@ //! The `historian` crate provides a microservice for generating a Proof-of-History. //! It manages a thread containing a Proof-of-History Logger. -use std::thread::JoinHandle; +use std::thread::{spawn, JoinHandle}; use std::collections::HashSet; use std::sync::mpsc::{sync_channel, Receiver, SyncSender}; use std::time::Instant; use log::{hash, Entry, Sha256Hash}; -use logger::{verify_event_and_reserve_signature, ExitReason, Logger}; -use event::{Event, Signature}; +use logger::{ExitReason, Logger}; +use event::{get_signature, Event, Signature}; use serde::Serialize; use std::fmt::Debug; -use std::thread; pub struct Historian { pub sender: SyncSender>, pub receiver: Receiver>, - pub thread_hdl: JoinHandle<(Entry, ExitReason)>, + pub thread_hdl: JoinHandle, pub signatures: HashSet, } @@ -34,10 +33,6 @@ impl Historian { } } - pub fn verify_event(self: &mut Self, event: &Event) -> bool { - return verify_event_and_reserve_signature(&mut self.signatures, event); - } - /// 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_logger( @@ -45,12 +40,12 @@ impl Historian { ms_per_tick: Option, receiver: Receiver>, sender: SyncSender>, - ) -> JoinHandle<(Entry, ExitReason)> { - thread::spawn(move || { + ) -> JoinHandle { + spawn(move || { let mut logger = Logger::new(receiver, sender, start_hash); let now = Instant::now(); loop { - if let Err(err) = logger.log_events(now, ms_per_tick) { + if let Err(err) = logger.process_events(now, ms_per_tick) { return err; } logger.last_id = hash(&logger.last_id); @@ -60,6 +55,16 @@ impl Historian { } } +pub fn reserve_signature(sigs: &mut HashSet, event: &Event) -> bool { + if let Some(sig) = get_signature(&event) { + if sigs.contains(&sig) { + return false; + } + sigs.insert(sig); + } + true +} + #[cfg(test)] mod tests { use super::*; @@ -85,7 +90,7 @@ mod tests { drop(hist.sender); assert_eq!( - hist.thread_hdl.join().unwrap().1, + hist.thread_hdl.join().unwrap(), ExitReason::RecvDisconnected ); @@ -99,26 +104,38 @@ mod tests { drop(hist.receiver); hist.sender.send(Event::Tick).unwrap(); assert_eq!( - hist.thread_hdl.join().unwrap().1, + hist.thread_hdl.join().unwrap(), ExitReason::SendDisconnected ); } + #[test] + fn test_duplicate_event_signature() { + let keypair = generate_keypair(); + let to = get_pubkey(&keypair); + let data = b"hello, world"; + let zero = Sha256Hash::default(); + let sig = sign_claim_data(&data, &keypair, &zero); + let event0 = Event::new_claim(to, &data, zero, sig); + let mut sigs = HashSet::new(); + assert!(reserve_signature(&mut sigs, &event0)); + assert!(!reserve_signature(&mut sigs, &event0)); + } + #[test] fn test_ticking_historian() { let zero = Sha256Hash::default(); let hist = Historian::new(&zero, Some(20)); sleep(Duration::from_millis(30)); hist.sender.send(Event::Tick).unwrap(); - sleep(Duration::from_millis(15)); drop(hist.sender); - assert_eq!( - hist.thread_hdl.join().unwrap().1, - ExitReason::RecvDisconnected - ); - let entries: Vec> = hist.receiver.iter().collect(); - assert!(entries.len() > 1); - assert!(verify_slice(&entries, &zero)); + + // Ensure one entry is sent back for each tick sent in. + assert_eq!(entries.len(), 1); + + // Ensure the ID is not the seed, which indicates another Tick + // was logged before the one we sent. + assert_ne!(entries[0].id, zero); } } diff --git a/src/lib.rs b/src/lib.rs index 78dbeadbcd..bf65fb2d54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,5 +14,6 @@ extern crate ring; extern crate serde; #[macro_use] extern crate serde_derive; +extern crate serde_json; extern crate sha2; extern crate untrusted; diff --git a/src/log.rs b/src/log.rs index 8762d289d0..3a254fd159 100644 --- a/src/log.rs +++ b/src/log.rs @@ -217,8 +217,18 @@ mod tests { // First, verify entries let keypair = generate_keypair(); - let event0 = Event::new_claim(get_pubkey(&keypair), zero, sign_claim_data(&zero, &keypair)); - let event1 = Event::new_claim(get_pubkey(&keypair), one, sign_claim_data(&one, &keypair)); + let event0 = Event::new_claim( + get_pubkey(&keypair), + zero, + zero, + sign_claim_data(&zero, &keypair, &zero), + ); + let event1 = Event::new_claim( + get_pubkey(&keypair), + one, + zero, + sign_claim_data(&one, &keypair, &zero), + ); let events = vec![event0, event1]; let mut entries = create_entries(&zero, events); assert!(verify_slice(&entries, &zero)); @@ -235,8 +245,13 @@ mod tests { fn test_claim() { let keypair = generate_keypair(); let data = hash(b"hello, world"); - let event0 = Event::new_claim(get_pubkey(&keypair), data, sign_claim_data(&data, &keypair)); let zero = Sha256Hash::default(); + let event0 = Event::new_claim( + get_pubkey(&keypair), + data, + zero, + sign_claim_data(&data, &keypair, &zero), + ); let entries = create_entries(&zero, vec![event0]); assert!(verify_slice(&entries, &zero)); } @@ -244,18 +259,20 @@ mod tests { #[test] fn test_wrong_data_claim_attack() { let keypair = generate_keypair(); + let zero = Sha256Hash::default(); let event0 = Event::new_claim( get_pubkey(&keypair), hash(b"goodbye cruel world"), - sign_claim_data(&hash(b"hello, world"), &keypair), + zero, + sign_claim_data(&hash(b"hello, world"), &keypair, &zero), ); - let zero = Sha256Hash::default(); let entries = create_entries(&zero, vec![event0]); assert!(!verify_slice(&entries, &zero)); } #[test] fn test_transfer() { + let zero = Sha256Hash::default(); let keypair0 = generate_keypair(); let keypair1 = generate_keypair(); let pubkey1 = get_pubkey(&keypair1); @@ -264,9 +281,9 @@ mod tests { from: get_pubkey(&keypair0), to: pubkey1, data, - sig: sign_transaction_data(&data, &keypair0, &pubkey1), + last_id: zero, + sig: sign_transaction_data(&data, &keypair0, &pubkey1, &zero), }; - let zero = Sha256Hash::default(); let entries = create_entries(&zero, vec![event0]); assert!(verify_slice(&entries, &zero)); } @@ -277,13 +294,14 @@ mod tests { let keypair1 = generate_keypair(); let pubkey1 = get_pubkey(&keypair1); let data = hash(b"hello, world"); + let zero = Sha256Hash::default(); let event0 = Event::Transaction { from: get_pubkey(&keypair0), to: pubkey1, data: hash(b"goodbye cruel world"), // <-- attack! - sig: sign_transaction_data(&data, &keypair0, &pubkey1), + last_id: zero, + sig: sign_transaction_data(&data, &keypair0, &pubkey1, &zero), }; - let zero = Sha256Hash::default(); let entries = create_entries(&zero, vec![event0]); assert!(!verify_slice(&entries, &zero)); } @@ -295,13 +313,14 @@ mod tests { let thief_keypair = generate_keypair(); let pubkey1 = get_pubkey(&keypair1); let data = hash(b"hello, world"); + let zero = Sha256Hash::default(); let event0 = Event::Transaction { from: get_pubkey(&keypair0), to: get_pubkey(&thief_keypair), // <-- attack! data: hash(b"goodbye cruel world"), - sig: sign_transaction_data(&data, &keypair0, &pubkey1), + last_id: zero, + sig: sign_transaction_data(&data, &keypair0, &pubkey1, &zero), }; - let zero = Sha256Hash::default(); let entries = create_entries(&zero, vec![event0]); assert!(!verify_slice(&entries, &zero)); } diff --git a/src/logger.rs b/src/logger.rs index c06300e23b..130f746602 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -5,13 +5,13 @@ //! Event, the latest hash, and the number of hashes since the last event. //! The resulting stream of entries represents ordered events in time. -use std::collections::HashSet; use std::sync::mpsc::{Receiver, SyncSender, TryRecvError}; use std::time::{Duration, Instant}; use log::{create_entry_mut, Entry, Sha256Hash}; -use event::{get_signature, verify_event, Event, Signature}; +use event::Event; use serde::Serialize; use std::fmt::Debug; +use serde_json; #[derive(Debug, PartialEq, Eq)] pub enum ExitReason { @@ -27,22 +27,6 @@ pub struct Logger { pub num_ticks: u64, } -pub fn verify_event_and_reserve_signature( - signatures: &mut HashSet, - event: &Event, -) -> bool { - if !verify_event(&event) { - return false; - } - if let Some(sig) = get_signature(&event) { - if signatures.contains(&sig) { - return false; - } - signatures.insert(sig); - } - true -} - impl Logger { pub fn new( receiver: Receiver>, @@ -58,19 +42,17 @@ impl Logger { } } - pub fn log_event(&mut self, event: Event) -> Result<(), (Entry, ExitReason)> { + pub fn log_event(&mut self, event: Event) -> Result, ExitReason> { let entry = create_entry_mut(&mut self.last_id, &mut self.num_hashes, event); - if let Err(_) = self.sender.send(entry.clone()) { - return Err((entry, ExitReason::SendDisconnected)); - } - Ok(()) + println!("{}", serde_json::to_string(&entry).unwrap()); + Ok(entry) } - pub fn log_events( + pub fn process_events( &mut self, epoch: Instant, ms_per_tick: Option, - ) -> Result<(), (Entry, ExitReason)> { + ) -> Result<(), ExitReason> { loop { if let Some(ms) = ms_per_tick { if epoch.elapsed() > Duration::from_millis((self.num_ticks + 1) * ms) { @@ -78,22 +60,17 @@ impl Logger { self.num_ticks += 1; } } + match self.receiver.try_recv() { Ok(event) => { - self.log_event(event)?; + let entry = self.log_event(event)?; + self.sender + .send(entry) + .or(Err(ExitReason::SendDisconnected))?; } - Err(TryRecvError::Empty) => { - return Ok(()); - } - Err(TryRecvError::Disconnected) => { - let entry = Entry { - id: self.last_id, - num_hashes: self.num_hashes, - event: Event::Tick, - }; - return Err((entry, ExitReason::RecvDisconnected)); - } - } + Err(TryRecvError::Empty) => return Ok(()), + Err(TryRecvError::Disconnected) => return Err(ExitReason::RecvDisconnected), + }; } } } @@ -103,51 +80,18 @@ mod tests { use super::*; use log::*; use event::*; - use genesis::*; - use std::sync::mpsc::sync_channel; #[test] fn test_bad_event_signature() { + let zero = Sha256Hash::default(); let keypair = generate_keypair(); - let sig = sign_claim_data(&hash(b"hello, world"), &keypair); - let event0 = Event::new_claim(get_pubkey(&keypair), hash(b"goodbye cruel world"), sig); - let mut sigs = HashSet::new(); - assert!(!verify_event_and_reserve_signature(&mut sigs, &event0)); - assert!(!sigs.contains(&sig)); - } - - #[test] - fn test_duplicate_event_signature() { - let keypair = generate_keypair(); - let to = get_pubkey(&keypair); - let data = &hash(b"hello, world"); - let sig = sign_claim_data(data, &keypair); - let event0 = Event::new_claim(to, data, sig); - let mut sigs = HashSet::new(); - assert!(verify_event_and_reserve_signature(&mut sigs, &event0)); - assert!(!verify_event_and_reserve_signature(&mut sigs, &event0)); - } - - fn run_genesis(gen: Genesis) -> Vec> { - let (_sender, event_receiver) = sync_channel(100); - let (entry_sender, receiver) = sync_channel(100); - let mut logger = Logger::new(event_receiver, entry_sender, hash(&gen.pkcs8)); - for tx in gen.create_events() { - logger.log_event(tx).unwrap(); - } - drop(logger.sender); - receiver.iter().collect::>() - } - - #[test] - fn test_genesis_no_creators() { - let entries = run_genesis(Genesis::new(100, vec![])); - assert!(verify_slice_u64(&entries, &entries[0].id)); - } - - #[test] - fn test_genesis() { - let entries = run_genesis(Genesis::new(100, vec![Creator::new(42)])); - assert!(verify_slice_u64(&entries, &entries[0].id)); + let sig = sign_claim_data(&hash(b"hello, world"), &keypair, &zero); + let event0 = Event::new_claim( + get_pubkey(&keypair), + hash(b"goodbye cruel world"), + zero, + sig, + ); + assert!(!verify_event(&event0)); } }