Merge pull request #45 from garious/init-from-log
Towards sending the log to clients
This commit is contained in:
commit
cb0ce9986c
104
README.md
104
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<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/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
|
||||
===
|
||||
|
|
|
@ -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/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.
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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!("]");
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
26
src/event.rs
26
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<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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
41
src/log.rs
41
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));
|
||||
}
|
||||
|
|
104
src/logger.rs
104
src/logger.rs
|
@ -5,13 +5,13 @@
|
|||
//! Event, the latest hash, and the number of hashes since the last event.
|
||||
//! The resulting stream of entries represents ordered events in time.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::sync::mpsc::{Receiver, SyncSender, TryRecvError};
|
||||
use std::time::{Duration, Instant};
|
||||
use log::{create_entry_mut, Entry, Sha256Hash};
|
||||
use event::{get_signature, verify_event, Event, Signature};
|
||||
use event::Event;
|
||||
use serde::Serialize;
|
||||
use std::fmt::Debug;
|
||||
use serde_json;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ExitReason {
|
||||
|
@ -27,22 +27,6 @@ pub struct Logger<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));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue