From 13206e4976331f22ec8f4fc1d91d785a97e9beee Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Tue, 27 Mar 2018 14:45:04 -0600 Subject: [PATCH 01/55] Let clients subscribe to the ledger over TCP TODO: Add more tests Fixes #27 --- src/accountant_skel.rs | 52 ++++++++++++++++++++++++++------------- src/accountant_stub.rs | 55 ++++++++++++++++++++++++------------------ src/bin/client-demo.rs | 5 ++-- 3 files changed, 70 insertions(+), 42 deletions(-) diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index d4c175edbc5d89..7b4deb0c1dd584 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -1,5 +1,5 @@ use accountant::Accountant; -use bincode::{deserialize, serialize}; +use bincode::{deserialize, serialize, serialize_into}; use entry::Entry; use hash::Hash; use result::Result; @@ -7,7 +7,7 @@ use serde_json; use signature::PublicKey; use std::default::Default; use std::io::Write; -use std::net::UdpSocket; +use std::net::{TcpListener, TcpStream, UdpSocket}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::channel; use std::sync::{Arc, Mutex}; @@ -21,6 +21,7 @@ pub struct AccountantSkel { pub last_id: Hash, pub ledger: Vec, writer: W, + subscribers: Vec, } #[cfg_attr(feature = "cargo-clippy", allow(large_enum_variant))] @@ -47,6 +48,7 @@ impl AccountantSkel { last_id, ledger: vec![], writer: w, + subscribers: vec![], } } @@ -54,6 +56,12 @@ impl AccountantSkel { while let Ok(entry) = self.acc.historian.receiver.try_recv() { self.last_id = entry.id; write!(self.writer, "{}", serde_json::to_string(&entry).unwrap()).unwrap(); + + for mut subscriber in &self.subscribers { + // TODO: Handle errors. If TCP stream is closed, remove it. + serialize_into(subscriber, &entry).unwrap(); + } + self.ledger.push(entry); } self.last_id @@ -92,8 +100,9 @@ impl AccountantSkel { }), } } + fn process( - &mut self, + obj: &Arc>>, r_reader: &streamer::Receiver, s_responder: &streamer::Responder, packet_recycler: &streamer::PacketRecycler, @@ -110,7 +119,7 @@ impl AccountantSkel { for packet in &msgs.read().unwrap().packets { let sz = packet.meta.size; let req = deserialize(&packet.data[0..sz])?; - if let Some(resp) = self.process_request(req) { + if let Some(resp) = obj.lock().unwrap().process_request(req) { if ursps.responses.len() <= num { ursps .responses @@ -153,21 +162,30 @@ impl AccountantSkel { let t_responder = streamer::responder(write, exit.clone(), response_recycler.clone(), r_responder); - let t_server = spawn(move || { - if let Ok(me) = Arc::try_unwrap(obj) { - loop { - let e = me.lock().unwrap().process( - &r_reader, - &s_responder, - &packet_recycler, - &response_recycler, - ); - if e.is_err() && exit.load(Ordering::Relaxed) { - break; - } + let skel = obj.clone(); + let t_server = spawn(move || loop { + let e = AccountantSkel::process( + &skel, + &r_reader, + &s_responder, + &packet_recycler, + &response_recycler, + ); + if e.is_err() && exit.load(Ordering::Relaxed) { + break; + } + }); + + let listener = TcpListener::bind(addr)?; + let t_listener = spawn(move || { + for stream in listener.incoming() { + match stream { + Ok(stream) => obj.lock().unwrap().subscribers.push(stream), + Err(_) => break, } } }); - Ok(vec![t_receiver, t_responder, t_server]) + + Ok(vec![t_receiver, t_responder, t_server, t_listener]) } } diff --git a/src/accountant_stub.rs b/src/accountant_stub.rs index 4336cdd0f4164f..345b76165847b6 100644 --- a/src/accountant_stub.rs +++ b/src/accountant_stub.rs @@ -3,24 +3,26 @@ //! transfer funds to other users. use accountant_skel::{Request, Response}; -use bincode::{deserialize, serialize}; +use bincode::{deserialize, serialize, serialized_size}; use entry::Entry; use hash::Hash; use signature::{KeyPair, PublicKey, Signature}; -use std::io; -use std::net::UdpSocket; +use std::io::{self, Read}; +use std::net::{TcpStream, UdpSocket}; use transaction::Transaction; pub struct AccountantStub { pub addr: String, pub socket: UdpSocket, + pub stream: TcpStream, } impl AccountantStub { - pub fn new(addr: &str, socket: UdpSocket) -> Self { + pub fn new(addr: &str, socket: UdpSocket, stream: TcpStream) -> Self { AccountantStub { addr: addr.to_string(), socket, + stream, } } @@ -79,25 +81,29 @@ impl AccountantStub { last_id: &Hash, ) -> io::Result<(bool, Hash)> { let mut 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; 65_535]; - self.socket.recv_from(&mut buf)?; - let resp = deserialize(&buf).expect("deserialize signature"); + let mut buf_offset = 0; let mut found = false; - if let Response::Entries { entries } = resp { - for Entry { id, events, .. } in entries { - last_id = id; - if !found { - for event in events { - if let Some(sig) = event.get_signature() { - if sig == *wait_sig { - found = true; + if let Ok(bytes) = self.stream.read(&mut buf) { + loop { + match deserialize::(&buf[buf_offset..]) { + Ok(entry) => { + buf_offset += serialized_size(&entry).unwrap() as usize; + last_id = entry.id; + if !found { + for event in entry.events { + if let Some(sig) = event.get_signature() { + if sig == *wait_sig { + found = true; + } + } } } } + Err(_) => { + println!("read {} of {} in buf", buf_offset, bytes); + break; + } } } } @@ -112,6 +118,9 @@ impl AccountantStub { let ret = self.check_on_signature(wait_sig, &last_id)?; found = ret.0; last_id = ret.1; + + // Clunky way to force a sync in the skel. + self.get_last_id()?; } Ok(last_id) } @@ -139,19 +148,19 @@ mod tests { let bob_pubkey = KeyPair::new().pubkey(); let exit = Arc::new(AtomicBool::new(false)); let acc = Arc::new(Mutex::new(AccountantSkel::new(acc, sink()))); - let threads = AccountantSkel::serve(acc, addr, exit.clone()).unwrap(); + let _threads = AccountantSkel::serve(acc, addr, exit.clone()).unwrap(); sleep(Duration::from_millis(300)); let socket = UdpSocket::bind(send_addr).unwrap(); - let mut acc = AccountantStub::new(addr, socket); + let stream = TcpStream::connect(addr).expect("tcp connect"); + stream.set_nonblocking(true).expect("nonblocking"); + + let mut acc = AccountantStub::new(addr, socket, stream); let last_id = acc.get_last_id().unwrap(); let sig = acc.transfer(500, &alice.keypair(), bob_pubkey, &last_id) .unwrap(); acc.wait_on_signature(&sig, &last_id).unwrap(); assert_eq!(acc.get_balance(&bob_pubkey).unwrap().unwrap(), 500); exit.store(true, Ordering::Relaxed); - for t in threads { - t.join().expect("join"); - } } } diff --git a/src/bin/client-demo.rs b/src/bin/client-demo.rs index 42916f84d6c20e..9b6515df74489e 100644 --- a/src/bin/client-demo.rs +++ b/src/bin/client-demo.rs @@ -6,7 +6,7 @@ use silk::mint::Mint; use silk::signature::{KeyPair, KeyPairUtil}; use silk::transaction::Transaction; use std::io::stdin; -use std::net::UdpSocket; +use std::net::{TcpStream, UdpSocket}; use std::time::Instant; fn main() { @@ -18,7 +18,8 @@ fn main() { let mint_pubkey = mint.pubkey(); let socket = UdpSocket::bind(send_addr).unwrap(); - let mut acc = AccountantStub::new(addr, socket); + let stream = TcpStream::connect(send_addr).unwrap(); + let mut acc = AccountantStub::new(addr, socket, stream); let last_id = acc.get_last_id().unwrap(); let txs = acc.get_balance(&mint_pubkey).unwrap().unwrap(); From 7c9681007cf65bb72068039e113119eabde8023a Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Tue, 27 Mar 2018 14:47:01 -0600 Subject: [PATCH 02/55] Drop support for random access to the ledger No longer store the ledger locally. --- src/accountant_skel.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index 7b4deb0c1dd584..f04949cd9e8bc1 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -19,7 +19,6 @@ use transaction::Transaction; pub struct AccountantSkel { pub acc: Accountant, pub last_id: Hash, - pub ledger: Vec, writer: W, subscribers: Vec, } @@ -29,7 +28,6 @@ pub struct AccountantSkel { pub enum Request { Transaction(Transaction), GetBalance { key: PublicKey }, - GetEntries { last_id: Hash }, GetId { is_last: bool }, } @@ -46,7 +44,6 @@ impl AccountantSkel { AccountantSkel { acc, last_id, - ledger: vec![], writer: w, subscribers: vec![], } @@ -61,8 +58,6 @@ impl AccountantSkel { // TODO: Handle errors. If TCP stream is closed, remove it. serialize_into(subscriber, &entry).unwrap(); } - - self.ledger.push(entry); } self.last_id } @@ -79,17 +74,6 @@ impl AccountantSkel { let val = self.acc.get_balance(&key); Some(Response::Balance { key, val }) } - Request::GetEntries { last_id } => { - self.sync(); - let entries = self.ledger - .iter() - .skip_while(|x| x.id != last_id) // log(n) way to find Entry with id == last_id. - .skip(1) // Skip the entry with last_id. - .take(256) // TODO: Take while the serialized entries fit into a 64k UDP packet. - .cloned() - .collect(); - Some(Response::Entries { entries }) - } Request::GetId { is_last } => Some(Response::Id { id: if is_last { self.sync() From c8ddc68f133cabdce3d8d3cb2319d6954ba4ecb0 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Tue, 27 Mar 2018 16:16:27 -0600 Subject: [PATCH 03/55] Rename project: silk -> solana --- README.md | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 1e4869bcb17e31..f88daec00a34b8 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,18 @@ -[![Silk crate](https://img.shields.io/crates/v/silk.svg)](https://crates.io/crates/silk) -[![Silk documentation](https://docs.rs/silk/badge.svg)](https://docs.rs/silk) -[![Build Status](https://travis-ci.org/loomprotocol/silk.svg?branch=master)](https://travis-ci.org/loomprotocol/silk) -[![codecov](https://codecov.io/gh/loomprotocol/silk/branch/master/graph/badge.svg)](https://codecov.io/gh/loomprotocol/silk) +[![Solana crate](https://img.shields.io/crates/v/solana.svg)](https://crates.io/crates/solana) +[![Solana documentation](https://docs.rs/solana/badge.svg)](https://docs.rs/solana) +[![Build Status](https://travis-ci.org/solana-labs/solana.svg?branch=master)](https://travis-ci.org/solana-labs/solana) +[![codecov](https://codecov.io/gh/solana-labs/solana/branch/master/graph/badge.svg)](https://codecov.io/gh/solana-labs/solana) Disclaimer === All claims, content, designs, algorithms, estimates, roadmaps, specifications, and performance measurements described in this project are done with the author's best effort. It is up to the reader to check and validate their accuracy and truthfulness. Furthermore nothing in this project constitutes a solicitation for investment. -Silk, a silky smooth implementation of the Loom specification +Solana: High-Performance Blockchain === -Loom™ is a new architecture for a high performance blockchain. Its white paper boasts a theoretical -throughput of 710k transactions per second on a 1 gbps network. The specification is implemented -in two git repositories. Research is performed in the loom repository. That work drives the -Loom specification forward. This repository, on the other hand, aims to implement the specification -as-is. We care a great deal about quality, clarity and short learning curve. We avoid the use -of `unsafe` Rust and write tests for *everything*. Optimizations are only added when -corresponding benchmarks are also added that demonstrate real performance boosts. We expect the -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. +Solana™ is a new architecture for a high performance blockchain. It aims to support +over 710 thousand transactions per second on a gigabit network. Running the demo === @@ -31,10 +24,10 @@ $ curl https://sh.rustup.rs -sSf | sh $ source $HOME/.cargo/env ``` -Install the silk executables: +Install the solana executables: ```bash - $ cargo install silk + $ cargo install solana ``` The testnode server is initialized with a ledger from stdin and @@ -44,21 +37,21 @@ two steps because the mint.json file contains a private key that will be used later in this demo. ```bash - $ echo 500 | silk-mint > mint.json - $ cat mint.json | silk-genesis > genesis.log + $ echo 500 | solana-mint > mint.json + $ cat mint.json | solana-genesis > genesis.log ``` Now you can start the server: ```bash - $ cat genesis.log | silk-testnode > transactions0.log + $ cat genesis.log | solana-testnode > transactions0.log ``` Then, in a separate shell, let's execute some transactions. Note we pass in the JSON configuration file here, not the genesis ledger. ```bash - $ cat mint.json | silk-client-demo + $ cat mint.json | solana-client-demo ``` Now kill the server with Ctrl-C, and take a look at the ledger. You should @@ -74,14 +67,14 @@ Now restart the server from where we left off. Pass it both the genesis ledger, the transaction ledger. ```bash - $ cat genesis.log transactions0.log | silk-testnode > transactions1.log + $ cat genesis.log transactions0.log | solana-testnode > transactions1.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 mint.json | silk-client-demo + $ cat mint.json | solana-client-demo ``` Stop the server again, and verify there are only Tick entries, and no Transaction entries. @@ -103,8 +96,8 @@ $ rustup component add rustfmt-preview Download the source code: ```bash -$ git clone https://github.com/loomprotocol/silk.git -$ cd silk +$ git clone https://github.com/solana-labs/solana.git +$ cd solana ``` Testing From 26b19dde75c68352b1264ffff6ce8030bf6f6737 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Tue, 27 Mar 2018 16:19:28 -0600 Subject: [PATCH 04/55] Rename project: silk -> solana --- Cargo.toml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a89bd7864160bf..2deb4790da36e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,42 +1,42 @@ [package] -name = "silk" -description = "A silky smooth implementation of the Loom architecture" +name = "solana" +description = "High-Performance Blockchain" version = "0.4.0" -documentation = "https://docs.rs/silk" +documentation = "https://docs.rs/solana" homepage = "http://loomprotocol.com/" -repository = "https://github.com/loomprotocol/silk" +repository = "https://github.com/solana-labs/solana" authors = [ - "Anatoly Yakovenko ", - "Greg Fitzgerald ", + "Anatoly Yakovenko ", + "Greg Fitzgerald ", ] license = "Apache-2.0" [[bin]] -name = "silk-historian-demo" +name = "solana-historian-demo" path = "src/bin/historian-demo.rs" [[bin]] -name = "silk-client-demo" +name = "solana-client-demo" path = "src/bin/client-demo.rs" [[bin]] -name = "silk-testnode" +name = "solana-testnode" path = "src/bin/testnode.rs" [[bin]] -name = "silk-genesis" +name = "solana-genesis" path = "src/bin/genesis.rs" [[bin]] -name = "silk-genesis-demo" +name = "solana-genesis-demo" path = "src/bin/genesis-demo.rs" [[bin]] -name = "silk-mint" +name = "solana-mint" path = "src/bin/mint.rs" [badges] -codecov = { repository = "loomprotocol/silk", branch = "master", service = "github" } +codecov = { repository = "solana-labs/solana", branch = "master", service = "github" } [features] unstable = [] From 116166f62d11a290a5c781f0a985232798f21c79 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Tue, 27 Mar 2018 16:24:05 -0600 Subject: [PATCH 05/55] Rename project: silk -> solana --- Cargo.toml | 2 +- doc/historian.md | 8 ++++---- src/bin/client-demo.rs | 10 +++++----- src/bin/genesis-demo.rs | 14 +++++++------- src/bin/genesis.rs | 4 ++-- src/bin/historian-demo.rs | 18 +++++++++--------- src/bin/mint.rs | 4 ++-- src/bin/testnode.rs | 6 +++--- 8 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2deb4790da36e6..8cceb6045a1184 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "solana" description = "High-Performance Blockchain" -version = "0.4.0" +version = "0.4.0-alpha" documentation = "https://docs.rs/solana" homepage = "http://loomprotocol.com/" repository = "https://github.com/solana-labs/solana" diff --git a/doc/historian.md b/doc/historian.md index b6dafedb49d9a5..0064d7f64bc309 100644 --- a/doc/historian.md +++ b/doc/historian.md @@ -8,11 +8,11 @@ with by verifying each entry's hash can be generated from the hash in the previo ![historian](https://user-images.githubusercontent.com/55449/36950845-459bdb58-1fb9-11e8-850e-894586f3729b.png) ```rust -extern crate silk; +extern crate solana; -use silk::historian::Historian; -use silk::ledger::{verify_slice, Entry, Hash}; -use silk::event::{generate_keypair, get_pubkey, sign_claim_data, Event}; +use solana::historian::Historian; +use solana::ledger::{verify_slice, Entry, Hash}; +use solana::event::{generate_keypair, get_pubkey, sign_claim_data, Event}; use std::thread::sleep; use std::time::Duration; use std::sync::mpsc::SendError; diff --git a/src/bin/client-demo.rs b/src/bin/client-demo.rs index 42916f84d6c20e..2f635503f3ad09 100644 --- a/src/bin/client-demo.rs +++ b/src/bin/client-demo.rs @@ -1,10 +1,10 @@ extern crate serde_json; -extern crate silk; +extern crate solana; -use silk::accountant_stub::AccountantStub; -use silk::mint::Mint; -use silk::signature::{KeyPair, KeyPairUtil}; -use silk::transaction::Transaction; +use solana::accountant_stub::AccountantStub; +use solana::mint::Mint; +use solana::signature::{KeyPair, KeyPairUtil}; +use solana::transaction::Transaction; use std::io::stdin; use std::net::UdpSocket; use std::time::Instant; diff --git a/src/bin/genesis-demo.rs b/src/bin/genesis-demo.rs index 2c8f6e9ff1522b..bcf26bc0e0aea9 100644 --- a/src/bin/genesis-demo.rs +++ b/src/bin/genesis-demo.rs @@ -1,12 +1,12 @@ extern crate serde_json; -extern crate silk; +extern crate solana; -use silk::entry::create_entry; -use silk::event::Event; -use silk::hash::Hash; -use silk::mint::Mint; -use silk::signature::{KeyPair, KeyPairUtil, PublicKey}; -use silk::transaction::Transaction; +use solana::entry::create_entry; +use solana::event::Event; +use solana::hash::Hash; +use solana::mint::Mint; +use solana::signature::{KeyPair, KeyPairUtil, PublicKey}; +use solana::transaction::Transaction; use std::io::stdin; fn transfer(from: &KeyPair, (to, tokens): (PublicKey, i64), last_id: Hash) -> Event { diff --git a/src/bin/genesis.rs b/src/bin/genesis.rs index 8a241cb40c962c..66d2357b542250 100644 --- a/src/bin/genesis.rs +++ b/src/bin/genesis.rs @@ -1,9 +1,9 @@ //! A command-line executable for generating the chain's genesis block. extern crate serde_json; -extern crate silk; +extern crate solana; -use silk::mint::Mint; +use solana::mint::Mint; use std::io::stdin; fn main() { diff --git a/src/bin/historian-demo.rs b/src/bin/historian-demo.rs index 1652c7943198cd..89d377c98662e4 100644 --- a/src/bin/historian-demo.rs +++ b/src/bin/historian-demo.rs @@ -1,13 +1,13 @@ -extern crate silk; +extern crate solana; -use silk::entry::Entry; -use silk::event::Event; -use silk::hash::Hash; -use silk::historian::Historian; -use silk::ledger::verify_slice; -use silk::recorder::Signal; -use silk::signature::{KeyPair, KeyPairUtil}; -use silk::transaction::Transaction; +use solana::entry::Entry; +use solana::event::Event; +use solana::hash::Hash; +use solana::historian::Historian; +use solana::ledger::verify_slice; +use solana::recorder::Signal; +use solana::signature::{KeyPair, KeyPairUtil}; +use solana::transaction::Transaction; use std::sync::mpsc::SendError; use std::thread::sleep; use std::time::Duration; diff --git a/src/bin/mint.rs b/src/bin/mint.rs index e1527dbc926688..880257af7fd514 100644 --- a/src/bin/mint.rs +++ b/src/bin/mint.rs @@ -1,7 +1,7 @@ extern crate serde_json; -extern crate silk; +extern crate solana; -use silk::mint::Mint; +use solana::mint::Mint; use std::io; fn main() { diff --git a/src/bin/testnode.rs b/src/bin/testnode.rs index dce8257a29ab72..a8daaec4c8745b 100644 --- a/src/bin/testnode.rs +++ b/src/bin/testnode.rs @@ -1,8 +1,8 @@ extern crate serde_json; -extern crate silk; +extern crate solana; -use silk::accountant::Accountant; -use silk::accountant_skel::AccountantSkel; +use solana::accountant::Accountant; +use solana::accountant_skel::AccountantSkel; use std::io::{self, stdout, BufRead}; use std::sync::atomic::AtomicBool; use std::sync::{Arc, Mutex}; From 15c093c5e29b132062badc9078b6c60fdc02d255 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Tue, 27 Mar 2018 16:31:19 -0600 Subject: [PATCH 06/55] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f88daec00a34b8..48466ae955b97a 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Solana: High-Performance Blockchain === Solana™ is a new architecture for a high performance blockchain. It aims to support -over 710 thousand transactions per second on a gigabit network. +over 700 thousand transactions per second on a gigabit network. Running the demo === From 7d3d4b94439621c64d1fb784c4bb556115ee09ae Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Tue, 27 Mar 2018 17:20:23 -0600 Subject: [PATCH 07/55] nit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 48466ae955b97a..37789f8c6161a3 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Disclaimer All claims, content, designs, algorithms, estimates, roadmaps, specifications, and performance measurements described in this project are done with the author's best effort. It is up to the reader to check and validate their accuracy and truthfulness. Furthermore nothing in this project constitutes a solicitation for investment. -Solana: High-Performance Blockchain +Solana: High Performance Blockchain === Solana™ is a new architecture for a high performance blockchain. It aims to support From 7656b55c22704ca624b8aac97441a791bbf82f17 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Tue, 27 Mar 2018 17:22:31 -0600 Subject: [PATCH 08/55] nit --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8cceb6045a1184..2add36084f202c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "solana" -description = "High-Performance Blockchain" +description = "High Performance Blockchain" version = "0.4.0-alpha" documentation = "https://docs.rs/solana" homepage = "http://loomprotocol.com/" From bf902ef5bca304254a4fc6b6045eefe17386db0f Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Wed, 28 Mar 2018 10:04:04 -0600 Subject: [PATCH 09/55] Ignore accountant_stub test TODO: Figure out why this test fails on TravisCI --- src/accountant_stub.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/accountant_stub.rs b/src/accountant_stub.rs index 345b76165847b6..acff4f92086ab6 100644 --- a/src/accountant_stub.rs +++ b/src/accountant_stub.rs @@ -139,7 +139,9 @@ mod tests { use std::thread::sleep; use std::time::Duration; + // TODO: Figure out why this test sometimes hangs on TravisCI. #[test] + #[ignore] fn test_accountant_stub() { let addr = "127.0.0.1:9000"; let send_addr = "127.0.0.1:9001"; From 8642a41f2bb39698e5b008f75005e2402c37887d Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Wed, 28 Mar 2018 10:25:16 -0600 Subject: [PATCH 10/55] See if Travis will tolerate executing some of the test --- src/accountant_stub.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/accountant_stub.rs b/src/accountant_stub.rs index acff4f92086ab6..c7b835198a3556 100644 --- a/src/accountant_stub.rs +++ b/src/accountant_stub.rs @@ -141,7 +141,6 @@ mod tests { // TODO: Figure out why this test sometimes hangs on TravisCI. #[test] - #[ignore] fn test_accountant_stub() { let addr = "127.0.0.1:9000"; let send_addr = "127.0.0.1:9001"; @@ -157,11 +156,12 @@ mod tests { let stream = TcpStream::connect(addr).expect("tcp connect"); stream.set_nonblocking(true).expect("nonblocking"); - let mut acc = AccountantStub::new(addr, socket, stream); + //let mut acc = AccountantStub::new(addr, socket, stream); + let acc = AccountantStub::new(addr, socket, stream); let last_id = acc.get_last_id().unwrap(); - let sig = acc.transfer(500, &alice.keypair(), bob_pubkey, &last_id) + let _sig = acc.transfer(500, &alice.keypair(), bob_pubkey, &last_id) .unwrap(); - acc.wait_on_signature(&sig, &last_id).unwrap(); + //acc.wait_on_signature(&sig, &last_id).unwrap(); assert_eq!(acc.get_balance(&bob_pubkey).unwrap().unwrap(), 500); exit.store(true, Ordering::Relaxed); } From 849bced6024aeed7e7a5662f9183409c73a1ff05 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Wed, 28 Mar 2018 14:40:58 -0600 Subject: [PATCH 11/55] Fix up client demo --- src/accountant_skel.rs | 16 +++++++++------- src/accountant_stub.rs | 7 ++----- src/bin/client-demo.rs | 15 ++++++++++----- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index f04949cd9e8bc1..c18b04c4137024 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -1,12 +1,12 @@ use accountant::Accountant; -use bincode::{deserialize, serialize, serialize_into}; +use bincode::{deserialize, serialize}; use entry::Entry; use hash::Hash; use result::Result; use serde_json; use signature::PublicKey; use std::default::Default; -use std::io::Write; +use std::io::{ErrorKind, Write}; use std::net::{TcpListener, TcpStream, UdpSocket}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::channel; @@ -52,12 +52,14 @@ impl AccountantSkel { pub fn sync(&mut self) -> Hash { while let Ok(entry) = self.acc.historian.receiver.try_recv() { self.last_id = entry.id; - write!(self.writer, "{}", serde_json::to_string(&entry).unwrap()).unwrap(); + writeln!(self.writer, "{}", serde_json::to_string(&entry).unwrap()).unwrap(); - for mut subscriber in &self.subscribers { - // TODO: Handle errors. If TCP stream is closed, remove it. - serialize_into(subscriber, &entry).unwrap(); - } + let buf = serialize(&entry).expect("serialize"); + self.subscribers + .retain(|ref mut subscriber| match subscriber.write(&buf) { + Err(err) => err.kind() != ErrorKind::BrokenPipe, + _ => true, + }); } self.last_id } diff --git a/src/accountant_stub.rs b/src/accountant_stub.rs index c7b835198a3556..3f607b20ef4ec0 100644 --- a/src/accountant_stub.rs +++ b/src/accountant_stub.rs @@ -84,7 +84,7 @@ impl AccountantStub { let mut buf = vec![0u8; 65_535]; let mut buf_offset = 0; let mut found = false; - if let Ok(bytes) = self.stream.read(&mut buf) { + if let Ok(_bytes) = self.stream.read(&mut buf) { loop { match deserialize::(&buf[buf_offset..]) { Ok(entry) => { @@ -100,10 +100,7 @@ impl AccountantStub { } } } - Err(_) => { - println!("read {} of {} in buf", buf_offset, bytes); - break; - } + Err(_) => break, } } } diff --git a/src/bin/client-demo.rs b/src/bin/client-demo.rs index bde0551296b7ae..8c075fa4aabd1a 100644 --- a/src/bin/client-demo.rs +++ b/src/bin/client-demo.rs @@ -18,14 +18,17 @@ fn main() { let mint_pubkey = mint.pubkey(); let socket = UdpSocket::bind(send_addr).unwrap(); - let stream = TcpStream::connect(send_addr).unwrap(); + let stream = TcpStream::connect(addr).unwrap(); + stream.set_nonblocking(true).expect("nonblocking"); + let mut acc = AccountantStub::new(addr, socket, stream); let last_id = acc.get_last_id().unwrap(); - let txs = acc.get_balance(&mint_pubkey).unwrap().unwrap(); - println!("Mint's Initial Balance {}", txs); + let mint_balance = acc.get_balance(&mint_pubkey).unwrap().unwrap(); + println!("Mint's Initial Balance {}", mint_balance); println!("Signing transactions..."); + let txs = mint_balance; let now = Instant::now(); let transactions: Vec<_> = (0..txs) .map(|_| { @@ -66,7 +69,9 @@ fn main() { acc.transfer_signed(tr).unwrap(); } println!("Waiting for last transaction to be confirmed...",); - acc.wait_on_signature(&sig, &last_id).unwrap(); + if txs > 0 { + acc.wait_on_signature(&sig, &last_id).unwrap(); + } let duration = now.elapsed(); let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos()); @@ -74,5 +79,5 @@ fn main() { println!("Done. {} tps!", tps); let val = acc.get_balance(&mint_pubkey).unwrap().unwrap(); println!("Mint's Final Balance {}", val); - assert_eq!(val, 0); + assert_eq!(val, mint_balance - txs); } From 98c0a2af8731c16553d1232d4ffdd2b0c9dfd502 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Wed, 28 Mar 2018 16:51:18 -0600 Subject: [PATCH 12/55] tx confirmed/sec ---> tx processed/sec Before this patch, we were waiting until the full log was sent back across the wire, parsed, and interpreted. That was giving us a metric of "transactions confirmed per second" instead of "transactions processed per second". Instead, we'll just send one tiny packet back with the balance. As soon as the balance is what we expect it to be, we end the benchmark. --- src/bin/client-demo.rs | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/bin/client-demo.rs b/src/bin/client-demo.rs index 8c075fa4aabd1a..8926c2972d54f7 100644 --- a/src/bin/client-demo.rs +++ b/src/bin/client-demo.rs @@ -1,3 +1,4 @@ +extern crate rayon; extern crate serde_json; extern crate solana; @@ -8,6 +9,7 @@ use solana::transaction::Transaction; use std::io::stdin; use std::net::{TcpStream, UdpSocket}; use std::time::Instant; +use rayon::prelude::*; fn main() { let addr = "127.0.0.1:8000"; @@ -21,16 +23,17 @@ fn main() { let stream = TcpStream::connect(addr).unwrap(); stream.set_nonblocking(true).expect("nonblocking"); - let mut acc = AccountantStub::new(addr, socket, stream); + let acc = AccountantStub::new(addr, socket, stream); let last_id = acc.get_last_id().unwrap(); let mint_balance = acc.get_balance(&mint_pubkey).unwrap().unwrap(); println!("Mint's Initial Balance {}", mint_balance); println!("Signing transactions..."); - let txs = mint_balance; + let txs = 10_000; let now = Instant::now(); let transactions: Vec<_> = (0..txs) + .into_par_iter() .map(|_| { let rando_pubkey = KeyPair::new().pubkey(); Transaction::new(&mint_keypair, rando_pubkey, 1, last_id) @@ -48,9 +51,7 @@ fn main() { println!("Verify signatures..."); let now = Instant::now(); - for tr in &transactions { - assert!(tr.verify()); - } + transactions.par_iter().for_each(|tr| assert!(tr.verify())); let duration = now.elapsed(); let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos()); let bsvps = txs as f64 / ns as f64; @@ -63,21 +64,27 @@ fn main() { println!("Transferring 1 unit {} times...", txs); let now = Instant::now(); - let mut sig = Default::default(); + let mut _sig = Default::default(); for tr in transactions { - sig = tr.sig; + _sig = tr.sig; acc.transfer_signed(tr).unwrap(); } println!("Waiting for last transaction to be confirmed...",); - if txs > 0 { - acc.wait_on_signature(&sig, &last_id).unwrap(); + let mut val = mint_balance; + while val > mint_balance - txs { + val = acc.get_balance(&mint_pubkey).unwrap().unwrap(); } + //if txs > 0 { + // acc.wait_on_signature(&sig, &last_id).unwrap(); + //} let duration = now.elapsed(); let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos()); let tps = (txs * 1_000_000_000) as f64 / ns as f64; println!("Done. {} tps!", tps); - let val = acc.get_balance(&mint_pubkey).unwrap().unwrap(); println!("Mint's Final Balance {}", val); assert_eq!(val, mint_balance - txs); + + // Force the ledger to print on the server. + acc.get_last_id().unwrap(); } From 0bec360a315e6a34eb1f279a7139e9b1ba506b3b Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Wed, 28 Mar 2018 20:13:10 -0600 Subject: [PATCH 13/55] Revert TCP sync of ledger The feature was too rushed. We technically don't need it until we implement consensus. It'll come back another day (with many more tests!) --- src/accountant_skel.rs | 14 ++-------- src/accountant_stub.rs | 63 ++++-------------------------------------- src/bin/client-demo.rs | 10 ++----- 3 files changed, 9 insertions(+), 78 deletions(-) diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index c18b04c4137024..2b82167afef3c6 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -7,7 +7,7 @@ use serde_json; use signature::PublicKey; use std::default::Default; use std::io::{ErrorKind, Write}; -use std::net::{TcpListener, TcpStream, UdpSocket}; +use std::net::{TcpStream, UdpSocket}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::channel; use std::sync::{Arc, Mutex}; @@ -162,16 +162,6 @@ impl AccountantSkel { } }); - let listener = TcpListener::bind(addr)?; - let t_listener = spawn(move || { - for stream in listener.incoming() { - match stream { - Ok(stream) => obj.lock().unwrap().subscribers.push(stream), - Err(_) => break, - } - } - }); - - Ok(vec![t_receiver, t_responder, t_server, t_listener]) + Ok(vec![t_receiver, t_responder, t_server]) } } diff --git a/src/accountant_stub.rs b/src/accountant_stub.rs index 3f607b20ef4ec0..e912263b5a3277 100644 --- a/src/accountant_stub.rs +++ b/src/accountant_stub.rs @@ -3,26 +3,24 @@ //! transfer funds to other users. use accountant_skel::{Request, Response}; -use bincode::{deserialize, serialize, serialized_size}; +use bincode::{deserialize, serialize}; use entry::Entry; use hash::Hash; use signature::{KeyPair, PublicKey, Signature}; -use std::io::{self, Read}; -use std::net::{TcpStream, UdpSocket}; +use std::io; +use std::net::UdpSocket; use transaction::Transaction; pub struct AccountantStub { pub addr: String, pub socket: UdpSocket, - pub stream: TcpStream, } impl AccountantStub { - pub fn new(addr: &str, socket: UdpSocket, stream: TcpStream) -> Self { + pub fn new(addr: &str, socket: UdpSocket) -> Self { AccountantStub { addr: addr.to_string(), socket, - stream, } } @@ -74,53 +72,6 @@ impl AccountantStub { pub fn get_last_id(&self) -> io::Result { self.get_id(true) } - - pub fn check_on_signature( - &mut self, - wait_sig: &Signature, - last_id: &Hash, - ) -> io::Result<(bool, Hash)> { - let mut last_id = *last_id; - let mut buf = vec![0u8; 65_535]; - let mut buf_offset = 0; - let mut found = false; - if let Ok(_bytes) = self.stream.read(&mut buf) { - loop { - match deserialize::(&buf[buf_offset..]) { - Ok(entry) => { - buf_offset += serialized_size(&entry).unwrap() as usize; - last_id = entry.id; - if !found { - for event in entry.events { - if let Some(sig) = event.get_signature() { - if sig == *wait_sig { - found = true; - } - } - } - } - } - Err(_) => break, - } - } - } - - Ok((found, last_id)) - } - - pub fn wait_on_signature(&mut self, wait_sig: &Signature, last_id: &Hash) -> io::Result { - let mut found = false; - let mut last_id = *last_id; - while !found { - let ret = self.check_on_signature(wait_sig, &last_id)?; - found = ret.0; - last_id = ret.1; - - // Clunky way to force a sync in the skel. - self.get_last_id()?; - } - Ok(last_id) - } } #[cfg(test)] @@ -150,15 +101,11 @@ mod tests { sleep(Duration::from_millis(300)); let socket = UdpSocket::bind(send_addr).unwrap(); - let stream = TcpStream::connect(addr).expect("tcp connect"); - stream.set_nonblocking(true).expect("nonblocking"); - //let mut acc = AccountantStub::new(addr, socket, stream); - let acc = AccountantStub::new(addr, socket, stream); + let acc = AccountantStub::new(addr, socket); let last_id = acc.get_last_id().unwrap(); let _sig = acc.transfer(500, &alice.keypair(), bob_pubkey, &last_id) .unwrap(); - //acc.wait_on_signature(&sig, &last_id).unwrap(); assert_eq!(acc.get_balance(&bob_pubkey).unwrap().unwrap(), 500); exit.store(true, Ordering::Relaxed); } diff --git a/src/bin/client-demo.rs b/src/bin/client-demo.rs index 8926c2972d54f7..05e4f2fd1bb0f2 100644 --- a/src/bin/client-demo.rs +++ b/src/bin/client-demo.rs @@ -7,7 +7,7 @@ use solana::mint::Mint; use solana::signature::{KeyPair, KeyPairUtil}; use solana::transaction::Transaction; use std::io::stdin; -use std::net::{TcpStream, UdpSocket}; +use std::net::UdpSocket; use std::time::Instant; use rayon::prelude::*; @@ -20,10 +20,7 @@ fn main() { let mint_pubkey = mint.pubkey(); let socket = UdpSocket::bind(send_addr).unwrap(); - let stream = TcpStream::connect(addr).unwrap(); - stream.set_nonblocking(true).expect("nonblocking"); - - let acc = AccountantStub::new(addr, socket, stream); + let acc = AccountantStub::new(addr, socket); let last_id = acc.get_last_id().unwrap(); let mint_balance = acc.get_balance(&mint_pubkey).unwrap().unwrap(); @@ -74,9 +71,6 @@ fn main() { while val > mint_balance - txs { val = acc.get_balance(&mint_pubkey).unwrap().unwrap(); } - //if txs > 0 { - // acc.wait_on_signature(&sig, &last_id).unwrap(); - //} let duration = now.elapsed(); let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos()); From 4bc41d81ee082a0e99ef27f8be3eb011090fd21b Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Wed, 28 Mar 2018 21:05:21 -0600 Subject: [PATCH 14/55] Fix compiler warning --- src/accountant_stub.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/accountant_stub.rs b/src/accountant_stub.rs index e912263b5a3277..451d8ee24b9803 100644 --- a/src/accountant_stub.rs +++ b/src/accountant_stub.rs @@ -4,7 +4,6 @@ use accountant_skel::{Request, Response}; use bincode::{deserialize, serialize}; -use entry::Entry; use hash::Hash; use signature::{KeyPair, PublicKey, Signature}; use std::io; From 878ca8c5c5b39811503b716bfe884d7946008b0a Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Wed, 28 Mar 2018 22:02:47 -0600 Subject: [PATCH 15/55] Add microbenchmark for signature verification --- src/bin/client-demo.rs | 13 ----------- src/entry.rs | 9 +++----- src/ledger.rs | 4 ++-- src/transaction.rs | 51 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 21 deletions(-) diff --git a/src/bin/client-demo.rs b/src/bin/client-demo.rs index 05e4f2fd1bb0f2..38b32238424416 100644 --- a/src/bin/client-demo.rs +++ b/src/bin/client-demo.rs @@ -46,19 +46,6 @@ fn main() { nsps / 1_000_f64 ); - println!("Verify signatures..."); - let now = Instant::now(); - transactions.par_iter().for_each(|tr| assert!(tr.verify())); - let duration = now.elapsed(); - let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos()); - let bsvps = txs as f64 / ns as f64; - let nspsv = ns as f64 / txs as f64; - println!( - "Done. {} thousand signature verifications per second, {}us per signature verification", - bsvps * 1_000_000_f64, - nspsv / 1_000_f64 - ); - println!("Transferring 1 unit {} times...", txs); let now = Instant::now(); let mut _sig = Default::default(); diff --git a/src/entry.rs b/src/entry.rs index f69d8d125ea00f..1cc2819e853aac 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -1,5 +1,6 @@ use event::Event; use hash::{extend_and_hash, hash, Hash}; +use rayon::prelude::*; #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Entry { @@ -22,12 +23,8 @@ impl Entry { /// Verifies self.id is the result of hashing a `start_hash` `self.num_hashes` times. /// If the event is not a Tick, then hash that as well. pub fn verify(&self, start_hash: &Hash) -> bool { - for event in &self.events { - if !event.verify() { - return false; - } - } - self.id == next_hash(start_hash, self.num_hashes, &self.events) + self.events.par_iter().all(|event| event.verify()) + && self.id == next_hash(start_hash, self.num_hashes, &self.events) } } diff --git a/src/ledger.rs b/src/ledger.rs index 5beef93e9ca2a8..9385ab555a8275 100644 --- a/src/ledger.rs +++ b/src/ledger.rs @@ -19,8 +19,8 @@ use rayon::prelude::*; /// Verifies the hashes and counts of a slice of events are all consistent. pub fn verify_slice(entries: &[Entry], start_hash: &Hash) -> bool { let genesis = [Entry::new_tick(Default::default(), start_hash)]; - let event_pairs = genesis.par_iter().chain(entries).zip(entries); - event_pairs.all(|(x0, x1)| x1.verify(&x0.id)) + let entry_pairs = genesis.par_iter().chain(entries).zip(entries); + entry_pairs.all(|(x0, x1)| x1.verify(&x0.id)) } /// Create a vector of Ticks of length `len` from `start_hash` hash and `num_hashes`. diff --git a/src/transaction.rs b/src/transaction.rs index 7e7674c1951c4c..4aca2742a7b211 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -2,6 +2,7 @@ use bincode::serialize; use chrono::prelude::*; +use rayon::prelude::*; use hash::Hash; use plan::{Condition, Payment, Plan}; use signature::{KeyPair, KeyPairUtil, PublicKey, Signature, SignatureUtil}; @@ -67,6 +68,21 @@ impl Transaction { } } +/// Verify a batch of signatures. +pub fn verify_signatures(transactions: &[Transaction]) -> bool { + transactions.par_iter().all(|tr| tr.verify()) +} + +/// Verify a batch of spending plans. +pub fn verify_plans(transactions: &[Transaction]) -> bool { + transactions.par_iter().all(|tr| tr.plan.verify(tr.tokens)) +} + +/// Verify a batch of transactions. +pub fn verify_transactions(transactions: &[Transaction]) -> bool { + verify_signatures(transactions) && verify_plans(transactions) +} + #[cfg(test)] mod tests { use super::*; @@ -133,4 +149,39 @@ mod tests { }; assert!(!tr.verify()); } + + #[test] + fn test_verify_transactions() { + let alice_keypair = KeyPair::new(); + let bob_pubkey = KeyPair::new().pubkey(); + let carol_pubkey = KeyPair::new().pubkey(); + let last_id = Hash::default(); + let tr0 = Transaction::new(&alice_keypair, bob_pubkey, 1, last_id); + let tr1 = Transaction::new(&alice_keypair, carol_pubkey, 1, last_id); + let transactions = vec![tr0, tr1]; + assert!(verify_transactions(&transactions)); + } +} + +#[cfg(all(feature = "unstable", test))] +mod bench { + extern crate test; + use self::test::Bencher; + use transaction::*; + + #[bench] + fn verify_signatures_bench(bencher: &mut Bencher) { + let alice_keypair = KeyPair::new(); + let last_id = Hash::default(); + let transactions: Vec<_> = (0..64) + .into_par_iter() + .map(|_| { + let rando_pubkey = KeyPair::new().pubkey(); + Transaction::new(&alice_keypair, rando_pubkey, 1, last_id) + }) + .collect(); + bencher.iter(|| { + assert!(verify_signatures(&transactions)); + }); + } } From 3bf225e85ff2828db8f49eea6792a513b17344b2 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Wed, 28 Mar 2018 22:18:33 -0600 Subject: [PATCH 16/55] Don't require install to run demo --- README.md | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 37789f8c6161a3..43a3782da9cf81 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,6 @@ $ curl https://sh.rustup.rs -sSf | sh $ source $HOME/.cargo/env ``` -Install the solana executables: - -```bash - $ cargo install solana -``` - The testnode server is initialized with a ledger from stdin and generates new ledger entries on stdout. To create the input ledger, we'll need to create *the mint* and use it to generate a *genesis ledger*. It's done in @@ -37,21 +31,21 @@ two steps because the mint.json file contains a private key that will be used later in this demo. ```bash - $ echo 500 | solana-mint > mint.json + $ echo 1000000000 | cargo run --release --bin solana-mint | tee mint.json $ cat mint.json | solana-genesis > genesis.log ``` Now you can start the server: ```bash - $ cat genesis.log | solana-testnode > transactions0.log + $ cat genesis.log | cargo run --release --bin solana-testnode | tee transactions0.log ``` Then, in a separate shell, let's execute some transactions. Note we pass in the JSON configuration file here, not the genesis ledger. ```bash - $ cat mint.json | solana-client-demo + $ cat mint.json | cargo run --release --bin solana-client-demo ``` Now kill the server with Ctrl-C, and take a look at the ledger. You should @@ -67,14 +61,14 @@ Now restart the server from where we left off. Pass it both the genesis ledger, the transaction ledger. ```bash - $ cat genesis.log transactions0.log | solana-testnode > transactions1.log + $ cat genesis.log transactions0.log | cargo run --release --bin solana-testnode > transactions1.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 mint.json | solana-client-demo + $ cat mint.json | cargo run --release --bin solana-client-demo ``` Stop the server again, and verify there are only Tick entries, and no Transaction entries. From a03d7bf5cd2d1ebef74d0211a47f8cbea9352b61 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Wed, 28 Mar 2018 22:20:31 -0600 Subject: [PATCH 17/55] Missed a couple --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 43a3782da9cf81..668f4c68c6c48f 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ used later in this demo. ```bash $ echo 1000000000 | cargo run --release --bin solana-mint | tee mint.json - $ cat mint.json | solana-genesis > genesis.log + $ cat mint.json | cargo run --release --bin solana-genesis | tee genesis.log ``` Now you can start the server: @@ -61,7 +61,7 @@ Now restart the server from where we left off. Pass it both the genesis ledger, the transaction ledger. ```bash - $ cat genesis.log transactions0.log | cargo run --release --bin solana-testnode > transactions1.log + $ cat genesis.log transactions0.log | cargo run --release --bin solana-testnode | tee transactions1.log ``` Lastly, run the client demo again, and verify that all funds were spent in the From 132495b1fc360a858a4bd171b0323caf2caf70fc Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Thu, 29 Mar 2018 10:51:17 -0600 Subject: [PATCH 18/55] A simple consensus diagram to guide rollback/coalescing Diagram for what's described in #84 for rollback support. --- doc/consensus.msc | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 doc/consensus.msc diff --git a/doc/consensus.msc b/doc/consensus.msc new file mode 100644 index 00000000000000..341548fdd56d33 --- /dev/null +++ b/doc/consensus.msc @@ -0,0 +1,15 @@ +msc { + client,leader,verifier_a,verifier_b,verifier_c; + + client=>leader [ label = "SUBMIT" ] ; + leader=>client [ label = "CONFIRMED" ] ; + leader=>verifier_a [ label = "CONFIRMED" ] ; + leader=>verifier_b [ label = "CONFIRMED" ] ; + leader=>verifier_c [ label = "CONFIRMED" ] ; + verifier_a=>leader [ label = "VERIFIED" ] ; + verifier_b=>leader [ label = "VERIFIED" ] ; + leader=>client [ label = "FINALIZED" ] ; + leader=>verifier_a [ label = "FINALIZED" ] ; + leader=>verifier_b [ label = "FINALIZED" ] ; + leader=>verifier_c [ label = "FINALIZED" ] ; +} From 55179101cd28ac289551fca05ff73cba14af2e28 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Thu, 29 Mar 2018 12:20:54 -0600 Subject: [PATCH 19/55] Add more documentation --- src/accountant.rs | 19 ++++++++++++++++--- src/accountant_skel.rs | 21 ++++++++++----------- src/accountant_stub.rs | 21 ++++++++++++++++++--- src/entry.rs | 15 +++++++++++++++ src/event.rs | 7 ++++++- src/hash.rs | 2 ++ src/historian.rs | 4 ++-- src/ledger.rs | 15 ++------------- src/plan.rs | 8 ++++++++ src/recorder.rs | 2 +- src/result.rs | 2 ++ src/signature.rs | 2 +- src/streamer.rs | 8 +++++--- src/transaction.rs | 6 +++++- 14 files changed, 93 insertions(+), 39 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 9d6602e57dec20..622b96a1c1042f 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -1,6 +1,7 @@ -//! The `accountant` is a client of the `historian`. It uses the historian's -//! event log to record transactions. Its users can deposit funds and -//! transfer funds to other users. +//! The Accountant tracks client balances and the progress of pending +//! transactions. It offers a high-level public API that signs transactions +//! on behalf of the caller and a private low-level API for when they have +//! already been signed and verified. use chrono::prelude::*; use entry::Entry; @@ -44,6 +45,7 @@ pub struct Accountant { } impl Accountant { + /// Create an Accountant using an existing ledger. pub fn new_from_entries(entries: I, ms_per_tick: Option) -> Self where I: IntoIterator, @@ -79,6 +81,7 @@ impl Accountant { acc } + /// Create an Accountant with only a Mint. Typically used by unit tests. pub fn new(mint: &Mint, ms_per_tick: Option) -> Self { Self::new_from_entries(mint.create_entries(), ms_per_tick) } @@ -91,6 +94,7 @@ impl Accountant { } } + /// Verify and process the given Transaction. pub fn process_transaction(self: &mut Self, tr: Transaction) -> Result<()> { if !tr.verify() { return Err(AccountingError::InvalidTransfer); @@ -111,6 +115,7 @@ impl Accountant { Ok(()) } + /// Process a Transaction that has already been verified. fn process_verified_transaction( self: &mut Self, tr: &Transaction, @@ -138,6 +143,7 @@ impl Accountant { Ok(()) } + /// Process a Witness Signature that has already been verified. fn process_verified_sig(&mut self, from: PublicKey, tx_sig: Signature) -> Result<()> { if let Occupied(mut e) = self.pending.entry(tx_sig) { e.get_mut().apply_witness(&Witness::Signature(from)); @@ -150,6 +156,7 @@ impl Accountant { Ok(()) } + /// Process a Witness Timestamp that has already been verified. fn process_verified_timestamp(&mut self, from: PublicKey, dt: DateTime) -> Result<()> { // If this is the first timestamp we've seen, it probably came from the genesis block, // so we'll trust it. @@ -182,6 +189,7 @@ impl Accountant { Ok(()) } + /// Process an Transaction or Witness that has already been verified. fn process_verified_event(self: &mut Self, event: &Event, allow_deposits: bool) -> Result<()> { match *event { Event::Transaction(ref tr) => self.process_verified_transaction(tr, allow_deposits), @@ -190,6 +198,8 @@ impl Accountant { } } + /// Create, sign, and process a Transaction from `keypair` to `to` of + /// `n` tokens where `last_id` is the last Entry ID observed by the client. pub fn transfer( self: &mut Self, n: i64, @@ -202,6 +212,9 @@ impl Accountant { self.process_transaction(tr).map(|_| sig) } + /// Create, sign, and process a postdated Transaction from `keypair` + /// to `to` of `n` tokens on `dt` where `last_id` is the last Entry ID + /// observed by the client. pub fn transfer_on_date( self: &mut Self, n: i64, diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index 2b82167afef3c6..6502d6172cc36f 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -1,3 +1,7 @@ +//! The AccountantSkel is a microservice that exposes the high-level +//! Accountant API to the network. Its message encoding is currently +//! in flux. Clients should AccountantStub to interact with it. + use accountant::Accountant; use bincode::{deserialize, serialize}; use entry::Entry; @@ -7,7 +11,7 @@ use serde_json; use signature::PublicKey; use std::default::Default; use std::io::{ErrorKind, Write}; -use std::net::{TcpStream, UdpSocket}; +use std::net::UdpSocket; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::channel; use std::sync::{Arc, Mutex}; @@ -20,7 +24,6 @@ pub struct AccountantSkel { pub acc: Accountant, pub last_id: Hash, writer: W, - subscribers: Vec, } #[cfg_attr(feature = "cargo-clippy", allow(large_enum_variant))] @@ -39,31 +42,26 @@ pub enum Response { } impl AccountantSkel { + /// Create a new AccountantSkel that wraps the given Accountant. pub fn new(acc: Accountant, w: W) -> Self { let last_id = acc.first_id; AccountantSkel { acc, last_id, writer: w, - subscribers: vec![], } } + /// Process any Entry items that have been published by the Historian. pub fn sync(&mut self) -> Hash { while let Ok(entry) = self.acc.historian.receiver.try_recv() { self.last_id = entry.id; writeln!(self.writer, "{}", serde_json::to_string(&entry).unwrap()).unwrap(); - - let buf = serialize(&entry).expect("serialize"); - self.subscribers - .retain(|ref mut subscriber| match subscriber.write(&buf) { - Err(err) => err.kind() != ErrorKind::BrokenPipe, - _ => true, - }); } self.last_id } + /// Process Request items sent by clients. pub fn process_request(self: &mut Self, msg: Request) -> Option { match msg { Request::Transaction(tr) => { @@ -127,7 +125,8 @@ impl AccountantSkel { Ok(()) } - /// UDP Server that forwards messages to Accountant methods. + /// Create a UDP microservice that forwards messages the given AccountantSkel. + /// Set `exit` to shutdown its threads. pub fn serve( obj: Arc>>, addr: &str, diff --git a/src/accountant_stub.rs b/src/accountant_stub.rs index 451d8ee24b9803..1b97a550c10d96 100644 --- a/src/accountant_stub.rs +++ b/src/accountant_stub.rs @@ -1,6 +1,7 @@ -//! The `accountant` is a client of the `historian`. It uses the historian's -//! event log to record transactions. Its users can deposit funds and -//! transfer funds to other users. +//! A AccountantStub is client-side object that interfaces with a server-side Accountant +//! object via the network interface exposed by AccountantSkel. Client code should use +//! this object instead of writing messages to the network directly. The binary +//! encoding of its messages are unstable and may change in future releases. use accountant_skel::{Request, Response}; use bincode::{deserialize, serialize}; @@ -16,6 +17,9 @@ pub struct AccountantStub { } impl AccountantStub { + /// Create a new AccountantStub that will interface with AccountantSkel + /// over `socket`. To receive responses, the caller must bind `socket` + /// to a public address before invoking AccountantStub methods. pub fn new(addr: &str, socket: UdpSocket) -> Self { AccountantStub { addr: addr.to_string(), @@ -23,12 +27,15 @@ impl AccountantStub { } } + /// Send a signed Transaction to the server for processing. This method + /// does not wait for a response. pub fn transfer_signed(&self, tr: Transaction) -> io::Result { let req = Request::Transaction(tr); let data = serialize(&req).unwrap(); self.socket.send_to(&data, &self.addr) } + /// Creates, signs, and processes a Transaction. Useful for writing unit-tests. pub fn transfer( &self, n: i64, @@ -41,6 +48,9 @@ impl AccountantStub { self.transfer_signed(tr).map(|_| sig) } + /// Request the balance of the user holding `pubkey`. This method blocks + /// until the server sends a response. If the response packet is dropped + /// by the network, this method will hang indefinitely. pub fn get_balance(&self, pubkey: &PublicKey) -> io::Result> { let req = Request::GetBalance { key: *pubkey }; let data = serialize(&req).expect("serialize GetBalance"); @@ -55,6 +65,7 @@ impl AccountantStub { Ok(None) } + /// Request the first or last Entry ID from the server. fn get_id(&self, is_last: bool) -> io::Result { let req = Request::GetId { is_last }; let data = serialize(&req).expect("serialize GetId"); @@ -68,6 +79,10 @@ impl AccountantStub { Ok(Default::default()) } + /// Request the last Entry ID from the server. This method blocks + /// until the server sends a response. At the time of this writing, + /// it also has the side-effect of causing the server to log any + /// entries that have been published by the Historian. pub fn get_last_id(&self) -> io::Result { self.get_id(true) } diff --git a/src/entry.rs b/src/entry.rs index 1cc2819e853aac..59b94ee40c3de4 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -1,7 +1,22 @@ +//! An Entry is a fundamental building block of Proof of History. It contains a +//! unqiue ID that is the hash of the Entry before it plus the hash of the +//! transactins within it. Entries cannot be reordered and its field `num_hashes` +//! represents an approximate amount of time since the last Entry was created. use event::Event; use hash::{extend_and_hash, hash, Hash}; use rayon::prelude::*; +/// Each Entry contains three pieces of data. The `num_hashes` field is the number +/// of hashes performed since the previous entry. The `id` field is the result +/// of hashing `id` from the previous entry `num_hashes` times. The `events` +/// field points to Events that took place shortly after `id` was generated. +/// +/// If you divide `num_hashes` by the amount of time it takes to generate a new hash, you +/// get a duration estimate since the last Entry. Since processing power increases +/// over time, one should expect the duration `num_hashes` represents to decrease proportionally. +/// Though processing power varies across nodes, the network gives priority to the +/// fastest processor. Duration should therefore be estimated by assuming that the hash +/// was generated by the fastest processor at the time the entry was recorded. #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Entry { pub num_hashes: u64, diff --git a/src/event.rs b/src/event.rs index a3933bc36aece2..c3620d0be46b45 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,4 +1,5 @@ -//! The `event` crate provides the data structures for log events. +//! An Event may be a Transaction or a Witness used to process a pending +//! Transaction. use bincode::serialize; use chrono::prelude::*; @@ -21,6 +22,7 @@ pub enum Event { } impl Event { + /// Create and sign a new Witness Timestamp. Used for unit-testing. pub fn new_timestamp(from: &KeyPair, dt: DateTime) -> Self { let sign_data = serialize(&dt).unwrap(); let sig = Signature::clone_from_slice(from.sign(&sign_data).as_ref()); @@ -32,6 +34,7 @@ impl Event { } // TODO: Rename this to transaction_signature(). + /// If the Event is a Transaction, return its Signature. pub fn get_signature(&self) -> Option { match *self { Event::Transaction(ref tr) => Some(tr.sig), @@ -39,6 +42,8 @@ impl Event { } } + /// Verify the Event's signature's are valid and if a transaction, that its + /// spending plan is valid. pub fn verify(&self) -> bool { match *self { Event::Transaction(ref tr) => tr.verify(), diff --git a/src/hash.rs b/src/hash.rs index 181db33499e75f..4b6dbff0532d4d 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -1,3 +1,5 @@ +//! A module for creating SHA-256 hashes. + use generic_array::GenericArray; use generic_array::typenum::U32; use sha2::{Digest, Sha256}; diff --git a/src/historian.rs b/src/historian.rs index 04a41703ef252c..9fadcb148e38a9 100644 --- a/src/historian.rs +++ b/src/historian.rs @@ -1,5 +1,5 @@ -//! The `historian` crate provides a microservice for generating a Proof-of-History. -//! It manages a thread containing a Proof-of-History Logger. +//! The Historian provides a microservice for generating a Proof of History. +//! It manages a thread containing a Proof of History Recorder. use entry::Entry; use hash::Hash; diff --git a/src/ledger.rs b/src/ledger.rs index 9385ab555a8275..f35deac73875f1 100644 --- a/src/ledger.rs +++ b/src/ledger.rs @@ -1,18 +1,7 @@ -//! The `ledger` crate provides the foundational data structures for Proof-of-History, -//! an ordered log of events in time. +//! The `ledger` module provides the functions for parallel verification of the +//! Proof of History ledger. use entry::{next_tick, Entry}; -/// Each entry contains three pieces of data. The `num_hashes` field is the number -/// of hashes performed since the previous entry. The `id` field is the result -/// of hashing `id` from the previous entry `num_hashes` times. The `event` -/// field points to an Event that took place shortly after `id` was generated. -/// -/// If you divide `num_hashes` by the amount of time it takes to generate a new hash, you -/// get a duration estimate since the last event. Since processing power increases -/// over time, one should expect the duration `num_hashes` represents to decrease proportionally. -/// Though processing power varies across nodes, the network gives priority to the -/// fastest processor. Duration should therefore be estimated by assuming that the hash -/// was generated by the fastest processor at the time the entry was recorded. use hash::Hash; use rayon::prelude::*; diff --git a/src/plan.rs b/src/plan.rs index 3ef51eed80443d..81061b4b5e796d 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -19,6 +19,7 @@ pub enum Condition { } impl Condition { + /// Return true if the given Witness satisfies this Condition. pub fn is_satisfied(&self, witness: &Witness) -> bool { match (self, witness) { (&Condition::Signature(ref pubkey), &Witness::Signature(ref from)) => pubkey == from, @@ -42,18 +43,23 @@ pub enum Plan { } impl Plan { + /// Create the simplest spending plan - one that pays `tokens` to PublicKey. pub fn new_payment(tokens: i64, to: PublicKey) -> Self { Plan::Pay(Payment { tokens, to }) } + /// Create a spending plan that pays `tokens` to `to` after being witnessed by `from`. pub fn new_authorized_payment(from: PublicKey, tokens: i64, to: PublicKey) -> Self { Plan::After(Condition::Signature(from), Payment { tokens, to }) } + /// Create a spending plan that pays `tokens` to `to` after the given DateTime. pub fn new_future_payment(dt: DateTime, tokens: i64, to: PublicKey) -> Self { Plan::After(Condition::Timestamp(dt), Payment { tokens, to }) } + /// Create a spending plan that pays `tokens` to `to` after the given DateTime + /// unless cancelled by `from`. pub fn new_cancelable_future_payment( dt: DateTime, from: PublicKey, @@ -66,6 +72,7 @@ impl Plan { ) } + /// Return true if the spending plan requires no additional Witnesses. pub fn is_complete(&self) -> bool { match *self { Plan::Pay(_) => true, @@ -73,6 +80,7 @@ impl Plan { } } + /// Return true if the plan spends exactly `spendable_tokens`. pub fn verify(&self, spendable_tokens: i64) -> bool { match *self { Plan::Pay(ref payment) | Plan::After(_, ref payment) => { diff --git a/src/recorder.rs b/src/recorder.rs index 14b6a263484ecc..0bfd666742837a 100644 --- a/src/recorder.rs +++ b/src/recorder.rs @@ -1,4 +1,4 @@ -//! The `recorder` crate provides an object for generating a Proof-of-History. +//! The `recorder` module provides an object for generating a Proof of History. //! It records Event items on behalf of its users. It continuously generates //! new hashes, only stopping to check if it has been sent an Event item. It //! tags each Event with an Entry and sends it back. The Entry includes the diff --git a/src/result.rs b/src/result.rs index ea96453175e9bb..7bef766b595e3d 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,3 +1,5 @@ +//! Exposes a Result type that propagates one of many different Error types. + use bincode; use serde_json; use std; diff --git a/src/signature.rs b/src/signature.rs index 9fd7b74e2e7580..2b5cbc37727c5f 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -1,4 +1,4 @@ -//! The `signature` crate provides functionality for public and private keys +//! The `signature` module provides functionality for public and private keys use generic_array::GenericArray; use generic_array::typenum::{U32, U64}; diff --git a/src/streamer.rs b/src/streamer.rs index 3ad270ad8d1247..5a26840ffcf82b 100644 --- a/src/streamer.rs +++ b/src/streamer.rs @@ -1,3 +1,5 @@ +//! A module for efficient batch processing of UDP packets. + use result::{Error, Result}; use std::fmt; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket}; @@ -320,7 +322,7 @@ mod bench { }) } - fn sinc( + fn sink( recycler: PacketRecycler, exit: Arc, rvs: Arc>, @@ -354,7 +356,7 @@ mod bench { let t_producer3 = producer(&addr, recycler.clone(), exit.clone()); let rvs = Arc::new(Mutex::new(0)); - let t_sinc = sinc(recycler.clone(), exit.clone(), rvs.clone(), r_reader); + let t_sink = sink(recycler.clone(), exit.clone(), rvs.clone(), r_reader); let start = SystemTime::now(); let start_val = *rvs.lock().unwrap(); @@ -370,7 +372,7 @@ mod bench { t_producer1.join()?; t_producer2.join()?; t_producer3.join()?; - t_sinc.join()?; + t_sink.join()?; Ok(()) } #[bench] diff --git a/src/transaction.rs b/src/transaction.rs index 4aca2742a7b211..4754335573b4b3 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -1,4 +1,4 @@ -//! The `transaction` crate provides functionality for creating log transactions. +//! The `transaction` module provides functionality for creating log transactions. use bincode::serialize; use chrono::prelude::*; @@ -17,6 +17,7 @@ pub struct Transaction { } impl Transaction { + /// Create and sign a new Transaction. Used for unit-testing. pub fn new(from_keypair: &KeyPair, to: PublicKey, tokens: i64, last_id: Hash) -> Self { let from = from_keypair.pubkey(); let plan = Plan::Pay(Payment { tokens, to }); @@ -31,6 +32,7 @@ impl Transaction { tr } + /// Create and sign a postdated Transaction. Used for unit-testing. pub fn new_on_date( from_keypair: &KeyPair, to: PublicKey, @@ -58,11 +60,13 @@ impl Transaction { serialize(&(&self.from, &self.plan, &self.tokens, &self.last_id)).unwrap() } + /// Sign this transaction. pub fn sign(&mut self, keypair: &KeyPair) { let sign_data = self.get_sign_data(); self.sig = Signature::clone_from_slice(keypair.sign(&sign_data).as_ref()); } + /// Verify this transaction's signature and its spending plan. pub fn verify(&self) -> bool { self.sig.verify(&self.from, &self.get_sign_data()) && self.plan.verify(self.tokens) } From 1fbb34620c579657ea83ee8095fe5de4de8ca34b Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Thu, 29 Mar 2018 12:54:10 -0600 Subject: [PATCH 20/55] Fix compiler warning --- src/accountant_skel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index 6502d6172cc36f..eff72389c7d207 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -10,7 +10,7 @@ use result::Result; use serde_json; use signature::PublicKey; use std::default::Default; -use std::io::{ErrorKind, Write}; +use std::io::Write; use std::net::UdpSocket; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::channel; From 232e1bb8a3f143e04e97daf5f5059542ab8daab8 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Thu, 29 Mar 2018 12:55:41 -0600 Subject: [PATCH 21/55] Colocate packet dependencies --- src/accountant_skel.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index eff72389c7d207..9558fab85c4ca0 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -101,6 +101,7 @@ impl AccountantSkel { let mut num = 0; let mut ursps = rsps.write().unwrap(); for packet in &msgs.read().unwrap().packets { + let rsp_addr = packet.meta.get_addr(); let sz = packet.meta.size; let req = deserialize(&packet.data[0..sz])?; if let Some(resp) = obj.lock().unwrap().process_request(req) { @@ -114,7 +115,7 @@ impl AccountantSkel { let len = v.len(); rsp.data[..len].copy_from_slice(&v); rsp.meta.size = len; - rsp.meta.set_addr(&packet.meta.get_addr()); + rsp.meta.set_addr(&rsp_addr); num += 1; } } From c59c38e50e324a35d5118b600f7574e5cff871cc Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Thu, 29 Mar 2018 13:09:21 -0600 Subject: [PATCH 22/55] Refactor for batch verification --- src/accountant_skel.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index 9558fab85c4ca0..4b8c611858bd48 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -11,7 +11,7 @@ use serde_json; use signature::PublicKey; use std::default::Default; use std::io::Write; -use std::net::UdpSocket; +use std::net::{SocketAddr, UdpSocket}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::channel; use std::sync::{Arc, Mutex}; @@ -34,6 +34,10 @@ pub enum Request { GetId { is_last: bool }, } +fn filter_valid_requests(reqs: Vec<(Request, SocketAddr)>) -> Vec<(Request, SocketAddr)> { + reqs +} + #[derive(Serialize, Deserialize, Debug)] pub enum Response { Balance { key: PublicKey, val: Option }, @@ -98,12 +102,18 @@ impl AccountantSkel { let rsps = streamer::allocate(response_recycler); let rsps_ = rsps.clone(); { - let mut num = 0; - let mut ursps = rsps.write().unwrap(); + let mut reqs = vec![]; for packet in &msgs.read().unwrap().packets { let rsp_addr = packet.meta.get_addr(); let sz = packet.meta.size; let req = deserialize(&packet.data[0..sz])?; + reqs.push((req, rsp_addr)); + } + let reqs = filter_valid_requests(reqs); + + let mut num = 0; + let mut ursps = rsps.write().unwrap(); + for (req, rsp_addr) in reqs { if let Some(resp) = obj.lock().unwrap().process_request(req) { if ursps.responses.len() <= num { ursps From 22f5985f1b22ee91fab49cc0df2ce998c8cea81c Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Thu, 29 Mar 2018 13:18:08 -0600 Subject: [PATCH 23/55] Do request verification in parallel, and then process the verified requests --- src/accountant.rs | 2 +- src/accountant_skel.rs | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 622b96a1c1042f..8d4d90995f650f 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -116,7 +116,7 @@ impl Accountant { } /// Process a Transaction that has already been verified. - fn process_verified_transaction( + pub fn process_verified_transaction( self: &mut Self, tr: &Transaction, allow_deposits: bool, diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index 4b8c611858bd48..390fab91dc01ab 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -19,6 +19,7 @@ use std::thread::{spawn, JoinHandle}; use std::time::Duration; use streamer; use transaction::Transaction; +use rayon::prelude::*; pub struct AccountantSkel { pub acc: Accountant, @@ -34,8 +35,19 @@ pub enum Request { GetId { is_last: bool }, } +impl Request { + /// Verify the request is valid. + pub fn verify(&self) -> bool { + match *self { + Request::Transaction(ref tr) => tr.verify(), + _ => true, + } + } +} + +/// Parallel verfication of a batch of requests. fn filter_valid_requests(reqs: Vec<(Request, SocketAddr)>) -> Vec<(Request, SocketAddr)> { - reqs + reqs.into_par_iter().filter({ |x| x.0.verify() }).collect() } #[derive(Serialize, Deserialize, Debug)] @@ -66,10 +78,10 @@ impl AccountantSkel { } /// Process Request items sent by clients. - pub fn process_request(self: &mut Self, msg: Request) -> Option { + pub fn process_verified_request(self: &mut Self, msg: Request) -> Option { match msg { Request::Transaction(tr) => { - if let Err(err) = self.acc.process_transaction(tr) { + if let Err(err) = self.acc.process_verified_transaction(&tr, false) { eprintln!("Transaction error: {:?}", err); } None @@ -114,7 +126,7 @@ impl AccountantSkel { let mut num = 0; let mut ursps = rsps.write().unwrap(); for (req, rsp_addr) in reqs { - if let Some(resp) = obj.lock().unwrap().process_request(req) { + if let Some(resp) = obj.lock().unwrap().process_verified_request(req) { if ursps.responses.len() <= num { ursps .responses From 2f1e585446fce1a69a19cbb788a65c72a4c5b483 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Thu, 29 Mar 2018 13:41:09 -0600 Subject: [PATCH 24/55] Better benchmark Tolerates dropped UDP packets --- src/bin/client-demo.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/bin/client-demo.rs b/src/bin/client-demo.rs index 38b32238424416..75f2b2b2959828 100644 --- a/src/bin/client-demo.rs +++ b/src/bin/client-demo.rs @@ -8,7 +8,8 @@ use solana::signature::{KeyPair, KeyPairUtil}; use solana::transaction::Transaction; use std::io::stdin; use std::net::UdpSocket; -use std::time::Instant; +use std::time::{Duration, Instant}; +use std::thread::sleep; use rayon::prelude::*; fn main() { @@ -27,7 +28,7 @@ fn main() { println!("Mint's Initial Balance {}", mint_balance); println!("Signing transactions..."); - let txs = 10_000; + let txs = 100_000; let now = Instant::now(); let transactions: Vec<_> = (0..txs) .into_par_iter() @@ -55,17 +56,18 @@ fn main() { } println!("Waiting for last transaction to be confirmed...",); let mut val = mint_balance; - while val > mint_balance - txs { + let mut prev = 0; + while val != prev { + sleep(Duration::from_millis(20)); + prev = val; val = acc.get_balance(&mint_pubkey).unwrap().unwrap(); } + println!("Mint's Final Balance {}", val); + let txs = mint_balance - val; + println!("Successful transactions {}", txs); let duration = now.elapsed(); let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos()); let tps = (txs * 1_000_000_000) as f64 / ns as f64; println!("Done. {} tps!", tps); - println!("Mint's Final Balance {}", val); - assert_eq!(val, mint_balance - txs); - - // Force the ledger to print on the server. - acc.get_last_id().unwrap(); } From b8cf5f9427e10884ec4c4bc06edeb3a279c11328 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Thu, 29 Mar 2018 13:50:32 -0600 Subject: [PATCH 25/55] Fix transaction logging --- src/accountant.rs | 27 ++++++++++++++++----------- src/accountant_skel.rs | 6 +++--- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 8d4d90995f650f..5f255893049e74 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -94,12 +94,8 @@ impl Accountant { } } - /// Verify and process the given Transaction. - pub fn process_transaction(self: &mut Self, tr: Transaction) -> Result<()> { - if !tr.verify() { - return Err(AccountingError::InvalidTransfer); - } - + /// Process and log the given Transaction. + pub fn log_verified_transaction(&mut self, tr: Transaction) -> Result<()> { if self.get_balance(&tr.from).unwrap_or(0) < tr.tokens { return Err(AccountingError::InsufficientFunds); } @@ -115,8 +111,17 @@ impl Accountant { Ok(()) } + /// Verify and process the given Transaction. + pub fn log_transaction(&mut self, tr: Transaction) -> Result<()> { + if !tr.verify() { + return Err(AccountingError::InvalidTransfer); + } + + self.log_verified_transaction(tr) + } + /// Process a Transaction that has already been verified. - pub fn process_verified_transaction( + fn process_verified_transaction( self: &mut Self, tr: &Transaction, allow_deposits: bool, @@ -209,7 +214,7 @@ impl Accountant { ) -> Result { let tr = Transaction::new(keypair, to, n, last_id); let sig = tr.sig; - self.process_transaction(tr).map(|_| sig) + self.log_transaction(tr).map(|_| sig) } /// Create, sign, and process a postdated Transaction from `keypair` @@ -225,7 +230,7 @@ impl Accountant { ) -> Result { let tr = Transaction::new_on_date(keypair, to, dt, n, last_id); let sig = tr.sig; - self.process_transaction(tr).map(|_| sig) + self.log_transaction(tr).map(|_| sig) } pub fn get_balance(self: &Self, pubkey: &PublicKey) -> Option { @@ -292,7 +297,7 @@ mod tests { payment.tokens = 2; // <-- attack! } assert_eq!( - acc.process_transaction(tr.clone()), + acc.log_transaction(tr.clone()), Err(AccountingError::InvalidTransfer) ); @@ -301,7 +306,7 @@ mod tests { payment.tokens = 0; // <-- whoops! } assert_eq!( - acc.process_transaction(tr.clone()), + acc.log_transaction(tr.clone()), Err(AccountingError::InvalidTransfer) ); } diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index 390fab91dc01ab..a2909473d00908 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -78,10 +78,10 @@ impl AccountantSkel { } /// Process Request items sent by clients. - pub fn process_verified_request(self: &mut Self, msg: Request) -> Option { + pub fn log_verified_request(&mut self, msg: Request) -> Option { match msg { Request::Transaction(tr) => { - if let Err(err) = self.acc.process_verified_transaction(&tr, false) { + if let Err(err) = self.acc.log_verified_transaction(tr) { eprintln!("Transaction error: {:?}", err); } None @@ -126,7 +126,7 @@ impl AccountantSkel { let mut num = 0; let mut ursps = rsps.write().unwrap(); for (req, rsp_addr) in reqs { - if let Some(resp) = obj.lock().unwrap().process_verified_request(req) { + if let Some(resp) = obj.lock().unwrap().log_verified_request(req) { if ursps.responses.len() <= num { ursps .responses From eaec25f940d37dd5a5916f289271c9e1ce0dd0a7 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Thu, 29 Mar 2018 15:05:38 -0600 Subject: [PATCH 26/55] Version bump --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2add36084f202c..64c9331ea666db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "solana" description = "High Performance Blockchain" -version = "0.4.0-alpha" +version = "0.4.0-beta" documentation = "https://docs.rs/solana" homepage = "http://loomprotocol.com/" repository = "https://github.com/solana-labs/solana" From ef169a6652153642311907b4a250c8271b14db5d Mon Sep 17 00:00:00 2001 From: Jackson Sandland Date: Fri, 30 Mar 2018 10:43:38 -0700 Subject: [PATCH 27/55] 94: source doc review --- src/accountant.rs | 4 ++-- src/accountant_skel.rs | 4 ++-- src/accountant_stub.rs | 2 +- src/entry.rs | 6 +++--- src/event.rs | 2 +- src/hash.rs | 2 +- src/historian.rs | 2 +- src/ledger.rs | 2 +- src/mint.rs | 2 +- src/plan.rs | 2 +- src/recorder.rs | 2 +- src/result.rs | 2 +- src/signature.rs | 2 +- src/streamer.rs | 2 +- 14 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 5f255893049e74..8c2fb048c86dd9 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -1,6 +1,6 @@ -//! The Accountant tracks client balances and the progress of pending +//! The `accountant` module tracks client balances, and the progress of pending //! transactions. It offers a high-level public API that signs transactions -//! on behalf of the caller and a private low-level API for when they have +//! on behalf of the caller, and a private low-level API for when they have //! already been signed and verified. use chrono::prelude::*; diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index a2909473d00908..924385df92a75b 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -1,6 +1,6 @@ -//! The AccountantSkel is a microservice that exposes the high-level +//! The `accountantSkel` module is a microservice that exposes the high-level //! Accountant API to the network. Its message encoding is currently -//! in flux. Clients should AccountantStub to interact with it. +//! in flux. Clients should use AccountantStub to interact with it. use accountant::Accountant; use bincode::{deserialize, serialize}; diff --git a/src/accountant_stub.rs b/src/accountant_stub.rs index 1b97a550c10d96..65aae7b713cbfb 100644 --- a/src/accountant_stub.rs +++ b/src/accountant_stub.rs @@ -1,4 +1,4 @@ -//! A AccountantStub is client-side object that interfaces with a server-side Accountant +//! The `accountantStub` module is a client-side object that interfaces with a server-side Accountant //! object via the network interface exposed by AccountantSkel. Client code should use //! this object instead of writing messages to the network directly. The binary //! encoding of its messages are unstable and may change in future releases. diff --git a/src/entry.rs b/src/entry.rs index 59b94ee40c3de4..985bab83ccb72e 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -1,6 +1,6 @@ -//! An Entry is a fundamental building block of Proof of History. It contains a -//! unqiue ID that is the hash of the Entry before it plus the hash of the -//! transactins within it. Entries cannot be reordered and its field `num_hashes` +//! The `entry` module is a fundamental building block of Proof of History. It contains a +//! unique ID that is the hash of the Entry before it, plus the hash of the +//! transactions within it. Entries cannot be reordered, and its field `num_hashes` //! represents an approximate amount of time since the last Entry was created. use event::Event; use hash::{extend_and_hash, hash, Hash}; diff --git a/src/event.rs b/src/event.rs index c3620d0be46b45..9940959a3de89b 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,4 +1,4 @@ -//! An Event may be a Transaction or a Witness used to process a pending +//! The `event` module handles events, which may be a `Transaction`, or a `Witness` used to process a pending //! Transaction. use bincode::serialize; diff --git a/src/hash.rs b/src/hash.rs index 4b6dbff0532d4d..ee7598a0dc29e2 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -1,4 +1,4 @@ -//! A module for creating SHA-256 hashes. +//! The `hash` module provides functions for creating SHA-256 hashes. use generic_array::GenericArray; use generic_array::typenum::U32; diff --git a/src/historian.rs b/src/historian.rs index 9fadcb148e38a9..3bf92668d00aee 100644 --- a/src/historian.rs +++ b/src/historian.rs @@ -1,4 +1,4 @@ -//! The Historian provides a microservice for generating a Proof of History. +//! The `historian` module provides a microservice for generating a Proof of History. //! It manages a thread containing a Proof of History Recorder. use entry::Entry; diff --git a/src/ledger.rs b/src/ledger.rs index f35deac73875f1..50657b828c1ede 100644 --- a/src/ledger.rs +++ b/src/ledger.rs @@ -1,4 +1,4 @@ -//! The `ledger` module provides the functions for parallel verification of the +//! The `ledger` module provides functions for parallel verification of the //! Proof of History ledger. use entry::{next_tick, Entry}; diff --git a/src/mint.rs b/src/mint.rs index 5799d9791060df..7f0339b546ce50 100644 --- a/src/mint.rs +++ b/src/mint.rs @@ -1,4 +1,4 @@ -//! A library for generating the chain's genesis block. +//! The `mint` module is a library for generating the chain's genesis block. use entry::Entry; use entry::create_entry; diff --git a/src/plan.rs b/src/plan.rs index 81061b4b5e796d..adefe613322b9c 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -1,4 +1,4 @@ -//! A domain-specific language for payment plans. Users create Plan objects that +//! The `plan` module provides a domain-specific language for payment plans. Users create Plan objects that //! are given to an interpreter. The interpreter listens for `Witness` events, //! which it uses to reduce the payment plan. When the plan is reduced to a //! `Payment`, the payment is executed. diff --git a/src/recorder.rs b/src/recorder.rs index 0bfd666742837a..40536458c7c8a0 100644 --- a/src/recorder.rs +++ b/src/recorder.rs @@ -1,7 +1,7 @@ //! The `recorder` module provides an object for generating a Proof of History. //! It records Event items on behalf of its users. It continuously generates //! new hashes, only stopping to check if it has been sent an Event item. It -//! tags each Event with an Entry and sends it back. The Entry includes the +//! tags each Event with an Entry, and sends it back. The Entry includes the //! Event, the latest hash, and the number of hashes since the last event. //! The resulting stream of entries represents ordered events in time. diff --git a/src/result.rs b/src/result.rs index 7bef766b595e3d..81a3a66678ea63 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,4 +1,4 @@ -//! Exposes a Result type that propagates one of many different Error types. +//! The `result` module exposes a Result type that propagates one of many different Error types. use bincode; use serde_json; diff --git a/src/signature.rs b/src/signature.rs index 2b5cbc37727c5f..5f3aee61ea2267 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -1,4 +1,4 @@ -//! The `signature` module provides functionality for public and private keys +//! The `signature` module provides functionality for public, and private keys. use generic_array::GenericArray; use generic_array::typenum::{U32, U64}; diff --git a/src/streamer.rs b/src/streamer.rs index 5a26840ffcf82b..9096f3fe64beec 100644 --- a/src/streamer.rs +++ b/src/streamer.rs @@ -1,4 +1,4 @@ -//! A module for efficient batch processing of UDP packets. +//! The 'streamer` module allows for efficient batch processing of UDP packets. use result::{Error, Result}; use std::fmt; From 9f256f0929a5370eaefa3ddc6f9e7b36379367bc Mon Sep 17 00:00:00 2001 From: Jackson Sandland Date: Fri, 30 Mar 2018 13:10:27 -0700 Subject: [PATCH 28/55] 94 - snakecase mod names --- src/accountant_skel.rs | 2 +- src/accountant_stub.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index 924385df92a75b..b8b1226eb7bb91 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -1,4 +1,4 @@ -//! The `accountantSkel` module is a microservice that exposes the high-level +//! The `accountant_skel` module is a microservice that exposes the high-level //! Accountant API to the network. Its message encoding is currently //! in flux. Clients should use AccountantStub to interact with it. diff --git a/src/accountant_stub.rs b/src/accountant_stub.rs index 65aae7b713cbfb..1c3c36d2fe3e9f 100644 --- a/src/accountant_stub.rs +++ b/src/accountant_stub.rs @@ -1,4 +1,4 @@ -//! The `accountantStub` module is a client-side object that interfaces with a server-side Accountant +//! The `accountant_stub` module is a client-side object that interfaces with a server-side Accountant //! object via the network interface exposed by AccountantSkel. Client code should use //! this object instead of writing messages to the network directly. The binary //! encoding of its messages are unstable and may change in future releases. From e683c34a8964f6532738dd44f602d801868c41cc Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Sat, 31 Mar 2018 14:44:12 -0600 Subject: [PATCH 29/55] Version bump --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 64c9331ea666db..fb0e76a7be2a5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "solana" description = "High Performance Blockchain" -version = "0.4.0-beta" +version = "0.4.0" documentation = "https://docs.rs/solana" homepage = "http://loomprotocol.com/" repository = "https://github.com/solana-labs/solana" From 46e8c09bd8c55f10121f2055fe8ac8cbc1484b8c Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 09:30:10 -0600 Subject: [PATCH 30/55] Revoke API access to first_id --- src/accountant.rs | 20 ++++++++++++-------- src/accountant_skel.rs | 24 ++++++++---------------- src/accountant_stub.rs | 21 ++++++++------------- src/bin/testnode.rs | 4 ++-- src/recorder.rs | 4 ++-- 5 files changed, 32 insertions(+), 41 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 8c2fb048c86dd9..df2addeef62ae5 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -15,7 +15,7 @@ use signature::{KeyPair, PublicKey, Signature}; use std::collections::hash_map::Entry::Occupied; use std::collections::{HashMap, HashSet}; use std::result; -use std::sync::mpsc::SendError; +use std::sync::mpsc::{Receiver, SendError}; use transaction::Transaction; #[derive(Debug, PartialEq, Eq)] @@ -36,9 +36,8 @@ fn complete_transaction(balances: &mut HashMap, plan: &Plan) { } pub struct Accountant { - pub historian: Historian, - pub balances: HashMap, - pub first_id: Hash, + historian: Historian, + balances: HashMap, pending: HashMap, time_sources: HashSet, last_time: DateTime, @@ -46,7 +45,7 @@ pub struct Accountant { impl Accountant { /// Create an Accountant using an existing ledger. - pub fn new_from_entries(entries: I, ms_per_tick: Option) -> Self + pub fn new_from_entries(entries: I, ms_per_tick: Option) -> (Self, Hash) where I: IntoIterator, { @@ -61,7 +60,6 @@ impl Accountant { let mut acc = Accountant { historian: hist, balances: HashMap::new(), - first_id: start_hash, pending: HashMap::new(), time_sources: HashSet::new(), last_time: Utc.timestamp(0, 0), @@ -73,17 +71,19 @@ impl Accountant { let entry1 = entries.next().unwrap(); acc.process_verified_event(&entry1.events[0], true).unwrap(); + let mut last_id = entry1.id; for entry in entries { + last_id = entry.id; for event in entry.events { acc.process_verified_event(&event, false).unwrap(); } } - acc + (acc, last_id) } /// Create an Accountant with only a Mint. Typically used by unit tests. pub fn new(mint: &Mint, ms_per_tick: Option) -> Self { - Self::new_from_entries(mint.create_entries(), ms_per_tick) + Self::new_from_entries(mint.create_entries(), ms_per_tick).0 } fn is_deposit(allow_deposits: bool, from: &PublicKey, plan: &Plan) -> bool { @@ -94,6 +94,10 @@ impl Accountant { } } + pub fn receiver(&self) -> &Receiver { + &self.historian.receiver + } + /// Process and log the given Transaction. pub fn log_verified_transaction(&mut self, tr: Transaction) -> Result<()> { if self.get_balance(&tr.from).unwrap_or(0) < tr.tokens { diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index b8b1226eb7bb91..26af073993b881 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -22,8 +22,8 @@ use transaction::Transaction; use rayon::prelude::*; pub struct AccountantSkel { - pub acc: Accountant, - pub last_id: Hash, + acc: Accountant, + last_id: Hash, writer: W, } @@ -32,7 +32,7 @@ pub struct AccountantSkel { pub enum Request { Transaction(Transaction), GetBalance { key: PublicKey }, - GetId { is_last: bool }, + GetLastId, } impl Request { @@ -54,23 +54,22 @@ fn filter_valid_requests(reqs: Vec<(Request, SocketAddr)>) -> Vec<(Request, Sock pub enum Response { Balance { key: PublicKey, val: Option }, Entries { entries: Vec }, - Id { id: Hash, is_last: bool }, + LastId { id: Hash }, } impl AccountantSkel { /// Create a new AccountantSkel that wraps the given Accountant. - pub fn new(acc: Accountant, w: W) -> Self { - let last_id = acc.first_id; + pub fn new(acc: Accountant, last_id: Hash, writer: W) -> Self { AccountantSkel { acc, last_id, - writer: w, + writer, } } /// Process any Entry items that have been published by the Historian. pub fn sync(&mut self) -> Hash { - while let Ok(entry) = self.acc.historian.receiver.try_recv() { + while let Ok(entry) = self.acc.receiver().try_recv() { self.last_id = entry.id; writeln!(self.writer, "{}", serde_json::to_string(&entry).unwrap()).unwrap(); } @@ -90,14 +89,7 @@ impl AccountantSkel { let val = self.acc.get_balance(&key); Some(Response::Balance { key, val }) } - Request::GetId { is_last } => Some(Response::Id { - id: if is_last { - self.sync() - } else { - self.acc.first_id - }, - is_last, - }), + Request::GetLastId => Some(Response::LastId { id: self.sync() }), } } diff --git a/src/accountant_stub.rs b/src/accountant_stub.rs index 1c3c36d2fe3e9f..518c08fafd3326 100644 --- a/src/accountant_stub.rs +++ b/src/accountant_stub.rs @@ -65,27 +65,22 @@ impl AccountantStub { Ok(None) } - /// Request the first or last Entry ID from the server. - fn get_id(&self, is_last: bool) -> io::Result { - let req = Request::GetId { is_last }; + /// Request the last Entry ID from the server. This method blocks + /// until the server sends a response. At the time of this writing, + /// it also has the side-effect of causing the server to log any + /// entries that have been published by the Historian. + pub fn get_last_id(&self) -> io::Result { + let req = Request::GetLastId; 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 { + if let Response::LastId { id } = resp { return Ok(id); } Ok(Default::default()) } - - /// Request the last Entry ID from the server. This method blocks - /// until the server sends a response. At the time of this writing, - /// it also has the side-effect of causing the server to log any - /// entries that have been published by the Historian. - pub fn get_last_id(&self) -> io::Result { - self.get_id(true) - } } #[cfg(test)] @@ -110,7 +105,7 @@ mod tests { let acc = Accountant::new(&alice, Some(30)); let bob_pubkey = KeyPair::new().pubkey(); let exit = Arc::new(AtomicBool::new(false)); - let acc = Arc::new(Mutex::new(AccountantSkel::new(acc, sink()))); + let acc = Arc::new(Mutex::new(AccountantSkel::new(acc, alice.seed(), sink()))); let _threads = AccountantSkel::serve(acc, addr, exit.clone()).unwrap(); sleep(Duration::from_millis(300)); diff --git a/src/bin/testnode.rs b/src/bin/testnode.rs index a8daaec4c8745b..1e562b74fd34af 100644 --- a/src/bin/testnode.rs +++ b/src/bin/testnode.rs @@ -14,9 +14,9 @@ fn main() { .lock() .lines() .map(|line| serde_json::from_str(&line.unwrap()).unwrap()); - let acc = Accountant::new_from_entries(entries, Some(1000)); + let (acc, last_id) = Accountant::new_from_entries(entries, Some(1000)); let exit = Arc::new(AtomicBool::new(false)); - let skel = Arc::new(Mutex::new(AccountantSkel::new(acc, stdout()))); + let skel = Arc::new(Mutex::new(AccountantSkel::new(acc, last_id, stdout()))); eprintln!("Listening on {}", addr); let threads = AccountantSkel::serve(skel, addr, exit.clone()).unwrap(); for t in threads { diff --git a/src/recorder.rs b/src/recorder.rs index 40536458c7c8a0..faf07593577759 100644 --- a/src/recorder.rs +++ b/src/recorder.rs @@ -34,11 +34,11 @@ pub struct Recorder { } impl Recorder { - pub fn new(receiver: Receiver, sender: SyncSender, start_hash: Hash) -> Self { + pub fn new(receiver: Receiver, sender: SyncSender, last_hash: Hash) -> Self { Recorder { receiver, sender, - last_hash: start_hash, + last_hash, events: vec![], num_hashes: 0, num_ticks: 0, From 3abe305a215874c8de19fd1f059c78fc5f98131f Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 09:36:22 -0600 Subject: [PATCH 31/55] Move reserve_signatures into accountant Reasons Transaction signatures need to be unique: 1. guard against duplicates 2. accountant uses them as IDs to link Witness signatures to transactions via the `pending` hash map --- src/accountant.rs | 21 ++++++++++++++++++++- src/historian.rs | 22 ---------------------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index df2addeef62ae5..43e4c8e259becc 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -39,6 +39,7 @@ pub struct Accountant { historian: Historian, balances: HashMap, pending: HashMap, + signatures: HashSet, time_sources: HashSet, last_time: DateTime, } @@ -61,6 +62,7 @@ impl Accountant { historian: hist, balances: HashMap::new(), pending: HashMap::new(), + signatures: HashSet::new(), time_sources: HashSet::new(), last_time: Utc.timestamp(0, 0), }; @@ -124,13 +126,21 @@ impl Accountant { self.log_verified_transaction(tr) } + fn reserve_signature(&mut self, sig: &Signature) -> bool { + if self.signatures.contains(sig) { + return false; + } + self.signatures.insert(*sig); + true + } + /// Process a Transaction that has already been verified. fn process_verified_transaction( self: &mut Self, tr: &Transaction, allow_deposits: bool, ) -> Result<()> { - if !self.historian.reserve_signature(&tr.sig) { + if !self.reserve_signature(&tr.sig) { return Err(AccountingError::InvalidTransferSignature); } @@ -400,4 +410,13 @@ mod tests { acc.process_verified_sig(alice.pubkey(), sig).unwrap(); // <-- Attack! Attempt to cancel completed transaction. assert_ne!(acc.get_balance(&alice.pubkey()), Some(2)); } + + #[test] + fn test_duplicate_event_signature() { + let alice = Mint::new(1); + let mut acc = Accountant::new(&alice, None); + let sig = Signature::default(); + assert!(acc.reserve_signature(&sig)); + assert!(!acc.reserve_signature(&sig)); + } } diff --git a/src/historian.rs b/src/historian.rs index 3bf92668d00aee..bc77b6416445f4 100644 --- a/src/historian.rs +++ b/src/historian.rs @@ -4,8 +4,6 @@ use entry::Entry; use hash::Hash; use recorder::{ExitReason, Recorder, Signal}; -use signature::Signature; -use std::collections::HashSet; use std::sync::mpsc::{sync_channel, Receiver, SyncSender}; use std::thread::{spawn, JoinHandle}; use std::time::Instant; @@ -14,7 +12,6 @@ pub struct Historian { pub sender: SyncSender, pub receiver: Receiver, pub thread_hdl: JoinHandle, - pub signatures: HashSet, } impl Historian { @@ -23,23 +20,13 @@ impl Historian { let (entry_sender, receiver) = sync_channel(1000); let thread_hdl = Historian::create_recorder(*start_hash, ms_per_tick, event_receiver, entry_sender); - let signatures = HashSet::new(); Historian { sender, receiver, thread_hdl, - signatures, } } - pub fn reserve_signature(&mut self, sig: &Signature) -> bool { - if self.signatures.contains(sig) { - return false; - } - self.signatures.insert(*sig); - true - } - /// 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_recorder( @@ -110,15 +97,6 @@ mod tests { ); } - #[test] - fn test_duplicate_event_signature() { - let zero = Hash::default(); - let mut hist = Historian::new(&zero, None); - let sig = Signature::default(); - assert!(hist.reserve_signature(&sig)); - assert!(!hist.reserve_signature(&sig)); - } - #[test] fn test_ticking_historian() { let zero = Hash::default(); From da2b4962a993953327d9aac89557dc05225080d0 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 11:36:51 -0600 Subject: [PATCH 32/55] Move verify_slice() into a trait --- src/bin/historian-demo.rs | 4 ++-- src/historian.rs | 4 ++-- src/ledger.rs | 32 +++++++++++++++++++------------- src/mint.rs | 4 ++-- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/bin/historian-demo.rs b/src/bin/historian-demo.rs index 89d377c98662e4..306b8ffbaa8611 100644 --- a/src/bin/historian-demo.rs +++ b/src/bin/historian-demo.rs @@ -4,7 +4,7 @@ use solana::entry::Entry; use solana::event::Event; use solana::hash::Hash; use solana::historian::Historian; -use solana::ledger::verify_slice; +use solana::ledger::Block; use solana::recorder::Signal; use solana::signature::{KeyPair, KeyPairUtil}; use solana::transaction::Transaction; @@ -33,5 +33,5 @@ fn main() { } // Proof-of-History: Verify the historian learned about the events // in the same order they appear in the vector. - assert!(verify_slice(&entries, &seed)); + assert!(entries[..].verify(&seed)); } diff --git a/src/historian.rs b/src/historian.rs index bc77b6416445f4..c48fa248c21926 100644 --- a/src/historian.rs +++ b/src/historian.rs @@ -53,7 +53,7 @@ impl Historian { #[cfg(test)] mod tests { use super::*; - use ledger::*; + use ledger::Block; use std::thread::sleep; use std::time::Duration; @@ -82,7 +82,7 @@ mod tests { ExitReason::RecvDisconnected ); - assert!(verify_slice(&[entry0, entry1, entry2], &zero)); + assert!([entry0, entry1, entry2].verify(&zero)); } #[test] diff --git a/src/ledger.rs b/src/ledger.rs index 50657b828c1ede..0056bd54e20894 100644 --- a/src/ledger.rs +++ b/src/ledger.rs @@ -5,11 +5,17 @@ use entry::{next_tick, Entry}; use hash::Hash; use rayon::prelude::*; -/// Verifies the hashes and counts of a slice of events are all consistent. -pub fn verify_slice(entries: &[Entry], start_hash: &Hash) -> bool { - let genesis = [Entry::new_tick(Default::default(), start_hash)]; - let entry_pairs = genesis.par_iter().chain(entries).zip(entries); - entry_pairs.all(|(x0, x1)| x1.verify(&x0.id)) +pub trait Block { + /// Verifies the hashes and counts of a slice of events are all consistent. + fn verify(&self, start_hash: &Hash) -> bool; +} + +impl Block for [Entry] { + fn verify(&self, start_hash: &Hash) -> bool { + let genesis = [Entry::new_tick(0, start_hash)]; + let entry_pairs = genesis.par_iter().chain(self).zip(self); + entry_pairs.all(|(x0, x1)| x1.verify(&x0.id)) + } } /// Create a vector of Ticks of length `len` from `start_hash` hash and `num_hashes`. @@ -33,14 +39,14 @@ mod tests { fn test_verify_slice() { let zero = Hash::default(); let one = hash(&zero); - assert!(verify_slice(&vec![], &zero)); // base case - assert!(verify_slice(&vec![Entry::new_tick(0, &zero)], &zero)); // singleton case 1 - assert!(!verify_slice(&vec![Entry::new_tick(0, &zero)], &one)); // singleton case 2, bad - assert!(verify_slice(&next_ticks(&zero, 0, 2), &zero)); // inductive step + assert!(vec![][..].verify(&zero)); // base case + assert!(vec![Entry::new_tick(0, &zero)][..].verify(&zero)); // singleton case 1 + assert!(!vec![Entry::new_tick(0, &zero)][..].verify(&one)); // singleton case 2, bad + assert!(next_ticks(&zero, 0, 2)[..].verify(&zero)); // inductive step let mut bad_ticks = next_ticks(&zero, 0, 2); bad_ticks[1].id = one; - assert!(!verify_slice(&bad_ticks, &zero)); // inductive step, bad + assert!(!bad_ticks.verify(&zero)); // inductive step, bad } } @@ -52,10 +58,10 @@ mod bench { #[bench] fn event_bench(bencher: &mut Bencher) { - let start_hash = Default::default(); - let events = next_ticks(&start_hash, 10_000, 8); + let start_hash = Hash::default(); + let entries = next_ticks(&start_hash, 10_000, 8); bencher.iter(|| { - assert!(verify_slice(&events, &start_hash)); + assert!(entries.verify(&start_hash)); }); } } diff --git a/src/mint.rs b/src/mint.rs index 7f0339b546ce50..fb9a1a2a817308 100644 --- a/src/mint.rs +++ b/src/mint.rs @@ -57,7 +57,7 @@ impl Mint { #[cfg(test)] mod tests { use super::*; - use ledger::verify_slice; + use ledger::Block; use plan::Plan; #[test] @@ -74,6 +74,6 @@ mod tests { #[test] fn test_verify_entries() { let entries = Mint::new(100).create_entries(); - assert!(verify_slice(&entries, &entries[0].id)); + assert!(entries[..].verify(&entries[0].id)); } } From fc540395f9e0a1d1d63be997d9f4015902b86dc2 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 11:51:56 -0600 Subject: [PATCH 33/55] Update docs --- doc/historian.md | 6 +++--- doc/historian.msc | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/historian.md b/doc/historian.md index 0064d7f64bc309..f2cb23c581d253 100644 --- a/doc/historian.md +++ b/doc/historian.md @@ -11,7 +11,7 @@ with by verifying each entry's hash can be generated from the hash in the previo extern crate solana; use solana::historian::Historian; -use solana::ledger::{verify_slice, Entry, Hash}; +use solana::ledger::{Block, Entry, Hash}; use solana::event::{generate_keypair, get_pubkey, sign_claim_data, Event}; use std::thread::sleep; use std::time::Duration; @@ -38,7 +38,7 @@ fn main() { } // Proof-of-History: Verify the historian learned about the events // in the same order they appear in the vector. - assert!(verify_slice(&entries, &seed)); + assert!(entries[..].verify(&seed)); } ``` @@ -56,7 +56,7 @@ Proof-of-History Take note of the last line: ```rust -assert!(verify_slice(&entries, &seed)); +assert!(entries[..].verify(&seed)); ``` [It's a proof!](https://en.wikipedia.org/wiki/Curry–Howard_correspondence) For each entry returned by the diff --git a/doc/historian.msc b/doc/historian.msc index ace614b40c9779..b9bc84bd49be42 100644 --- a/doc/historian.msc +++ b/doc/historian.msc @@ -14,5 +14,5 @@ msc { recorder=>historian [ label = "e2 = Entry{id: h6, n: 3, event: Tick}" ] ; client=>historian [ label = "collect()" ] ; historian=>client [ label = "entries = [e0, e1, e2]" ] ; - client=>client [ label = "verify_slice(entries, h0)" ] ; + client=>client [ label = "entries.verify(h0)" ] ; } From 17de6876bb2814820abf7b16acda7f114618d87c Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 13:51:44 -0600 Subject: [PATCH 34/55] Add simpler accountant constructor --- src/accountant.rs | 71 +++++++++++++++++++++++++++++------------------ src/plan.rs | 8 +++--- 2 files changed, 48 insertions(+), 31 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 43e4c8e259becc..0fd889a2cdbdca 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -9,7 +9,7 @@ use event::Event; use hash::Hash; use historian::Historian; use mint::Mint; -use plan::{Plan, Witness}; +use plan::{Payment, Plan, Witness}; use recorder::Signal; use signature::{KeyPair, PublicKey, Signature}; use std::collections::hash_map::Entry::Occupied; @@ -29,10 +29,8 @@ pub enum AccountingError { pub type Result = result::Result; /// Commit funds to the 'to' party. -fn complete_transaction(balances: &mut HashMap, plan: &Plan) { - if let Plan::Pay(ref payment) = *plan { - *balances.entry(payment.to).or_insert(0) += payment.tokens; - } +fn apply_payment(balances: &mut HashMap, payment: &Payment) { + *balances.entry(payment.to).or_insert(0) += payment.tokens; } pub struct Accountant { @@ -45,6 +43,34 @@ pub struct Accountant { } impl Accountant { + /// Create an Accountant using a deposit. + pub fn new_from_deposit( + start_hash: &Hash, + deposit: &Payment, + ms_per_tick: Option, + ) -> Self { + let mut balances = HashMap::new(); + apply_payment(&mut balances, &deposit); + let historian = Historian::new(&start_hash, ms_per_tick); + Accountant { + historian, + balances, + pending: HashMap::new(), + signatures: HashSet::new(), + time_sources: HashSet::new(), + last_time: Utc.timestamp(0, 0), + } + } + + /// Create an Accountant with only a Mint. Typically used by unit tests. + pub fn new(mint: &Mint, ms_per_tick: Option) -> Self { + let deposit = Payment { + to: mint.pubkey(), + tokens: mint.tokens, + }; + Self::new_from_deposit(&mint.seed(), &deposit, ms_per_tick) + } + /// Create an Accountant using an existing ledger. pub fn new_from_entries(entries: I, ms_per_tick: Option) -> (Self, Hash) where @@ -57,21 +83,17 @@ impl Accountant { let entry0 = entries.next().unwrap(); let start_hash = entry0.id; - let hist = Historian::new(&start_hash, ms_per_tick); - let mut acc = Accountant { - historian: hist, - balances: HashMap::new(), - pending: HashMap::new(), - signatures: HashSet::new(), - time_sources: HashSet::new(), - last_time: Utc.timestamp(0, 0), - }; - // The second item in the ledger 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.events[0], true).unwrap(); + let deposit = if let Event::Transaction(ref tr) = entry1.events[0] { + tr.plan.final_payment() + } else { + None + }; + + let mut acc = Self::new_from_deposit(&start_hash, &deposit.unwrap(), ms_per_tick); let mut last_id = entry1.id; for entry in entries { @@ -83,11 +105,6 @@ impl Accountant { (acc, last_id) } - /// Create an Accountant with only a Mint. Typically used by unit tests. - pub fn new(mint: &Mint, ms_per_tick: Option) -> Self { - Self::new_from_entries(mint.create_entries(), ms_per_tick).0 - } - fn is_deposit(allow_deposits: bool, from: &PublicKey, plan: &Plan) -> bool { if let Plan::Pay(ref payment) = *plan { allow_deposits && *from == payment.to @@ -153,8 +170,8 @@ impl Accountant { let mut plan = tr.plan.clone(); plan.apply_witness(&Witness::Timestamp(self.last_time)); - if plan.is_complete() { - complete_transaction(&mut self.balances, &plan); + if let Some(ref payment) = plan.final_payment() { + apply_payment(&mut self.balances, payment); } else { self.pending.insert(tr.sig, plan); } @@ -166,8 +183,8 @@ impl Accountant { fn process_verified_sig(&mut self, from: PublicKey, tx_sig: Signature) -> Result<()> { if let Occupied(mut e) = self.pending.entry(tx_sig) { e.get_mut().apply_witness(&Witness::Signature(from)); - if e.get().is_complete() { - complete_transaction(&mut self.balances, e.get()); + if let Some(ref payment) = e.get().final_payment() { + apply_payment(&mut self.balances, payment); e.remove_entry(); } }; @@ -195,8 +212,8 @@ impl Accountant { let mut completed = vec![]; for (key, plan) in &mut self.pending { plan.apply_witness(&Witness::Timestamp(self.last_time)); - if plan.is_complete() { - complete_transaction(&mut self.balances, plan); + if let Some(ref payment) = plan.final_payment() { + apply_payment(&mut self.balances, payment); completed.push(key.clone()); } } diff --git a/src/plan.rs b/src/plan.rs index adefe613322b9c..d1bc40745e5a95 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -72,11 +72,11 @@ impl Plan { ) } - /// Return true if the spending plan requires no additional Witnesses. - pub fn is_complete(&self) -> bool { + /// Return Payment if the spending plan requires no additional Witnesses. + pub fn final_payment(&self) -> Option { match *self { - Plan::Pay(_) => true, - _ => false, + Plan::Pay(ref payment) => Some(payment.clone()), + _ => None, } } From d63506f98ce0df10d92e8253cfdf7a57ce14aba2 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 14:00:42 -0600 Subject: [PATCH 35/55] No longer allow deposits outside the constructor --- src/accountant.rs | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 0fd889a2cdbdca..7f7422686b1234 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -99,20 +99,12 @@ impl Accountant { for entry in entries { last_id = entry.id; for event in entry.events { - acc.process_verified_event(&event, false).unwrap(); + acc.process_verified_event(&event).unwrap(); } } (acc, last_id) } - fn is_deposit(allow_deposits: bool, from: &PublicKey, plan: &Plan) -> bool { - if let Plan::Pay(ref payment) = *plan { - allow_deposits && *from == payment.to - } else { - false - } - } - pub fn receiver(&self) -> &Receiver { &self.historian.receiver } @@ -123,7 +115,7 @@ impl Accountant { return Err(AccountingError::InsufficientFunds); } - self.process_verified_transaction(&tr, false)?; + self.process_verified_transaction(&tr)?; if let Err(SendError(_)) = self.historian .sender .send(Signal::Event(Event::Transaction(tr))) @@ -152,19 +144,13 @@ impl Accountant { } /// Process a Transaction that has already been verified. - fn process_verified_transaction( - self: &mut Self, - tr: &Transaction, - allow_deposits: bool, - ) -> Result<()> { + fn process_verified_transaction(&mut self, tr: &Transaction) -> Result<()> { if !self.reserve_signature(&tr.sig) { return Err(AccountingError::InvalidTransferSignature); } - if !Self::is_deposit(allow_deposits, &tr.from, &tr.plan) { - if let Some(x) = self.balances.get_mut(&tr.from) { - *x -= tr.tokens; - } + if let Some(x) = self.balances.get_mut(&tr.from) { + *x -= tr.tokens; } let mut plan = tr.plan.clone(); @@ -226,9 +212,9 @@ impl Accountant { } /// Process an Transaction or Witness that has already been verified. - fn process_verified_event(self: &mut Self, event: &Event, allow_deposits: bool) -> Result<()> { + fn process_verified_event(&mut self, event: &Event) -> Result<()> { match *event { - Event::Transaction(ref tr) => self.process_verified_transaction(tr, allow_deposits), + Event::Transaction(ref tr) => self.process_verified_transaction(tr), Event::Signature { from, tx_sig, .. } => self.process_verified_sig(from, tx_sig), Event::Timestamp { from, dt, .. } => self.process_verified_timestamp(from, dt), } @@ -237,7 +223,7 @@ impl Accountant { /// Create, sign, and process a Transaction from `keypair` to `to` of /// `n` tokens where `last_id` is the last Entry ID observed by the client. pub fn transfer( - self: &mut Self, + &mut self, n: i64, keypair: &KeyPair, to: PublicKey, @@ -252,7 +238,7 @@ impl Accountant { /// to `to` of `n` tokens on `dt` where `last_id` is the last Entry ID /// observed by the client. pub fn transfer_on_date( - self: &mut Self, + &mut self, n: i64, keypair: &KeyPair, to: PublicKey, @@ -264,7 +250,7 @@ impl Accountant { self.log_transaction(tr).map(|_| sig) } - pub fn get_balance(self: &Self, pubkey: &PublicKey) -> Option { + pub fn get_balance(&self, pubkey: &PublicKey) -> Option { self.balances.get(pubkey).cloned() } } From 90cd9bd5338141b9b9c30178971b574ebf4b9a05 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 14:14:49 -0600 Subject: [PATCH 36/55] Move balance check so that log_* methods are only used to add logging --- src/accountant.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 7f7422686b1234..d459dc3322fb4e 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -111,10 +111,6 @@ impl Accountant { /// Process and log the given Transaction. pub fn log_verified_transaction(&mut self, tr: Transaction) -> Result<()> { - if self.get_balance(&tr.from).unwrap_or(0) < tr.tokens { - return Err(AccountingError::InsufficientFunds); - } - self.process_verified_transaction(&tr)?; if let Err(SendError(_)) = self.historian .sender @@ -144,7 +140,11 @@ impl Accountant { } /// Process a Transaction that has already been verified. - fn process_verified_transaction(&mut self, tr: &Transaction) -> Result<()> { + pub fn process_verified_transaction(&mut self, tr: &Transaction) -> Result<()> { + if self.get_balance(&tr.from).unwrap_or(0) < tr.tokens { + return Err(AccountingError::InsufficientFunds); + } + if !self.reserve_signature(&tr.sig) { return Err(AccountingError::InvalidTransferSignature); } From 2b788d06b744c2fcb69732b4c23d722623ad00aa Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 14:41:07 -0600 Subject: [PATCH 37/55] Move the historian up to accountant_skel --- src/accountant.rs | 88 ++++++++++-------------------------------- src/accountant_skel.rs | 18 +++++++-- src/accountant_stub.rs | 11 +++++- src/bin/testnode.rs | 11 +++++- 4 files changed, 52 insertions(+), 76 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index d459dc3322fb4e..561b317c7f6e6c 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -7,15 +7,12 @@ use chrono::prelude::*; use entry::Entry; use event::Event; use hash::Hash; -use historian::Historian; use mint::Mint; use plan::{Payment, Plan, Witness}; -use recorder::Signal; use signature::{KeyPair, PublicKey, Signature}; use std::collections::hash_map::Entry::Occupied; use std::collections::{HashMap, HashSet}; use std::result; -use std::sync::mpsc::{Receiver, SendError}; use transaction::Transaction; #[derive(Debug, PartialEq, Eq)] @@ -23,7 +20,6 @@ pub enum AccountingError { InsufficientFunds, InvalidTransfer, InvalidTransferSignature, - SendError, } pub type Result = result::Result; @@ -34,7 +30,6 @@ fn apply_payment(balances: &mut HashMap, payment: &Payment) { } pub struct Accountant { - historian: Historian, balances: HashMap, pending: HashMap, signatures: HashSet, @@ -44,16 +39,10 @@ pub struct Accountant { impl Accountant { /// Create an Accountant using a deposit. - pub fn new_from_deposit( - start_hash: &Hash, - deposit: &Payment, - ms_per_tick: Option, - ) -> Self { + pub fn new_from_deposit(deposit: &Payment) -> Self { let mut balances = HashMap::new(); apply_payment(&mut balances, &deposit); - let historian = Historian::new(&start_hash, ms_per_tick); Accountant { - historian, balances, pending: HashMap::new(), signatures: HashSet::new(), @@ -63,16 +52,16 @@ impl Accountant { } /// Create an Accountant with only a Mint. Typically used by unit tests. - pub fn new(mint: &Mint, ms_per_tick: Option) -> Self { + pub fn new(mint: &Mint) -> Self { let deposit = Payment { to: mint.pubkey(), tokens: mint.tokens, }; - Self::new_from_deposit(&mint.seed(), &deposit, ms_per_tick) + Self::new_from_deposit(&deposit) } /// Create an Accountant using an existing ledger. - pub fn new_from_entries(entries: I, ms_per_tick: Option) -> (Self, Hash) + pub fn new_from_entries(entries: I) -> (Self, Hash) where I: IntoIterator, { @@ -80,8 +69,7 @@ impl Accountant { // The first item in the ledger is required to be an entry with zero num_hashes, // which implies its id can be used as the ledger's seed. - let entry0 = entries.next().unwrap(); - let start_hash = entry0.id; + entries.next().unwrap(); // The second item in the ledger is a special transaction where the to and from // fields are the same. That entry should be treated as a deposit, not a @@ -93,7 +81,7 @@ impl Accountant { None }; - let mut acc = Self::new_from_deposit(&start_hash, &deposit.unwrap(), ms_per_tick); + let mut acc = Self::new_from_deposit(&deposit.unwrap()); let mut last_id = entry1.id; for entry in entries { @@ -105,30 +93,13 @@ impl Accountant { (acc, last_id) } - pub fn receiver(&self) -> &Receiver { - &self.historian.receiver - } - - /// Process and log the given Transaction. - pub fn log_verified_transaction(&mut self, tr: Transaction) -> Result<()> { - self.process_verified_transaction(&tr)?; - if let Err(SendError(_)) = self.historian - .sender - .send(Signal::Event(Event::Transaction(tr))) - { - return Err(AccountingError::SendError); - } - - Ok(()) - } - /// Verify and process the given Transaction. - pub fn log_transaction(&mut self, tr: Transaction) -> Result<()> { + pub fn process_transaction(&mut self, tr: Transaction) -> Result<()> { if !tr.verify() { return Err(AccountingError::InvalidTransfer); } - self.log_verified_transaction(tr) + self.process_verified_transaction(&tr) } fn reserve_signature(&mut self, sig: &Signature) -> bool { @@ -231,7 +202,7 @@ impl Accountant { ) -> Result { let tr = Transaction::new(keypair, to, n, last_id); let sig = tr.sig; - self.log_transaction(tr).map(|_| sig) + self.process_transaction(tr).map(|_| sig) } /// Create, sign, and process a postdated Transaction from `keypair` @@ -247,7 +218,7 @@ impl Accountant { ) -> Result { let tr = Transaction::new_on_date(keypair, to, dt, n, last_id); let sig = tr.sig; - self.log_transaction(tr).map(|_| sig) + self.process_transaction(tr).map(|_| sig) } pub fn get_balance(&self, pubkey: &PublicKey) -> Option { @@ -258,14 +229,13 @@ impl Accountant { #[cfg(test)] mod tests { use super::*; - use recorder::ExitReason; use signature::KeyPairUtil; #[test] fn test_accountant() { let alice = Mint::new(10_000); let bob_pubkey = KeyPair::new().pubkey(); - let mut acc = Accountant::new(&alice, Some(2)); + let mut acc = Accountant::new(&alice); acc.transfer(1_000, &alice.keypair(), bob_pubkey, alice.seed()) .unwrap(); assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_000); @@ -273,18 +243,12 @@ mod tests { acc.transfer(500, &alice.keypair(), bob_pubkey, alice.seed()) .unwrap(); assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_500); - - drop(acc.historian.sender); - assert_eq!( - acc.historian.thread_hdl.join().unwrap(), - ExitReason::RecvDisconnected - ); } #[test] fn test_invalid_transfer() { let alice = Mint::new(11_000); - let mut acc = Accountant::new(&alice, Some(2)); + let mut acc = Accountant::new(&alice); let bob_pubkey = KeyPair::new().pubkey(); acc.transfer(1_000, &alice.keypair(), bob_pubkey, alice.seed()) .unwrap(); @@ -296,25 +260,19 @@ mod tests { let alice_pubkey = alice.keypair().pubkey(); assert_eq!(acc.get_balance(&alice_pubkey).unwrap(), 10_000); assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_000); - - drop(acc.historian.sender); - assert_eq!( - acc.historian.thread_hdl.join().unwrap(), - ExitReason::RecvDisconnected - ); } #[test] fn test_overspend_attack() { let alice = Mint::new(1); - let mut acc = Accountant::new(&alice, None); + let mut acc = Accountant::new(&alice); let bob_pubkey = KeyPair::new().pubkey(); let mut tr = Transaction::new(&alice.keypair(), bob_pubkey, 1, alice.seed()); if let Plan::Pay(ref mut payment) = tr.plan { payment.tokens = 2; // <-- attack! } assert_eq!( - acc.log_transaction(tr.clone()), + acc.process_transaction(tr.clone()), Err(AccountingError::InvalidTransfer) ); @@ -323,7 +281,7 @@ mod tests { payment.tokens = 0; // <-- whoops! } assert_eq!( - acc.log_transaction(tr.clone()), + acc.process_transaction(tr.clone()), Err(AccountingError::InvalidTransfer) ); } @@ -331,24 +289,18 @@ mod tests { #[test] fn test_transfer_to_newb() { let alice = Mint::new(10_000); - let mut acc = Accountant::new(&alice, Some(2)); + let mut acc = Accountant::new(&alice); let alice_keypair = alice.keypair(); let bob_pubkey = KeyPair::new().pubkey(); acc.transfer(500, &alice_keypair, bob_pubkey, alice.seed()) .unwrap(); assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 500); - - drop(acc.historian.sender); - assert_eq!( - acc.historian.thread_hdl.join().unwrap(), - ExitReason::RecvDisconnected - ); } #[test] fn test_transfer_on_date() { let alice = Mint::new(1); - let mut acc = Accountant::new(&alice, Some(2)); + let mut acc = Accountant::new(&alice); let alice_keypair = alice.keypair(); let bob_pubkey = KeyPair::new().pubkey(); let dt = Utc::now(); @@ -374,7 +326,7 @@ mod tests { #[test] fn test_transfer_after_date() { let alice = Mint::new(1); - let mut acc = Accountant::new(&alice, Some(2)); + let mut acc = Accountant::new(&alice); let alice_keypair = alice.keypair(); let bob_pubkey = KeyPair::new().pubkey(); let dt = Utc::now(); @@ -391,7 +343,7 @@ mod tests { #[test] fn test_cancel_transfer() { let alice = Mint::new(1); - let mut acc = Accountant::new(&alice, Some(2)); + let mut acc = Accountant::new(&alice); let alice_keypair = alice.keypair(); let bob_pubkey = KeyPair::new().pubkey(); let dt = Utc::now(); @@ -417,7 +369,7 @@ mod tests { #[test] fn test_duplicate_event_signature() { let alice = Mint::new(1); - let mut acc = Accountant::new(&alice, None); + let mut acc = Accountant::new(&alice); let sig = Signature::default(); assert!(acc.reserve_signature(&sig)); assert!(!acc.reserve_signature(&sig)); diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index 26af073993b881..8893fcb1f7f043 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -3,8 +3,11 @@ //! in flux. Clients should use AccountantStub to interact with it. use accountant::Accountant; +use historian::Historian; +use recorder::Signal; use bincode::{deserialize, serialize}; use entry::Entry; +use event::Event; use hash::Hash; use result::Result; use serde_json; @@ -13,7 +16,7 @@ use std::default::Default; use std::io::Write; use std::net::{SocketAddr, UdpSocket}; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc::channel; +use std::sync::mpsc::{channel, SendError}; use std::sync::{Arc, Mutex}; use std::thread::{spawn, JoinHandle}; use std::time::Duration; @@ -25,6 +28,7 @@ pub struct AccountantSkel { acc: Accountant, last_id: Hash, writer: W, + historian: Historian, } #[cfg_attr(feature = "cargo-clippy", allow(large_enum_variant))] @@ -59,17 +63,18 @@ pub enum Response { impl AccountantSkel { /// Create a new AccountantSkel that wraps the given Accountant. - pub fn new(acc: Accountant, last_id: Hash, writer: W) -> Self { + pub fn new(acc: Accountant, last_id: Hash, writer: W, historian: Historian) -> Self { AccountantSkel { acc, last_id, writer, + historian, } } /// Process any Entry items that have been published by the Historian. pub fn sync(&mut self) -> Hash { - while let Ok(entry) = self.acc.receiver().try_recv() { + while let Ok(entry) = self.historian.receiver.try_recv() { self.last_id = entry.id; writeln!(self.writer, "{}", serde_json::to_string(&entry).unwrap()).unwrap(); } @@ -80,8 +85,13 @@ impl AccountantSkel { pub fn log_verified_request(&mut self, msg: Request) -> Option { match msg { Request::Transaction(tr) => { - if let Err(err) = self.acc.log_verified_transaction(tr) { + if let Err(err) = self.acc.process_verified_transaction(&tr) { eprintln!("Transaction error: {:?}", err); + } else if let Err(SendError(_)) = self.historian + .sender + .send(Signal::Event(Event::Transaction(tr))) + { + eprintln!("Channel send error"); } None } diff --git a/src/accountant_stub.rs b/src/accountant_stub.rs index 518c08fafd3326..cd8b1c87a21126 100644 --- a/src/accountant_stub.rs +++ b/src/accountant_stub.rs @@ -87,6 +87,7 @@ impl AccountantStub { mod tests { use super::*; use accountant::Accountant; + use historian::Historian; use accountant_skel::AccountantSkel; use mint::Mint; use signature::{KeyPair, KeyPairUtil}; @@ -102,10 +103,16 @@ mod tests { let addr = "127.0.0.1:9000"; let send_addr = "127.0.0.1:9001"; let alice = Mint::new(10_000); - let acc = Accountant::new(&alice, Some(30)); + let acc = Accountant::new(&alice); let bob_pubkey = KeyPair::new().pubkey(); let exit = Arc::new(AtomicBool::new(false)); - let acc = Arc::new(Mutex::new(AccountantSkel::new(acc, alice.seed(), sink()))); + let historian = Historian::new(&alice.seed(), Some(30)); + let acc = Arc::new(Mutex::new(AccountantSkel::new( + acc, + alice.seed(), + sink(), + historian, + ))); let _threads = AccountantSkel::serve(acc, addr, exit.clone()).unwrap(); sleep(Duration::from_millis(300)); diff --git a/src/bin/testnode.rs b/src/bin/testnode.rs index 1e562b74fd34af..346da9f70cec8d 100644 --- a/src/bin/testnode.rs +++ b/src/bin/testnode.rs @@ -2,6 +2,7 @@ extern crate serde_json; extern crate solana; use solana::accountant::Accountant; +use solana::historian::Historian; use solana::accountant_skel::AccountantSkel; use std::io::{self, stdout, BufRead}; use std::sync::atomic::AtomicBool; @@ -14,9 +15,15 @@ fn main() { .lock() .lines() .map(|line| serde_json::from_str(&line.unwrap()).unwrap()); - let (acc, last_id) = Accountant::new_from_entries(entries, Some(1000)); + let (acc, last_id) = Accountant::new_from_entries(entries); + let historian = Historian::new(&last_id, Some(1000)); let exit = Arc::new(AtomicBool::new(false)); - let skel = Arc::new(Mutex::new(AccountantSkel::new(acc, last_id, stdout()))); + let skel = Arc::new(Mutex::new(AccountantSkel::new( + acc, + last_id, + stdout(), + historian, + ))); eprintln!("Listening on {}", addr); let threads = AccountantSkel::serve(skel, addr, exit.clone()).unwrap(); for t in threads { From daadae79879cc3a5bde5c03d48e6747881645f2c Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 14:51:38 -0600 Subject: [PATCH 38/55] Move replaying ledger out of accountant --- src/accountant.rs | 36 +----------------------------------- src/bin/testnode.rs | 30 ++++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 37 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 561b317c7f6e6c..dc73c6278d1bdd 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -4,7 +4,6 @@ //! already been signed and verified. use chrono::prelude::*; -use entry::Entry; use event::Event; use hash::Hash; use mint::Mint; @@ -60,39 +59,6 @@ impl Accountant { Self::new_from_deposit(&deposit) } - /// Create an Accountant using an existing ledger. - pub fn new_from_entries(entries: I) -> (Self, Hash) - where - I: IntoIterator, - { - let mut entries = entries.into_iter(); - - // The first item in the ledger is required to be an entry with zero num_hashes, - // which implies its id can be used as the ledger's seed. - entries.next().unwrap(); - - // The second item in the ledger 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(); - let deposit = if let Event::Transaction(ref tr) = entry1.events[0] { - tr.plan.final_payment() - } else { - None - }; - - let mut acc = Self::new_from_deposit(&deposit.unwrap()); - - let mut last_id = entry1.id; - for entry in entries { - last_id = entry.id; - for event in entry.events { - acc.process_verified_event(&event).unwrap(); - } - } - (acc, last_id) - } - /// Verify and process the given Transaction. pub fn process_transaction(&mut self, tr: Transaction) -> Result<()> { if !tr.verify() { @@ -183,7 +149,7 @@ impl Accountant { } /// Process an Transaction or Witness that has already been verified. - fn process_verified_event(&mut self, event: &Event) -> Result<()> { + pub fn process_verified_event(&mut self, event: &Event) -> Result<()> { match *event { Event::Transaction(ref tr) => self.process_verified_transaction(tr), Event::Signature { from, tx_sig, .. } => self.process_verified_sig(from, tx_sig), diff --git a/src/bin/testnode.rs b/src/bin/testnode.rs index 346da9f70cec8d..ef6700d3098830 100644 --- a/src/bin/testnode.rs +++ b/src/bin/testnode.rs @@ -2,6 +2,8 @@ extern crate serde_json; extern crate solana; use solana::accountant::Accountant; +use solana::event::Event; +use solana::entry::Entry; use solana::historian::Historian; use solana::accountant_skel::AccountantSkel; use std::io::{self, stdout, BufRead}; @@ -11,11 +13,35 @@ use std::sync::{Arc, Mutex}; fn main() { let addr = "127.0.0.1:8000"; let stdin = io::stdin(); - let entries = stdin + let mut entries = stdin .lock() .lines() .map(|line| serde_json::from_str(&line.unwrap()).unwrap()); - let (acc, last_id) = Accountant::new_from_entries(entries); + + // The first item in the ledger is required to be an entry with zero num_hashes, + // which implies its id can be used as the ledger's seed. + entries.next().unwrap(); + + // The second item in the ledger 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: Entry = entries.next().unwrap(); + let deposit = if let Event::Transaction(ref tr) = entry1.events[0] { + tr.plan.final_payment() + } else { + None + }; + + let mut acc = Accountant::new_from_deposit(&deposit.unwrap()); + + let mut last_id = entry1.id; + for entry in entries { + last_id = entry.id; + for event in entry.events { + acc.process_verified_event(&event).unwrap(); + } + } + let historian = Historian::new(&last_id, Some(1000)); let exit = Arc::new(AtomicBool::new(false)); let skel = Arc::new(Mutex::new(AccountantSkel::new( From 49708e92d3135975bdbbe1307f351fa4487e8533 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 15:02:23 -0600 Subject: [PATCH 39/55] Use last_id instead of seed It doesn't really matter, but was confusing since the seed points to an entry before the mint's deposit. --- src/accountant.rs | 18 +++++++++--------- src/accountant_stub.rs | 4 ++-- src/mint.rs | 4 ++++ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index dc73c6278d1bdd..9a04f6b9d48853 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -202,11 +202,11 @@ mod tests { let alice = Mint::new(10_000); let bob_pubkey = KeyPair::new().pubkey(); let mut acc = Accountant::new(&alice); - acc.transfer(1_000, &alice.keypair(), bob_pubkey, alice.seed()) + acc.transfer(1_000, &alice.keypair(), bob_pubkey, alice.last_id()) .unwrap(); assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_000); - acc.transfer(500, &alice.keypair(), bob_pubkey, alice.seed()) + acc.transfer(500, &alice.keypair(), bob_pubkey, alice.last_id()) .unwrap(); assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_500); } @@ -216,10 +216,10 @@ mod tests { let alice = Mint::new(11_000); let mut acc = Accountant::new(&alice); let bob_pubkey = KeyPair::new().pubkey(); - acc.transfer(1_000, &alice.keypair(), bob_pubkey, alice.seed()) + acc.transfer(1_000, &alice.keypair(), bob_pubkey, alice.last_id()) .unwrap(); assert_eq!( - acc.transfer(10_001, &alice.keypair(), bob_pubkey, alice.seed()), + acc.transfer(10_001, &alice.keypair(), bob_pubkey, alice.last_id()), Err(AccountingError::InsufficientFunds) ); @@ -233,7 +233,7 @@ mod tests { let alice = Mint::new(1); let mut acc = Accountant::new(&alice); let bob_pubkey = KeyPair::new().pubkey(); - let mut tr = Transaction::new(&alice.keypair(), bob_pubkey, 1, alice.seed()); + let mut tr = Transaction::new(&alice.keypair(), bob_pubkey, 1, alice.last_id()); if let Plan::Pay(ref mut payment) = tr.plan { payment.tokens = 2; // <-- attack! } @@ -258,7 +258,7 @@ mod tests { let mut acc = Accountant::new(&alice); let alice_keypair = alice.keypair(); let bob_pubkey = KeyPair::new().pubkey(); - acc.transfer(500, &alice_keypair, bob_pubkey, alice.seed()) + acc.transfer(500, &alice_keypair, bob_pubkey, alice.last_id()) .unwrap(); assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 500); } @@ -270,7 +270,7 @@ mod tests { let alice_keypair = alice.keypair(); let bob_pubkey = KeyPair::new().pubkey(); let dt = Utc::now(); - acc.transfer_on_date(1, &alice_keypair, bob_pubkey, dt, alice.seed()) + acc.transfer_on_date(1, &alice_keypair, bob_pubkey, dt, alice.last_id()) .unwrap(); // Alice's balance will be zero because all funds are locked up. @@ -299,7 +299,7 @@ mod tests { acc.process_verified_timestamp(alice.pubkey(), dt).unwrap(); // It's now past now, so this transfer should be processed immediately. - acc.transfer_on_date(1, &alice_keypair, bob_pubkey, dt, alice.seed()) + acc.transfer_on_date(1, &alice_keypair, bob_pubkey, dt, alice.last_id()) .unwrap(); assert_eq!(acc.get_balance(&alice.pubkey()), Some(0)); @@ -313,7 +313,7 @@ mod tests { let alice_keypair = alice.keypair(); let bob_pubkey = KeyPair::new().pubkey(); let dt = Utc::now(); - let sig = acc.transfer_on_date(1, &alice_keypair, bob_pubkey, dt, alice.seed()) + let sig = acc.transfer_on_date(1, &alice_keypair, bob_pubkey, dt, alice.last_id()) .unwrap(); // Alice's balance will be zero because all funds are locked up. diff --git a/src/accountant_stub.rs b/src/accountant_stub.rs index cd8b1c87a21126..0c7c52808746dd 100644 --- a/src/accountant_stub.rs +++ b/src/accountant_stub.rs @@ -106,10 +106,10 @@ mod tests { let acc = Accountant::new(&alice); let bob_pubkey = KeyPair::new().pubkey(); let exit = Arc::new(AtomicBool::new(false)); - let historian = Historian::new(&alice.seed(), Some(30)); + let historian = Historian::new(&alice.last_id(), Some(30)); let acc = Arc::new(Mutex::new(AccountantSkel::new( acc, - alice.seed(), + alice.last_id(), sink(), historian, ))); diff --git a/src/mint.rs b/src/mint.rs index fb9a1a2a817308..5b869c3152fd31 100644 --- a/src/mint.rs +++ b/src/mint.rs @@ -33,6 +33,10 @@ impl Mint { hash(&self.pkcs8) } + pub fn last_id(&self) -> Hash { + self.create_entries()[1].id + } + pub fn keypair(&self) -> KeyPair { KeyPair::from_pkcs8(Input::from(&self.pkcs8)).unwrap() } From 6fec8fad573623a23b2cf6a6cd66a9f80e542be1 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 20:34:18 -0600 Subject: [PATCH 40/55] Adding from to the signature is redundant --- src/transaction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transaction.rs b/src/transaction.rs index 4754335573b4b3..09821ec7af08c0 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -57,7 +57,7 @@ impl Transaction { } fn get_sign_data(&self) -> Vec { - serialize(&(&self.from, &self.plan, &self.tokens, &self.last_id)).unwrap() + serialize(&(&self.plan, &self.tokens, &self.last_id)).unwrap() } /// Sign this transaction. From 07aa2e12602b684eb8f328af4b15239ce0549fb1 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 20:47:48 -0600 Subject: [PATCH 41/55] Add witness data to entry hash Otherwise, witnesses can be dropped or reordered by a malicious generator. --- src/entry.rs | 22 ++++++++++++++++++---- src/event.rs | 25 +++++++++++++++++++------ 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/entry.rs b/src/entry.rs index 985bab83ccb72e..e9d13157f27a21 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -43,6 +43,23 @@ impl Entry { } } +fn add_event_data(hash_data: &mut Vec, event: &Event) { + match *event { + Event::Transaction(ref tr) => { + hash_data.push(0u8); + hash_data.extend_from_slice(&tr.sig); + } + Event::Signature { ref sig, .. } => { + hash_data.push(1u8); + hash_data.extend_from_slice(sig); + } + Event::Timestamp { ref sig, .. } => { + hash_data.push(2u8); + hash_data.extend_from_slice(sig); + } + } +} + /// Creates the hash `num_hashes` after `start_hash`. If the event contains /// signature, the final hash will be a hash of both the previous ID and /// the signature. @@ -55,10 +72,7 @@ pub fn next_hash(start_hash: &Hash, num_hashes: u64, events: &[Event]) -> Hash { // Hash all the event data let mut hash_data = vec![]; for event in events { - let sig = event.get_signature(); - if let Some(sig) = sig { - hash_data.extend_from_slice(&sig); - } + add_event_data(&mut hash_data, event); } if !hash_data.is_empty() { diff --git a/src/event.rs b/src/event.rs index 9940959a3de89b..dbd2a934127487 100644 --- a/src/event.rs +++ b/src/event.rs @@ -33,12 +33,13 @@ impl Event { } } - // TODO: Rename this to transaction_signature(). - /// If the Event is a Transaction, return its Signature. - pub fn get_signature(&self) -> Option { - match *self { - Event::Transaction(ref tr) => Some(tr.sig), - Event::Signature { .. } | Event::Timestamp { .. } => None, + /// Create and sign a new Witness Signature. Used for unit-testing. + pub fn new_signature(from: &KeyPair, tx_sig: Signature) -> Self { + let sig = Signature::clone_from_slice(from.sign(&tx_sig).as_ref()); + Event::Signature { + from: from.pubkey(), + tx_sig, + sig, } } @@ -52,3 +53,15 @@ impl Event { } } } + +#[cfg(test)] +mod tests { + use super::*; + use signature::{KeyPair, KeyPairUtil}; + + #[test] + fn test_event_verify() { + assert!(Event::new_timestamp(&KeyPair::new(), Utc::now()).verify()); + assert!(Event::new_signature(&KeyPair::new(), Signature::default()).verify()); + } +} From fe32159673160c5db8dc2f30f9b9b913ffaaf16a Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 21:07:38 -0600 Subject: [PATCH 42/55] Add a test to ensure witness data continues to be hashed --- src/entry.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/entry.rs b/src/entry.rs index e9d13157f27a21..e672e71c047c25 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -113,6 +113,7 @@ pub fn next_tick(start_hash: &Hash, num_hashes: u64) -> Entry { #[cfg(test)] mod tests { use super::*; + use chrono::prelude::*; use entry::create_entry; use event::Event; use hash::hash; @@ -146,6 +147,23 @@ mod tests { assert!(!e0.verify(&zero)); } + #[test] + fn test_witness_reorder_attack() { + let zero = Hash::default(); + + // First, verify entries + let keypair = KeyPair::new(); + let tr0 = Event::new_timestamp(&keypair, Utc::now()); + let tr1 = Event::new_signature(&keypair, Default::default()); + let mut e0 = create_entry(&zero, 0, vec![tr0.clone(), tr1.clone()]); + assert!(e0.verify(&zero)); + + // Next, swap two witness events and ensure verification fails. + e0.events[0] = tr1; // <-- attack + e0.events[1] = tr0; + assert!(!e0.verify(&zero)); + } + #[test] fn test_next_tick() { let zero = Hash::default(); From 94eea3abecbad2fd1fcdfb466566b22dff1ddde9 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 21:15:21 -0600 Subject: [PATCH 43/55] fmt --- src/accountant_skel.rs | 6 +++--- src/accountant_stub.rs | 2 +- src/bin/client-demo.rs | 4 ++-- src/bin/testnode.rs | 4 ++-- src/transaction.rs | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index 8893fcb1f7f043..91c85e2405d724 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -3,12 +3,13 @@ //! in flux. Clients should use AccountantStub to interact with it. use accountant::Accountant; -use historian::Historian; -use recorder::Signal; use bincode::{deserialize, serialize}; use entry::Entry; use event::Event; use hash::Hash; +use historian::Historian; +use rayon::prelude::*; +use recorder::Signal; use result::Result; use serde_json; use signature::PublicKey; @@ -22,7 +23,6 @@ use std::thread::{spawn, JoinHandle}; use std::time::Duration; use streamer; use transaction::Transaction; -use rayon::prelude::*; pub struct AccountantSkel { acc: Accountant, diff --git a/src/accountant_stub.rs b/src/accountant_stub.rs index 0c7c52808746dd..a83862ef2009b4 100644 --- a/src/accountant_stub.rs +++ b/src/accountant_stub.rs @@ -87,8 +87,8 @@ impl AccountantStub { mod tests { use super::*; use accountant::Accountant; - use historian::Historian; use accountant_skel::AccountantSkel; + use historian::Historian; use mint::Mint; use signature::{KeyPair, KeyPairUtil}; use std::io::sink; diff --git a/src/bin/client-demo.rs b/src/bin/client-demo.rs index 75f2b2b2959828..5dc4c9f056f8c5 100644 --- a/src/bin/client-demo.rs +++ b/src/bin/client-demo.rs @@ -2,15 +2,15 @@ extern crate rayon; extern crate serde_json; extern crate solana; +use rayon::prelude::*; use solana::accountant_stub::AccountantStub; use solana::mint::Mint; use solana::signature::{KeyPair, KeyPairUtil}; use solana::transaction::Transaction; use std::io::stdin; use std::net::UdpSocket; -use std::time::{Duration, Instant}; use std::thread::sleep; -use rayon::prelude::*; +use std::time::{Duration, Instant}; fn main() { let addr = "127.0.0.1:8000"; diff --git a/src/bin/testnode.rs b/src/bin/testnode.rs index ef6700d3098830..6ef325c425e880 100644 --- a/src/bin/testnode.rs +++ b/src/bin/testnode.rs @@ -2,10 +2,10 @@ extern crate serde_json; extern crate solana; use solana::accountant::Accountant; -use solana::event::Event; +use solana::accountant_skel::AccountantSkel; use solana::entry::Entry; +use solana::event::Event; use solana::historian::Historian; -use solana::accountant_skel::AccountantSkel; use std::io::{self, stdout, BufRead}; use std::sync::atomic::AtomicBool; use std::sync::{Arc, Mutex}; diff --git a/src/transaction.rs b/src/transaction.rs index 09821ec7af08c0..c88a1eea90d86b 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -2,9 +2,9 @@ use bincode::serialize; use chrono::prelude::*; -use rayon::prelude::*; use hash::Hash; use plan::{Condition, Payment, Plan}; +use rayon::prelude::*; use signature::{KeyPair, KeyPairUtil, PublicKey, Signature, SignatureUtil}; #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] From 2f80747dc74f3222533db1473a5c4d17a73af88e Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Mon, 2 Apr 2018 21:45:17 -0600 Subject: [PATCH 44/55] Move tests After we restructured for parallel verification, the tests here were unreferenced by the accountant, but still meaningful to transaction verification. --- src/accountant.rs | 38 ++------------------------------------ src/transaction.rs | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 36 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 9a04f6b9d48853..1cc22072794479 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -17,7 +17,6 @@ use transaction::Transaction; #[derive(Debug, PartialEq, Eq)] pub enum AccountingError { InsufficientFunds, - InvalidTransfer, InvalidTransferSignature, } @@ -59,15 +58,6 @@ impl Accountant { Self::new_from_deposit(&deposit) } - /// Verify and process the given Transaction. - pub fn process_transaction(&mut self, tr: Transaction) -> Result<()> { - if !tr.verify() { - return Err(AccountingError::InvalidTransfer); - } - - self.process_verified_transaction(&tr) - } - fn reserve_signature(&mut self, sig: &Signature) -> bool { if self.signatures.contains(sig) { return false; @@ -168,7 +158,7 @@ impl Accountant { ) -> Result { let tr = Transaction::new(keypair, to, n, last_id); let sig = tr.sig; - self.process_transaction(tr).map(|_| sig) + self.process_verified_transaction(&tr).map(|_| sig) } /// Create, sign, and process a postdated Transaction from `keypair` @@ -184,7 +174,7 @@ impl Accountant { ) -> Result { let tr = Transaction::new_on_date(keypair, to, dt, n, last_id); let sig = tr.sig; - self.process_transaction(tr).map(|_| sig) + self.process_verified_transaction(&tr).map(|_| sig) } pub fn get_balance(&self, pubkey: &PublicKey) -> Option { @@ -228,30 +218,6 @@ mod tests { assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_000); } - #[test] - fn test_overspend_attack() { - let alice = Mint::new(1); - let mut acc = Accountant::new(&alice); - let bob_pubkey = KeyPair::new().pubkey(); - let mut tr = Transaction::new(&alice.keypair(), bob_pubkey, 1, alice.last_id()); - if let Plan::Pay(ref mut payment) = tr.plan { - payment.tokens = 2; // <-- attack! - } - assert_eq!( - acc.process_transaction(tr.clone()), - Err(AccountingError::InvalidTransfer) - ); - - // Also, ensure all branchs of the plan spend all tokens - if let Plan::Pay(ref mut payment) = tr.plan { - payment.tokens = 0; // <-- whoops! - } - assert_eq!( - acc.process_transaction(tr.clone()), - Err(AccountingError::InvalidTransfer) - ); - } - #[test] fn test_transfer_to_newb() { let alice = Mint::new(10_000); diff --git a/src/transaction.rs b/src/transaction.rs index c88a1eea90d86b..be43b88373bb00 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -154,6 +154,24 @@ mod tests { assert!(!tr.verify()); } + #[test] + fn test_overspend_attack() { + let keypair0 = KeyPair::new(); + let keypair1 = KeyPair::new(); + let zero = Hash::default(); + let mut tr = Transaction::new(&keypair0, keypair1.pubkey(), 1, zero); + if let Plan::Pay(ref mut payment) = tr.plan { + payment.tokens = 2; // <-- attack! + } + assert!(!tr.verify()); + + // Also, ensure all branchs of the plan spend all tokens + if let Plan::Pay(ref mut payment) = tr.plan { + payment.tokens = 0; // <-- whoops! + } + assert!(!tr.verify()); + } + #[test] fn test_verify_transactions() { let alice_keypair = KeyPair::new(); From c323bd3c87d2be4fdc42496ffd95a88999e06a09 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Tue, 3 Apr 2018 09:55:33 -0600 Subject: [PATCH 45/55] Fix clippy warnings --- src/accountant.rs | 2 +- src/accountant_skel.rs | 3 +-- src/bin/client-demo.rs | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/accountant.rs b/src/accountant.rs index 1cc22072794479..9ed88571ce0b6b 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -39,7 +39,7 @@ impl Accountant { /// Create an Accountant using a deposit. pub fn new_from_deposit(deposit: &Payment) -> Self { let mut balances = HashMap::new(); - apply_payment(&mut balances, &deposit); + apply_payment(&mut balances, deposit); Accountant { balances, pending: HashMap::new(), diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index 91c85e2405d724..c5e283ce72197f 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -172,10 +172,9 @@ impl AccountantSkel { let t_responder = streamer::responder(write, exit.clone(), response_recycler.clone(), r_responder); - let skel = obj.clone(); let t_server = spawn(move || loop { let e = AccountantSkel::process( - &skel, + &obj, &r_reader, &s_responder, &packet_recycler, diff --git a/src/bin/client-demo.rs b/src/bin/client-demo.rs index 5dc4c9f056f8c5..a91b4c2cd44f33 100644 --- a/src/bin/client-demo.rs +++ b/src/bin/client-demo.rs @@ -39,8 +39,8 @@ fn main() { .collect(); let duration = now.elapsed(); let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos()); - let bsps = txs as f64 / ns as f64; - let nsps = ns as f64 / txs as f64; + let bsps = f64::from(txs) / ns as f64; + let nsps = ns as f64 / f64::from(txs); println!( "Done. {} thousand signatures per second, {}us per signature", bsps * 1_000_000_f64, From 5ac7df17f9f6e393bea314d01ffd8b3288a16167 Mon Sep 17 00:00:00 2001 From: Anatoly Yakovenko Date: Mon, 2 Apr 2018 19:32:58 -0700 Subject: [PATCH 46/55] Implement window service Batch out of order blobs until we have a contigious window. --- Cargo.toml | 1 + src/accountant_skel.rs | 78 ++++---- src/accountant_stub.rs | 3 +- src/bin/testnode.rs | 2 +- src/lib.rs | 4 +- src/packet.rs | 381 +++++++++++++++++++++++++++++++++++ src/streamer.rs | 441 +++++++++++++++-------------------------- 7 files changed, 585 insertions(+), 325 deletions(-) create mode 100644 src/packet.rs diff --git a/Cargo.toml b/Cargo.toml index fb0e76a7be2a5b..1e914dc35d8ff6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,3 +55,4 @@ bincode = "1.0.0" chrono = { version = "0.4.0", features = ["serde"] } log = "^0.4.1" matches = "^0.1.6" +byteorder = "^1.2.1" diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index c5e283ce72197f..dae08cd718c1f4 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -13,16 +13,17 @@ use recorder::Signal; use result::Result; use serde_json; use signature::PublicKey; -use std::default::Default; use std::io::Write; use std::net::{SocketAddr, UdpSocket}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{channel, SendError}; -use std::sync::{Arc, Mutex}; use std::thread::{spawn, JoinHandle}; use std::time::Duration; use streamer; +use packet; +use std::sync::{Arc, Mutex}; use transaction::Transaction; +use std::collections::VecDeque; pub struct AccountantSkel { acc: Accountant, @@ -105,55 +106,51 @@ impl AccountantSkel { fn process( obj: &Arc>>, - r_reader: &streamer::Receiver, - s_responder: &streamer::Responder, - packet_recycler: &streamer::PacketRecycler, - response_recycler: &streamer::ResponseRecycler, + packet_receiver: &streamer::PacketReceiver, + blob_sender: &streamer::BlobSender, + packet_recycler: &packet::PacketRecycler, + blob_recycler: &packet::BlobRecycler, ) -> Result<()> { let timer = Duration::new(1, 0); - let msgs = r_reader.recv_timeout(timer)?; + let msgs = packet_receiver.recv_timeout(timer)?; let msgs_ = msgs.clone(); - let rsps = streamer::allocate(response_recycler); - let rsps_ = rsps.clone(); + let mut rsps = VecDeque::new(); { let mut reqs = vec![]; for packet in &msgs.read().unwrap().packets { - let rsp_addr = packet.meta.get_addr(); + let rsp_addr = packet.meta.addr(); let sz = packet.meta.size; let req = deserialize(&packet.data[0..sz])?; reqs.push((req, rsp_addr)); } let reqs = filter_valid_requests(reqs); - - let mut num = 0; - let mut ursps = rsps.write().unwrap(); for (req, rsp_addr) in reqs { if let Some(resp) = obj.lock().unwrap().log_verified_request(req) { - if ursps.responses.len() <= num { - ursps - .responses - .resize((num + 1) * 2, streamer::Response::default()); + let blob = blob_recycler.allocate(); + { + let mut b = blob.write().unwrap(); + let v = serialize(&resp)?; + let len = v.len(); + b.data[..len].copy_from_slice(&v); + b.meta.size = len; + b.meta.set_addr(&rsp_addr); } - let rsp = &mut ursps.responses[num]; - let v = serialize(&resp)?; - let len = v.len(); - rsp.data[..len].copy_from_slice(&v); - rsp.meta.size = len; - rsp.meta.set_addr(&rsp_addr); - num += 1; + rsps.push_back(blob); } } - ursps.responses.resize(num, streamer::Response::default()); } - s_responder.send(rsps_)?; - streamer::recycle(packet_recycler, msgs_); + if !rsps.is_empty() { + //don't wake up the other side if there is nothing + blob_sender.send(rsps)?; + } + packet_recycler.recycle(msgs_); Ok(()) } /// Create a UDP microservice that forwards messages the given AccountantSkel. /// Set `exit` to shutdown its threads. pub fn serve( - obj: Arc>>, + obj: &Arc>>, addr: &str, exit: Arc, ) -> Result>> { @@ -163,28 +160,27 @@ impl AccountantSkel { local.set_port(0); let write = UdpSocket::bind(local)?; - let packet_recycler = Arc::new(Mutex::new(Vec::new())); - let response_recycler = Arc::new(Mutex::new(Vec::new())); - let (s_reader, r_reader) = channel(); - let t_receiver = streamer::receiver(read, exit.clone(), packet_recycler.clone(), s_reader)?; - - let (s_responder, r_responder) = channel(); + let packet_recycler = packet::PacketRecycler::default(); + let blob_recycler = packet::BlobRecycler::default(); + let (packet_sender, packet_receiver) = channel(); + let t_receiver = + streamer::receiver(read, exit.clone(), packet_recycler.clone(), packet_sender)?; + let (blob_sender, blob_receiver) = channel(); let t_responder = - streamer::responder(write, exit.clone(), response_recycler.clone(), r_responder); - + streamer::responder(write, exit.clone(), blob_recycler.clone(), blob_receiver); + let skel = obj.clone(); let t_server = spawn(move || loop { let e = AccountantSkel::process( - &obj, - &r_reader, - &s_responder, + &skel, + &packet_receiver, + &blob_sender, &packet_recycler, - &response_recycler, + &blob_recycler, ); if e.is_err() && exit.load(Ordering::Relaxed) { break; } }); - Ok(vec![t_receiver, t_responder, t_server]) } } diff --git a/src/accountant_stub.rs b/src/accountant_stub.rs index a83862ef2009b4..69d2f959897444 100644 --- a/src/accountant_stub.rs +++ b/src/accountant_stub.rs @@ -113,10 +113,11 @@ mod tests { sink(), historian, ))); - let _threads = AccountantSkel::serve(acc, addr, exit.clone()).unwrap(); + let _threads = AccountantSkel::serve(&acc, addr, exit.clone()).unwrap(); sleep(Duration::from_millis(300)); let socket = UdpSocket::bind(send_addr).unwrap(); + socket.set_read_timeout(Some(Duration::new(5, 0))).unwrap(); let acc = AccountantStub::new(addr, socket); let last_id = acc.get_last_id().unwrap(); diff --git a/src/bin/testnode.rs b/src/bin/testnode.rs index 6ef325c425e880..40047a9ffd7f19 100644 --- a/src/bin/testnode.rs +++ b/src/bin/testnode.rs @@ -51,7 +51,7 @@ fn main() { historian, ))); eprintln!("Listening on {}", addr); - let threads = AccountantSkel::serve(skel, addr, exit.clone()).unwrap(); + let threads = AccountantSkel::serve(&skel, addr, exit.clone()).unwrap(); for t in threads { t.join().expect("join"); } diff --git a/src/lib.rs b/src/lib.rs index 3826a1bba692ed..a44a14be2b56ec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,16 +5,18 @@ pub mod accountant_stub; pub mod entry; pub mod event; pub mod hash; -pub mod historian; pub mod ledger; pub mod mint; pub mod plan; pub mod recorder; +pub mod historian; +pub mod packet; pub mod result; pub mod signature; pub mod streamer; pub mod transaction; extern crate bincode; +extern crate byteorder; extern crate chrono; extern crate generic_array; #[macro_use] diff --git a/src/packet.rs b/src/packet.rs new file mode 100644 index 00000000000000..e6c91592d0b2f4 --- /dev/null +++ b/src/packet.rs @@ -0,0 +1,381 @@ +use std::sync::{Arc, Mutex, RwLock}; +use std::fmt; +use std::io; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket}; +use std::collections::VecDeque; +use result::{Error, Result}; + +pub type SharedPackets = Arc>; +pub type SharedBlob = Arc>; +pub type PacketRecycler = Recycler; +pub type BlobRecycler = Recycler; + +const NUM_PACKETS: usize = 1024 * 8; +const BLOB_SIZE: usize = 64 * 1024; +pub const PACKET_SIZE: usize = 256; +pub const NUM_BLOBS: usize = (NUM_PACKETS * PACKET_SIZE) / BLOB_SIZE; + +#[derive(Clone, Default)] +pub struct Meta { + pub size: usize, + pub addr: [u16; 8], + pub port: u16, + pub v6: bool, +} + +#[derive(Clone)] +pub struct Packet { + pub data: [u8; PACKET_SIZE], + pub meta: Meta, +} + +impl fmt::Debug for Packet { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Packet {{ size: {:?}, addr: {:?} }}", + self.meta.size, + self.meta.addr() + ) + } +} + +impl Default for Packet { + fn default() -> Packet { + Packet { + data: [0u8; PACKET_SIZE], + meta: Meta::default(), + } + } +} + +impl Meta { + pub fn addr(&self) -> SocketAddr { + if !self.v6 { + let addr = [ + self.addr[0] as u8, + self.addr[1] as u8, + self.addr[2] as u8, + self.addr[3] as u8, + ]; + let ipv4: Ipv4Addr = From::<[u8; 4]>::from(addr); + SocketAddr::new(IpAddr::V4(ipv4), self.port) + } else { + let ipv6: Ipv6Addr = From::<[u16; 8]>::from(self.addr); + SocketAddr::new(IpAddr::V6(ipv6), self.port) + } + } + + pub fn set_addr(&mut self, a: &SocketAddr) { + match *a { + SocketAddr::V4(v4) => { + let ip = v4.ip().octets(); + self.addr[0] = u16::from(ip[0]); + self.addr[1] = u16::from(ip[1]); + self.addr[2] = u16::from(ip[2]); + self.addr[3] = u16::from(ip[3]); + self.port = a.port(); + } + SocketAddr::V6(v6) => { + self.addr = v6.ip().segments(); + self.port = a.port(); + self.v6 = true; + } + } + } +} + +#[derive(Debug)] +pub struct Packets { + pub packets: Vec, +} + +//auto derive doesn't support large arrays +impl Default for Packets { + fn default() -> Packets { + Packets { + packets: vec![Packet::default(); NUM_PACKETS], + } + } +} + +#[derive(Clone)] +pub struct Blob { + pub data: [u8; BLOB_SIZE], + pub meta: Meta, +} + +impl fmt::Debug for Blob { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Blob {{ size: {:?}, addr: {:?} }}", + self.meta.size, + self.meta.addr() + ) + } +} + +//auto derive doesn't support large arrays +impl Default for Blob { + fn default() -> Blob { + Blob { + data: [0u8; BLOB_SIZE], + meta: Meta::default(), + } + } +} + +pub struct Recycler { + gc: Arc>>>>, +} + +impl Default for Recycler { + fn default() -> Recycler { + Recycler { + gc: Arc::new(Mutex::new(vec![])), + } + } +} + +impl Clone for Recycler { + fn clone(&self) -> Recycler { + Recycler { + gc: self.gc.clone(), + } + } +} + +impl Recycler { + pub fn allocate(&self) -> Arc> { + let mut gc = self.gc.lock().expect("recycler lock"); + gc.pop() + .unwrap_or_else(|| Arc::new(RwLock::new(Default::default()))) + } + pub fn recycle(&self, msgs: Arc>) { + let mut gc = self.gc.lock().expect("recycler lock"); + gc.push(msgs); + } +} + +impl Packets { + fn run_read_from(&mut self, socket: &UdpSocket) -> Result { + self.packets.resize(NUM_PACKETS, Packet::default()); + let mut i = 0; + //DOCUMENTED SIDE-EFFECT + //Performance out of the IO without poll + // * block on the socket until its readable + // * set the socket to non blocking + // * read until it fails + // * set it back to blocking before returning + socket.set_nonblocking(false)?; + for p in &mut self.packets { + p.meta.size = 0; + match socket.recv_from(&mut p.data) { + Err(_) if i > 0 => { + trace!("got {:?} messages", i); + break; + } + Err(e) => { + info!("recv_from err {:?}", e); + return Err(Error::IO(e)); + } + Ok((nrecv, from)) => { + p.meta.size = nrecv; + p.meta.set_addr(&from); + if i == 0 { + socket.set_nonblocking(true)?; + } + } + } + i += 1; + } + Ok(i) + } + pub fn recv_from(&mut self, socket: &UdpSocket) -> Result<()> { + let sz = self.run_read_from(socket)?; + self.packets.resize(sz, Packet::default()); + Ok(()) + } + pub fn send_to(&self, socket: &UdpSocket) -> Result<()> { + for p in &self.packets { + let a = p.meta.addr(); + socket.send_to(&p.data[..p.meta.size], &a)?; + } + Ok(()) + } +} + +impl Blob { + pub fn get_index(&self) -> Result { + let mut rdr = io::Cursor::new(&self.data[0..8]); + let r = rdr.read_u64::()?; + Ok(r) + } + pub fn set_index(&mut self, ix: u64) -> Result<()> { + let mut wtr = vec![]; + wtr.write_u64::(ix)?; + self.data[..8].clone_from_slice(&wtr); + Ok(()) + } + pub fn data(&self) -> &[u8] { + &self.data[8..] + } + pub fn data_mut(&mut self) -> &mut [u8] { + &mut self.data[8..] + } + pub fn recv_from(re: &BlobRecycler, socket: &UdpSocket) -> Result> { + let mut v = VecDeque::new(); + //DOCUMENTED SIDE-EFFECT + //Performance out of the IO without poll + // * block on the socket until its readable + // * set the socket to non blocking + // * read until it fails + // * set it back to blocking before returning + socket.set_nonblocking(false)?; + for i in 0..NUM_BLOBS { + let r = re.allocate(); + { + let mut p = r.write().unwrap(); + match socket.recv_from(&mut p.data) { + Err(_) if i > 0 => { + trace!("got {:?} messages", i); + break; + } + Err(e) => { + info!("recv_from err {:?}", e); + return Err(Error::IO(e)); + } + Ok((nrecv, from)) => { + p.meta.size = nrecv; + p.meta.set_addr(&from); + if i == 0 { + socket.set_nonblocking(true)?; + } + } + } + } + v.push_back(r); + } + Ok(v) + } + pub fn send_to( + re: &BlobRecycler, + socket: &UdpSocket, + v: &mut VecDeque, + ) -> Result<()> { + while let Some(r) = v.pop_front() { + { + let p = r.read().unwrap(); + let a = p.meta.addr(); + socket.send_to(&p.data[..p.meta.size], &a)?; + } + re.recycle(r); + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use std::net::UdpSocket; + use std::io::Write; + use std::io; + use std::collections::VecDeque; + use packet::{Blob, BlobRecycler, Packet, PacketRecycler, Packets}; + #[test] + pub fn packet_recycler_test() { + let r = PacketRecycler::default(); + let p = r.allocate(); + r.recycle(p); + } + #[test] + pub fn blob_recycler_test() { + let r = BlobRecycler::default(); + let p = r.allocate(); + r.recycle(p); + } + #[test] + pub fn packet_send_recv() { + let reader = UdpSocket::bind("127.0.0.1:0").expect("bind"); + let addr = reader.local_addr().unwrap(); + let sender = UdpSocket::bind("127.0.0.1:0").expect("bind"); + let saddr = sender.local_addr().unwrap(); + let r = PacketRecycler::default(); + let p = r.allocate(); + p.write().unwrap().packets.resize(10, Packet::default()); + for m in p.write().unwrap().packets.iter_mut() { + m.meta.set_addr(&addr); + m.meta.size = 256; + } + p.read().unwrap().send_to(&sender).unwrap(); + p.write().unwrap().recv_from(&reader).unwrap(); + for m in p.write().unwrap().packets.iter_mut() { + assert_eq!(m.meta.size, 256); + assert_eq!(m.meta.addr(), saddr); + } + + r.recycle(p); + } + + #[test] + pub fn blob_send_recv() { + trace!("start"); + let reader = UdpSocket::bind("127.0.0.1:0").expect("bind"); + let addr = reader.local_addr().unwrap(); + let sender = UdpSocket::bind("127.0.0.1:0").expect("bind"); + let r = BlobRecycler::default(); + let p = r.allocate(); + p.write().unwrap().meta.set_addr(&addr); + p.write().unwrap().meta.size = 1024; + let mut v = VecDeque::new(); + v.push_back(p); + assert_eq!(v.len(), 1); + Blob::send_to(&r, &sender, &mut v).unwrap(); + trace!("send_to"); + assert_eq!(v.len(), 0); + let mut rv = Blob::recv_from(&r, &reader).unwrap(); + trace!("recv_from"); + assert_eq!(rv.len(), 1); + let rp = rv.pop_front().unwrap(); + assert_eq!(rp.write().unwrap().meta.size, 1024); + r.recycle(rp); + } + + #[cfg(all(feature = "ipv6", test))] + #[test] + pub fn blob_ipv6_send_recv() { + let reader = UdpSocket::bind("[::1]:0").expect("bind"); + let addr = reader.local_addr().unwrap(); + let sender = UdpSocket::bind("[::1]:0").expect("bind"); + let r = BlobRecycler::default(); + let p = r.allocate(); + p.write().unwrap().meta.set_addr(&addr); + p.write().unwrap().meta.size = 1024; + let mut v = VecDeque::default(); + v.push_back(p); + Blob::send_to(&r, &sender, &mut v).unwrap(); + let mut rv = Blob::recv_from(&r, &reader).unwrap(); + let rp = rv.pop_front().unwrap(); + assert_eq!(rp.write().unwrap().meta.size, 1024); + r.recycle(rp); + } + + #[test] + pub fn debug_trait() { + write!(io::sink(), "{:?}", Packet::default()).unwrap(); + write!(io::sink(), "{:?}", Packets::default()).unwrap(); + write!(io::sink(), "{:?}", Blob::default()).unwrap(); + } + #[test] + pub fn blob_test() { + let mut b = Blob::default(); + b.set_index(::max_value()).unwrap(); + assert_eq!(b.get_index().unwrap(), ::max_value()); + b.data_mut()[0] = 1; + assert_eq!(b.data()[0], 1); + assert_eq!(b.get_index().unwrap(), ::max_value()); + } + +} diff --git a/src/streamer.rs b/src/streamer.rs index 9096f3fe64beec..87581f32df793c 100644 --- a/src/streamer.rs +++ b/src/streamer.rs @@ -1,240 +1,36 @@ -//! The 'streamer` module allows for efficient batch processing of UDP packets. - -use result::{Error, Result}; -use std::fmt; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket}; +use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc; -use std::sync::{Arc, Mutex, RwLock}; -use std::thread::{spawn, JoinHandle}; use std::time::Duration; +use std::net::UdpSocket; +use std::thread::{spawn, JoinHandle}; +use std::collections::VecDeque; +use result::Result; +use packet::{Blob, BlobRecycler, PacketRecycler, SharedBlob, SharedPackets, NUM_BLOBS}; -const BLOCK_SIZE: usize = 1024 * 8; -pub const PACKET_SIZE: usize = 256; -pub const RESP_SIZE: usize = 64 * 1024; -pub const NUM_RESP: usize = (BLOCK_SIZE * PACKET_SIZE) / RESP_SIZE; - -#[derive(Clone, Default)] -pub struct Meta { - pub size: usize, - pub addr: [u16; 8], - pub port: u16, - pub v6: bool, -} - -#[derive(Clone)] -pub struct Packet { - pub data: [u8; PACKET_SIZE], - pub meta: Meta, -} - -impl fmt::Debug for Packet { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "Packet {{ size: {:?}, addr: {:?} }}", - self.meta.size, - self.meta.get_addr() - ) - } -} - -impl Default for Packet { - fn default() -> Packet { - Packet { - data: [0u8; PACKET_SIZE], - meta: Meta::default(), - } - } -} - -impl Meta { - pub fn get_addr(&self) -> SocketAddr { - if !self.v6 { - let ipv4 = Ipv4Addr::new( - self.addr[0] as u8, - self.addr[1] as u8, - self.addr[2] as u8, - self.addr[3] as u8, - ); - SocketAddr::new(IpAddr::V4(ipv4), self.port) - } else { - let ipv6 = Ipv6Addr::new( - self.addr[0], - self.addr[1], - self.addr[2], - self.addr[3], - self.addr[4], - self.addr[5], - self.addr[6], - self.addr[7], - ); - SocketAddr::new(IpAddr::V6(ipv6), self.port) - } - } - - pub fn set_addr(&mut self, a: &SocketAddr) { - match *a { - SocketAddr::V4(v4) => { - let ip = v4.ip().octets(); - self.addr[0] = u16::from(ip[0]); - self.addr[1] = u16::from(ip[1]); - self.addr[2] = u16::from(ip[2]); - self.addr[3] = u16::from(ip[3]); - self.port = a.port(); - } - SocketAddr::V6(v6) => { - self.addr = v6.ip().segments(); - self.port = a.port(); - self.v6 = true; - } - } - } -} - -#[derive(Debug)] -pub struct Packets { - pub packets: Vec, -} - -impl Default for Packets { - fn default() -> Packets { - Packets { - packets: vec![Packet::default(); BLOCK_SIZE], - } - } -} - -#[derive(Clone)] -pub struct Response { - pub data: [u8; RESP_SIZE], - pub meta: Meta, -} - -impl fmt::Debug for Response { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "Response {{ size: {:?}, addr: {:?} }}", - self.meta.size, - self.meta.get_addr() - ) - } -} - -impl Default for Response { - fn default() -> Response { - Response { - data: [0u8; RESP_SIZE], - meta: Meta::default(), - } - } -} - -#[derive(Debug)] -pub struct Responses { - pub responses: Vec, -} - -impl Default for Responses { - fn default() -> Responses { - Responses { - responses: vec![Response::default(); NUM_RESP], - } - } -} - -pub type SharedPackets = Arc>; -pub type PacketRecycler = Arc>>; -pub type Receiver = mpsc::Receiver; -pub type Sender = mpsc::Sender; -pub type SharedResponses = Arc>; -pub type ResponseRecycler = Arc>>; -pub type Responder = mpsc::Sender; -pub type ResponseReceiver = mpsc::Receiver; - -impl Packets { - fn run_read_from(&mut self, socket: &UdpSocket) -> Result { - self.packets.resize(BLOCK_SIZE, Packet::default()); - let mut i = 0; - socket.set_nonblocking(false)?; - for p in &mut self.packets { - p.meta.size = 0; - match socket.recv_from(&mut p.data) { - Err(_) if i > 0 => { - trace!("got {:?} messages", i); - break; - } - Err(e) => { - info!("recv_from err {:?}", e); - return Err(Error::IO(e)); - } - Ok((nrecv, from)) => { - p.meta.size = nrecv; - p.meta.set_addr(&from); - if i == 0 { - socket.set_nonblocking(true)?; - } - } - } - i += 1; - } - Ok(i) - } - fn read_from(&mut self, socket: &UdpSocket) -> Result<()> { - let sz = self.run_read_from(socket)?; - self.packets.resize(sz, Packet::default()); - Ok(()) - } -} - -impl Responses { - fn send_to(&self, socket: &UdpSocket, num: &mut usize) -> Result<()> { - for p in &self.responses { - let a = p.meta.get_addr(); - socket.send_to(&p.data[..p.meta.size], &a)?; - //TODO(anatoly): wtf do we do about errors? - *num += 1; - } - Ok(()) - } -} - -pub fn allocate(recycler: &Arc>>>>) -> Arc> -where - T: Default, -{ - let mut gc = recycler.lock().expect("lock"); - gc.pop() - .unwrap_or_else(|| Arc::new(RwLock::new(Default::default()))) -} - -pub fn recycle(recycler: &Arc>>>>, msgs: Arc>) -where - T: Default, -{ - let mut gc = recycler.lock().expect("lock"); - gc.push(msgs); -} +pub type PacketReceiver = mpsc::Receiver; +pub type PacketSender = mpsc::Sender; +pub type BlobSender = mpsc::Sender>; +pub type BlobReceiver = mpsc::Receiver>; fn recv_loop( sock: &UdpSocket, exit: &Arc, - recycler: &PacketRecycler, - channel: &Sender, + re: &PacketRecycler, + channel: &PacketSender, ) -> Result<()> { loop { - let msgs = allocate(recycler); + let msgs = re.allocate(); let msgs_ = msgs.clone(); loop { - match msgs.write().unwrap().read_from(sock) { + match msgs.write().unwrap().recv_from(sock) { Ok(()) => { channel.send(msgs_)?; break; } Err(_) => { if exit.load(Ordering::Relaxed) { - recycle(recycler, msgs_); + re.recycle(msgs_); return Ok(()); } } @@ -247,7 +43,7 @@ pub fn receiver( sock: UdpSocket, exit: Arc, recycler: PacketRecycler, - channel: Sender, + channel: PacketSender, ) -> Result> { let timer = Duration::new(1, 0); sock.set_read_timeout(Some(timer))?; @@ -257,43 +53,105 @@ pub fn receiver( })) } -fn recv_send(sock: &UdpSocket, recycler: &ResponseRecycler, r: &ResponseReceiver) -> Result<()> { +fn recv_send(sock: &UdpSocket, recycler: &BlobRecycler, r: &BlobReceiver) -> Result<()> { let timer = Duration::new(1, 0); - let msgs = r.recv_timeout(timer)?; - let msgs_ = msgs.clone(); - let mut num = 0; - msgs.read().unwrap().send_to(sock, &mut num)?; - recycle(recycler, msgs_); + let mut msgs = r.recv_timeout(timer)?; + Blob::send_to(recycler, sock, &mut msgs)?; Ok(()) } pub fn responder( sock: UdpSocket, exit: Arc, - recycler: ResponseRecycler, - r: ResponseReceiver, + recycler: BlobRecycler, + r: BlobReceiver, ) -> JoinHandle<()> { spawn(move || loop { - if recv_send(&sock, &recycler, &r).is_err() && exit.load(Ordering::Relaxed) { + if recv_send(&sock, &recycler, &r).is_err() || exit.load(Ordering::Relaxed) { break; } }) } +//TODO, we would need to stick block authentication before we create the +//window. +fn recv_window( + window: &mut Vec>, + recycler: &BlobRecycler, + consumed: &mut usize, + socket: &UdpSocket, + s: &BlobSender, +) -> Result<()> { + let mut dq = Blob::recv_from(recycler, socket)?; + while let Some(b) = dq.pop_front() { + let b_ = b.clone(); + let mut p = b.write().unwrap(); + let pix = p.get_index()? as usize; + let w = pix % NUM_BLOBS; + //TODO, after the block are authenticated + //if we get different blocks at the same index + //that is a network failure/attack + { + if window[w].is_none() { + window[w] = Some(b_); + } else { + debug!("duplicate blob at index {:}", w); + } + //send a contiguous set of blocks + let mut dq = VecDeque::new(); + loop { + let k = *consumed % NUM_BLOBS; + if window[k].is_none() { + break; + } + dq.push_back(window[k].clone().unwrap()); + window[k] = None; + *consumed += 1; + } + if !dq.is_empty() { + s.send(dq)?; + } + } + } + Ok(()) +} + +pub fn window( + sock: UdpSocket, + exit: Arc, + r: BlobRecycler, + s: BlobSender, +) -> JoinHandle<()> { + spawn(move || { + let mut window = vec![None; NUM_BLOBS]; + let mut consumed = 0; + let timer = Duration::new(1, 0); + sock.set_read_timeout(Some(timer)).unwrap(); + loop { + if recv_window(&mut window, &r, &mut consumed, &sock, &s).is_err() + || exit.load(Ordering::Relaxed) + { + break; + } + } + }) +} + #[cfg(all(feature = "unstable", test))] mod bench { extern crate test; use self::test::Bencher; use result::Result; use std::net::{SocketAddr, UdpSocket}; - use std::sync::atomic::{AtomicBool, Ordering}; - use std::sync::mpsc::channel; use std::sync::{Arc, Mutex}; use std::thread::sleep; use std::thread::{spawn, JoinHandle}; use std::time::Duration; use std::time::SystemTime; - use streamer::{allocate, receiver, recycle, Packet, PacketRecycler, Receiver, PACKET_SIZE}; + use std::sync::mpsc::channel; + use std::sync::atomic::{AtomicBool, Ordering}; + use packet::{Packet, PacketRecycler, PACKET_SIZE}; + use streamer::{receiver, PacketReceiver}; fn producer( addr: &SocketAddr, @@ -301,7 +159,7 @@ mod bench { exit: Arc, ) -> JoinHandle<()> { let send = UdpSocket::bind("0.0.0.0:0").unwrap(); - let msgs = allocate(&recycler); + let msgs = recycler.allocate(); let msgs_ = msgs.clone(); msgs.write().unwrap().packets.resize(10, Packet::default()); for w in msgs.write().unwrap().packets.iter_mut() { @@ -314,7 +172,7 @@ mod bench { } let mut num = 0; for p in msgs_.read().unwrap().packets.iter() { - let a = p.meta.get_addr(); + let a = p.meta.addr(); send.send_to(&p.data[..p.meta.size], &a).unwrap(); num += 1; } @@ -326,7 +184,7 @@ mod bench { recycler: PacketRecycler, exit: Arc, rvs: Arc>, - r: Receiver, + r: PacketReceiver, ) -> JoinHandle<()> { spawn(move || loop { if exit.load(Ordering::Relaxed) { @@ -337,7 +195,7 @@ mod bench { Ok(msgs) => { let msgs_ = msgs.clone(); *rvs.lock().unwrap() += msgs.read().unwrap().packets.len(); - recycle(&recycler, msgs_); + recycler.recycle(msgs_); } _ => (), } @@ -347,16 +205,16 @@ mod bench { let read = UdpSocket::bind("127.0.0.1:0")?; let addr = read.local_addr()?; let exit = Arc::new(AtomicBool::new(false)); - let recycler = Arc::new(Mutex::new(Vec::new())); + let pack_recycler = PacketRecycler::default(); let (s_reader, r_reader) = channel(); - let t_reader = receiver(read, exit.clone(), recycler.clone(), s_reader)?; - let t_producer1 = producer(&addr, recycler.clone(), exit.clone()); - let t_producer2 = producer(&addr, recycler.clone(), exit.clone()); - let t_producer3 = producer(&addr, recycler.clone(), exit.clone()); + let t_reader = receiver(read, exit.clone(), pack_recycler.clone(), s_reader)?; + let t_producer1 = producer(&addr, pack_recycler.clone(), exit.clone()); + let t_producer2 = producer(&addr, pack_recycler.clone(), exit.clone()); + let t_producer3 = producer(&addr, pack_recycler.clone(), exit.clone()); let rvs = Arc::new(Mutex::new(0)); - let t_sink = sink(recycler.clone(), exit.clone(), rvs.clone(), r_reader); + let t_sink = sink(pack_recycler.clone(), exit.clone(), rvs.clone(), r_reader); let start = SystemTime::now(); let start_val = *rvs.lock().unwrap(); @@ -383,17 +241,18 @@ mod bench { #[cfg(test)] mod test { - use std::io; - use std::io::Write; use std::net::UdpSocket; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::channel; - use std::sync::{Arc, Mutex}; + use std::io::Write; + use std::io; + use std::collections::VecDeque; use std::time::Duration; - use streamer::{allocate, receiver, responder, Packet, Packets, Receiver, Response, Responses, - PACKET_SIZE}; + use std::sync::Arc; + use packet::{Blob, BlobRecycler, Packet, PacketRecycler, Packets, PACKET_SIZE}; + use streamer::{receiver, responder, window, BlobReceiver, PacketReceiver}; - fn get_msgs(r: Receiver, num: &mut usize) { + fn get_msgs(r: PacketReceiver, num: &mut usize) { for _t in 0..5 { let timer = Duration::new(1, 0); match r.recv_timeout(timer) { @@ -405,25 +264,33 @@ mod test { } } } - #[cfg(ipv6)] #[test] - pub fn streamer_send_test_ipv6() { - let read = UdpSocket::bind("[::1]:0").expect("bind"); + pub fn streamer_debug() { + write!(io::sink(), "{:?}", Packet::default()).unwrap(); + write!(io::sink(), "{:?}", Packets::default()).unwrap(); + write!(io::sink(), "{:?}", Blob::default()).unwrap(); + } + #[test] + pub fn streamer_send_test() { + let read = UdpSocket::bind("127.0.0.1:0").expect("bind"); let addr = read.local_addr().unwrap(); - let send = UdpSocket::bind("[::1]:0").expect("bind"); - let exit = Arc::new(Mutex::new(false)); - let recycler = Arc::new(Mutex::new(Vec::new())); + let send = UdpSocket::bind("127.0.0.1:0").expect("bind"); + let exit = Arc::new(AtomicBool::new(false)); + let pack_recycler = PacketRecycler::default(); + let resp_recycler = BlobRecycler::default(); let (s_reader, r_reader) = channel(); - let t_receiver = receiver(read, exit.clone(), recycler.clone(), s_reader).unwrap(); + let t_receiver = receiver(read, exit.clone(), pack_recycler.clone(), s_reader).unwrap(); let (s_responder, r_responder) = channel(); - let t_responder = responder(send, exit.clone(), recycler.clone(), r_responder); - let msgs = allocate(&recycler); - msgs.write().unwrap().packets.resize(10, Packet::default()); - for (i, w) in msgs.write().unwrap().packets.iter_mut().enumerate() { + let t_responder = responder(send, exit.clone(), resp_recycler.clone(), r_responder); + let mut msgs = VecDeque::new(); + for i in 0..10 { + let b = resp_recycler.allocate(); + let b_ = b.clone(); + let mut w = b.write().unwrap(); w.data[0] = i as u8; - w.size = PACKET_SIZE; - w.set_addr(&addr); - assert_eq!(w.get_addr(), addr); + w.meta.size = PACKET_SIZE; + w.meta.set_addr(&addr); + msgs.push_back(b_); } s_responder.send(msgs).expect("send"); let mut num = 0; @@ -433,39 +300,51 @@ mod test { t_receiver.join().expect("join"); t_responder.join().expect("join"); } - #[test] - pub fn streamer_debug() { - write!(io::sink(), "{:?}", Packet::default()).unwrap(); - write!(io::sink(), "{:?}", Packets::default()).unwrap(); - write!(io::sink(), "{:?}", Response::default()).unwrap(); - write!(io::sink(), "{:?}", Responses::default()).unwrap(); + + fn get_blobs(r: BlobReceiver, num: &mut usize) { + for _t in 0..5 { + let timer = Duration::new(1, 0); + match r.recv_timeout(timer) { + Ok(m) => { + for (i, v) in m.iter().enumerate() { + assert_eq!(v.read().unwrap().get_index().unwrap() as usize, *num + i); + } + *num += m.len(); + } + e => println!("error {:?}", e), + } + if *num == 10 { + break; + } + } } + #[test] - pub fn streamer_send_test() { + pub fn window_send_test() { let read = UdpSocket::bind("127.0.0.1:0").expect("bind"); let addr = read.local_addr().unwrap(); let send = UdpSocket::bind("127.0.0.1:0").expect("bind"); let exit = Arc::new(AtomicBool::new(false)); - let packet_recycler = Arc::new(Mutex::new(Vec::new())); - let resp_recycler = Arc::new(Mutex::new(Vec::new())); + let resp_recycler = BlobRecycler::default(); let (s_reader, r_reader) = channel(); - let t_receiver = receiver(read, exit.clone(), packet_recycler.clone(), s_reader).unwrap(); + let t_receiver = window(read, exit.clone(), resp_recycler.clone(), s_reader); let (s_responder, r_responder) = channel(); let t_responder = responder(send, exit.clone(), resp_recycler.clone(), r_responder); - let msgs = allocate(&resp_recycler); - msgs.write() - .unwrap() - .responses - .resize(10, Response::default()); - for (i, w) in msgs.write().unwrap().responses.iter_mut().enumerate() { - w.data[0] = i as u8; + let mut msgs = VecDeque::new(); + for v in 0..10 { + let i = 9 - v; + let b = resp_recycler.allocate(); + let b_ = b.clone(); + let mut w = b.write().unwrap(); + w.set_index(i).unwrap(); + assert_eq!(i, w.get_index().unwrap()); w.meta.size = PACKET_SIZE; w.meta.set_addr(&addr); - assert_eq!(w.meta.get_addr(), addr); + msgs.push_back(b_); } s_responder.send(msgs).expect("send"); let mut num = 0; - get_msgs(r_reader, &mut num); + get_blobs(r_reader, &mut num); assert_eq!(num, 10); exit.store(true, Ordering::Relaxed); t_receiver.join().expect("join"); From 4fd1ebc0da46b177f7a61682ceabe36ee7adc21f Mon Sep 17 00:00:00 2001 From: Stephen Akridge Date: Mon, 26 Mar 2018 21:07:11 -0700 Subject: [PATCH 47/55] In-progress change for cuda verify integration --- Cargo.toml | 1 + Makefile | 46 ++++++++++++ build.rs | 8 ++ dummy.c | 51 +++++++++++++ src/accountant.rs | 10 ++- src/accountant_skel.rs | 162 +++++++++++++++++++++++++++++++---------- src/bin/client-demo.rs | 4 +- src/bin/testnode.rs | 4 +- src/event.rs | 2 +- src/gpu.rs | 84 +++++++++++++++++++++ src/lib.rs | 6 +- src/mint.rs | 4 +- src/packet.rs | 26 ++++--- src/plan.rs | 1 + src/streamer.rs | 32 ++++---- src/transaction.rs | 106 +++++++++++++++++++-------- 16 files changed, 437 insertions(+), 110 deletions(-) create mode 100644 Makefile create mode 100644 build.rs create mode 100644 dummy.c create mode 100644 src/gpu.rs diff --git a/Cargo.toml b/Cargo.toml index 1e914dc35d8ff6..9c5655e09cba03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,3 +56,4 @@ chrono = { version = "0.4.0", features = ["serde"] } log = "^0.4.1" matches = "^0.1.6" byteorder = "^1.2.1" +libc = "^0.2.1" diff --git a/Makefile b/Makefile new file mode 100644 index 00000000000000..c7f06fd5e7c753 --- /dev/null +++ b/Makefile @@ -0,0 +1,46 @@ +export RUST_LOG=packet=TRACE +#export RUST_BACKTRACE=1 + +all: htest + +htest:wfmt + #cargo test accountant_skel::tests::test_layout -- --nocapture 2>&1 | head -n 30 + #cargo test accountant_skel::tests::test_layout -- --nocapture + cargo test accountant_stub -- --nocapture 2>&1 | head -n 30 + +ci: test bench release clippy ipv6 + +build: + cargo build 2>&1 | head -n 30 + +loop: + while true; do fswatch -1 -r src; make; done + +test: + cargo test + +clippy: + cargo +nightly clippy --features="unstable" + +cov: + docker run -it --rm --security-opt seccomp=unconfined --volume "$$PWD:/volume" elmtai/docker-rust-kcov + +wfmt: + cargo fmt -- --write-mode=overwrite + +release: + cargo build --all-targets --release + +node: + cat genesis.log | cargo run --bin silk-testnode > transactions0.log + +bench: + cargo +nightly bench --features="unstable" -- --nocapture + +ipv6: + cargo test ipv6 --features="ipv6" -- --nocapture + +lib:libcuda_verify_ed25519.a +libcuda_verify_ed25519.a:dummy.c + cc -o dummy.o -c dummy.c + ar -cvq libcuda_verify_ed25519.a dummy.o diff --git a/build.rs b/build.rs new file mode 100644 index 00000000000000..10cc373cb53854 --- /dev/null +++ b/build.rs @@ -0,0 +1,8 @@ +fn main() { + println!("cargo:rustc-link-search=native=."); + println!("cargo:rustc-link-lib=static=cuda_verify_ed25519"); + println!("cargo:rustc-link-search=native=/usr/local/cuda/lib64"); + println!("cargo:rustc-link-lib=dylib=cudart"); + println!("cargo:rustc-link-lib=dylib=cuda"); + println!("cargo:rustc-link-lib=dylib=cudadevrt"); +} diff --git a/dummy.c b/dummy.c new file mode 100644 index 00000000000000..850df1eb729557 --- /dev/null +++ b/dummy.c @@ -0,0 +1,51 @@ +#include +#include +#include + +#define PACKET_SIZE 288 +#define PACKET_DATA_SIZE 256 +union Packet { + char data[PACKET_DATA_SIZE]; + char total[PACKET_SIZE]; +}; + +struct Elems { + union Packet *packet; + uint32_t len; +}; + +int ed25519_verify_many( + const struct Elems *vecs, + uint32_t num, + uint32_t message_size, + uint32_t public_key_offset, + uint32_t signature_offset, + uint32_t signed_message_offset, + uint32_t signed_message_len_offset, + uint8_t *out +) { + int i, p = 0; + assert(num > 0); + for(i = 0; i < num; ++i) { + int j; + assert(vecs[i].len > 0); + assert(message_size == PACKET_SIZE); + assert(signed_message_len_offset == PACKET_DATA_SIZE); + for(j = 0; j < vecs[i].len; ++j) { + uint32_t *len = (uint32_t*)&vecs[i].packet[j].total[signed_message_len_offset]; + assert(*len <= PACKET_DATA_SIZE); + p += 1; + if(public_key_offset > *len - 32) { + continue; + } + if(signature_offset > *len - 64) { + continue; + } + if(signed_message_offset > *len) { + continue; + } + out[p - 1] = 1; + } + } + return 0; +} diff --git a/src/accountant.rs b/src/accountant.rs index 9ed88571ce0b6b..7ab0f0772af53d 100644 --- a/src/accountant.rs +++ b/src/accountant.rs @@ -3,6 +3,8 @@ //! on behalf of the caller, and a private low-level API for when they have //! already been signed and verified. +extern crate libc; + use chrono::prelude::*; use event::Event; use hash::Hash; @@ -68,7 +70,7 @@ impl Accountant { /// Process a Transaction that has already been verified. pub fn process_verified_transaction(&mut self, tr: &Transaction) -> Result<()> { - if self.get_balance(&tr.from).unwrap_or(0) < tr.tokens { + if self.get_balance(&tr.data.from).unwrap_or(0) < tr.data.tokens { return Err(AccountingError::InsufficientFunds); } @@ -76,11 +78,11 @@ impl Accountant { return Err(AccountingError::InvalidTransferSignature); } - if let Some(x) = self.balances.get_mut(&tr.from) { - *x -= tr.tokens; + if let Some(x) = self.balances.get_mut(&tr.data.from) { + *x -= tr.data.tokens; } - let mut plan = tr.plan.clone(); + let mut plan = tr.data.plan.clone(); plan.apply_witness(&Witness::Timestamp(self.last_time)); if let Some(ref payment) = plan.final_payment() { diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index dae08cd718c1f4..973a37e240cfcc 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -6,24 +6,26 @@ use accountant::Accountant; use bincode::{deserialize, serialize}; use entry::Entry; use event::Event; +use gpu; use hash::Hash; use historian::Historian; +use packet; +use packet::SharedPackets; use rayon::prelude::*; use recorder::Signal; use result::Result; use serde_json; use signature::PublicKey; +use std::collections::VecDeque; use std::io::Write; use std::net::{SocketAddr, UdpSocket}; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc::{channel, SendError}; +use std::sync::mpsc::{channel, Receiver, SendError, Sender}; +use std::sync::{Arc, Mutex}; use std::thread::{spawn, JoinHandle}; use std::time::Duration; use streamer; -use packet; -use std::sync::{Arc, Mutex}; use transaction::Transaction; -use std::collections::VecDeque; pub struct AccountantSkel { acc: Accountant, @@ -44,14 +46,14 @@ impl Request { /// Verify the request is valid. pub fn verify(&self) -> bool { match *self { - Request::Transaction(ref tr) => tr.verify(), + Request::Transaction(ref tr) => tr.plan_verify(), _ => true, } } } /// Parallel verfication of a batch of requests. -fn filter_valid_requests(reqs: Vec<(Request, SocketAddr)>) -> Vec<(Request, SocketAddr)> { +pub fn filter_valid_requests(reqs: Vec<(Request, SocketAddr)>) -> Vec<(Request, SocketAddr)> { reqs.into_par_iter().filter({ |x| x.0.verify() }).collect() } @@ -83,14 +85,18 @@ impl AccountantSkel { } /// Process Request items sent by clients. - pub fn log_verified_request(&mut self, msg: Request) -> Option { + pub fn log_verified_request(&mut self, msg: Request, verify: u8) -> Option { match msg { + Request::Transaction(_) if verify == 0 => { + eprintln!("Transaction falid sigverify"); + None + } Request::Transaction(tr) => { if let Err(err) = self.acc.process_verified_transaction(&tr) { eprintln!("Transaction error: {:?}", err); } else if let Err(SendError(_)) = self.historian .sender - .send(Signal::Event(Event::Transaction(tr))) + .send(Signal::Event(Event::Transaction(tr.clone()))) { eprintln!("Channel send error"); } @@ -104,46 +110,99 @@ impl AccountantSkel { } } + fn verifier( + recvr: &streamer::PacketReceiver, + sendr: &Sender<(Vec, Vec>)>, + ) -> Result<()> { + let timer = Duration::new(1, 0); + let msgs = recvr.recv_timeout(timer)?; + //println!("got msgs"); + let mut v = Vec::new(); + v.push(msgs); + while let Ok(more) = recvr.try_recv() { + //println!("got more msgs"); + v.push(more); + } + //println!("verifying"); + let rvs = gpu::ecdsa_verify(&v); + //println!("verified!"); + let mut len = 0; + let mut sv = Vec::new(); + let mut sr = Vec::new(); + for (v, r) in v.iter().zip(rvs.iter()) { + if len + r.len() >= 256 { + println!("sending {}", len); + sendr.send((sv, sr))?; + sv = Vec::new(); + sr = Vec::new(); + len = 0; + } + sv.push(v.clone()); + sr.push(r.clone()); + len += r.len(); + assert!(len < 256); + } + if !sv.is_empty() { + sendr.send((sv, sr))?; + } + Ok(()) + } + + pub fn deserialize_packets(p: &packet::Packets) -> Vec> { + //deserealize in parallel + let mut r = vec![]; + for x in &p.packets { + let rsp_addr = x.meta.addr(); + let sz = x.meta.size; + if let Ok(req) = deserialize(&x.data[0..sz]) { + r.push(Some((req, rsp_addr))); + } else { + r.push(None); + } + } + r + } + fn process( obj: &Arc>>, - packet_receiver: &streamer::PacketReceiver, + verified_receiver: &Receiver<(Vec, Vec>)>, blob_sender: &streamer::BlobSender, packet_recycler: &packet::PacketRecycler, blob_recycler: &packet::BlobRecycler, ) -> Result<()> { let timer = Duration::new(1, 0); - let msgs = packet_receiver.recv_timeout(timer)?; - let msgs_ = msgs.clone(); - let mut rsps = VecDeque::new(); - { - let mut reqs = vec![]; - for packet in &msgs.read().unwrap().packets { - let rsp_addr = packet.meta.addr(); - let sz = packet.meta.size; - let req = deserialize(&packet.data[0..sz])?; - reqs.push((req, rsp_addr)); - } - let reqs = filter_valid_requests(reqs); - for (req, rsp_addr) in reqs { - if let Some(resp) = obj.lock().unwrap().log_verified_request(req) { - let blob = blob_recycler.allocate(); - { - let mut b = blob.write().unwrap(); - let v = serialize(&resp)?; - let len = v.len(); - b.data[..len].copy_from_slice(&v); - b.meta.size = len; - b.meta.set_addr(&rsp_addr); + let (mms, vvs) = verified_receiver.recv_timeout(timer)?; + for (msgs, vers) in mms.into_iter().zip(vvs.into_iter()) { + let msgs_ = msgs.clone(); + let mut rsps = VecDeque::new(); + { + let reqs = Self::deserialize_packets(&((*msgs).read().unwrap())); + for (data, v) in reqs.into_iter().zip(vers.into_iter()) { + if let Some((req, rsp_addr)) = data { + if !req.verify() { + continue; + } + if let Some(resp) = obj.lock().unwrap().log_verified_request(req, v) { + let blob = blob_recycler.allocate(); + { + let mut b = blob.write().unwrap(); + let v = serialize(&resp)?; + let len = v.len(); + b.data[..len].copy_from_slice(&v); + b.meta.size = len; + b.meta.set_addr(&rsp_addr); + } + rsps.push_back(blob); + } } - rsps.push_back(blob); } } + if !rsps.is_empty() { + //don't wake up the other side if there is nothing + blob_sender.send(rsps)?; + } + packet_recycler.recycle(msgs_); } - if !rsps.is_empty() { - //don't wake up the other side if there is nothing - blob_sender.send(rsps)?; - } - packet_recycler.recycle(msgs_); Ok(()) } @@ -168,11 +227,21 @@ impl AccountantSkel { let (blob_sender, blob_receiver) = channel(); let t_responder = streamer::responder(write, exit.clone(), blob_recycler.clone(), blob_receiver); + let (verified_sender, verified_receiver) = channel(); + + let exit_ = exit.clone(); + let t_verifier = spawn(move || loop { + let e = Self::verifier(&packet_receiver, &verified_sender); + if e.is_err() && exit_.load(Ordering::Relaxed) { + break; + } + }); + let skel = obj.clone(); let t_server = spawn(move || loop { let e = AccountantSkel::process( &skel, - &packet_receiver, + &verified_receiver, &blob_sender, &packet_recycler, &blob_recycler, @@ -181,6 +250,21 @@ impl AccountantSkel { break; } }); - Ok(vec![t_receiver, t_responder, t_server]) + Ok(vec![t_receiver, t_responder, t_server, t_verifier]) + } +} + +#[cfg(test)] +mod tests { + use accountant_skel::Request; + use bincode::serialize; + use gpu; + use transaction::{memfind, test_tx}; + #[test] + fn test_layout() { + let tr = test_tx(); + let tx = serialize(&tr).unwrap(); + let packet = serialize(&Request::Transaction(tr)).unwrap(); + assert_matches!(memfind(&packet, &tx), Some(gpu::TX_OFFSET)); } } diff --git a/src/bin/client-demo.rs b/src/bin/client-demo.rs index a91b4c2cd44f33..802953273f5f10 100644 --- a/src/bin/client-demo.rs +++ b/src/bin/client-demo.rs @@ -13,8 +13,8 @@ use std::thread::sleep; use std::time::{Duration, Instant}; fn main() { - let addr = "127.0.0.1:8000"; - let send_addr = "127.0.0.1:8001"; + let addr = "127.0.0.1:9000"; + let send_addr = "127.0.0.1:9001"; let mint: Mint = serde_json::from_reader(stdin()).unwrap(); let mint_keypair = mint.keypair(); diff --git a/src/bin/testnode.rs b/src/bin/testnode.rs index 40047a9ffd7f19..21b4fa5b4de19e 100644 --- a/src/bin/testnode.rs +++ b/src/bin/testnode.rs @@ -11,7 +11,7 @@ use std::sync::atomic::AtomicBool; use std::sync::{Arc, Mutex}; fn main() { - let addr = "127.0.0.1:8000"; + let addr = "127.0.0.1:9000"; let stdin = io::stdin(); let mut entries = stdin .lock() @@ -27,7 +27,7 @@ fn main() { // transfer to oneself. let entry1: Entry = entries.next().unwrap(); let deposit = if let Event::Transaction(ref tr) = entry1.events[0] { - tr.plan.final_payment() + tr.data.plan.final_payment() } else { None }; diff --git a/src/event.rs b/src/event.rs index dbd2a934127487..6cb6ffdda7fe53 100644 --- a/src/event.rs +++ b/src/event.rs @@ -47,7 +47,7 @@ impl Event { /// spending plan is valid. pub fn verify(&self) -> bool { match *self { - Event::Transaction(ref tr) => tr.verify(), + Event::Transaction(ref tr) => tr.plan_verify(), Event::Signature { from, tx_sig, sig } => sig.verify(&from, &tx_sig), Event::Timestamp { from, dt, sig } => sig.verify(&from, &serialize(&dt).unwrap()), } diff --git a/src/gpu.rs b/src/gpu.rs new file mode 100644 index 00000000000000..4d0369c8b5ea78 --- /dev/null +++ b/src/gpu.rs @@ -0,0 +1,84 @@ +use packet::SharedPackets; +use packet::{Packet, PACKET_DATA_SIZE}; +use std::mem::size_of; + +pub const TX_OFFSET: usize = 4; +pub const SIGNED_DATA_OFFSET: usize = 72; +pub const SIG_OFFSET: usize = 8; +pub const PUB_KEY_OFFSET: usize = 80; + +#[repr(C)] +struct Elems { + elems: *const Packet, + num: u32, +} + +#[link(name = "cuda_verify_ed25519")] +extern "C" { + fn ed25519_verify_many( + vecs: *const Elems, + num: u32, //number of vecs + message_size: u32, //size of each element inside the elems field of the vec + public_key_offset: u32, + signature_offset: u32, + signed_message_offset: u32, + signed_message_len_offset: u32, + out: *mut u8, //combined length of all the items in vecs + ) -> u32; +} + +pub fn ecdsa_verify(batches: &Vec) -> Vec> { + let mut out = Vec::new(); + let mut elems = Vec::new(); + let mut locks = Vec::new(); + let mut rvs = Vec::new(); + for packets in batches { + locks.push(packets.read().unwrap()); + } + let mut num = 0; + for p in locks { + elems.push(Elems { + elems: p.packets.as_ptr(), + num: p.packets.len() as u32, + }); + let mut v = Vec::new(); + v.resize(p.packets.len(), 0); + rvs.push(v); + num += p.packets.len(); + } + out.resize(num, 0); + //println!("Starting verify num packets: {}", num); + //println!("elem len: {}", elems.len() as u32); + //println!("packet sizeof: {}", size_of::() as u32); + //println!("pub key: {}", (TX_OFFSET + PUB_KEY_OFFSET) as u32); + //println!("sig offset: {}", (TX_OFFSET + SIG_OFFSET) as u32); + //println!("sign data: {}", (TX_OFFSET + SIGNED_DATA_OFFSET) as u32); + //println!("len offset: {}", PACKET_DATA_SIZE as u32); + unsafe { + let res = ed25519_verify_many( + elems.as_ptr(), + elems.len() as u32, + size_of::() as u32, + (TX_OFFSET + PUB_KEY_OFFSET) as u32, + (TX_OFFSET + SIG_OFFSET) as u32, + (TX_OFFSET + SIGNED_DATA_OFFSET) as u32, + PACKET_DATA_SIZE as u32, + out.as_mut_ptr(), + ); + if res != 0 { + // println!("RETURN!!!: {}", res); + } + } + //println!("done verify"); + let mut num = 0; + for vs in rvs.iter_mut() { + for mut v in vs.iter_mut() { + *v = out[num]; + if *v != 0 { + // println!("VERIFIED PACKET!!!!!"); + } + num += 1; + } + } + rvs +} diff --git a/src/lib.rs b/src/lib.rs index a44a14be2b56ec..4e32974ca355aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,13 +4,14 @@ pub mod accountant_skel; pub mod accountant_stub; pub mod entry; pub mod event; +pub mod gpu; pub mod hash; +pub mod historian; pub mod ledger; pub mod mint; +pub mod packet; pub mod plan; pub mod recorder; -pub mod historian; -pub mod packet; pub mod result; pub mod signature; pub mod streamer; @@ -19,6 +20,7 @@ extern crate bincode; extern crate byteorder; extern crate chrono; extern crate generic_array; +extern crate libc; #[macro_use] extern crate log; extern crate rayon; diff --git a/src/mint.rs b/src/mint.rs index 5b869c3152fd31..1510b210dab133 100644 --- a/src/mint.rs +++ b/src/mint.rs @@ -68,8 +68,8 @@ mod tests { fn test_create_events() { let mut events = Mint::new(100).create_events().into_iter(); if let Event::Transaction(tr) = events.next().unwrap() { - if let Plan::Pay(payment) = tr.plan { - assert_eq!(tr.from, payment.to); + if let Plan::Pay(payment) = tr.data.plan { + assert_eq!(tr.data.from, payment.to); } } assert_eq!(events.next(), None); diff --git a/src/packet.rs b/src/packet.rs index e6c91592d0b2f4..e75f0a820b0123 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -1,10 +1,10 @@ -use std::sync::{Arc, Mutex, RwLock}; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use result::{Error, Result}; +use std::collections::VecDeque; use std::fmt; use std::io; -use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket}; -use std::collections::VecDeque; -use result::{Error, Result}; +use std::sync::{Arc, Mutex, RwLock}; pub type SharedPackets = Arc>; pub type SharedBlob = Arc>; @@ -13,10 +13,11 @@ pub type BlobRecycler = Recycler; const NUM_PACKETS: usize = 1024 * 8; const BLOB_SIZE: usize = 64 * 1024; -pub const PACKET_SIZE: usize = 256; -pub const NUM_BLOBS: usize = (NUM_PACKETS * PACKET_SIZE) / BLOB_SIZE; +pub const PACKET_DATA_SIZE: usize = 256; +pub const NUM_BLOBS: usize = (NUM_PACKETS * PACKET_DATA_SIZE) / BLOB_SIZE; #[derive(Clone, Default)] +#[repr(C)] pub struct Meta { pub size: usize, pub addr: [u16; 8], @@ -25,8 +26,9 @@ pub struct Meta { } #[derive(Clone)] +#[repr(C)] pub struct Packet { - pub data: [u8; PACKET_SIZE], + pub data: [u8; PACKET_DATA_SIZE], pub meta: Meta, } @@ -44,7 +46,7 @@ impl fmt::Debug for Packet { impl Default for Packet { fn default() -> Packet { Packet { - data: [0u8; PACKET_SIZE], + data: [0u8; PACKET_DATA_SIZE], meta: Meta::default(), } } @@ -279,11 +281,11 @@ impl Blob { #[cfg(test)] mod test { - use std::net::UdpSocket; - use std::io::Write; - use std::io; - use std::collections::VecDeque; use packet::{Blob, BlobRecycler, Packet, PacketRecycler, Packets}; + use std::collections::VecDeque; + use std::io; + use std::io::Write; + use std::net::UdpSocket; #[test] pub fn packet_recycler_test() { let r = PacketRecycler::default(); diff --git a/src/plan.rs b/src/plan.rs index d1bc40745e5a95..72aa41f1c8e9e1 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -35,6 +35,7 @@ pub struct Payment { pub to: PublicKey, } +#[repr(C)] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub enum Plan { Pay(Payment), diff --git a/src/streamer.rs b/src/streamer.rs index 87581f32df793c..1725c5064da879 100644 --- a/src/streamer.rs +++ b/src/streamer.rs @@ -1,12 +1,12 @@ +use packet::{Blob, BlobRecycler, PacketRecycler, SharedBlob, SharedPackets, NUM_BLOBS}; +use result::Result; +use std::collections::VecDeque; +use std::net::UdpSocket; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc; -use std::time::Duration; -use std::net::UdpSocket; use std::thread::{spawn, JoinHandle}; -use std::collections::VecDeque; -use result::Result; -use packet::{Blob, BlobRecycler, PacketRecycler, SharedBlob, SharedPackets, NUM_BLOBS}; +use std::time::Duration; pub type PacketReceiver = mpsc::Receiver; pub type PacketSender = mpsc::Sender; @@ -141,16 +141,16 @@ pub fn window( mod bench { extern crate test; use self::test::Bencher; + use packet::{Packet, PacketRecycler, PACKET_DATA_SIZE}; use result::Result; use std::net::{SocketAddr, UdpSocket}; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::mpsc::channel; use std::sync::{Arc, Mutex}; use std::thread::sleep; use std::thread::{spawn, JoinHandle}; use std::time::Duration; use std::time::SystemTime; - use std::sync::mpsc::channel; - use std::sync::atomic::{AtomicBool, Ordering}; - use packet::{Packet, PacketRecycler, PACKET_SIZE}; use streamer::{receiver, PacketReceiver}; fn producer( @@ -163,7 +163,7 @@ mod bench { let msgs_ = msgs.clone(); msgs.write().unwrap().packets.resize(10, Packet::default()); for w in msgs.write().unwrap().packets.iter_mut() { - w.meta.size = PACKET_SIZE; + w.meta.size = PACKET_DATA_SIZE; w.meta.set_addr(&addr); } spawn(move || loop { @@ -241,15 +241,15 @@ mod bench { #[cfg(test)] mod test { + use packet::{Blob, BlobRecycler, Packet, PacketRecycler, Packets, PACKET_DATA_SIZE}; + use std::collections::VecDeque; + use std::io; + use std::io::Write; use std::net::UdpSocket; + use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::channel; - use std::io::Write; - use std::io; - use std::collections::VecDeque; use std::time::Duration; - use std::sync::Arc; - use packet::{Blob, BlobRecycler, Packet, PacketRecycler, Packets, PACKET_SIZE}; use streamer::{receiver, responder, window, BlobReceiver, PacketReceiver}; fn get_msgs(r: PacketReceiver, num: &mut usize) { @@ -288,7 +288,7 @@ mod test { let b_ = b.clone(); let mut w = b.write().unwrap(); w.data[0] = i as u8; - w.meta.size = PACKET_SIZE; + w.meta.size = PACKET_DATA_SIZE; w.meta.set_addr(&addr); msgs.push_back(b_); } @@ -338,7 +338,7 @@ mod test { let mut w = b.write().unwrap(); w.set_index(i).unwrap(); assert_eq!(i, w.get_index().unwrap()); - w.meta.size = PACKET_SIZE; + w.meta.size = PACKET_DATA_SIZE; w.meta.set_addr(&addr); msgs.push_back(b_); } diff --git a/src/transaction.rs b/src/transaction.rs index be43b88373bb00..e50fb15e728979 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -8,12 +8,17 @@ use rayon::prelude::*; use signature::{KeyPair, KeyPairUtil, PublicKey, Signature, SignatureUtil}; #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub struct Transaction { +pub struct Signed { pub from: PublicKey, - pub plan: Plan, pub tokens: i64, pub last_id: Hash, + pub plan: Plan, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub struct Transaction { pub sig: Signature, + pub data: Signed, } impl Transaction { @@ -22,11 +27,13 @@ impl Transaction { let from = from_keypair.pubkey(); let plan = Plan::Pay(Payment { tokens, to }); let mut tr = Transaction { - from, - plan, - tokens, - last_id, sig: Signature::default(), + data: Signed { + from, + plan, + tokens, + last_id, + }, }; tr.sign(from_keypair); tr @@ -46,10 +53,12 @@ impl Transaction { (Condition::Signature(from), Payment { tokens, to: from }), ); let mut tr = Transaction { - from, - plan, - tokens, - last_id, + data: Signed { + from, + plan, + tokens, + last_id, + }, sig: Signature::default(), }; tr.sign(from_keypair); @@ -57,7 +66,7 @@ impl Transaction { } fn get_sign_data(&self) -> Vec { - serialize(&(&self.plan, &self.tokens, &self.last_id)).unwrap() + serialize(&(&self.data)).unwrap() } /// Sign this transaction. @@ -66,20 +75,44 @@ impl Transaction { self.sig = Signature::clone_from_slice(keypair.sign(&sign_data).as_ref()); } - /// Verify this transaction's signature and its spending plan. - pub fn verify(&self) -> bool { - self.sig.verify(&self.from, &self.get_sign_data()) && self.plan.verify(self.tokens) + pub fn sig_verify(&self) -> bool { + self.sig.verify(&self.data.from, &self.get_sign_data()) + } + pub fn plan_verify(&self) -> bool { + self.data.plan.verify(self.data.tokens) } } +#[cfg(test)] +pub fn test_tx() -> Transaction { + let keypair1 = KeyPair::new(); + let pubkey1 = keypair1.pubkey(); + let zero = Hash::default(); + let mut tr = Transaction::new(&keypair1, pubkey1, 42, zero); + tr.sign(&keypair1); + return tr; +} + +#[cfg(test)] +pub fn memfind(a: &[A], b: &[A]) -> Option { + assert!(a.len() >= b.len()); + let end = a.len() - b.len() + 1; + for i in 0..end { + if a[i..i + b.len()] == b[..] { + return Some(i); + } + } + None +} + /// Verify a batch of signatures. pub fn verify_signatures(transactions: &[Transaction]) -> bool { - transactions.par_iter().all(|tr| tr.verify()) + transactions.par_iter().all(|tr| tr.sig_verify()) } /// Verify a batch of spending plans. pub fn verify_plans(transactions: &[Transaction]) -> bool { - transactions.par_iter().all(|tr| tr.plan.verify(tr.tokens)) + transactions.par_iter().all(|tr| tr.plan_verify()) } /// Verify a batch of transactions. @@ -91,13 +124,14 @@ pub fn verify_transactions(transactions: &[Transaction]) -> bool { mod tests { use super::*; use bincode::{deserialize, serialize}; + use gpu; #[test] fn test_claim() { let keypair = KeyPair::new(); let zero = Hash::default(); let tr0 = Transaction::new(&keypair, keypair.pubkey(), 42, zero); - assert!(tr0.verify()); + assert!(tr0.plan_verify()); } #[test] @@ -107,7 +141,7 @@ mod tests { let keypair1 = KeyPair::new(); let pubkey1 = keypair1.pubkey(); let tr0 = Transaction::new(&keypair0, pubkey1, 42, zero); - assert!(tr0.verify()); + assert!(tr0.plan_verify()); } #[test] @@ -117,10 +151,12 @@ mod tests { to: Default::default(), }); let claim0 = Transaction { - from: Default::default(), - plan, - tokens: 0, - last_id: Default::default(), + data: Signed { + from: Default::default(), + plan, + tokens: 0, + last_id: Default::default(), + }, sig: Default::default(), }; let buf = serialize(&claim0).unwrap(); @@ -135,8 +171,8 @@ mod tests { let pubkey = keypair.pubkey(); let mut tr = Transaction::new(&keypair, pubkey, 42, zero); tr.sign(&keypair); - tr.tokens = 1_000_000; // <-- attack! - assert!(!tr.verify()); + tr.data.tokens = 1_000_000; // <-- attack! + assert!(!tr.plan_verify()); } #[test] @@ -148,10 +184,20 @@ mod tests { let zero = Hash::default(); let mut tr = Transaction::new(&keypair0, pubkey1, 42, zero); tr.sign(&keypair0); - if let Plan::Pay(ref mut payment) = tr.plan { + if let Plan::Pay(ref mut payment) = tr.data.plan { payment.to = thief_keypair.pubkey(); // <-- attack! }; - assert!(!tr.verify()); + assert!(tr.plan_verify()); + assert!(!tr.sig_verify()); + } + #[test] + fn test_layout() { + let tr = test_tx(); + let sign_data = tr.get_sign_data(); + let tx = serialize(&tr).unwrap(); + assert_matches!(memfind(&tx, &sign_data), Some(gpu::SIGNED_DATA_OFFSET)); + assert_matches!(memfind(&tx, &tr.sig), Some(gpu::SIG_OFFSET)); + assert_matches!(memfind(&tx, &tr.data.from), Some(gpu::PUB_KEY_OFFSET)); } #[test] @@ -160,16 +206,16 @@ mod tests { let keypair1 = KeyPair::new(); let zero = Hash::default(); let mut tr = Transaction::new(&keypair0, keypair1.pubkey(), 1, zero); - if let Plan::Pay(ref mut payment) = tr.plan { + if let Plan::Pay(ref mut payment) = tr.data.plan { payment.tokens = 2; // <-- attack! } - assert!(!tr.verify()); + assert!(!tr.plan_verify()); // Also, ensure all branchs of the plan spend all tokens - if let Plan::Pay(ref mut payment) = tr.plan { + if let Plan::Pay(ref mut payment) = tr.data.plan { payment.tokens = 0; // <-- whoops! } - assert!(!tr.verify()); + assert!(!tr.plan_verify()); } #[test] From 8e83ba891e1ace51f4eb23c0b7728b80bdc3a877 Mon Sep 17 00:00:00 2001 From: Anatoly Yakovenko Date: Wed, 4 Apr 2018 17:12:41 -0700 Subject: [PATCH 48/55] we have logs --- Cargo.toml | 1 + src/bin/client-demo.rs | 3 +++ src/bin/testnode.rs | 2 ++ src/gpu.rs | 20 ++++++++++---------- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9c5655e09cba03..1452932002a4f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,7 @@ untrusted = "0.5.1" bincode = "1.0.0" chrono = { version = "0.4.0", features = ["serde"] } log = "^0.4.1" +env_logger = "^0.4.1" matches = "^0.1.6" byteorder = "^1.2.1" libc = "^0.2.1" diff --git a/src/bin/client-demo.rs b/src/bin/client-demo.rs index 802953273f5f10..348337059faec7 100644 --- a/src/bin/client-demo.rs +++ b/src/bin/client-demo.rs @@ -21,9 +21,12 @@ fn main() { let mint_pubkey = mint.pubkey(); let socket = UdpSocket::bind(send_addr).unwrap(); + println!("Stub new"); let acc = AccountantStub::new(addr, socket); + println!("Get last id"); let last_id = acc.get_last_id().unwrap(); + println!("Get Balance"); let mint_balance = acc.get_balance(&mint_pubkey).unwrap().unwrap(); println!("Mint's Initial Balance {}", mint_balance); diff --git a/src/bin/testnode.rs b/src/bin/testnode.rs index 21b4fa5b4de19e..3cf1b54138cbf0 100644 --- a/src/bin/testnode.rs +++ b/src/bin/testnode.rs @@ -1,3 +1,4 @@ +extern crate env_logger; extern crate serde_json; extern crate solana; @@ -11,6 +12,7 @@ use std::sync::atomic::AtomicBool; use std::sync::{Arc, Mutex}; fn main() { + env_logger::init().unwrap(); let addr = "127.0.0.1:9000"; let stdin = io::stdin(); let mut entries = stdin diff --git a/src/gpu.rs b/src/gpu.rs index 4d0369c8b5ea78..d3ec5f5a5b86d9 100644 --- a/src/gpu.rs +++ b/src/gpu.rs @@ -47,13 +47,13 @@ pub fn ecdsa_verify(batches: &Vec) -> Vec> { num += p.packets.len(); } out.resize(num, 0); - //println!("Starting verify num packets: {}", num); - //println!("elem len: {}", elems.len() as u32); - //println!("packet sizeof: {}", size_of::() as u32); - //println!("pub key: {}", (TX_OFFSET + PUB_KEY_OFFSET) as u32); - //println!("sig offset: {}", (TX_OFFSET + SIG_OFFSET) as u32); - //println!("sign data: {}", (TX_OFFSET + SIGNED_DATA_OFFSET) as u32); - //println!("len offset: {}", PACKET_DATA_SIZE as u32); + trace!("Starting verify num packets: {}", num); + trace!("elem len: {}", elems.len() as u32); + trace!("packet sizeof: {}", size_of::() as u32); + trace!("pub key: {}", (TX_OFFSET + PUB_KEY_OFFSET) as u32); + trace!("sig offset: {}", (TX_OFFSET + SIG_OFFSET) as u32); + trace!("sign data: {}", (TX_OFFSET + SIGNED_DATA_OFFSET) as u32); + trace!("len offset: {}", PACKET_DATA_SIZE as u32); unsafe { let res = ed25519_verify_many( elems.as_ptr(), @@ -66,16 +66,16 @@ pub fn ecdsa_verify(batches: &Vec) -> Vec> { out.as_mut_ptr(), ); if res != 0 { - // println!("RETURN!!!: {}", res); + trace!("RETURN!!!: {}", res); } } - //println!("done verify"); + trace!("done verify"); let mut num = 0; for vs in rvs.iter_mut() { for mut v in vs.iter_mut() { *v = out[num]; if *v != 0 { - // println!("VERIFIED PACKET!!!!!"); + trace!("VERIFIED PACKET!!!!!"); } num += 1; } From 9cf72282dafe35f9079de330d23c8c47ab021743 Mon Sep 17 00:00:00 2001 From: Anatoly Yakovenko Date: Wed, 4 Apr 2018 17:13:17 -0700 Subject: [PATCH 49/55] enable trace --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c7f06fd5e7c753..5f986b23e07259 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -export RUST_LOG=packet=TRACE +export RUST_LOG=solana::gpu=TRACE #export RUST_BACKTRACE=1 all: htest From 5553f31f41764cc30db928d9923ca3f27f88a9b2 Mon Sep 17 00:00:00 2001 From: Anatoly Yakovenko Date: Wed, 4 Apr 2018 17:14:28 -0700 Subject: [PATCH 50/55] trace msgs --- src/accountant_skel.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index 973a37e240cfcc..dfdf9093a71d81 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -116,22 +116,22 @@ impl AccountantSkel { ) -> Result<()> { let timer = Duration::new(1, 0); let msgs = recvr.recv_timeout(timer)?; - //println!("got msgs"); + trace!("got msgs"); let mut v = Vec::new(); v.push(msgs); while let Ok(more) = recvr.try_recv() { - //println!("got more msgs"); + trace!("got more msgs"); v.push(more); } - //println!("verifying"); + trace!("verifying"); let rvs = gpu::ecdsa_verify(&v); - //println!("verified!"); + trace!("verified!"); let mut len = 0; let mut sv = Vec::new(); let mut sr = Vec::new(); for (v, r) in v.iter().zip(rvs.iter()) { if len + r.len() >= 256 { - println!("sending {}", len); + trace!("sending {}", len); sendr.send((sv, sr))?; sv = Vec::new(); sr = Vec::new(); From 35c86856bc3f0e655a2106fda8a7ea550150dca4 Mon Sep 17 00:00:00 2001 From: Anatoly Yakovenko Date: Wed, 4 Apr 2018 20:53:23 -0700 Subject: [PATCH 51/55] cleanup --- src/accountant_skel.rs | 28 ++++++---------------------- src/bin/client-demo.rs | 2 +- src/streamer.rs | 2 +- 3 files changed, 8 insertions(+), 24 deletions(-) diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index dfdf9093a71d81..3ba1c98e767fdc 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -88,17 +88,17 @@ impl AccountantSkel { pub fn log_verified_request(&mut self, msg: Request, verify: u8) -> Option { match msg { Request::Transaction(_) if verify == 0 => { - eprintln!("Transaction falid sigverify"); + trace!("Transaction falid sigverify"); None } Request::Transaction(tr) => { if let Err(err) = self.acc.process_verified_transaction(&tr) { - eprintln!("Transaction error: {:?}", err); + trace!("Transaction error: {:?}", err); } else if let Err(SendError(_)) = self.historian .sender .send(Signal::Event(Event::Transaction(tr.clone()))) { - eprintln!("Channel send error"); + error!("Channel send error"); } None } @@ -125,25 +125,9 @@ impl AccountantSkel { } trace!("verifying"); let rvs = gpu::ecdsa_verify(&v); - trace!("verified!"); - let mut len = 0; - let mut sv = Vec::new(); - let mut sr = Vec::new(); - for (v, r) in v.iter().zip(rvs.iter()) { - if len + r.len() >= 256 { - trace!("sending {}", len); - sendr.send((sv, sr))?; - sv = Vec::new(); - sr = Vec::new(); - len = 0; - } - sv.push(v.clone()); - sr.push(r.clone()); - len += r.len(); - assert!(len < 256); - } - if !sv.is_empty() { - sendr.send((sv, sr))?; + info!("verified batches {}", rvs.len()); + if !rvs.is_empty() { + sendr.send((v, rvs))?; } Ok(()) } diff --git a/src/bin/client-demo.rs b/src/bin/client-demo.rs index 348337059faec7..3fba20099bf99d 100644 --- a/src/bin/client-demo.rs +++ b/src/bin/client-demo.rs @@ -31,7 +31,7 @@ fn main() { println!("Mint's Initial Balance {}", mint_balance); println!("Signing transactions..."); - let txs = 100_000; + let txs = 10_000_000; let now = Instant::now(); let transactions: Vec<_> = (0..txs) .into_par_iter() diff --git a/src/streamer.rs b/src/streamer.rs index 1725c5064da879..fad9f5407044ea 100644 --- a/src/streamer.rs +++ b/src/streamer.rs @@ -67,7 +67,7 @@ pub fn responder( r: BlobReceiver, ) -> JoinHandle<()> { spawn(move || loop { - if recv_send(&sock, &recycler, &r).is_err() || exit.load(Ordering::Relaxed) { + if recv_send(&sock, &recycler, &r).is_err() && exit.load(Ordering::Relaxed) { break; } }) From f10a24ed220ff166ccd54432a5da4fc0b4a3ca5d Mon Sep 17 00:00:00 2001 From: Anatoly Yakovenko Date: Wed, 4 Apr 2018 20:54:32 -0700 Subject: [PATCH 52/55] 100m test --- src/bin/client-demo.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/client-demo.rs b/src/bin/client-demo.rs index 3fba20099bf99d..7e1ae0478148f3 100644 --- a/src/bin/client-demo.rs +++ b/src/bin/client-demo.rs @@ -31,7 +31,7 @@ fn main() { println!("Mint's Initial Balance {}", mint_balance); println!("Signing transactions..."); - let txs = 10_000_000; + let txs = 1_000_000; let now = Instant::now(); let transactions: Vec<_> = (0..txs) .into_par_iter() From 89d2f2279eade97e823a76c44adf9a3d3b50df6b Mon Sep 17 00:00:00 2001 From: Anatoly Yakovenko Date: Wed, 4 Apr 2018 21:14:06 -0700 Subject: [PATCH 53/55] easy peasy --- src/accountant_skel.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index 3ba1c98e767fdc..9ac26137f2f88d 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -16,6 +16,7 @@ use recorder::Signal; use result::Result; use serde_json; use signature::PublicKey; +use std::cmp::max; use std::collections::VecDeque; use std::io::Write; use std::net::{SocketAddr, UdpSocket}; @@ -123,11 +124,15 @@ impl AccountantSkel { trace!("got more msgs"); v.push(more); } - trace!("verifying"); - let rvs = gpu::ecdsa_verify(&v); - info!("verified batches {}", rvs.len()); - if !rvs.is_empty() { - sendr.send((v, rvs))?; + info!("batch {}", v.len()); + let chunk = max(1, (v.len() + 2) / 4); + let chunks: Vec<_> = v.chunks(chunk).collect(); + let rvs: Vec<_> = chunks + .into_par_iter() + .map(|x| gpu::ecdsa_verify(&x.to_vec())) + .collect(); + for (v, r) in v.chunks(chunk).zip(rvs) { + sendr.send((v.to_vec(), r))?; } Ok(()) } From fe49519c2926bdd71068fe00a5dfdc14551e5d5a Mon Sep 17 00:00:00 2001 From: Anatoly Yakovenko Date: Wed, 4 Apr 2018 21:15:10 -0700 Subject: [PATCH 54/55] update --- src/accountant_skel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/accountant_skel.rs b/src/accountant_skel.rs index 9ac26137f2f88d..4349ba28c47979 100644 --- a/src/accountant_skel.rs +++ b/src/accountant_skel.rs @@ -125,7 +125,7 @@ impl AccountantSkel { v.push(more); } info!("batch {}", v.len()); - let chunk = max(1, (v.len() + 2) / 4); + let chunk = max(1, (v.len() + 3) / 4); let chunks: Vec<_> = v.chunks(chunk).collect(); let rvs: Vec<_> = chunks .into_par_iter() From b49b9f313a249e6e819c765b195979ca1211e07e Mon Sep 17 00:00:00 2001 From: Anatoly Yakovenko Date: Sat, 7 Apr 2018 20:15:42 -0700 Subject: [PATCH 55/55] stash --- src/bin/client-demo.rs | 2 +- src/bin/testnode.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/client-demo.rs b/src/bin/client-demo.rs index 7e1ae0478148f3..402c0ab453fefa 100644 --- a/src/bin/client-demo.rs +++ b/src/bin/client-demo.rs @@ -13,7 +13,7 @@ use std::thread::sleep; use std::time::{Duration, Instant}; fn main() { - let addr = "127.0.0.1:9000"; + let addr = "192.168.88.253:9000"; let send_addr = "127.0.0.1:9001"; let mint: Mint = serde_json::from_reader(stdin()).unwrap(); diff --git a/src/bin/testnode.rs b/src/bin/testnode.rs index 3cf1b54138cbf0..0df55487cdb408 100644 --- a/src/bin/testnode.rs +++ b/src/bin/testnode.rs @@ -13,7 +13,7 @@ use std::sync::{Arc, Mutex}; fn main() { env_logger::init().unwrap(); - let addr = "127.0.0.1:9000"; + let addr = "192.168.88.253:9000"; let stdin = io::stdin(); let mut entries = stdin .lock()