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
|
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.
|
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
|
First, build the demo executables in release mode (optimized for performance):
|
||||||
of your Cargo.toml.
|
|
||||||
|
|
||||||
Create a *Historian* and send it *events* to generate an *event log*, where each log *entry*
|
```bash
|
||||||
is tagged with the historian's latest *hash*. Then ensure the order of events was not tampered
|
$ cargo build --release
|
||||||
with by verifying each entry's hash can be generated from the hash in the previous entry:
|
$ cd target/release
|
||||||
|
|
||||||
![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:
|
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
|
```bash
|
||||||
Entry { num_hashes: 0, id: [0, ...], event: Tick }
|
$ ./silk-genesis-file-demo > demo-genesis.jsoc
|
||||||
Entry { num_hashes: 3, id: [67, ...], event: Transaction { data: [37, ...] } }
|
$ cat demo-genesis.json | ./silk-genesis-block > demo-genesis.log
|
||||||
Entry { num_hashes: 3, id: [123, ...], event: Tick }
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Proof-of-History
|
Now you can start the server:
|
||||||
---
|
|
||||||
|
|
||||||
Take note of the last line:
|
```bash
|
||||||
|
$ cat demo-genesis.log | ./silk-testnode > demo-entries0.log
|
||||||
```rust
|
|
||||||
assert!(verify_slice(&entries, &seed));
|
|
||||||
```
|
```
|
||||||
|
|
||||||
[It's a proof!](https://en.wikipedia.org/wiki/Curry–Howard_correspondence) For each entry returned by the
|
Then, in a seperate shell, let's execute some transactions. Note we pass in
|
||||||
historian, we can verify that `id` is the result of applying a sha256 hash to the previous `id`
|
the JSON configuration file here, not the genesis log.
|
||||||
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.
|
```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
|
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
|
//! event log to record transactions. Its users can deposit funds and
|
||||||
//! transfer funds to other users.
|
//! transfer funds to other users.
|
||||||
|
|
||||||
use log::{hash, Entry, Sha256Hash};
|
use log::{Entry, Sha256Hash};
|
||||||
use event::{get_pubkey, sign_transaction_data, Event, PublicKey, Signature};
|
use event::{get_pubkey, sign_transaction_data, verify_event, Event, PublicKey, Signature};
|
||||||
use genesis::Genesis;
|
use genesis::Genesis;
|
||||||
use historian::Historian;
|
use historian::{reserve_signature, Historian};
|
||||||
use ring::signature::Ed25519KeyPair;
|
use ring::signature::Ed25519KeyPair;
|
||||||
use std::sync::mpsc::SendError;
|
use std::sync::mpsc::SendError;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::thread::sleep;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum AccountingError {
|
pub enum AccountingError {
|
||||||
|
@ -25,35 +23,51 @@ pub type Result<T> = result::Result<T, AccountingError>;
|
||||||
pub struct Accountant {
|
pub struct Accountant {
|
||||||
pub historian: Historian<u64>,
|
pub historian: Historian<u64>,
|
||||||
pub balances: HashMap<PublicKey, u64>,
|
pub balances: HashMap<PublicKey, u64>,
|
||||||
|
pub first_id: Sha256Hash,
|
||||||
pub last_id: Sha256Hash,
|
pub last_id: Sha256Hash,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Accountant {
|
impl Accountant {
|
||||||
pub fn new(gen: &Genesis, ms_per_tick: Option<u64>) -> Self {
|
pub fn new_from_entries<I>(entries: I, ms_per_tick: Option<u64>) -> Self
|
||||||
let start_hash = hash(&gen.pkcs8);
|
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 hist = Historian::<u64>::new(&start_hash, ms_per_tick);
|
||||||
let mut acc = Accountant {
|
let mut acc = Accountant {
|
||||||
historian: hist,
|
historian: hist,
|
||||||
balances: HashMap::new(),
|
balances: HashMap::new(),
|
||||||
|
first_id: start_hash,
|
||||||
last_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
|
acc
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sync(self: &mut Self) -> Vec<Entry<u64>> {
|
pub fn new(gen: &Genesis, ms_per_tick: Option<u64>) -> Self {
|
||||||
let mut entries = vec![];
|
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() {
|
while let Ok(entry) = self.historian.receiver.try_recv() {
|
||||||
entries.push(entry);
|
self.last_id = entry.id;
|
||||||
}
|
}
|
||||||
|
self.last_id
|
||||||
if let Some(last_entry) = entries.last() {
|
|
||||||
self.last_id = last_entry.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
entries
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_deposit(allow_deposits: bool, from: &PublicKey, to: &PublicKey) -> bool {
|
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<()> {
|
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);
|
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(
|
fn process_verified_event(
|
||||||
self: &mut Self,
|
self: &mut Self,
|
||||||
event: Event<u64>,
|
event: &Event<u64>,
|
||||||
allow_deposits: bool,
|
allow_deposits: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
match event {
|
if !reserve_signature(&mut self.historian.signatures, event) {
|
||||||
Event::Tick => Ok(()),
|
return Err(AccountingError::InvalidEvent);
|
||||||
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 let Err(SendError(_)) = self.historian.sender.send(event) {
|
if let Event::Transaction { from, to, data, .. } = *event {
|
||||||
return Err(AccountingError::SendError);
|
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 self.balances.contains_key(&to) {
|
||||||
if let Some(x) = self.balances.get_mut(&from) {
|
if let Some(x) = self.balances.get_mut(&to) {
|
||||||
*x -= data;
|
*x += data;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
if self.balances.contains_key(&to) {
|
self.balances.insert(to, data);
|
||||||
if let Some(x) = self.balances.get_mut(&to) {
|
|
||||||
*x += data;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.balances.insert(to, data);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transfer(
|
pub fn transfer(
|
||||||
|
@ -110,11 +127,13 @@ impl Accountant {
|
||||||
to: PublicKey,
|
to: PublicKey,
|
||||||
) -> Result<Signature> {
|
) -> Result<Signature> {
|
||||||
let from = get_pubkey(keypair);
|
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 {
|
let event = Event::Transaction {
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
data: n,
|
data: n,
|
||||||
|
last_id,
|
||||||
sig,
|
sig,
|
||||||
};
|
};
|
||||||
self.process_event(event).map(|_| sig)
|
self.process_event(event).map(|_| sig)
|
||||||
|
@ -123,21 +142,6 @@ impl Accountant {
|
||||||
pub fn get_balance(self: &Self, pubkey: &PublicKey) -> Option<u64> {
|
pub fn get_balance(self: &Self, pubkey: &PublicKey) -> Option<u64> {
|
||||||
self.balances.get(pubkey).map(|x| *x)
|
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)]
|
#[cfg(test)]
|
||||||
|
@ -146,8 +150,6 @@ mod tests {
|
||||||
use event::{generate_keypair, get_pubkey};
|
use event::{generate_keypair, get_pubkey};
|
||||||
use logger::ExitReason;
|
use logger::ExitReason;
|
||||||
use genesis::Creator;
|
use genesis::Creator;
|
||||||
use std::thread::sleep;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_accountant() {
|
fn test_accountant() {
|
||||||
|
@ -156,14 +158,12 @@ mod tests {
|
||||||
let alice = Genesis::new(10_000, vec![bob]);
|
let alice = Genesis::new(10_000, vec![bob]);
|
||||||
let mut acc = Accountant::new(&alice, Some(2));
|
let mut acc = Accountant::new(&alice, Some(2));
|
||||||
|
|
||||||
let sig = acc.transfer(500, &alice.get_keypair(), bob_pubkey).unwrap();
|
acc.transfer(500, &alice.get_keypair(), bob_pubkey).unwrap();
|
||||||
acc.wait_on_signature(&sig);
|
|
||||||
|
|
||||||
assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_500);
|
assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_500);
|
||||||
|
|
||||||
drop(acc.historian.sender);
|
drop(acc.historian.sender);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
acc.historian.thread_hdl.join().unwrap().1,
|
acc.historian.thread_hdl.join().unwrap(),
|
||||||
ExitReason::RecvDisconnected
|
ExitReason::RecvDisconnected
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -174,12 +174,10 @@ mod tests {
|
||||||
let bob_pubkey = bob.pubkey;
|
let bob_pubkey = bob.pubkey;
|
||||||
let alice = Genesis::new(11_000, vec![bob]);
|
let alice = Genesis::new(11_000, vec![bob]);
|
||||||
let mut acc = Accountant::new(&alice, Some(2));
|
let mut acc = Accountant::new(&alice, Some(2));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
acc.transfer(10_001, &alice.get_keypair(), bob_pubkey),
|
acc.transfer(10_001, &alice.get_keypair(), bob_pubkey),
|
||||||
Err(AccountingError::InsufficientFunds)
|
Err(AccountingError::InsufficientFunds)
|
||||||
);
|
);
|
||||||
sleep(Duration::from_millis(30));
|
|
||||||
|
|
||||||
let alice_pubkey = get_pubkey(&alice.get_keypair());
|
let alice_pubkey = get_pubkey(&alice.get_keypair());
|
||||||
assert_eq!(acc.get_balance(&alice_pubkey).unwrap(), 10_000);
|
assert_eq!(acc.get_balance(&alice_pubkey).unwrap(), 10_000);
|
||||||
|
@ -187,7 +185,7 @@ mod tests {
|
||||||
|
|
||||||
drop(acc.historian.sender);
|
drop(acc.historian.sender);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
acc.historian.thread_hdl.join().unwrap().1,
|
acc.historian.thread_hdl.join().unwrap(),
|
||||||
ExitReason::RecvDisconnected
|
ExitReason::RecvDisconnected
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -199,13 +197,12 @@ mod tests {
|
||||||
let alice_keypair = alice.get_keypair();
|
let alice_keypair = alice.get_keypair();
|
||||||
let bob_keypair = generate_keypair();
|
let bob_keypair = generate_keypair();
|
||||||
let bob_pubkey = get_pubkey(&bob_keypair);
|
let bob_pubkey = get_pubkey(&bob_keypair);
|
||||||
let sig = acc.transfer(500, &alice_keypair, bob_pubkey).unwrap();
|
acc.transfer(500, &alice_keypair, bob_pubkey).unwrap();
|
||||||
acc.wait_on_signature(&sig);
|
|
||||||
assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 500);
|
assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 500);
|
||||||
|
|
||||||
drop(acc.historian.sender);
|
drop(acc.historian.sender);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
acc.historian.thread_hdl.join().unwrap().1,
|
acc.historian.thread_hdl.join().unwrap(),
|
||||||
ExitReason::RecvDisconnected
|
ExitReason::RecvDisconnected
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use std::io;
|
use std::io;
|
||||||
use accountant::Accountant;
|
use accountant::Accountant;
|
||||||
use event::{Event, PublicKey, Signature};
|
use event::{Event, PublicKey, Signature};
|
||||||
|
use log::{Entry, Sha256Hash};
|
||||||
use std::net::UdpSocket;
|
use std::net::UdpSocket;
|
||||||
use bincode::{deserialize, serialize};
|
use bincode::{deserialize, serialize};
|
||||||
|
|
||||||
pub struct AccountantSkel {
|
pub struct AccountantSkel {
|
||||||
pub obj: Accountant,
|
pub acc: Accountant,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
@ -14,49 +15,67 @@ pub enum Request {
|
||||||
from: PublicKey,
|
from: PublicKey,
|
||||||
to: PublicKey,
|
to: PublicKey,
|
||||||
val: u64,
|
val: u64,
|
||||||
|
last_id: Sha256Hash,
|
||||||
sig: Signature,
|
sig: Signature,
|
||||||
},
|
},
|
||||||
GetBalance {
|
GetBalance {
|
||||||
key: PublicKey,
|
key: PublicKey,
|
||||||
},
|
},
|
||||||
Wait {
|
GetEntries {
|
||||||
sig: Signature,
|
last_id: Sha256Hash,
|
||||||
|
},
|
||||||
|
GetId {
|
||||||
|
is_last: bool,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub enum Response {
|
pub enum Response {
|
||||||
Balance { key: PublicKey, val: u64 },
|
Balance { key: PublicKey, val: Option<u64> },
|
||||||
Confirmed { sig: Signature },
|
Entries { entries: Vec<Entry<u64>> },
|
||||||
|
Id { id: Sha256Hash, is_last: bool },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccountantSkel {
|
impl AccountantSkel {
|
||||||
pub fn new(obj: Accountant) -> Self {
|
pub fn new(acc: Accountant) -> Self {
|
||||||
AccountantSkel { obj }
|
AccountantSkel { acc }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_request(self: &mut Self, msg: Request) -> Option<Response> {
|
pub fn process_request(self: &mut Self, msg: Request) -> Option<Response> {
|
||||||
match msg {
|
match msg {
|
||||||
Request::Transfer { from, to, val, sig } => {
|
Request::Transfer {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
val,
|
||||||
|
last_id,
|
||||||
|
sig,
|
||||||
|
} => {
|
||||||
let event = Event::Transaction {
|
let event = Event::Transaction {
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
data: val,
|
data: val,
|
||||||
|
last_id,
|
||||||
sig,
|
sig,
|
||||||
};
|
};
|
||||||
if let Err(err) = self.obj.process_event(event) {
|
if let Err(err) = self.acc.process_event(event) {
|
||||||
println!("Transfer error: {:?}", err);
|
eprintln!("Transfer error: {:?}", err);
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
Request::GetBalance { key } => {
|
Request::GetBalance { key } => {
|
||||||
let val = self.obj.get_balance(&key).unwrap();
|
let val = self.acc.get_balance(&key);
|
||||||
Some(Response::Balance { key, val })
|
Some(Response::Balance { key, val })
|
||||||
}
|
}
|
||||||
Request::Wait { sig } => {
|
Request::GetEntries { .. } => Some(Response::Entries { entries: vec![] }),
|
||||||
self.obj.wait_on_signature(&sig);
|
Request::GetId { is_last } => Some(Response::Id {
|
||||||
Some(Response::Confirmed { sig })
|
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::net::UdpSocket;
|
||||||
use std::io;
|
use std::io;
|
||||||
use bincode::{deserialize, serialize};
|
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 ring::signature::Ed25519KeyPair;
|
||||||
use accountant_skel::{Request, Response};
|
use accountant_skel::{Request, Response};
|
||||||
|
|
||||||
pub struct AccountantStub {
|
pub struct AccountantStub {
|
||||||
pub addr: String,
|
pub addr: String,
|
||||||
pub socket: UdpSocket,
|
pub socket: UdpSocket,
|
||||||
|
pub last_id: Option<Sha256Hash>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccountantStub {
|
impl AccountantStub {
|
||||||
|
@ -19,33 +21,43 @@ impl AccountantStub {
|
||||||
AccountantStub {
|
AccountantStub {
|
||||||
addr: addr.to_string(),
|
addr: addr.to_string(),
|
||||||
socket,
|
socket,
|
||||||
|
last_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transfer_signed(
|
pub fn transfer_signed(
|
||||||
self: &Self,
|
&self,
|
||||||
from: PublicKey,
|
from: PublicKey,
|
||||||
to: PublicKey,
|
to: PublicKey,
|
||||||
val: u64,
|
val: u64,
|
||||||
|
last_id: Sha256Hash,
|
||||||
sig: Signature,
|
sig: Signature,
|
||||||
) -> io::Result<usize> {
|
) -> 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();
|
let data = serialize(&req).unwrap();
|
||||||
self.socket.send_to(&data, &self.addr)
|
self.socket.send_to(&data, &self.addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transfer(
|
pub fn transfer(
|
||||||
self: &Self,
|
&self,
|
||||||
n: u64,
|
n: u64,
|
||||||
keypair: &Ed25519KeyPair,
|
keypair: &Ed25519KeyPair,
|
||||||
to: PublicKey,
|
to: PublicKey,
|
||||||
|
last_id: &Sha256Hash,
|
||||||
) -> io::Result<Signature> {
|
) -> io::Result<Signature> {
|
||||||
let from = get_pubkey(keypair);
|
let from = get_pubkey(keypair);
|
||||||
let sig = sign_transaction_data(&n, keypair, &to);
|
let sig = sign_transaction_data(&n, keypair, &to, last_id);
|
||||||
self.transfer_signed(from, to, n, sig).map(|_| sig)
|
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 req = Request::GetBalance { key: *pubkey };
|
||||||
let data = serialize(&req).expect("serialize GetBalance");
|
let data = serialize(&req).expect("serialize GetBalance");
|
||||||
self.socket.send_to(&data, &self.addr)?;
|
self.socket.send_to(&data, &self.addr)?;
|
||||||
|
@ -56,20 +68,55 @@ impl AccountantStub {
|
||||||
assert_eq!(key, *pubkey);
|
assert_eq!(key, *pubkey);
|
||||||
return Ok(val);
|
return Ok(val);
|
||||||
}
|
}
|
||||||
Ok(0)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wait_on_signature(self: &Self, wait_sig: &Signature) -> io::Result<()> {
|
fn get_id(&self, is_last: bool) -> io::Result<Sha256Hash> {
|
||||||
let req = Request::Wait { sig: *wait_sig };
|
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();
|
let data = serialize(&req).unwrap();
|
||||||
self.socket.send_to(&data, &self.addr).map(|_| ())?;
|
self.socket.send_to(&data, &self.addr).map(|_| ())?;
|
||||||
|
|
||||||
let mut buf = vec![0u8; 1024];
|
let mut buf = vec![0u8; 1024];
|
||||||
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::Confirmed { sig } = resp {
|
if let Response::Entries { entries } = resp {
|
||||||
assert_eq!(sig, *wait_sig);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,9 +142,11 @@ mod tests {
|
||||||
sleep(Duration::from_millis(30));
|
sleep(Duration::from_millis(30));
|
||||||
|
|
||||||
let socket = UdpSocket::bind(send_addr).unwrap();
|
let socket = UdpSocket::bind(send_addr).unwrap();
|
||||||
let acc = AccountantStub::new(addr, socket);
|
let mut acc = AccountantStub::new(addr, socket);
|
||||||
let sig = acc.transfer(500, &alice.get_keypair(), bob_pubkey).unwrap();
|
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();
|
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;
|
extern crate silk;
|
||||||
|
|
||||||
use silk::accountant_stub::AccountantStub;
|
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::event::{generate_keypair, get_pubkey, sign_transaction_data, verify_event, Event};
|
||||||
use silk::genesis::Genesis;
|
use silk::genesis::Genesis;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::net::UdpSocket;
|
use std::net::UdpSocket;
|
||||||
use std::thread::{sleep, spawn};
|
use std::io::stdin;
|
||||||
use std::time::Duration;
|
|
||||||
//use std::io::stdin;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let addr = "127.0.0.1:8000";
|
let addr = "127.0.0.1:8000";
|
||||||
let send_addr = "127.0.0.1:8001";
|
let send_addr = "127.0.0.1:8001";
|
||||||
|
|
||||||
let txs = 200;
|
let gen: Genesis = serde_json::from_reader(stdin()).unwrap();
|
||||||
|
let alice_keypair = gen.get_keypair();
|
||||||
let gen = Genesis::new(txs, vec![]);
|
let alice_pubkey = gen.get_pubkey();
|
||||||
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 socket = UdpSocket::bind(send_addr).unwrap();
|
let socket = UdpSocket::bind(send_addr).unwrap();
|
||||||
let acc = AccountantStub::new(addr, socket);
|
let mut acc = AccountantStub::new(addr, socket);
|
||||||
let alice_pubkey = get_pubkey(&alice_keypair);
|
let 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;
|
let one = 1;
|
||||||
println!("Signing transactions...");
|
println!("Signing transactions...");
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
@ -37,7 +30,7 @@ fn main() {
|
||||||
.map(|_| {
|
.map(|_| {
|
||||||
let rando_keypair = generate_keypair();
|
let rando_keypair = generate_keypair();
|
||||||
let rando_pubkey = get_pubkey(&rando_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)
|
(rando_pubkey, sig)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -58,6 +51,7 @@ fn main() {
|
||||||
from: alice_pubkey,
|
from: alice_pubkey,
|
||||||
to: k,
|
to: k,
|
||||||
data: one,
|
data: one,
|
||||||
|
last_id,
|
||||||
sig: s,
|
sig: s,
|
||||||
};
|
};
|
||||||
assert!(verify_event(&e));
|
assert!(verify_event(&e));
|
||||||
|
@ -76,7 +70,8 @@ fn main() {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let mut sig = Default::default();
|
let mut sig = Default::default();
|
||||||
for (k, s) in sigs {
|
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;
|
sig = s;
|
||||||
}
|
}
|
||||||
println!("Waiting for last transaction to be confirmed...",);
|
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 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;
|
let tps = (txs * 1_000_000_000) as f64 / ns as f64;
|
||||||
println!("Done. {} tps!", tps);
|
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);
|
println!("Alice's Final Balance {}", val);
|
||||||
assert_eq!(val, 0);
|
assert_eq!(val, 0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,19 @@ use std::thread::sleep;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::sync::mpsc::SendError;
|
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));
|
sleep(Duration::from_millis(15));
|
||||||
let data = Sha256Hash::default();
|
let data = Sha256Hash::default();
|
||||||
let keypair = generate_keypair();
|
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)?;
|
hist.sender.send(event0)?;
|
||||||
sleep(Duration::from_millis(10));
|
sleep(Duration::from_millis(10));
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -20,7 +28,7 @@ fn create_log(hist: &Historian<Sha256Hash>) -> Result<(), SendError<Event<Sha256
|
||||||
fn main() {
|
fn main() {
|
||||||
let seed = Sha256Hash::default();
|
let seed = Sha256Hash::default();
|
||||||
let hist = Historian::new(&seed, Some(10));
|
let hist = Historian::new(&seed, Some(10));
|
||||||
create_log(&hist).expect("send error");
|
create_log(&hist, &seed).expect("send error");
|
||||||
drop(hist.sender);
|
drop(hist.sender);
|
||||||
let entries: Vec<Entry<Sha256Hash>> = hist.receiver.iter().collect();
|
let entries: Vec<Entry<Sha256Hash>> = hist.receiver.iter().collect();
|
||||||
for entry in &entries {
|
for entry in &entries {
|
||||||
|
|
|
@ -5,20 +5,14 @@ extern crate serde_json;
|
||||||
extern crate silk;
|
extern crate silk;
|
||||||
|
|
||||||
use silk::genesis::Genesis;
|
use silk::genesis::Genesis;
|
||||||
use silk::log::{create_entries, hash, verify_slice_u64};
|
use silk::log::verify_slice_u64;
|
||||||
use std::io::stdin;
|
use std::io::stdin;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let gen: Genesis = serde_json::from_reader(stdin()).unwrap();
|
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);
|
verify_slice_u64(&entries, &entries[0].id);
|
||||||
println!("[");
|
for x in entries {
|
||||||
let len = entries.len();
|
println!("{}", serde_json::to_string(&x).unwrap());
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
println!("]");
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,6 @@ fn main() {
|
||||||
pubkey: get_pubkey(&generate_keypair()),
|
pubkey: get_pubkey(&generate_keypair()),
|
||||||
};
|
};
|
||||||
let creators = vec![alice, bob];
|
let creators = vec![alice, bob];
|
||||||
let gen = Genesis::new(300, creators);
|
let gen = Genesis::new(500, creators);
|
||||||
println!("{}", serde_json::to_string(&gen).unwrap());
|
println!("{}", serde_json::to_string(&gen).unwrap());
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,17 @@ extern crate silk;
|
||||||
|
|
||||||
use silk::accountant_skel::AccountantSkel;
|
use silk::accountant_skel::AccountantSkel;
|
||||||
use silk::accountant::Accountant;
|
use silk::accountant::Accountant;
|
||||||
use silk::genesis::Genesis;
|
use std::io::{self, BufRead};
|
||||||
use std::io::stdin;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let addr = "127.0.0.1:8000";
|
let addr = "127.0.0.1:8000";
|
||||||
let gen: Genesis = serde_json::from_reader(stdin()).unwrap();
|
let stdin = io::stdin();
|
||||||
let acc = Accountant::new(&gen, Some(1000));
|
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);
|
let mut skel = AccountantSkel::new(acc);
|
||||||
println!("Listening on {}", addr);
|
eprintln!("Listening on {}", addr);
|
||||||
skel.serve(addr).unwrap();
|
skel.serve(addr).unwrap();
|
||||||
}
|
}
|
||||||
|
|
26
src/event.rs
26
src/event.rs
|
@ -20,6 +20,7 @@ use ring::{rand, signature};
|
||||||
use untrusted;
|
use untrusted;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use bincode::serialize;
|
use bincode::serialize;
|
||||||
|
use log::Sha256Hash;
|
||||||
|
|
||||||
pub type PublicKey = GenericArray<u8, U32>;
|
pub type PublicKey = GenericArray<u8, U32>;
|
||||||
pub type Signature = GenericArray<u8, U64>;
|
pub type Signature = GenericArray<u8, U64>;
|
||||||
|
@ -36,16 +37,18 @@ pub enum Event<T> {
|
||||||
from: PublicKey,
|
from: PublicKey,
|
||||||
to: PublicKey,
|
to: PublicKey,
|
||||||
data: T,
|
data: T,
|
||||||
|
last_id: Sha256Hash,
|
||||||
sig: Signature,
|
sig: Signature,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Event<T> {
|
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 {
|
Event::Transaction {
|
||||||
from: to,
|
from: to,
|
||||||
to,
|
to,
|
||||||
data,
|
data,
|
||||||
|
last_id,
|
||||||
sig,
|
sig,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,14 +77,19 @@ pub fn sign_transaction_data<T: Serialize>(
|
||||||
data: &T,
|
data: &T,
|
||||||
keypair: &Ed25519KeyPair,
|
keypair: &Ed25519KeyPair,
|
||||||
to: &PublicKey,
|
to: &PublicKey,
|
||||||
|
last_id: &Sha256Hash,
|
||||||
) -> Signature {
|
) -> Signature {
|
||||||
let from = &get_pubkey(keypair);
|
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.
|
/// 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 {
|
pub fn sign_claim_data<T: Serialize>(
|
||||||
sign_transaction_data(data, keypair, &get_pubkey(keypair))
|
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.
|
/// Verify a signed message with the given public key.
|
||||||
|
@ -104,10 +112,11 @@ pub fn verify_event<T: Serialize>(event: &Event<T>) -> bool {
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
ref data,
|
ref data,
|
||||||
|
last_id,
|
||||||
sig,
|
sig,
|
||||||
} = *event
|
} = *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) {
|
if !verify_signature(&from, &sign_data, &sig) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -122,7 +131,12 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serialize_claim() {
|
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 buf = serialize(&claim0).unwrap();
|
||||||
let claim1: Event<u8> = deserialize(&buf).unwrap();
|
let claim1: Event<u8> = deserialize(&buf).unwrap();
|
||||||
assert_eq!(claim1, claim0);
|
assert_eq!(claim1, claim0);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
//! A library for generating the chain's genesis block.
|
//! A library for generating the chain's genesis block.
|
||||||
|
|
||||||
use event::{generate_keypair, get_pubkey, sign_transaction_data, Event, PublicKey};
|
use event::{generate_keypair, get_pubkey, sign_transaction_data, Event, PublicKey};
|
||||||
|
use log::{create_entries, hash, Entry, Sha256Hash};
|
||||||
use ring::rand::SystemRandom;
|
use ring::rand::SystemRandom;
|
||||||
use ring::signature::Ed25519KeyPair;
|
use ring::signature::Ed25519KeyPair;
|
||||||
use untrusted::Input;
|
use untrusted::Input;
|
||||||
|
@ -31,6 +32,7 @@ impl Genesis {
|
||||||
pub fn new(tokens: u64, creators: Vec<Creator>) -> Self {
|
pub fn new(tokens: u64, creators: Vec<Creator>) -> Self {
|
||||||
let rnd = SystemRandom::new();
|
let rnd = SystemRandom::new();
|
||||||
let pkcs8 = Ed25519KeyPair::generate_pkcs8(&rnd).unwrap().to_vec();
|
let pkcs8 = Ed25519KeyPair::generate_pkcs8(&rnd).unwrap().to_vec();
|
||||||
|
println!("{:?}", pkcs8);
|
||||||
Genesis {
|
Genesis {
|
||||||
pkcs8,
|
pkcs8,
|
||||||
tokens,
|
tokens,
|
||||||
|
@ -38,6 +40,10 @@ impl Genesis {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_seed(&self) -> Sha256Hash {
|
||||||
|
hash(&self.pkcs8)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_keypair(&self) -> Ed25519KeyPair {
|
pub fn get_keypair(&self) -> Ed25519KeyPair {
|
||||||
Ed25519KeyPair::from_pkcs8(Input::from(&self.pkcs8)).unwrap()
|
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> {
|
pub fn create_transaction(&self, data: u64, to: &PublicKey) -> Event<u64> {
|
||||||
|
let last_id = self.get_seed();
|
||||||
let from = self.get_pubkey();
|
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 {
|
Event::Transaction {
|
||||||
from,
|
from,
|
||||||
to: *to,
|
to: *to,
|
||||||
data,
|
data,
|
||||||
|
last_id,
|
||||||
sig,
|
sig,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,11 +78,16 @@ impl Genesis {
|
||||||
|
|
||||||
events
|
events
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_entries(&self) -> Vec<Entry<u64>> {
|
||||||
|
create_entries(&self.get_seed(), self.create_events())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use log::verify_slice_u64;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_events() {
|
fn test_create_events() {
|
||||||
|
@ -97,4 +110,16 @@ mod tests {
|
||||||
3
|
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.
|
//! The `historian` crate provides a microservice for generating a Proof-of-History.
|
||||||
//! It manages a thread containing a Proof-of-History Logger.
|
//! 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::collections::HashSet;
|
||||||
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
|
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use log::{hash, Entry, Sha256Hash};
|
use log::{hash, Entry, Sha256Hash};
|
||||||
use logger::{verify_event_and_reserve_signature, ExitReason, Logger};
|
use logger::{ExitReason, Logger};
|
||||||
use event::{Event, Signature};
|
use event::{get_signature, Event, Signature};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
pub struct Historian<T> {
|
pub struct Historian<T> {
|
||||||
pub sender: SyncSender<Event<T>>,
|
pub sender: SyncSender<Event<T>>,
|
||||||
pub receiver: Receiver<Entry<T>>,
|
pub receiver: Receiver<Entry<T>>,
|
||||||
pub thread_hdl: JoinHandle<(Entry<T>, ExitReason)>,
|
pub thread_hdl: JoinHandle<ExitReason>,
|
||||||
pub signatures: HashSet<Signature>,
|
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
|
/// A background thread that will continue tagging received Event messages and
|
||||||
/// sending back Entry messages until either the receiver or sender channel is closed.
|
/// sending back Entry messages until either the receiver or sender channel is closed.
|
||||||
fn create_logger(
|
fn create_logger(
|
||||||
|
@ -45,12 +40,12 @@ impl<T: 'static + Serialize + Clone + Debug + Send> Historian<T> {
|
||||||
ms_per_tick: Option<u64>,
|
ms_per_tick: Option<u64>,
|
||||||
receiver: Receiver<Event<T>>,
|
receiver: Receiver<Event<T>>,
|
||||||
sender: SyncSender<Entry<T>>,
|
sender: SyncSender<Entry<T>>,
|
||||||
) -> JoinHandle<(Entry<T>, ExitReason)> {
|
) -> JoinHandle<ExitReason> {
|
||||||
thread::spawn(move || {
|
spawn(move || {
|
||||||
let mut logger = Logger::new(receiver, sender, start_hash);
|
let mut logger = Logger::new(receiver, sender, start_hash);
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
loop {
|
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;
|
return err;
|
||||||
}
|
}
|
||||||
logger.last_id = hash(&logger.last_id);
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -85,7 +90,7 @@ mod tests {
|
||||||
|
|
||||||
drop(hist.sender);
|
drop(hist.sender);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
hist.thread_hdl.join().unwrap().1,
|
hist.thread_hdl.join().unwrap(),
|
||||||
ExitReason::RecvDisconnected
|
ExitReason::RecvDisconnected
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -99,26 +104,38 @@ mod tests {
|
||||||
drop(hist.receiver);
|
drop(hist.receiver);
|
||||||
hist.sender.send(Event::Tick).unwrap();
|
hist.sender.send(Event::Tick).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
hist.thread_hdl.join().unwrap().1,
|
hist.thread_hdl.join().unwrap(),
|
||||||
ExitReason::SendDisconnected
|
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]
|
#[test]
|
||||||
fn test_ticking_historian() {
|
fn test_ticking_historian() {
|
||||||
let zero = Sha256Hash::default();
|
let zero = Sha256Hash::default();
|
||||||
let hist = Historian::new(&zero, Some(20));
|
let hist = Historian::new(&zero, Some(20));
|
||||||
sleep(Duration::from_millis(30));
|
sleep(Duration::from_millis(30));
|
||||||
hist.sender.send(Event::Tick).unwrap();
|
hist.sender.send(Event::Tick).unwrap();
|
||||||
sleep(Duration::from_millis(15));
|
|
||||||
drop(hist.sender);
|
drop(hist.sender);
|
||||||
assert_eq!(
|
|
||||||
hist.thread_hdl.join().unwrap().1,
|
|
||||||
ExitReason::RecvDisconnected
|
|
||||||
);
|
|
||||||
|
|
||||||
let entries: Vec<Entry<Sha256Hash>> = hist.receiver.iter().collect();
|
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;
|
extern crate serde;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
extern crate serde_json;
|
||||||
extern crate sha2;
|
extern crate sha2;
|
||||||
extern crate untrusted;
|
extern crate untrusted;
|
||||||
|
|
41
src/log.rs
41
src/log.rs
|
@ -217,8 +217,18 @@ mod tests {
|
||||||
|
|
||||||
// First, verify entries
|
// First, verify entries
|
||||||
let keypair = generate_keypair();
|
let keypair = generate_keypair();
|
||||||
let event0 = Event::new_claim(get_pubkey(&keypair), zero, sign_claim_data(&zero, &keypair));
|
let event0 = Event::new_claim(
|
||||||
let event1 = Event::new_claim(get_pubkey(&keypair), one, sign_claim_data(&one, &keypair));
|
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 events = vec![event0, event1];
|
||||||
let mut entries = create_entries(&zero, events);
|
let mut entries = create_entries(&zero, events);
|
||||||
assert!(verify_slice(&entries, &zero));
|
assert!(verify_slice(&entries, &zero));
|
||||||
|
@ -235,8 +245,13 @@ mod tests {
|
||||||
fn test_claim() {
|
fn test_claim() {
|
||||||
let keypair = generate_keypair();
|
let keypair = generate_keypair();
|
||||||
let data = hash(b"hello, world");
|
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 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]);
|
let entries = create_entries(&zero, vec![event0]);
|
||||||
assert!(verify_slice(&entries, &zero));
|
assert!(verify_slice(&entries, &zero));
|
||||||
}
|
}
|
||||||
|
@ -244,18 +259,20 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_wrong_data_claim_attack() {
|
fn test_wrong_data_claim_attack() {
|
||||||
let keypair = generate_keypair();
|
let keypair = generate_keypair();
|
||||||
|
let zero = Sha256Hash::default();
|
||||||
let event0 = Event::new_claim(
|
let event0 = Event::new_claim(
|
||||||
get_pubkey(&keypair),
|
get_pubkey(&keypair),
|
||||||
hash(b"goodbye cruel world"),
|
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]);
|
let entries = create_entries(&zero, vec![event0]);
|
||||||
assert!(!verify_slice(&entries, &zero));
|
assert!(!verify_slice(&entries, &zero));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_transfer() {
|
fn test_transfer() {
|
||||||
|
let zero = Sha256Hash::default();
|
||||||
let keypair0 = generate_keypair();
|
let keypair0 = generate_keypair();
|
||||||
let keypair1 = generate_keypair();
|
let keypair1 = generate_keypair();
|
||||||
let pubkey1 = get_pubkey(&keypair1);
|
let pubkey1 = get_pubkey(&keypair1);
|
||||||
|
@ -264,9 +281,9 @@ mod tests {
|
||||||
from: get_pubkey(&keypair0),
|
from: get_pubkey(&keypair0),
|
||||||
to: pubkey1,
|
to: pubkey1,
|
||||||
data,
|
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]);
|
let entries = create_entries(&zero, vec![event0]);
|
||||||
assert!(verify_slice(&entries, &zero));
|
assert!(verify_slice(&entries, &zero));
|
||||||
}
|
}
|
||||||
|
@ -277,13 +294,14 @@ mod tests {
|
||||||
let keypair1 = generate_keypair();
|
let keypair1 = generate_keypair();
|
||||||
let pubkey1 = get_pubkey(&keypair1);
|
let pubkey1 = get_pubkey(&keypair1);
|
||||||
let data = hash(b"hello, world");
|
let data = hash(b"hello, world");
|
||||||
|
let zero = Sha256Hash::default();
|
||||||
let event0 = Event::Transaction {
|
let event0 = Event::Transaction {
|
||||||
from: get_pubkey(&keypair0),
|
from: get_pubkey(&keypair0),
|
||||||
to: pubkey1,
|
to: pubkey1,
|
||||||
data: hash(b"goodbye cruel world"), // <-- attack!
|
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]);
|
let entries = create_entries(&zero, vec![event0]);
|
||||||
assert!(!verify_slice(&entries, &zero));
|
assert!(!verify_slice(&entries, &zero));
|
||||||
}
|
}
|
||||||
|
@ -295,13 +313,14 @@ mod tests {
|
||||||
let thief_keypair = generate_keypair();
|
let thief_keypair = generate_keypair();
|
||||||
let pubkey1 = get_pubkey(&keypair1);
|
let pubkey1 = get_pubkey(&keypair1);
|
||||||
let data = hash(b"hello, world");
|
let data = hash(b"hello, world");
|
||||||
|
let zero = Sha256Hash::default();
|
||||||
let event0 = Event::Transaction {
|
let event0 = Event::Transaction {
|
||||||
from: get_pubkey(&keypair0),
|
from: get_pubkey(&keypair0),
|
||||||
to: get_pubkey(&thief_keypair), // <-- attack!
|
to: get_pubkey(&thief_keypair), // <-- attack!
|
||||||
data: hash(b"goodbye cruel world"),
|
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]);
|
let entries = create_entries(&zero, vec![event0]);
|
||||||
assert!(!verify_slice(&entries, &zero));
|
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.
|
//! Event, the latest hash, and the number of hashes since the last event.
|
||||||
//! The resulting stream of entries represents ordered events in time.
|
//! The resulting stream of entries represents ordered events in time.
|
||||||
|
|
||||||
use std::collections::HashSet;
|
|
||||||
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 log::{create_entry_mut, Entry, Sha256Hash};
|
use log::{create_entry_mut, Entry, Sha256Hash};
|
||||||
use event::{get_signature, verify_event, Event, Signature};
|
use event::Event;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum ExitReason {
|
pub enum ExitReason {
|
||||||
|
@ -27,22 +27,6 @@ pub struct Logger<T> {
|
||||||
pub num_ticks: u64,
|
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> {
|
impl<T: Serialize + Clone + Debug> Logger<T> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
receiver: Receiver<Event<T>>,
|
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);
|
let entry = create_entry_mut(&mut self.last_id, &mut self.num_hashes, event);
|
||||||
if let Err(_) = self.sender.send(entry.clone()) {
|
println!("{}", serde_json::to_string(&entry).unwrap());
|
||||||
return Err((entry, ExitReason::SendDisconnected));
|
Ok(entry)
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn log_events(
|
pub fn process_events(
|
||||||
&mut self,
|
&mut self,
|
||||||
epoch: Instant,
|
epoch: Instant,
|
||||||
ms_per_tick: Option<u64>,
|
ms_per_tick: Option<u64>,
|
||||||
) -> Result<(), (Entry<T>, ExitReason)> {
|
) -> Result<(), ExitReason> {
|
||||||
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) {
|
||||||
|
@ -78,22 +60,17 @@ impl<T: Serialize + Clone + Debug> Logger<T> {
|
||||||
self.num_ticks += 1;
|
self.num_ticks += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.receiver.try_recv() {
|
match self.receiver.try_recv() {
|
||||||
Ok(event) => {
|
Ok(event) => {
|
||||||
self.log_event(event)?;
|
let entry = self.log_event(event)?;
|
||||||
|
self.sender
|
||||||
|
.send(entry)
|
||||||
|
.or(Err(ExitReason::SendDisconnected))?;
|
||||||
}
|
}
|
||||||
Err(TryRecvError::Empty) => {
|
Err(TryRecvError::Empty) => return Ok(()),
|
||||||
return Ok(());
|
Err(TryRecvError::Disconnected) => return Err(ExitReason::RecvDisconnected),
|
||||||
}
|
};
|
||||||
Err(TryRecvError::Disconnected) => {
|
|
||||||
let entry = Entry {
|
|
||||||
id: self.last_id,
|
|
||||||
num_hashes: self.num_hashes,
|
|
||||||
event: Event::Tick,
|
|
||||||
};
|
|
||||||
return Err((entry, ExitReason::RecvDisconnected));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,51 +80,18 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use log::*;
|
use log::*;
|
||||||
use event::*;
|
use event::*;
|
||||||
use genesis::*;
|
|
||||||
use std::sync::mpsc::sync_channel;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bad_event_signature() {
|
fn test_bad_event_signature() {
|
||||||
|
let zero = Sha256Hash::default();
|
||||||
let keypair = generate_keypair();
|
let keypair = generate_keypair();
|
||||||
let sig = sign_claim_data(&hash(b"hello, world"), &keypair);
|
let sig = sign_claim_data(&hash(b"hello, world"), &keypair, &zero);
|
||||||
let event0 = Event::new_claim(get_pubkey(&keypair), hash(b"goodbye cruel world"), sig);
|
let event0 = Event::new_claim(
|
||||||
let mut sigs = HashSet::new();
|
get_pubkey(&keypair),
|
||||||
assert!(!verify_event_and_reserve_signature(&mut sigs, &event0));
|
hash(b"goodbye cruel world"),
|
||||||
assert!(!sigs.contains(&sig));
|
zero,
|
||||||
}
|
sig,
|
||||||
|
);
|
||||||
#[test]
|
assert!(!verify_event(&event0));
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue