From aa5f1699a71741db146b2f1133bf3c093f8d57cc Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Sun, 4 Mar 2018 22:26:46 -0700 Subject: [PATCH 1/8] Update the set of unique signatures when loading an existing log. --- src/accountant.rs | 62 +++++++++++++++++++++++++---------------------- src/historian.rs | 35 +++++++++++++++++++------- src/logger.rs | 35 ++------------------------ 3 files changed, 61 insertions(+), 71 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 74c4922c6..770494e6c 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -3,9 +3,9 @@ //! transfer funds to other users. use log::{hash, Entry, Sha256Hash}; -use event::{get_pubkey, sign_transaction_data, Event, PublicKey, Signature}; +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; @@ -37,7 +37,7 @@ impl Accountant { balances: HashMap::new(), last_id: start_hash, }; - for (i, event) in gen.create_events().into_iter().enumerate() { + for (i, event) in gen.create_events().iter().enumerate() { acc.process_verified_event(event, i < 2).unwrap(); } acc @@ -61,46 +61,50 @@ 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( diff --git a/src/historian.rs b/src/historian.rs index 745416c0a..8e88f2acd 100644 --- a/src/historian.rs +++ b/src/historian.rs @@ -1,16 +1,15 @@ //! 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>, @@ -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( @@ -46,7 +41,7 @@ impl Historian { receiver: Receiver>, sender: SyncSender>, ) -> JoinHandle<(Entry, ExitReason)> { - thread::spawn(move || { + spawn(move || { let mut logger = Logger::new(receiver, sender, start_hash); let now = Instant::now(); loop { @@ -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::*; @@ -104,6 +109,18 @@ mod tests { ); } + #[test] + fn test_duplicate_event_signature() { + let keypair = generate_keypair(); + let to = get_pubkey(&keypair); + let data = b"hello, world"; + let sig = sign_claim_data(&data, &keypair); + let event0 = Event::new_claim(to, &data, 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(); diff --git a/src/logger.rs b/src/logger.rs index c06300e23..67c812419 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -5,11 +5,10 @@ //! 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; @@ -27,22 +26,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>, @@ -111,21 +94,7 @@ mod tests { 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)); + assert!(!verify_event(&event0)); } fn run_genesis(gen: Genesis) -> Vec> { From c9e03f37ce05a72d8a54d0739162968efcf59918 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 5 Mar 2018 10:30:05 -0700 Subject: [PATCH 2/8] Logger now only speaks when spoken to Before this change, the logger's send channel could quickly be flooded with Tick events. Those events should only be passed to a writer. Also, the log_event() function no longer sends entries. That functionality moved to the new process_events() function. This will allow us to initialize the with the genesis block without flooding the send channel with events the historian won't read. --- src/accountant.rs | 6 +++--- src/historian.rs | 25 ++++++++++++------------- src/logger.rs | 37 +++++++++++++++---------------------- 3 files changed, 30 insertions(+), 38 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 770494e6c..cc9503f3e 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -167,7 +167,7 @@ mod tests { drop(acc.historian.sender); assert_eq!( - acc.historian.thread_hdl.join().unwrap().1, + acc.historian.thread_hdl.join().unwrap(), ExitReason::RecvDisconnected ); } @@ -191,7 +191,7 @@ mod tests { drop(acc.historian.sender); assert_eq!( - acc.historian.thread_hdl.join().unwrap().1, + acc.historian.thread_hdl.join().unwrap(), ExitReason::RecvDisconnected ); } @@ -209,7 +209,7 @@ mod tests { 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/historian.rs b/src/historian.rs index 8e88f2acd..7f87e0f94 100644 --- a/src/historian.rs +++ b/src/historian.rs @@ -14,7 +14,7 @@ use std::fmt::Debug; pub struct Historian { pub sender: SyncSender>, pub receiver: Receiver>, - pub thread_hdl: JoinHandle<(Entry, ExitReason)>, + pub thread_hdl: JoinHandle, pub signatures: HashSet, } @@ -40,12 +40,12 @@ impl Historian { ms_per_tick: Option, receiver: Receiver>, sender: SyncSender>, - ) -> JoinHandle<(Entry, ExitReason)> { + ) -> 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); @@ -90,7 +90,7 @@ mod tests { drop(hist.sender); assert_eq!( - hist.thread_hdl.join().unwrap().1, + hist.thread_hdl.join().unwrap(), ExitReason::RecvDisconnected ); @@ -104,7 +104,7 @@ 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 ); } @@ -127,15 +127,14 @@ mod tests { 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/logger.rs b/src/logger.rs index 67c812419..a4def4a8d 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -41,19 +41,16 @@ 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(()) + 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) { @@ -61,22 +58,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), + }; } } } @@ -98,12 +90,13 @@ mod tests { } fn run_genesis(gen: Genesis) -> Vec> { - let (_sender, event_receiver) = sync_channel(100); + 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(); + sender.send(tx).unwrap(); } + logger.process_events(Instant::now(), None).unwrap(); drop(logger.sender); receiver.iter().collect::>() } From 79fb9c00aac102e8dc662e11d40e7e801b19b140 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 5 Mar 2018 10:45:11 -0700 Subject: [PATCH 3/8] Boot wait_on_signature() from accountant Instead, there should be a way to query for a page of log data, and checking whether it has a signature should be done client-side. --- src/accountant.rs | 28 ++-------------------------- src/accountant_skel.rs | 5 +---- 2 files changed, 3 insertions(+), 30 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index cc9503f3e..6cb11734c 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -10,8 +10,6 @@ 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 { @@ -127,21 +125,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)] @@ -150,8 +133,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() { @@ -160,9 +141,7 @@ 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); @@ -178,12 +157,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); @@ -203,8 +180,7 @@ 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); diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index af9365ffe..4abc8c208 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -53,10 +53,7 @@ impl AccountantSkel { let val = self.obj.get_balance(&key).unwrap(); Some(Response::Balance { key, val }) } - Request::Wait { sig } => { - self.obj.wait_on_signature(&sig); - Some(Response::Confirmed { sig }) - } + Request::Wait { sig } => Some(Response::Confirmed { sig }), } } From d76ecbc9c9239ce06d16f47c304df6587a5d6aab Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 5 Mar 2018 11:11:00 -0700 Subject: [PATCH 4/8] Don't block the server --- src/accountant_skel.rs | 8 +++++--- src/accountant_stub.rs | 32 +++++++++++++++++++++++--------- src/bin/client-demo.rs | 2 +- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index 4abc8c208..32296cd09 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -1,6 +1,7 @@ use std::io; use accountant::Accountant; use event::{Event, PublicKey, Signature}; +use log::{Entry, Sha256Hash}; use std::net::UdpSocket; use bincode::{deserialize, serialize}; @@ -19,8 +20,8 @@ pub enum Request { GetBalance { key: PublicKey, }, - Wait { - sig: Signature, + GetEntries { + last_id: Option, }, } @@ -28,6 +29,7 @@ pub enum Request { pub enum Response { Balance { key: PublicKey, val: u64 }, Confirmed { sig: Signature }, + Entries { entries: Vec> }, } impl AccountantSkel { @@ -53,7 +55,7 @@ impl AccountantSkel { let val = self.obj.get_balance(&key).unwrap(); Some(Response::Balance { key, val }) } - Request::Wait { sig } => Some(Response::Confirmed { sig }), + Request::GetEntries { .. } => Some(Response::Entries { entries: vec![] }), } } diff --git a/src/accountant_stub.rs b/src/accountant_stub.rs index 6d5929f68..cbb5b336b 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,11 +21,12 @@ impl AccountantStub { AccountantStub { addr: addr.to_string(), socket, + last_id: None, } } pub fn transfer_signed( - self: &Self, + &self, from: PublicKey, to: PublicKey, val: u64, @@ -35,7 +38,7 @@ impl AccountantStub { } pub fn transfer( - self: &Self, + &self, n: u64, keypair: &Ed25519KeyPair, to: PublicKey, @@ -45,7 +48,7 @@ impl AccountantStub { self.transfer_signed(from, to, n, 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)?; @@ -59,17 +62,28 @@ impl AccountantStub { Ok(0) } - pub fn wait_on_signature(self: &Self, wait_sig: &Signature) -> io::Result<()> { - let req = Request::Wait { sig: *wait_sig }; + pub fn wait_on_signature(&mut self, wait_sig: &Signature) -> io::Result<()> { + let req = Request::GetEntries { + last_id: self.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,7 +109,7 @@ mod tests { sleep(Duration::from_millis(30)); let socket = UdpSocket::bind(send_addr).unwrap(); - let acc = AccountantStub::new(addr, socket); + let mut acc = AccountantStub::new(addr, socket); let sig = acc.transfer(500, &alice.get_keypair(), bob_pubkey).unwrap(); acc.wait_on_signature(&sig).unwrap(); assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_500); diff --git a/src/bin/client-demo.rs b/src/bin/client-demo.rs index 7cd4220b3..3b93ad778 100644 --- a/src/bin/client-demo.rs +++ b/src/bin/client-demo.rs @@ -28,7 +28,7 @@ fn main() { sleep(Duration::from_millis(30)); let socket = UdpSocket::bind(send_addr).unwrap(); - let acc = AccountantStub::new(addr, socket); + let mut acc = AccountantStub::new(addr, socket); let alice_pubkey = get_pubkey(&alice_keypair); let one = 1; println!("Signing transactions..."); From 48c28c2267489fdb35b118de299606f146587bf1 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 5 Mar 2018 12:48:09 -0700 Subject: [PATCH 5/8] Transactions now require a hash of the last entry they've seen This ensures the transaction cannot be processed on a chain that forked before that ID. It will also provide a basis for expiration constraints. A client may want their transaction to expire, and the generators may want to reject transactions that have been floating in the ether for years. --- src/accountant.rs | 22 +++++++++----------- src/accountant_skel.rs | 36 +++++++++++++++++++++++++------- src/accountant_stub.rs | 47 ++++++++++++++++++++++++++++++++++++------ src/bin/client-demo.rs | 7 +++++-- src/bin/demo.rs | 14 ++++++++++--- src/event.rs | 26 +++++++++++++++++------ src/genesis.rs | 9 +++++++- src/historian.rs | 5 +++-- src/log.rs | 41 ++++++++++++++++++++++++++---------- src/logger.rs | 12 ++++++++--- 10 files changed, 165 insertions(+), 54 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 6cb11734c..c3a5100fd 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -2,7 +2,7 @@ //! event log to record transactions. Its users can deposit funds and //! transfer funds to other users. -use log::{hash, Entry, Sha256Hash}; +use log::Sha256Hash; use event::{get_pubkey, sign_transaction_data, verify_event, Event, PublicKey, Signature}; use genesis::Genesis; use historian::{reserve_signature, Historian}; @@ -23,16 +23,18 @@ 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); + let start_hash = gen.get_seed(); 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().iter().enumerate() { @@ -41,17 +43,11 @@ impl Accountant { acc } - pub fn sync(self: &mut Self) -> Vec> { - let mut entries = vec![]; + 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 { @@ -112,11 +108,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) diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index 32296cd09..1dd95c8dc 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -6,7 +6,7 @@ use std::net::UdpSocket; use bincode::{deserialize, serialize}; pub struct AccountantSkel { - pub obj: Accountant, + pub acc: Accountant, } #[derive(Serialize, Deserialize, Debug)] @@ -15,47 +15,67 @@ pub enum Request { from: PublicKey, to: PublicKey, val: u64, + last_id: Sha256Hash, sig: Signature, }, GetBalance { key: PublicKey, }, GetEntries { - last_id: Option, + last_id: Sha256Hash, + }, + GetId { + is_last: bool, }, } #[derive(Serialize, Deserialize, Debug)] pub enum Response { Balance { key: PublicKey, val: u64 }, - Confirmed { sig: Signature }, 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) { + if let Err(err) = self.acc.process_event(event) { println!("Transfer error: {:?}", err); } None } Request::GetBalance { key } => { - let val = self.obj.get_balance(&key).unwrap(); + let val = self.acc.get_balance(&key).unwrap(); Some(Response::Balance { key, val }) } 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 cbb5b336b..15615275c 100644 --- a/src/accountant_stub.rs +++ b/src/accountant_stub.rs @@ -30,9 +30,16 @@ impl AccountantStub { 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) } @@ -42,10 +49,12 @@ impl AccountantStub { 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, pubkey: &PublicKey) -> io::Result { @@ -62,10 +71,34 @@ impl AccountantStub { Ok(0) } + 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 req = Request::GetEntries { - last_id: self.last_id, + 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(|_| ())?; @@ -110,7 +143,9 @@ mod tests { let socket = UdpSocket::bind(send_addr).unwrap(); let mut acc = AccountantStub::new(addr, socket); - let sig = acc.transfer(500, &alice.get_keypair(), bob_pubkey).unwrap(); + 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); } diff --git a/src/bin/client-demo.rs b/src/bin/client-demo.rs index 3b93ad778..e25541980 100644 --- a/src/bin/client-demo.rs +++ b/src/bin/client-demo.rs @@ -29,6 +29,7 @@ fn main() { let socket = UdpSocket::bind(send_addr).unwrap(); let mut acc = AccountantStub::new(addr, socket); + let last_id = acc.get_last_id().unwrap(); let alice_pubkey = get_pubkey(&alice_keypair); let one = 1; println!("Signing transactions..."); @@ -37,7 +38,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 +59,7 @@ fn main() { from: alice_pubkey, to: k, data: one, + last_id, sig: s, }; assert!(verify_event(&e)); @@ -76,7 +78,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...",); diff --git a/src/bin/demo.rs b/src/bin/demo.rs index c4b212169..4f6967e41 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/event.rs b/src/event.rs index 7c263e437..1b1fd2f38 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 798241179..8d09dc646 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::{hash, Sha256Hash}; use ring::rand::SystemRandom; use ring::signature::Ed25519KeyPair; use untrusted::Input; @@ -38,6 +39,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 +52,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, } } diff --git a/src/historian.rs b/src/historian.rs index 7f87e0f94..6db553b47 100644 --- a/src/historian.rs +++ b/src/historian.rs @@ -114,8 +114,9 @@ mod tests { let keypair = generate_keypair(); let to = get_pubkey(&keypair); let data = b"hello, world"; - let sig = sign_claim_data(&data, &keypair); - let event0 = Event::new_claim(to, &data, sig); + 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)); diff --git a/src/log.rs b/src/log.rs index 8762d289d..3a254fd15 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 a4def4a8d..46218d93a 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -83,16 +83,22 @@ mod tests { #[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 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)); } 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)); + let mut logger = Logger::new(event_receiver, entry_sender, gen.get_seed()); for tx in gen.create_events() { sender.send(tx).unwrap(); } From 298989c4b98a2e1541208fc4420073c8fc66a23a Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 5 Mar 2018 13:03:56 -0700 Subject: [PATCH 6/8] Generate log from Genesis --- src/bin/genesis-block.rs | 4 ++-- src/genesis.rs | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/bin/genesis-block.rs b/src/bin/genesis-block.rs index 5a7f19a1c..a64c4f9cd 100644 --- a/src/bin/genesis-block.rs +++ b/src/bin/genesis-block.rs @@ -5,12 +5,12 @@ 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(); diff --git a/src/genesis.rs b/src/genesis.rs index 8d09dc646..bd009504a 100644 --- a/src/genesis.rs +++ b/src/genesis.rs @@ -1,7 +1,7 @@ //! A library for generating the chain's genesis block. use event::{generate_keypair, get_pubkey, sign_transaction_data, Event, PublicKey}; -use log::{hash, Sha256Hash}; +use log::{create_entries, hash, Entry, Sha256Hash}; use ring::rand::SystemRandom; use ring::signature::Ed25519KeyPair; use untrusted::Input; @@ -77,6 +77,10 @@ impl Genesis { events } + + pub fn create_entries(&self) -> Vec> { + create_entries(&self.get_seed(), self.create_events()) + } } #[cfg(test)] From a4336a39d6ac84cd5a96a0426984bc2cc6fa29d4 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 5 Mar 2018 15:34:15 -0700 Subject: [PATCH 7/8] Initialize the testnode from a log $ cargo run --bin silk-genesis-file-demo > demo-genesis.json $ cat demo-genesis.json | cargo run --bin silk-genesis-block > demo-genesis.log $ cat demo-genesis.log | cargo run --bin silk-testnode --- src/accountant.rs | 31 +++++++++++++++++++++++++------ src/accountant_skel.rs | 6 +++--- src/accountant_stub.rs | 6 +++--- src/bin/client-demo.rs | 28 ++++++++++------------------ src/bin/genesis-block.rs | 10 ++-------- src/bin/genesis-file-demo.rs | 2 +- src/bin/testnode.rs | 13 ++++++++----- src/genesis.rs | 14 ++++++++++++++ src/lib.rs | 1 + src/logger.rs | 28 ++-------------------------- 10 files changed, 69 insertions(+), 70 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index c3a5100fd..a531ff005 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -2,7 +2,7 @@ //! event log to record transactions. Its users can deposit funds and //! transfer funds to other users. -use log::Sha256Hash; +use log::{Entry, Sha256Hash}; use event::{get_pubkey, sign_transaction_data, verify_event, Event, PublicKey, Signature}; use genesis::Genesis; use historian::{reserve_signature, Historian}; @@ -28,8 +28,17 @@ pub struct Accountant { } impl Accountant { - pub fn new(gen: &Genesis, ms_per_tick: Option) -> Self { - let start_hash = gen.get_seed(); + 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, @@ -37,12 +46,23 @@ impl Accountant { first_id: start_hash, last_id: start_hash, }; - for (i, event) in gen.create_events().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 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() { self.last_id = entry.id; @@ -66,7 +86,6 @@ impl Accountant { } self.process_verified_event(&event, false)?; - if let Err(SendError(_)) = self.historian.sender.send(event) { return Err(AccountingError::SendError); } diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index 1dd95c8dc..4666e97ed 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -31,7 +31,7 @@ pub enum Request { #[derive(Serialize, Deserialize, Debug)] pub enum Response { - Balance { key: PublicKey, val: u64 }, + Balance { key: PublicKey, val: Option }, Entries { entries: Vec> }, Id { id: Sha256Hash, is_last: bool }, } @@ -58,12 +58,12 @@ impl AccountantSkel { sig, }; if let Err(err) = self.acc.process_event(event) { - println!("Transfer error: {:?}", err); + eprintln!("Transfer error: {:?}", err); } None } Request::GetBalance { key } => { - let val = self.acc.get_balance(&key).unwrap(); + let val = self.acc.get_balance(&key); Some(Response::Balance { key, val }) } Request::GetEntries { .. } => Some(Response::Entries { entries: vec![] }), diff --git a/src/accountant_stub.rs b/src/accountant_stub.rs index 15615275c..8016620a9 100644 --- a/src/accountant_stub.rs +++ b/src/accountant_stub.rs @@ -57,7 +57,7 @@ impl AccountantStub { .map(|_| sig) } - pub fn get_balance(&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)?; @@ -68,7 +68,7 @@ impl AccountantStub { assert_eq!(key, *pubkey); return Ok(val); } - Ok(0) + Ok(None) } fn get_id(&self, is_last: bool) -> io::Result { @@ -147,6 +147,6 @@ mod tests { 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 e25541980..b54331a4c 100644 --- a/src/bin/client-demo.rs +++ b/src/bin/client-demo.rs @@ -1,36 +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 mut acc = AccountantStub::new(addr, socket); let last_id = acc.get_last_id().unwrap(); - let alice_pubkey = get_pubkey(&alice_keypair); + + 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(); @@ -89,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/genesis-block.rs b/src/bin/genesis-block.rs index a64c4f9cd..ce3e8e589 100644 --- a/src/bin/genesis-block.rs +++ b/src/bin/genesis-block.rs @@ -12,13 +12,7 @@ fn main() { let gen: Genesis = serde_json::from_reader(stdin()).unwrap(); 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 65f28edaf..26a47c5e8 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 141fffb90..22a73e6eb 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/genesis.rs b/src/genesis.rs index bd009504a..df331532d 100644 --- a/src/genesis.rs +++ b/src/genesis.rs @@ -32,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, @@ -86,6 +87,7 @@ impl Genesis { #[cfg(test)] mod tests { use super::*; + use log::verify_slice_u64; #[test] fn test_create_events() { @@ -108,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/lib.rs b/src/lib.rs index 78dbeadbc..bf65fb2d5 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/logger.rs b/src/logger.rs index 46218d93a..130f74660 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -11,6 +11,7 @@ use log::{create_entry_mut, Entry, Sha256Hash}; use event::Event; use serde::Serialize; use std::fmt::Debug; +use serde_json; #[derive(Debug, PartialEq, Eq)] pub enum ExitReason { @@ -43,6 +44,7 @@ impl Logger { pub fn log_event(&mut self, event: Event) -> Result, ExitReason> { let entry = create_entry_mut(&mut self.last_id, &mut self.num_hashes, event); + println!("{}", serde_json::to_string(&entry).unwrap()); Ok(entry) } @@ -78,8 +80,6 @@ mod tests { use super::*; use log::*; use event::*; - use genesis::*; - use std::sync::mpsc::sync_channel; #[test] fn test_bad_event_signature() { @@ -94,28 +94,4 @@ mod tests { ); assert!(!verify_event(&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, gen.get_seed()); - for tx in gen.create_events() { - sender.send(tx).unwrap(); - } - logger.process_events(Instant::now(), None).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)); - } } From 064eba00fdfad0747a979de271866abe50b01571 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 5 Mar 2018 16:05:16 -0700 Subject: [PATCH 8/8] Update readme --- README.md | 104 +++++++++++++++----------------- doc/historian.md | 65 ++++++++++++++++++++ {diagrams => doc}/historian.msc | 0 3 files changed, 112 insertions(+), 57 deletions(-) create mode 100644 doc/historian.md rename {diagrams => doc}/historian.msc (100%) diff --git a/README.md b/README.md index 3696a7fa2..301538c2b 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 000000000..ee647dbf9 --- /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