Merge pull request #128 from garious/faster-demo
Utilize parallelized accountant in demo
This commit is contained in:
commit
7077f4cbe2
|
@ -36,6 +36,10 @@ path = "src/bin/genesis-demo.rs"
|
||||||
name = "solana-mint"
|
name = "solana-mint"
|
||||||
path = "src/bin/mint.rs"
|
path = "src/bin/mint.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "solana-mint-demo"
|
||||||
|
path = "src/bin/mint-demo.rs"
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
codecov = { repository = "solana-labs/solana", branch = "master", service = "github" }
|
codecov = { repository = "solana-labs/solana", branch = "master", service = "github" }
|
||||||
|
|
||||||
|
|
17
README.md
17
README.md
|
@ -39,25 +39,28 @@ $ cd solana
|
||||||
The testnode server is initialized with a ledger from stdin and
|
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
|
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
|
to create *the mint* and use it to generate a *genesis ledger*. It's done in
|
||||||
two steps because the mint.json file contains a private key that will be
|
two steps because the mint-demo.json file contains private keys that will be
|
||||||
used later in this demo.
|
used later in this demo.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ echo 1000000000 | cargo run --release --bin solana-mint | tee mint.json
|
$ echo 1000000000 | cargo run --release --bin solana-mint-demo > mint-demo.json
|
||||||
$ cat mint.json | cargo run --release --bin solana-genesis | tee genesis.log
|
$ cat mint-demo.json | cargo run --release --bin solana-genesis-demo > genesis.log
|
||||||
```
|
```
|
||||||
|
|
||||||
Now you can start the server:
|
Now you can start the server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cat genesis.log | cargo run --release --bin solana-testnode | tee transactions0.log
|
$ cat genesis.log | cargo run --release --bin solana-testnode > transactions0.log
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Wait a few seconds for the server to initialize. It will print "Ready." when it's safe
|
||||||
|
to start sending it transactions.
|
||||||
|
|
||||||
Then, in a separate shell, let's execute some transactions. Note we pass in
|
Then, in a separate shell, let's execute some transactions. Note we pass in
|
||||||
the JSON configuration file here, not the genesis ledger.
|
the JSON configuration file here, not the genesis ledger.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cat mint.json | cargo run --release --bin solana-client-demo
|
$ cat mint-demo.json | cargo run --release --bin solana-client-demo
|
||||||
```
|
```
|
||||||
|
|
||||||
Now kill the server with Ctrl-C, and take a look at the ledger. You should
|
Now kill the server with Ctrl-C, and take a look at the ledger. You should
|
||||||
|
@ -73,14 +76,14 @@ Now restart the server from where we left off. Pass it both the genesis ledger,
|
||||||
the transaction ledger.
|
the transaction ledger.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cat genesis.log transactions0.log | cargo run --release --bin solana-testnode | tee 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
|
Lastly, run the client demo again, and verify that all funds were spent in the
|
||||||
previous round, and so no additional transactions are added.
|
previous round, and so no additional transactions are added.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cat mint.json | cargo run --release --bin solana-client-demo
|
$ cat mint-demo.json | cargo run --release --bin solana-client-demo
|
||||||
```
|
```
|
||||||
|
|
||||||
Stop the server again, and verify there are only Tick entries, and no Transaction entries.
|
Stop the server again, and verify there are only Tick entries, and no Transaction entries.
|
||||||
|
|
|
@ -33,6 +33,7 @@ pub struct AccountantSkel<W: Write + Send + 'static> {
|
||||||
last_id: Hash,
|
last_id: Hash,
|
||||||
writer: W,
|
writer: W,
|
||||||
historian: Historian,
|
historian: Historian,
|
||||||
|
entry_info_subscribers: Vec<SocketAddr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(large_enum_variant))]
|
#[cfg_attr(feature = "cargo-clippy", allow(large_enum_variant))]
|
||||||
|
@ -41,6 +42,19 @@ pub enum Request {
|
||||||
Transaction(Transaction),
|
Transaction(Transaction),
|
||||||
GetBalance { key: PublicKey },
|
GetBalance { key: PublicKey },
|
||||||
GetLastId,
|
GetLastId,
|
||||||
|
Subscribe { subscriptions: Vec<Subscription> },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub enum Subscription {
|
||||||
|
EntryInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct EntryInfo {
|
||||||
|
pub id: Hash,
|
||||||
|
pub num_hashes: u64,
|
||||||
|
pub num_events: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Request {
|
impl Request {
|
||||||
|
@ -56,7 +70,7 @@ impl Request {
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub enum Response {
|
pub enum Response {
|
||||||
Balance { key: PublicKey, val: Option<i64> },
|
Balance { key: PublicKey, val: Option<i64> },
|
||||||
Entries { entries: Vec<Entry> },
|
EntryInfo(EntryInfo),
|
||||||
LastId { id: Hash },
|
LastId { id: Hash },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +82,22 @@ impl<W: Write + Send + 'static> AccountantSkel<W> {
|
||||||
last_id,
|
last_id,
|
||||||
writer,
|
writer,
|
||||||
historian,
|
historian,
|
||||||
|
entry_info_subscribers: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn notify_entry_info_subscribers(&mut self, entry: &Entry) {
|
||||||
|
// TODO: No need to bind().
|
||||||
|
let socket = UdpSocket::bind("127.0.0.1:0").expect("bind");
|
||||||
|
|
||||||
|
for addr in &self.entry_info_subscribers {
|
||||||
|
let entry_info = EntryInfo {
|
||||||
|
id: entry.id,
|
||||||
|
num_hashes: entry.num_hashes,
|
||||||
|
num_events: entry.events.len() as u64,
|
||||||
|
};
|
||||||
|
let data = serialize(&Response::EntryInfo(entry_info)).expect("serialize EntryInfo");
|
||||||
|
let _res = socket.send_to(&data, addr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +107,7 @@ impl<W: Write + Send + 'static> AccountantSkel<W> {
|
||||||
self.last_id = entry.id;
|
self.last_id = entry.id;
|
||||||
self.acc.register_entry_id(&self.last_id);
|
self.acc.register_entry_id(&self.last_id);
|
||||||
writeln!(self.writer, "{}", serde_json::to_string(&entry).unwrap()).unwrap();
|
writeln!(self.writer, "{}", serde_json::to_string(&entry).unwrap()).unwrap();
|
||||||
|
self.notify_entry_info_subscribers(&entry);
|
||||||
}
|
}
|
||||||
self.last_id
|
self.last_id
|
||||||
}
|
}
|
||||||
|
@ -94,6 +125,14 @@ impl<W: Write + Send + 'static> AccountantSkel<W> {
|
||||||
}
|
}
|
||||||
Request::GetLastId => Some((Response::LastId { id: self.sync() }, rsp_addr)),
|
Request::GetLastId => Some((Response::LastId { id: self.sync() }, rsp_addr)),
|
||||||
Request::Transaction(_) => unreachable!(),
|
Request::Transaction(_) => unreachable!(),
|
||||||
|
Request::Subscribe { subscriptions } => {
|
||||||
|
for subscription in subscriptions {
|
||||||
|
match subscription {
|
||||||
|
Subscription::EntryInfo => self.entry_info_subscribers.push(rsp_addr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,7 +281,11 @@ impl<W: Write + Send + 'static> AccountantSkel<W> {
|
||||||
blob_sender.send(blobs)?;
|
blob_sender.send(blobs)?;
|
||||||
}
|
}
|
||||||
packet_recycler.recycle(msgs);
|
packet_recycler.recycle(msgs);
|
||||||
|
|
||||||
|
// Write new entries to the ledger and notify subscribers.
|
||||||
|
obj.lock().unwrap().sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,9 +329,14 @@ impl<W: Write + Send + 'static> AccountantSkel<W> {
|
||||||
&packet_recycler,
|
&packet_recycler,
|
||||||
&blob_recycler,
|
&blob_recycler,
|
||||||
);
|
);
|
||||||
if e.is_err() && exit.load(Ordering::Relaxed) {
|
if e.is_err() {
|
||||||
|
// Assume this was a timeout, so sync any empty entries.
|
||||||
|
skel.lock().unwrap().sync();
|
||||||
|
|
||||||
|
if exit.load(Ordering::Relaxed) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
Ok(vec![t_receiver, t_responder, t_server, t_verifier])
|
Ok(vec![t_receiver, t_responder, t_server, t_verifier])
|
||||||
}
|
}
|
||||||
|
@ -426,7 +474,7 @@ mod tests {
|
||||||
let socket = UdpSocket::bind(send_addr).unwrap();
|
let socket = UdpSocket::bind(send_addr).unwrap();
|
||||||
socket.set_read_timeout(Some(Duration::new(5, 0))).unwrap();
|
socket.set_read_timeout(Some(Duration::new(5, 0))).unwrap();
|
||||||
|
|
||||||
let acc = AccountantStub::new(&addr, socket);
|
let mut acc = AccountantStub::new(&addr, socket);
|
||||||
let last_id = acc.get_last_id().wait().unwrap();
|
let last_id = acc.get_last_id().wait().unwrap();
|
||||||
|
|
||||||
let tr = Transaction::new(&alice.keypair(), bob_pubkey, 500, last_id);
|
let tr = Transaction::new(&alice.keypair(), bob_pubkey, 500, last_id);
|
||||||
|
|
|
@ -3,11 +3,12 @@
|
||||||
//! this object instead of writing messages to the network directly. The binary
|
//! this object instead of writing messages to the network directly. The binary
|
||||||
//! encoding of its messages are unstable and may change in future releases.
|
//! encoding of its messages are unstable and may change in future releases.
|
||||||
|
|
||||||
use accountant_skel::{Request, Response};
|
use accountant_skel::{Request, Response, Subscription};
|
||||||
use bincode::{deserialize, serialize};
|
use bincode::{deserialize, serialize};
|
||||||
use futures::future::{err, ok, FutureResult};
|
use futures::future::{ok, FutureResult};
|
||||||
use hash::Hash;
|
use hash::Hash;
|
||||||
use signature::{KeyPair, PublicKey, Signature};
|
use signature::{KeyPair, PublicKey, Signature};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::net::UdpSocket;
|
use std::net::UdpSocket;
|
||||||
use transaction::Transaction;
|
use transaction::Transaction;
|
||||||
|
@ -15,6 +16,9 @@ use transaction::Transaction;
|
||||||
pub struct AccountantStub {
|
pub struct AccountantStub {
|
||||||
pub addr: String,
|
pub addr: String,
|
||||||
pub socket: UdpSocket,
|
pub socket: UdpSocket,
|
||||||
|
last_id: Option<Hash>,
|
||||||
|
num_events: u64,
|
||||||
|
balances: HashMap<PublicKey, Option<i64>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccountantStub {
|
impl AccountantStub {
|
||||||
|
@ -22,9 +26,43 @@ impl AccountantStub {
|
||||||
/// over `socket`. To receive responses, the caller must bind `socket`
|
/// over `socket`. To receive responses, the caller must bind `socket`
|
||||||
/// to a public address before invoking AccountantStub methods.
|
/// to a public address before invoking AccountantStub methods.
|
||||||
pub fn new(addr: &str, socket: UdpSocket) -> Self {
|
pub fn new(addr: &str, socket: UdpSocket) -> Self {
|
||||||
AccountantStub {
|
let stub = AccountantStub {
|
||||||
addr: addr.to_string(),
|
addr: addr.to_string(),
|
||||||
socket,
|
socket,
|
||||||
|
last_id: None,
|
||||||
|
num_events: 0,
|
||||||
|
balances: HashMap::new(),
|
||||||
|
};
|
||||||
|
stub.init();
|
||||||
|
stub
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(&self) {
|
||||||
|
let subscriptions = vec![Subscription::EntryInfo];
|
||||||
|
let req = Request::Subscribe { subscriptions };
|
||||||
|
let data = serialize(&req).expect("serialize Subscribe");
|
||||||
|
let _res = self.socket.send_to(&data, &self.addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recv_response(&self) -> io::Result<Response> {
|
||||||
|
let mut buf = vec![0u8; 1024];
|
||||||
|
self.socket.recv_from(&mut buf)?;
|
||||||
|
let resp = deserialize(&buf).expect("deserialize balance");
|
||||||
|
Ok(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_response(&mut self, resp: Response) {
|
||||||
|
match resp {
|
||||||
|
Response::Balance { key, val } => {
|
||||||
|
self.balances.insert(key, val);
|
||||||
|
}
|
||||||
|
Response::LastId { id } => {
|
||||||
|
self.last_id = Some(id);
|
||||||
|
}
|
||||||
|
Response::EntryInfo(entry_info) => {
|
||||||
|
self.last_id = Some(entry_info.id);
|
||||||
|
self.num_events += entry_info.num_events;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,42 +90,67 @@ impl AccountantStub {
|
||||||
/// Request the balance of the user holding `pubkey`. This method blocks
|
/// Request the balance of the user holding `pubkey`. This method blocks
|
||||||
/// until the server sends a response. If the response packet is dropped
|
/// until the server sends a response. If the response packet is dropped
|
||||||
/// by the network, this method will hang indefinitely.
|
/// by the network, this method will hang indefinitely.
|
||||||
pub fn get_balance(&self, pubkey: &PublicKey) -> FutureResult<i64, i64> {
|
pub fn get_balance(&mut self, pubkey: &PublicKey) -> FutureResult<i64, i64> {
|
||||||
let req = Request::GetBalance { key: *pubkey };
|
let req = Request::GetBalance { key: *pubkey };
|
||||||
let data = serialize(&req).expect("serialize GetBalance");
|
let data = serialize(&req).expect("serialize GetBalance");
|
||||||
self.socket
|
self.socket
|
||||||
.send_to(&data, &self.addr)
|
.send_to(&data, &self.addr)
|
||||||
.expect("buffer error");
|
.expect("buffer error");
|
||||||
let mut buf = vec![0u8; 1024];
|
let mut done = false;
|
||||||
self.socket.recv_from(&mut buf).expect("buffer error");
|
while !done {
|
||||||
let resp = deserialize(&buf).expect("deserialize balance");
|
let resp = self.recv_response().expect("recv response");
|
||||||
if let Response::Balance { key, val } = resp {
|
if let &Response::Balance { ref key, .. } = &resp {
|
||||||
assert_eq!(key, *pubkey);
|
done = key == pubkey;
|
||||||
return match val {
|
|
||||||
Some(x) => ok(x),
|
|
||||||
_ => err(0),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
err(0)
|
self.process_response(resp);
|
||||||
|
}
|
||||||
|
ok(self.balances[pubkey].unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request the last Entry ID from the server. This method blocks
|
/// Request the last Entry ID from the server. This method blocks
|
||||||
/// until the server sends a response. At the time of this writing,
|
/// 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
|
/// it also has the side-effect of causing the server to log any
|
||||||
/// entries that have been published by the Historian.
|
/// entries that have been published by the Historian.
|
||||||
pub fn get_last_id(&self) -> FutureResult<Hash, ()> {
|
pub fn get_last_id(&mut self) -> FutureResult<Hash, ()> {
|
||||||
let req = Request::GetLastId;
|
let req = Request::GetLastId;
|
||||||
let data = serialize(&req).expect("serialize GetId");
|
let data = serialize(&req).expect("serialize GetId");
|
||||||
self.socket
|
self.socket
|
||||||
.send_to(&data, &self.addr)
|
.send_to(&data, &self.addr)
|
||||||
.expect("buffer error");
|
.expect("buffer error");
|
||||||
let mut buf = vec![0u8; 1024];
|
let mut done = false;
|
||||||
self.socket.recv_from(&mut buf).expect("buffer error");
|
while !done {
|
||||||
let resp = deserialize(&buf).expect("deserialize Id");
|
let resp = self.recv_response().expect("recv response");
|
||||||
if let Response::LastId { id } = resp {
|
if let &Response::LastId { .. } = &resp {
|
||||||
return ok(id);
|
done = true;
|
||||||
}
|
}
|
||||||
ok(Default::default())
|
self.process_response(resp);
|
||||||
|
}
|
||||||
|
ok(self.last_id.unwrap_or(Hash::default()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the number of transactions the server processed since creating
|
||||||
|
/// this stub instance.
|
||||||
|
pub fn transaction_count(&mut self) -> u64 {
|
||||||
|
// Wait for at least one EntryInfo.
|
||||||
|
let mut done = false;
|
||||||
|
while !done {
|
||||||
|
let resp = self.recv_response().expect("recv response");
|
||||||
|
if let &Response::EntryInfo(_) = &resp {
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
self.process_response(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then take the rest.
|
||||||
|
self.socket.set_nonblocking(true).expect("set nonblocking");
|
||||||
|
loop {
|
||||||
|
match self.recv_response() {
|
||||||
|
Err(_) => break,
|
||||||
|
Ok(resp) => self.process_response(resp),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.socket.set_nonblocking(false).expect("set blocking");
|
||||||
|
self.num_events
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +191,7 @@ mod tests {
|
||||||
let socket = UdpSocket::bind(send_addr).unwrap();
|
let socket = UdpSocket::bind(send_addr).unwrap();
|
||||||
socket.set_read_timeout(Some(Duration::new(5, 0))).unwrap();
|
socket.set_read_timeout(Some(Duration::new(5, 0))).unwrap();
|
||||||
|
|
||||||
let acc = AccountantStub::new(addr, socket);
|
let mut acc = AccountantStub::new(addr, socket);
|
||||||
let last_id = acc.get_last_id().wait().unwrap();
|
let last_id = acc.get_last_id().wait().unwrap();
|
||||||
let _sig = acc.transfer(500, &alice.keypair(), bob_pubkey, &last_id)
|
let _sig = acc.transfer(500, &alice.keypair(), bob_pubkey, &last_id)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -4,21 +4,22 @@ extern crate isatty;
|
||||||
extern crate rayon;
|
extern crate rayon;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
extern crate solana;
|
extern crate solana;
|
||||||
|
extern crate untrusted;
|
||||||
|
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use getopts::Options;
|
use getopts::Options;
|
||||||
use isatty::stdin_isatty;
|
use isatty::stdin_isatty;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use solana::accountant_stub::AccountantStub;
|
use solana::accountant_stub::AccountantStub;
|
||||||
use solana::mint::Mint;
|
use solana::mint::MintDemo;
|
||||||
use solana::signature::{KeyPair, KeyPairUtil};
|
use solana::signature::{KeyPair, KeyPairUtil};
|
||||||
use solana::transaction::Transaction;
|
use solana::transaction::Transaction;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::io::{stdin, Read};
|
use std::io::{stdin, Read};
|
||||||
use std::net::UdpSocket;
|
use std::net::UdpSocket;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::thread::sleep;
|
use std::time::Instant;
|
||||||
use std::time::{Duration, Instant};
|
use untrusted::Input;
|
||||||
|
|
||||||
fn print_usage(program: &str, opts: Options) {
|
fn print_usage(program: &str, opts: Options) {
|
||||||
let mut brief = format!("Usage: cat <mint.json> | {} [options]\n\n", program);
|
let mut brief = format!("Usage: cat <mint.json> | {} [options]\n\n", program);
|
||||||
|
@ -75,50 +76,49 @@ fn main() {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mint: Mint = serde_json::from_str(&buffer).unwrap_or_else(|e| {
|
println!("Parsing stdin...");
|
||||||
|
let demo: MintDemo = serde_json::from_str(&buffer).unwrap_or_else(|e| {
|
||||||
eprintln!("failed to parse json: {}", e);
|
eprintln!("failed to parse json: {}", e);
|
||||||
exit(1);
|
exit(1);
|
||||||
});
|
});
|
||||||
let mint_keypair = mint.keypair();
|
|
||||||
let mint_pubkey = mint.pubkey();
|
|
||||||
|
|
||||||
let socket = UdpSocket::bind(&send_addr).unwrap();
|
let socket = UdpSocket::bind(&send_addr).unwrap();
|
||||||
println!("Stub new");
|
let mut acc = AccountantStub::new(&addr, socket);
|
||||||
let acc = AccountantStub::new(&addr, socket);
|
|
||||||
println!("Get last id");
|
println!("Get last ID...");
|
||||||
let last_id = acc.get_last_id().wait().unwrap();
|
let last_id = acc.get_last_id().wait().unwrap();
|
||||||
|
|
||||||
println!("Get Balance");
|
println!("Creating keypairs...");
|
||||||
let mint_balance = acc.get_balance(&mint_pubkey).wait().unwrap();
|
let txs = demo.users.len() / 2;
|
||||||
println!("Mint's Initial Balance {}", mint_balance);
|
let keypairs: Vec<_> = demo.users
|
||||||
|
.into_par_iter()
|
||||||
|
.map(|(pkcs8, _)| KeyPair::from_pkcs8(Input::from(&pkcs8)).unwrap())
|
||||||
|
.collect();
|
||||||
|
let keypair_pairs: Vec<_> = keypairs.chunks(2).collect();
|
||||||
|
|
||||||
println!("Signing transactions...");
|
println!("Signing transactions...");
|
||||||
let txs = 1_000_000;
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let transactions: Vec<_> = (0..txs)
|
let transactions: Vec<_> = keypair_pairs
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|_| {
|
.map(|chunk| Transaction::new(&chunk[0], chunk[1].pubkey(), 1, last_id))
|
||||||
let rando_pubkey = KeyPair::new().pubkey();
|
|
||||||
Transaction::new(&mint_keypair, rando_pubkey, 1, last_id)
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
let duration = now.elapsed();
|
let duration = now.elapsed();
|
||||||
let ns = duration.as_secs() * 2_000_000_000 + u64::from(duration.subsec_nanos());
|
let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos());
|
||||||
let bsps = f64::from(txs) / ns as f64;
|
let bsps = txs as f64 / ns as f64;
|
||||||
let nsps = ns as f64 / f64::from(txs);
|
let nsps = ns as f64 / txs as f64;
|
||||||
println!(
|
println!(
|
||||||
"Done. {} thousand signatures per second, {}us per signature",
|
"Done. {} thousand signatures per second, {}us per signature",
|
||||||
bsps * 1_000_000_f64,
|
bsps * 1_000_000_f64,
|
||||||
nsps / 1_000_f64
|
nsps / 1_000_f64
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let initial_tx_count = acc.transaction_count();
|
||||||
|
|
||||||
println!("Transfering {} transactions in {} batches", txs, threads);
|
println!("Transfering {} transactions in {} batches", txs, threads);
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let sz = transactions.len() / threads;
|
let sz = transactions.len() / threads;
|
||||||
let chunks: Vec<_> = transactions.chunks(sz).collect();
|
let chunks: Vec<_> = transactions.chunks(sz).collect();
|
||||||
let _: Vec<_> = chunks
|
chunks.into_par_iter().for_each(|trs| {
|
||||||
.into_par_iter()
|
|
||||||
.map(|trs| {
|
|
||||||
println!("Transferring 1 unit {} times...", trs.len());
|
println!("Transferring 1 unit {} times...", trs.len());
|
||||||
let send_addr = "0.0.0.0:0";
|
let send_addr = "0.0.0.0:0";
|
||||||
let socket = UdpSocket::bind(send_addr).unwrap();
|
let socket = UdpSocket::bind(send_addr).unwrap();
|
||||||
|
@ -126,23 +126,18 @@ fn main() {
|
||||||
for tr in trs {
|
for tr in trs {
|
||||||
acc.transfer_signed(tr.clone()).unwrap();
|
acc.transfer_signed(tr.clone()).unwrap();
|
||||||
}
|
}
|
||||||
()
|
});
|
||||||
})
|
|
||||||
.collect();
|
println!("Waiting for half the transactions to complete...",);
|
||||||
println!("Waiting for last transaction to be confirmed...",);
|
let mut tx_count = acc.transaction_count();
|
||||||
let mut val = mint_balance;
|
while tx_count < transactions.len() as u64 / 2 {
|
||||||
let mut prev = 0;
|
tx_count = acc.transaction_count();
|
||||||
while val != prev {
|
|
||||||
sleep(Duration::from_millis(20));
|
|
||||||
prev = val;
|
|
||||||
val = acc.get_balance(&mint_pubkey).wait().unwrap();
|
|
||||||
}
|
}
|
||||||
println!("Mint's Final Balance {}", val);
|
let txs = tx_count - initial_tx_count;
|
||||||
let txs = mint_balance - val;
|
println!("Transactions processed {}", txs);
|
||||||
println!("Successful transactions {}", txs);
|
|
||||||
|
|
||||||
let duration = now.elapsed();
|
let duration = now.elapsed();
|
||||||
let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos());
|
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;
|
let tps = (txs * 1_000_000_000) as f64 / ns as f64;
|
||||||
println!("Done. {} tps!", tps);
|
println!("Done. {} tps", tps);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
extern crate isatty;
|
extern crate isatty;
|
||||||
|
extern crate rayon;
|
||||||
|
extern crate ring;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
extern crate solana;
|
extern crate solana;
|
||||||
|
extern crate untrusted;
|
||||||
|
|
||||||
use isatty::stdin_isatty;
|
use isatty::stdin_isatty;
|
||||||
use solana::entry::create_entry;
|
use rayon::prelude::*;
|
||||||
|
use solana::accountant::MAX_ENTRY_IDS;
|
||||||
|
use solana::entry::{create_entry, next_tick};
|
||||||
use solana::event::Event;
|
use solana::event::Event;
|
||||||
use solana::hash::Hash;
|
use solana::mint::MintDemo;
|
||||||
use solana::mint::Mint;
|
use solana::signature::{KeyPair, KeyPairUtil};
|
||||||
use solana::signature::{KeyPair, KeyPairUtil, PublicKey};
|
|
||||||
use solana::transaction::Transaction;
|
use solana::transaction::Transaction;
|
||||||
use std::io::{stdin, Read};
|
use std::io::{stdin, Read};
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
use untrusted::Input;
|
||||||
|
|
||||||
fn transfer(from: &KeyPair, (to, tokens): (PublicKey, i64), last_id: Hash) -> Event {
|
// Generate a ledger with lots and lots of accounts.
|
||||||
Event::Transaction(Transaction::new(from, to, tokens, last_id))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
if stdin_isatty() {
|
if stdin_isatty() {
|
||||||
eprintln!("nothing found on stdin, expected a json file");
|
eprintln!("nothing found on stdin, expected a json file");
|
||||||
|
@ -29,20 +31,39 @@ fn main() {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mint: Mint = serde_json::from_str(&buffer).unwrap_or_else(|e| {
|
let demo: MintDemo = serde_json::from_str(&buffer).unwrap_or_else(|e| {
|
||||||
eprintln!("failed to parse json: {}", e);
|
eprintln!("failed to parse json: {}", e);
|
||||||
exit(1);
|
exit(1);
|
||||||
});
|
});
|
||||||
let mut entries = mint.create_entries();
|
|
||||||
|
|
||||||
let from = mint.keypair();
|
let num_accounts = demo.users.len();
|
||||||
let seed = mint.seed();
|
let last_id = demo.mint.last_id();
|
||||||
let alice = (KeyPair::new().pubkey(), 200);
|
let mint_keypair = demo.mint.keypair();
|
||||||
let bob = (KeyPair::new().pubkey(), 100);
|
|
||||||
let events = vec![transfer(&from, alice, seed), transfer(&from, bob, seed)];
|
|
||||||
entries.push(create_entry(&seed, 0, events));
|
|
||||||
|
|
||||||
for entry in entries {
|
eprintln!("Signing {} transactions...", num_accounts);
|
||||||
|
let events: Vec<_> = demo.users
|
||||||
|
.into_par_iter()
|
||||||
|
.map(|(pkcs8, tokens)| {
|
||||||
|
let rando = KeyPair::from_pkcs8(Input::from(&pkcs8)).unwrap();
|
||||||
|
let tr = Transaction::new(&mint_keypair, rando.pubkey(), tokens, last_id);
|
||||||
|
Event::Transaction(tr)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for entry in demo.mint.create_entries() {
|
||||||
|
println!("{}", serde_json::to_string(&entry).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
eprintln!("Logging the creation of {} accounts...", num_accounts);
|
||||||
|
let entry = create_entry(&last_id, 0, events);
|
||||||
|
println!("{}", serde_json::to_string(&entry).unwrap());
|
||||||
|
|
||||||
|
eprintln!("Creating {} empty entries...", MAX_ENTRY_IDS);
|
||||||
|
// Offer client lots of entry IDs to use for each transaction's last_id.
|
||||||
|
let mut last_id = last_id;
|
||||||
|
for _ in 0..MAX_ENTRY_IDS {
|
||||||
|
let entry = next_tick(&last_id, 1);
|
||||||
|
last_id = entry.id;
|
||||||
let serialized = serde_json::to_string(&entry).unwrap_or_else(|e| {
|
let serialized = serde_json::to_string(&entry).unwrap_or_else(|e| {
|
||||||
eprintln!("failed to serialize: {}", e);
|
eprintln!("failed to serialize: {}", e);
|
||||||
exit(1);
|
exit(1);
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
extern crate rayon;
|
||||||
|
extern crate ring;
|
||||||
|
extern crate serde_json;
|
||||||
|
extern crate solana;
|
||||||
|
|
||||||
|
use rayon::prelude::*;
|
||||||
|
use ring::rand::SystemRandom;
|
||||||
|
use solana::mint::{Mint, MintDemo};
|
||||||
|
use solana::signature::KeyPair;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut input_text = String::new();
|
||||||
|
io::stdin().read_line(&mut input_text).unwrap();
|
||||||
|
let trimmed = input_text.trim();
|
||||||
|
let tokens = trimmed.parse::<i64>().unwrap();
|
||||||
|
|
||||||
|
let mint = Mint::new(tokens);
|
||||||
|
let tokens_per_user = 1_000;
|
||||||
|
let num_accounts = tokens / tokens_per_user;
|
||||||
|
let rnd = SystemRandom::new();
|
||||||
|
|
||||||
|
let users: Vec<_> = (0..num_accounts)
|
||||||
|
.into_par_iter()
|
||||||
|
.map(|_| {
|
||||||
|
let pkcs8 = KeyPair::generate_pkcs8(&rnd).unwrap().to_vec();
|
||||||
|
(pkcs8, tokens_per_user)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let demo = MintDemo { mint, users };
|
||||||
|
println!("{}", serde_json::to_string(&demo).unwrap());
|
||||||
|
}
|
|
@ -62,6 +62,7 @@ fn main() {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eprintln!("Initializing...");
|
||||||
let mut entries = buffer.lines().map(|line| {
|
let mut entries = buffer.lines().map(|line| {
|
||||||
serde_json::from_str(&line).unwrap_or_else(|e| {
|
serde_json::from_str(&line).unwrap_or_else(|e| {
|
||||||
eprintln!("failed to parse json: {}", e);
|
eprintln!("failed to parse json: {}", e);
|
||||||
|
@ -71,7 +72,7 @@ fn main() {
|
||||||
|
|
||||||
// The first item in the ledger is required to be an entry with zero num_hashes,
|
// 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.
|
// which implies its id can be used as the ledger's seed.
|
||||||
entries.next().unwrap();
|
let entry0 = entries.next().unwrap();
|
||||||
|
|
||||||
// The second item in the ledger is a special transaction where the to and from
|
// 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
|
// fields are the same. That entry should be treated as a deposit, not a
|
||||||
|
@ -84,11 +85,14 @@ fn main() {
|
||||||
};
|
};
|
||||||
|
|
||||||
let acc = Accountant::new_from_deposit(&deposit.unwrap());
|
let acc = Accountant::new_from_deposit(&deposit.unwrap());
|
||||||
|
acc.register_entry_id(&entry0.id);
|
||||||
|
acc.register_entry_id(&entry1.id);
|
||||||
|
|
||||||
let mut last_id = entry1.id;
|
let mut last_id = entry1.id;
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
last_id = entry.id;
|
last_id = entry.id;
|
||||||
acc.process_verified_events(entry.events).unwrap();
|
acc.process_verified_events(entry.events).unwrap();
|
||||||
|
acc.register_entry_id(&last_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
let historian = Historian::new(&last_id, Some(1000));
|
let historian = Historian::new(&last_id, Some(1000));
|
||||||
|
@ -99,8 +103,8 @@ fn main() {
|
||||||
stdout(),
|
stdout(),
|
||||||
historian,
|
historian,
|
||||||
)));
|
)));
|
||||||
eprintln!("Listening on {}", addr);
|
|
||||||
let threads = AccountantSkel::serve(&skel, &addr, exit.clone()).unwrap();
|
let threads = AccountantSkel::serve(&skel, &addr, exit.clone()).unwrap();
|
||||||
|
eprintln!("Ready. Listening on {}", addr);
|
||||||
for t in threads {
|
for t in threads {
|
||||||
t.join().expect("join");
|
t.join().expect("join");
|
||||||
}
|
}
|
||||||
|
|
14
src/entry.rs
14
src/entry.rs
|
@ -61,7 +61,7 @@ fn add_event_data(hash_data: &mut Vec<u8>, event: &Event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates the hash `num_hashes` after `start_hash`. If the event contains
|
/// Creates the hash `num_hashes` after `start_hash`. If the event contains
|
||||||
/// signature, the final hash will be a hash of both the previous ID and
|
/// a signature, the final hash will be a hash of both the previous ID and
|
||||||
/// the signature.
|
/// the signature.
|
||||||
pub fn next_hash(start_hash: &Hash, num_hashes: u64, events: &[Event]) -> Hash {
|
pub fn next_hash(start_hash: &Hash, num_hashes: u64, events: &[Event]) -> Hash {
|
||||||
let mut id = *start_hash;
|
let mut id = *start_hash;
|
||||||
|
@ -76,10 +76,12 @@ pub fn next_hash(start_hash: &Hash, num_hashes: u64, events: &[Event]) -> Hash {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hash_data.is_empty() {
|
if !hash_data.is_empty() {
|
||||||
return extend_and_hash(&id, &hash_data);
|
extend_and_hash(&id, &hash_data)
|
||||||
}
|
} else if num_hashes != 0 {
|
||||||
|
hash(&id)
|
||||||
|
} else {
|
||||||
id
|
id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates the next Entry `num_hashes` after `start_hash`.
|
/// Creates the next Entry `num_hashes` after `start_hash`.
|
||||||
|
@ -167,6 +169,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_next_tick() {
|
fn test_next_tick() {
|
||||||
let zero = Hash::default();
|
let zero = Hash::default();
|
||||||
assert_eq!(next_tick(&zero, 1).num_hashes, 1)
|
let tick = next_tick(&zero, 1);
|
||||||
|
assert_eq!(tick.num_hashes, 1);
|
||||||
|
assert_ne!(tick.id, zero);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,12 @@ impl Mint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct MintDemo {
|
||||||
|
pub mint: Mint,
|
||||||
|
pub users: Vec<(Vec<u8>, i64)>,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
Loading…
Reference in New Issue