Merge pull request #45 from garious/init-from-log

Towards sending the log to clients
This commit is contained in:
Greg Fitzgerald 2018-03-05 16:17:41 -07:00 committed by GitHub
commit cb0ce9986c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 459 additions and 319 deletions

104
README.md
View File

@ -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<Sha256Hash>) -> Result<(), SendError<Event<Sha256Hash>>> {
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<Entry<Sha256Hash>> = 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/CurryHoward_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
===

65
doc/historian.md Normal file
View File

@ -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<Sha256Hash>) -> Result<(), SendError<Event<Sha256Hash>>> {
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<Entry<Sha256Hash>> = 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/CurryHoward_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.

View File

@ -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<T> = result::Result<T, AccountingError>;
pub struct Accountant {
pub historian: Historian<u64>,
pub balances: HashMap<PublicKey, u64>,
pub first_id: Sha256Hash,
pub last_id: Sha256Hash,
}
impl Accountant {
pub fn new(gen: &Genesis, ms_per_tick: Option<u64>) -> Self {
let start_hash = hash(&gen.pkcs8);
pub fn new_from_entries<I>(entries: I, ms_per_tick: Option<u64>) -> Self
where
I: IntoIterator<Item = Entry<u64>>,
{
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::<u64>::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<Entry<u64>> {
let mut entries = vec![];
pub fn new(gen: &Genesis, ms_per_tick: Option<u64>) -> 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<u64>) -> 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<u64>,
event: &Event<u64>,
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<Signature> {
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<u64> {
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
);
}

View File

@ -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<u64> },
Entries { entries: Vec<Entry<u64>> },
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<Response> {
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,
}),
}
}

View File

@ -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<Sha256Hash>,
}
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<usize> {
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<Signature> {
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<u64> {
pub fn get_balance(&self, pubkey: &PublicKey) -> io::Result<Option<u64>> {
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<Sha256Hash> {
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<Sha256Hash> {
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);
}
}

View File

@ -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);
}

View File

@ -7,11 +7,19 @@ use std::thread::sleep;
use std::time::Duration;
use std::sync::mpsc::SendError;
fn create_log(hist: &Historian<Sha256Hash>) -> Result<(), SendError<Event<Sha256Hash>>> {
fn create_log(
hist: &Historian<Sha256Hash>,
seed: &Sha256Hash,
) -> Result<(), SendError<Event<Sha256Hash>>> {
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<Sha256Hash>) -> Result<(), SendError<Event<Sha256
fn main() {
let seed = Sha256Hash::default();
let hist = Historian::new(&seed, Some(10));
create_log(&hist).expect("send error");
create_log(&hist, &seed).expect("send error");
drop(hist.sender);
let entries: Vec<Entry<Sha256Hash>> = hist.receiver.iter().collect();
for entry in &entries {

View File

@ -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!("]");
}

View File

@ -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());
}

View File

@ -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();
}

View File

@ -20,6 +20,7 @@ use ring::{rand, signature};
use untrusted;
use serde::Serialize;
use bincode::serialize;
use log::Sha256Hash;
pub type PublicKey = GenericArray<u8, U32>;
pub type Signature = GenericArray<u8, U64>;
@ -36,16 +37,18 @@ pub enum Event<T> {
from: PublicKey,
to: PublicKey,
data: T,
last_id: Sha256Hash,
sig: Signature,
},
}
impl<T> Event<T> {
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<T: Serialize>(
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<T: Serialize>(data: &T, keypair: &Ed25519KeyPair) -> Signature {
sign_transaction_data(data, keypair, &get_pubkey(keypair))
pub fn sign_claim_data<T: Serialize>(
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<T: Serialize>(event: &Event<T>) -> 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<u8> = deserialize(&buf).unwrap();
assert_eq!(claim1, claim0);

View File

@ -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<Creator>) -> 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<u64> {
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<Entry<u64>> {
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));
}
}

View File

@ -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<T> {
pub sender: SyncSender<Event<T>>,
pub receiver: Receiver<Entry<T>>,
pub thread_hdl: JoinHandle<(Entry<T>, ExitReason)>,
pub thread_hdl: JoinHandle<ExitReason>,
pub signatures: HashSet<Signature>,
}
@ -34,10 +33,6 @@ impl<T: 'static + Serialize + Clone + Debug + Send> Historian<T> {
}
}
pub fn verify_event(self: &mut Self, event: &Event<T>) -> 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<T: 'static + Serialize + Clone + Debug + Send> Historian<T> {
ms_per_tick: Option<u64>,
receiver: Receiver<Event<T>>,
sender: SyncSender<Entry<T>>,
) -> JoinHandle<(Entry<T>, ExitReason)> {
thread::spawn(move || {
) -> JoinHandle<ExitReason> {
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<T: 'static + Serialize + Clone + Debug + Send> Historian<T> {
}
}
pub fn reserve_signature<T>(sigs: &mut HashSet<Signature>, event: &Event<T>) -> 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<Entry<Sha256Hash>> = 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);
}
}

View File

@ -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;

View File

@ -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));
}

View File

@ -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<T> {
pub num_ticks: u64,
}
pub fn verify_event_and_reserve_signature<T: Serialize>(
signatures: &mut HashSet<Signature>,
event: &Event<T>,
) -> 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<T: Serialize + Clone + Debug> Logger<T> {
pub fn new(
receiver: Receiver<Event<T>>,
@ -58,19 +42,17 @@ impl<T: Serialize + Clone + Debug> Logger<T> {
}
}
pub fn log_event(&mut self, event: Event<T>) -> Result<(), (Entry<T>, ExitReason)> {
pub fn log_event(&mut self, event: Event<T>) -> Result<Entry<T>, 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<u64>,
) -> Result<(), (Entry<T>, 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<T: Serialize + Clone + Debug> Logger<T> {
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<Entry<u64>> {
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::<Vec<_>>()
}
#[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));
}
}