Batch events
It's now a Tick that locks down event order. Before this change, the event order would be locked down in the order the server sees it. Fixes #59 Fixes #61
This commit is contained in:
parent
209910299d
commit
cc9f0788aa
|
@ -355,7 +355,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "silk"
|
||||
version = "0.3.3"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"bincode 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -61,10 +61,12 @@ impl Accountant {
|
|||
// 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();
|
||||
acc.process_verified_event(&entry1.events[0], true).unwrap();
|
||||
|
||||
for entry in entries {
|
||||
acc.process_verified_event(&entry.event, false).unwrap();
|
||||
for event in entry.events {
|
||||
acc.process_verified_event(&event, false).unwrap();
|
||||
}
|
||||
}
|
||||
acc
|
||||
}
|
||||
|
|
|
@ -93,11 +93,13 @@ impl AccountantStub {
|
|||
self.socket.recv_from(&mut buf)?;
|
||||
let resp = deserialize(&buf).expect("deserialize signature");
|
||||
if let Response::Entries { entries } = resp {
|
||||
for Entry { id, event, .. } in entries {
|
||||
for Entry { id, events, .. } in entries {
|
||||
self.last_id = Some(id);
|
||||
if let Some(sig) = event.get_signature() {
|
||||
if sig == *wait_sig {
|
||||
return Ok(());
|
||||
for event in events {
|
||||
if let Some(sig) = event.get_signature() {
|
||||
if sig == *wait_sig {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ extern crate silk;
|
|||
use silk::mint::Mint;
|
||||
use silk::event::Event;
|
||||
use silk::transaction::Transaction;
|
||||
use silk::log::create_entries;
|
||||
use silk::entry::create_entry;
|
||||
use silk::signature::{KeyPair, KeyPairUtil, PublicKey};
|
||||
use silk::hash::Hash;
|
||||
use std::io::stdin;
|
||||
|
@ -14,17 +14,17 @@ fn transfer(from: &KeyPair, (to, tokens): (PublicKey, i64), last_id: Hash) -> Ev
|
|||
}
|
||||
|
||||
fn main() {
|
||||
let alice = (KeyPair::new().pubkey(), 200);
|
||||
let bob = (KeyPair::new().pubkey(), 100);
|
||||
|
||||
let mint: Mint = serde_json::from_reader(stdin()).unwrap();
|
||||
let mut entries = mint.create_entries();
|
||||
|
||||
let from = mint.keypair();
|
||||
let seed = mint.seed();
|
||||
let mut events = mint.create_events();
|
||||
events.push(transfer(&from, alice, seed));
|
||||
events.push(transfer(&from, bob, seed));
|
||||
let alice = (KeyPair::new().pubkey(), 200);
|
||||
let bob = (KeyPair::new().pubkey(), 100);
|
||||
let events = vec![transfer(&from, alice, seed), transfer(&from, bob, seed)];
|
||||
entries.push(create_entry(&seed, 0, events));
|
||||
|
||||
for entry in create_entries(&seed, events) {
|
||||
for entry in entries {
|
||||
println!("{}", serde_json::to_string(&entry).unwrap());
|
||||
}
|
||||
}
|
||||
|
|
51
src/entry.rs
51
src/entry.rs
|
@ -5,7 +5,7 @@ use event::Event;
|
|||
pub struct Entry {
|
||||
pub num_hashes: u64,
|
||||
pub id: Hash,
|
||||
pub event: Event,
|
||||
pub events: Vec<Event>,
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
|
@ -15,51 +15,61 @@ impl Entry {
|
|||
Entry {
|
||||
num_hashes,
|
||||
id: *id,
|
||||
event: Event::Tick,
|
||||
events: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies self.id is the result of hashing a 'start_hash' 'self.num_hashes' times.
|
||||
/// If the event is not a Tick, then hash that as well.
|
||||
pub fn verify(&self, start_hash: &Hash) -> bool {
|
||||
if !self.event.verify() {
|
||||
return false;
|
||||
for event in &self.events {
|
||||
if !event.verify() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
self.id == next_hash(start_hash, self.num_hashes, &self.event)
|
||||
self.id == next_hash(start_hash, self.num_hashes, &self.events)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the hash 'num_hashes' after start_hash. If the event contains
|
||||
/// signature, the final hash will be a hash of both the previous ID and
|
||||
/// the signature.
|
||||
pub fn next_hash(start_hash: &Hash, num_hashes: u64, event: &Event) -> Hash {
|
||||
pub fn next_hash(start_hash: &Hash, num_hashes: u64, events: &[Event]) -> Hash {
|
||||
let mut id = *start_hash;
|
||||
let sig = event.get_signature();
|
||||
let start_index = if sig.is_some() { 1 } else { 0 };
|
||||
for _ in start_index..num_hashes {
|
||||
for _ in 1..num_hashes {
|
||||
id = hash(&id);
|
||||
}
|
||||
if let Some(sig) = sig {
|
||||
id = extend_and_hash(&id, &sig);
|
||||
|
||||
// Hash all the event data
|
||||
let mut hash_data = vec![];
|
||||
for event in events {
|
||||
let sig = event.get_signature();
|
||||
if let Some(sig) = sig {
|
||||
hash_data.extend_from_slice(&sig);
|
||||
}
|
||||
}
|
||||
|
||||
if !hash_data.is_empty() {
|
||||
return extend_and_hash(&id, &hash_data);
|
||||
}
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
/// Creates the next Entry 'num_hashes' after 'start_hash'.
|
||||
pub fn create_entry(start_hash: &Hash, cur_hashes: u64, event: Event) -> Entry {
|
||||
let sig = event.get_signature();
|
||||
let num_hashes = cur_hashes + if sig.is_some() { 1 } else { 0 };
|
||||
let id = next_hash(start_hash, 0, &event);
|
||||
pub fn create_entry(start_hash: &Hash, cur_hashes: u64, events: Vec<Event>) -> Entry {
|
||||
let num_hashes = cur_hashes + if events.is_empty() { 0 } else { 1 };
|
||||
let id = next_hash(start_hash, 0, &events);
|
||||
Entry {
|
||||
num_hashes,
|
||||
id,
|
||||
event,
|
||||
events,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the next Tick Entry 'num_hashes' after 'start_hash'.
|
||||
pub fn create_entry_mut(start_hash: &mut Hash, cur_hashes: &mut u64, event: Event) -> Entry {
|
||||
let entry = create_entry(start_hash, *cur_hashes, event);
|
||||
pub fn create_entry_mut(start_hash: &mut Hash, cur_hashes: &mut u64, events: Vec<Event>) -> Entry {
|
||||
let entry = create_entry(start_hash, *cur_hashes, events);
|
||||
*start_hash = entry.id;
|
||||
*cur_hashes = 0;
|
||||
entry
|
||||
|
@ -67,11 +77,10 @@ pub fn create_entry_mut(start_hash: &mut Hash, cur_hashes: &mut u64, event: Even
|
|||
|
||||
/// Creates the next Tick Entry 'num_hashes' after 'start_hash'.
|
||||
pub fn next_tick(start_hash: &Hash, num_hashes: u64) -> Entry {
|
||||
let event = Event::Tick;
|
||||
Entry {
|
||||
num_hashes,
|
||||
id: next_hash(start_hash, num_hashes, &event),
|
||||
event,
|
||||
id: next_hash(start_hash, num_hashes, &[]),
|
||||
events: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
49
src/log.rs
49
src/log.rs
|
@ -14,23 +14,19 @@
|
|||
/// was generated by the fastest processor at the time the entry was logged.
|
||||
|
||||
use hash::Hash;
|
||||
use entry::{create_entry_mut, next_tick, Entry};
|
||||
use entry::{create_entry, next_tick, Entry};
|
||||
use event::Event;
|
||||
use rayon::prelude::*;
|
||||
|
||||
/// Verifies the hashes and counts of a slice of events are all consistent.
|
||||
pub fn verify_slice(events: &[Entry], start_hash: &Hash) -> bool {
|
||||
pub fn verify_slice(entries: &[Entry], start_hash: &Hash) -> bool {
|
||||
let genesis = [Entry::new_tick(Default::default(), start_hash)];
|
||||
let event_pairs = genesis.par_iter().chain(events).zip(events);
|
||||
let event_pairs = genesis.par_iter().chain(entries).zip(entries);
|
||||
event_pairs.all(|(x0, x1)| x1.verify(&x0.id))
|
||||
}
|
||||
|
||||
pub fn create_entries(start_hash: &Hash, events: Vec<Event>) -> Vec<Entry> {
|
||||
let mut id = *start_hash;
|
||||
events
|
||||
.into_iter()
|
||||
.map(|event| create_entry_mut(&mut id, &mut 0, event))
|
||||
.collect()
|
||||
vec![create_entry(start_hash, 0, events)]
|
||||
}
|
||||
|
||||
/// Create a vector of Ticks of length 'len' from 'start_hash' hash and 'num_hashes'.
|
||||
|
@ -48,8 +44,6 @@ pub fn next_ticks(start_hash: &Hash, num_hashes: u64, len: usize) -> Vec<Entry>
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use signature::{KeyPair, KeyPairUtil};
|
||||
use transaction::Transaction;
|
||||
use hash::hash;
|
||||
|
||||
#[test]
|
||||
|
@ -66,25 +60,26 @@ mod tests {
|
|||
assert!(!verify_slice(&bad_ticks, &zero)); // inductive step, bad
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reorder_attack() {
|
||||
let zero = Hash::default();
|
||||
// TODO: This is no longer relevant. Instead, test for reordered ticks.
|
||||
//#[test]
|
||||
//fn test_reorder_attack() {
|
||||
// let zero = Hash::default();
|
||||
|
||||
// First, verify entries
|
||||
let keypair = KeyPair::new();
|
||||
let tr0 = Transaction::new(&keypair, keypair.pubkey(), 0, zero);
|
||||
let tr1 = Transaction::new(&keypair, keypair.pubkey(), 1, zero);
|
||||
let events = vec![Event::Transaction(tr0), Event::Transaction(tr1)];
|
||||
let mut entries = create_entries(&zero, events);
|
||||
assert!(verify_slice(&entries, &zero));
|
||||
// // First, verify entries
|
||||
// let keypair = KeyPair::new();
|
||||
// let tr0 = Transaction::new(&keypair, keypair.pubkey(), 0, zero);
|
||||
// let tr1 = Transaction::new(&keypair, keypair.pubkey(), 1, zero);
|
||||
// let events = vec![Event::Transaction(tr0), Event::Transaction(tr1)];
|
||||
// let mut entries = create_entries(&zero, events);
|
||||
// assert!(verify_slice(&entries, &zero));
|
||||
|
||||
// Next, swap two events and ensure verification fails.
|
||||
let event0 = entries[0].event.clone();
|
||||
let event1 = entries[1].event.clone();
|
||||
entries[0].event = event1;
|
||||
entries[1].event = event0;
|
||||
assert!(!verify_slice(&entries, &zero));
|
||||
}
|
||||
// // Next, swap two events and ensure verification fails.
|
||||
// let event0 = entries[0].event.clone();
|
||||
// let event1 = entries[1].event.clone();
|
||||
// entries[0].event = event1;
|
||||
// entries[1].event = event0;
|
||||
// assert!(!verify_slice(&entries, &zero));
|
||||
//}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "unstable", test))]
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
use std::sync::mpsc::{Receiver, SyncSender, TryRecvError};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::mem;
|
||||
use hash::Hash;
|
||||
use entry::{create_entry_mut, Entry};
|
||||
use event::Event;
|
||||
|
@ -22,6 +23,7 @@ pub struct Logger {
|
|||
pub sender: SyncSender<Entry>,
|
||||
pub receiver: Receiver<Event>,
|
||||
pub last_id: Hash,
|
||||
pub events: Vec<Event>,
|
||||
pub num_hashes: u64,
|
||||
pub num_ticks: u64,
|
||||
}
|
||||
|
@ -32,13 +34,15 @@ impl Logger {
|
|||
receiver,
|
||||
sender,
|
||||
last_id: start_hash,
|
||||
events: vec![],
|
||||
num_hashes: 0,
|
||||
num_ticks: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_event(&mut self, event: Event) -> Result<Entry, ExitReason> {
|
||||
let entry = create_entry_mut(&mut self.last_id, &mut self.num_hashes, event);
|
||||
pub fn log_entry(&mut self) -> Result<Entry, ExitReason> {
|
||||
let events = mem::replace(&mut self.events, vec![]);
|
||||
let entry = create_entry_mut(&mut self.last_id, &mut self.num_hashes, events);
|
||||
println!("{}", serde_json::to_string(&entry).unwrap());
|
||||
Ok(entry)
|
||||
}
|
||||
|
@ -51,17 +55,21 @@ impl Logger {
|
|||
loop {
|
||||
if let Some(ms) = ms_per_tick {
|
||||
if epoch.elapsed() > Duration::from_millis((self.num_ticks + 1) * ms) {
|
||||
self.log_event(Event::Tick)?;
|
||||
self.log_entry()?;
|
||||
self.num_ticks += 1;
|
||||
}
|
||||
}
|
||||
|
||||
match self.receiver.try_recv() {
|
||||
Ok(event) => {
|
||||
let entry = self.log_event(event)?;
|
||||
self.sender
|
||||
.send(entry)
|
||||
.or(Err(ExitReason::SendDisconnected))?;
|
||||
if let Event::Tick = event {
|
||||
let entry = self.log_entry()?;
|
||||
self.sender
|
||||
.send(entry)
|
||||
.or(Err(ExitReason::SendDisconnected))?;
|
||||
} else {
|
||||
self.events.push(event);
|
||||
}
|
||||
}
|
||||
Err(TryRecvError::Empty) => return Ok(()),
|
||||
Err(TryRecvError::Disconnected) => return Err(ExitReason::RecvDisconnected),
|
||||
|
|
11
src/mint.rs
11
src/mint.rs
|
@ -4,7 +4,7 @@ use event::Event;
|
|||
use transaction::Transaction;
|
||||
use signature::{KeyPair, KeyPairUtil, PublicKey};
|
||||
use entry::Entry;
|
||||
use log::create_entries;
|
||||
use entry::create_entry;
|
||||
use hash::{hash, Hash};
|
||||
use ring::rand::SystemRandom;
|
||||
use untrusted::Input;
|
||||
|
@ -44,11 +44,13 @@ impl Mint {
|
|||
pub fn create_events(&self) -> Vec<Event> {
|
||||
let keypair = self.keypair();
|
||||
let tr = Transaction::new(&keypair, self.pubkey(), self.tokens, self.seed());
|
||||
vec![Event::Tick, Event::Transaction(tr)]
|
||||
vec![Event::Transaction(tr)]
|
||||
}
|
||||
|
||||
pub fn create_entries(&self) -> Vec<Entry> {
|
||||
create_entries(&self.seed(), self.create_events())
|
||||
let e0 = create_entry(&self.seed(), 0, vec![]);
|
||||
let e1 = create_entry(&e0.id, 0, self.create_events());
|
||||
vec![e0, e1]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,11 +62,8 @@ mod tests {
|
|||
#[test]
|
||||
fn test_create_events() {
|
||||
let mut events = Mint::new(100).create_events().into_iter();
|
||||
assert_eq!(events.next().unwrap(), Event::Tick);
|
||||
if let Event::Transaction(tr) = events.next().unwrap() {
|
||||
assert_eq!(tr.from, tr.to);
|
||||
} else {
|
||||
assert!(false);
|
||||
}
|
||||
assert_eq!(events.next(), None);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue