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