Compare commits

...

123 Commits

Author SHA1 Message Date
e683c34a89 Version bump 2018-03-31 14:44:43 -06:00
54e4f75081 Merge pull request #95 from jackson-sandland/source-documentation-review
94: source doc review
2018-03-30 14:50:51 -06:00
9f256f0929 94 - snakecase mod names 2018-03-30 13:10:27 -07:00
ef169a6652 94: source doc review 2018-03-30 10:43:38 -07:00
eaec25f940 Version bump 2018-03-29 15:05:38 -06:00
6a87d8975c Merge pull request #93 from garious/par-req-processing
Better benchmark, fix logging
2018-03-29 14:02:40 -06:00
b8cf5f9427 Fix transaction logging 2018-03-29 13:50:32 -06:00
2f1e585446 Better benchmark
Tolerates dropped UDP packets
2018-03-29 13:41:11 -06:00
f9309b46aa Merge pull request #92 from garious/par-req-processing
Parallel request verification
2018-03-29 13:28:21 -06:00
22f5985f1b Do request verification in parallel, and then process the verified requests 2018-03-29 13:18:08 -06:00
c59c38e50e Refactor for batch verification 2018-03-29 13:09:21 -06:00
232e1bb8a3 Colocate packet dependencies 2018-03-29 12:55:41 -06:00
1fbb34620c Fix compiler warning 2018-03-29 12:54:10 -06:00
89f5b803c9 Merge pull request #91 from garious/more-docs
Add more documentation
2018-03-29 12:39:03 -06:00
55179101cd Add more documentation 2018-03-29 12:20:54 -06:00
132495b1fc A simple consensus diagram to guide rollback/coalescing
Diagram for what's described in #84 for rollback support.
2018-03-29 10:52:02 -06:00
a03d7bf5cd Missed a couple 2018-03-28 22:20:31 -06:00
3bf225e85f Don't require install to run demo 2018-03-28 22:18:33 -06:00
cc2bb290c4 Merge pull request #89 from garious/sig-verify-bench
Add microbenchmark for signature verification
2018-03-28 22:15:10 -06:00
878ca8c5c5 Add microbenchmark for signature verification 2018-03-28 22:02:47 -06:00
4bc41d81ee Fix compiler warning 2018-03-28 21:05:21 -06:00
f6ca176fc8 Merge pull request #88 from garious/revert-tcp-client
Revert TCP sync of ledger
2018-03-28 20:28:05 -06:00
0bec360a31 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!)
2018-03-28 20:16:15 -06:00
04f30710c5 Merge pull request #87 from garious/tcp-client
tx confirmed/sec ---> tx processed/sec
2018-03-28 17:04:36 -06:00
98c0a2af87 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.
2018-03-28 16:51:21 -06:00
9db42c1769 Merge pull request #86 from garious/tcp-client
Fix up client demo
2018-03-28 14:57:09 -06:00
849bced602 Fix up client demo 2018-03-28 14:40:58 -06:00
27f29019ef Merge pull request #83 from garious/tcp-client
TCP subscription service
2018-03-28 13:19:38 -06:00
8642a41f2b See if Travis will tolerate executing some of the test 2018-03-28 10:25:16 -06:00
bf902ef5bc Ignore accountant_stub test
TODO: Figure out why this test fails on TravisCI
2018-03-28 10:05:00 -06:00
7656b55c22 nit 2018-03-27 17:22:31 -06:00
7d3d4b9443 nit 2018-03-27 17:20:23 -06:00
15c093c5e2 typo 2018-03-27 16:31:19 -06:00
116166f62d Rename project: silk -> solana 2018-03-27 16:25:12 -06:00
26b19dde75 Rename project: silk -> solana 2018-03-27 16:19:28 -06:00
c8ddc68f13 Rename project: silk -> solana 2018-03-27 16:16:27 -06:00
7c9681007c Drop support for random access to the ledger
No longer store the ledger locally.
2018-03-27 14:47:03 -06:00
13206e4976 Let clients subscribe to the ledger over TCP
TODO: Add more tests

Fixes #27
2018-03-27 14:46:24 -06:00
2f18302d32 Merge pull request #80 from garious/fix-ci
Fix CI
2018-03-26 22:13:11 -06:00
ddb21d151d Nightly rustfmt
Format code with the nightly version of rustfmt, which sorts imports.
2018-03-26 22:03:28 -06:00
c64a9fb456 Give Travis a little more time to start threads 2018-03-26 22:02:05 -06:00
ee19b4f86e See if CI hangs because of wait_on_signature() 2018-03-26 21:53:30 -06:00
14239e584f fix writer 2018-03-26 21:36:29 -06:00
112aecf6eb Merge pull request #77 from aeyakovenko/responder
Responder
2018-03-25 17:01:53 -07:00
c1783d77d7 fixed test 2018-03-25 16:18:27 -07:00
f089abb3c5 fix bench 2018-03-25 15:37:00 -07:00
8e551f5e32 debug trait tests 2018-03-25 08:22:04 -07:00
290960c3b5 wip 2018-03-25 08:06:33 -07:00
62af09adbe wip 2018-03-25 08:05:03 -07:00
e39c0b34e5 update 2018-03-25 00:06:48 -07:00
8ad90807ee responder with larger block size 2018-03-24 23:46:25 -07:00
533b3170a7 responder 2018-03-24 23:31:54 -07:00
7732f3f5fb services 2018-03-24 18:01:54 -07:00
f52f02a434 services 2018-03-24 18:01:40 -07:00
4d7d4d673e Merge pull request #75 from garious/fix-testnode
Revive silk-testnode
2018-03-23 22:16:59 -06:00
9a437f0d38 Revive silk-testnode 2018-03-23 21:49:28 -06:00
c385f8bb6e Merge pull request #73 from garious/yes-clippy
Automated mentoring by clippy
2018-03-22 15:22:12 -06:00
fa44be2a9d Ignore some clippy advice 2018-03-22 14:59:25 -06:00
117ab0c141 Clippy review 2018-03-22 14:50:24 -06:00
7488d19ae6 Clippy review 2018-03-22 14:40:28 -06:00
60524ad5f2 Clippy review 2018-03-22 14:38:06 -06:00
fad7ff8bf0 Clippy review 2018-03-22 14:31:58 -06:00
383d445ba1 Clippy review 2018-03-22 14:15:29 -06:00
803dcb0800 Mutex<bool> -> AtomicBool 2018-03-22 14:05:23 -06:00
fde320e2f2 Merge pull request #71 from garious/rework-recorder
Replicate the ledger
2018-03-21 17:23:55 -06:00
8ea97141ea Update the test to replicate the ledger 2018-03-21 17:15:32 -06:00
9f232bac58 Allow clients to sync the ledger
Fixes #4
2018-03-21 15:46:49 -06:00
8295cc11c0 Move JSON printing up the stack 2018-03-20 23:15:44 -06:00
70f80adb9a Merge pull request #70 from garious/planevent-to-witness
Cleanup
2018-03-20 19:13:02 -06:00
9a7cac1e07 Use the Entry API to remove the double lookup 2018-03-20 18:07:54 -06:00
c584a25ec9 Move complete_transaction from method to function
So that we can hold separate mutable references to the pending queue
and the map of balances.
2018-03-20 17:47:57 -06:00
bff32bf7bc Cleanup 2018-03-20 17:32:02 -06:00
d0e7450389 Add docs 2018-03-20 16:58:14 -06:00
4da89ac8a9 Cleanup naming 2018-03-20 16:53:41 -06:00
f7032f7d9a Cleanup: replace bool retval with is_complete() method 2018-03-20 16:52:47 -06:00
7c7e3931a0 Better docs 2018-03-20 15:52:46 -06:00
6be3d62d89 Remove Action from spending plans 2018-03-20 15:43:07 -06:00
6f509a8a1e Reorder 2018-03-20 15:31:28 -06:00
4379fabf16 PlanEvent -> Witness
The term used by the Simplicity smart contract language
2018-03-20 15:25:50 -06:00
6b66e1a077 Merge pull request #69 from garious/move-streamer-benchmark
Move streamer benchmark out of unit tests
2018-03-19 17:33:45 -06:00
c11a3e0fdc Move streamer benchmark out of unit tests 2018-03-19 17:10:01 -06:00
3418033c55 Merge pull request #68 from garious/fix-bench
Fix bench
2018-03-19 16:52:41 -06:00
caa9a846ed Boot sha2-asm
Stick with pure Rust until someone can write a benchmark that
demonstrates that sha2-asm adds value. If we go with a GPU
implementation first, we may never need to do that.
2018-03-19 16:42:30 -06:00
8ee76bcea0 Fix benchmark build 2018-03-19 16:41:01 -06:00
47325cbe01 Merge pull request #67 from garious/cleanup-naming
Cleanup naming
2018-03-19 16:29:08 -06:00
e0c8417297 Apply renames to docs 2018-03-19 10:23:43 -06:00
9238ee9572 No longer rename log crate 2018-03-19 10:18:51 -06:00
64af37e0cd logger -> recorder
Free up namespace for a traditional runtime logger.
2018-03-19 10:16:21 -06:00
9f9b79f30b log -> ledger
Free up namespace for traditional runtime logs.
2018-03-19 10:09:19 -06:00
265f41887f asset -> tokens 2018-03-19 10:03:41 -06:00
4f09e5d04c Merge pull request #66 from garious/conditional-plan
Simplify contract language
2018-03-18 21:12:26 -06:00
434f321336 Add spending plan tests 2018-03-18 21:02:28 -06:00
f4e0d1be58 Make conditions explicit in races
And boot recursive spending plans. That path required heap allocations.
Since we don't have a need for this generality right now, reduce the
language to the smallest one that can pass our test suite.
2018-03-17 20:43:05 -06:00
e5bae0604b Specialize transaction assets to i64
Proof-of-history is generic, but now that we're using it entirely
for tokens, we can specialize the type and start doing more interesting
things than just Eq and Serialize operations.
2018-03-17 19:56:15 -06:00
e7da083c31 Move spending plans to their own crate 2018-03-17 19:56:15 -06:00
367c32dabe Guard spending plans, not just payments 2018-03-17 19:56:15 -06:00
e054238af6 Merge pull request #65 from aeyakovenko/fixtest
fix test
2018-03-14 12:21:08 -07:00
e8faf6d59a trait test 2018-03-14 11:28:05 -07:00
baa4ea3cd8 wfmt 2018-03-14 11:14:40 -07:00
75ef0f0329 fix test 2018-03-14 11:02:38 -07:00
65185c0011 Merge pull request #63 from aeyakovenko/streamer-integrated
Streamer integrated
2018-03-12 08:38:59 -06:00
eb94613d7d Use streaming socket interface within accountant
Pull messages from streamer process them and forward them to the sender.
2018-03-11 23:41:09 -05:00
67f4f4fb49 Merge pull request #64 from garious/dumb-contracts
Entry-level smart contracts
2018-03-11 13:23:11 -06:00
a7ecf4ac4c Merge pull request #57 from aeyakovenko/streamer
Streamer
2018-03-11 13:22:49 -06:00
45765b625a Don't let users accidentally burn their funds either 2018-03-11 12:04:49 -06:00
aa0a184ebe Ensure the server isn't passed a Plan that spends more than is bonded 2018-03-11 11:53:45 -06:00
069f9f0d5d add ipv6 flag to cargo.toml 2018-03-11 12:53:16 -05:00
c82b520ea8 remove unecessary returns 2018-03-11 11:45:17 -05:00
9d6e5bde4a ipv6 test with a separate flag 2018-03-11 11:22:21 -05:00
0eb3669fbf cleanup timestamp processing 2018-03-11 00:30:01 -07:00
30449b6054 cleanup sig processing 2018-03-11 00:11:08 -07:00
f5f71a19b8 First go at smart contracts
Needs lots of cleanup.
2018-03-10 22:00:48 -07:00
0135971769 Fast UdpSocket reader
* message needs to fit into 256 bytes
* allocator to keep track of blocks of messages
* udp socket receiver server that fills up the block as fast as possible
* udp socket sender server that sends out the block as fast as possible
2018-03-10 21:09:23 -06:00
8579795c40 Ensure transactions won't get canceled after next refactor 2018-03-10 19:44:45 -07:00
9d77fd7eec Store only spending plans, not full transactions 2018-03-10 18:35:10 -07:00
8c40d1bd72 Move spending endpoints into expressions 2018-03-10 17:41:18 -07:00
7a0bc7d888 Move smart contract fields into their own struct 2018-03-10 16:55:39 -07:00
1e07014f86 Merge pull request #62 from garious/batch-events
Batch events
2018-03-09 17:37:02 -07:00
49281b24e5 Move Tick out of Event
Every Entry is now a Tick and the entries contain events.
2018-03-09 17:22:17 -07:00
a8b1980de4 Restore reorder attack test 2018-03-09 17:02:17 -07:00
b8cd5f0482 Boot Cargo.lock from git
Only add Cargo.lock to downstream dependencies.
2018-03-09 16:26:26 -07:00
cc9f0788aa Batch events
It's now a Tick that locks down event order. Before this change, the
event order would be locked down in the order the server sees it.

Fixes #59
Fixes #61
2018-03-09 16:16:33 -07:00
209910299d Version bump
Next release probably won't have a compatible entry log with the
0.3.x line.
2018-03-09 14:33:37 -07:00
32 changed files with 1634 additions and 1189 deletions

2
.gitignore vendored
View File

@ -1,3 +1,3 @@
Cargo.lock
/target/
**/*.rs.bk

View File

@ -9,7 +9,7 @@ matrix:
- rust: stable
- rust: nightly
env:
- FEATURES='asm,unstable'
- FEATURES='unstable'
before_script: |
export PATH="$PATH:$HOME/.cargo/bin"
rustup component add rustfmt-preview

482
Cargo.lock generated
View File

@ -1,482 +0,0 @@
[[package]]
name = "arrayref"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "arrayvec"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bincode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "block-buffer"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "byte-tools"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byteorder"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "chrono"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-deque"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-epoch 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-epoch"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-utils"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "digest"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "dtoa"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "either"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fake-simd"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fuchsia-zircon"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "fuchsia-zircon-sys"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "gcc"
version = "0.3.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "generic-array"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"typenum 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "generic-array"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"serde 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
"typenum 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "itoa"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lazy_static"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lazy_static"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "memoffset"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "nodrop"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "num"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
"num-iter 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-integer"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-iter"
version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "num_cpus"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quote"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rayon"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rayon"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rayon-core"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "redox_syscall"
version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ring"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "scopeguard"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde_derive"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive_internals 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.12.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_derive_internals"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.12.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_json"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "sha2"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"digest 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "sha2-asm"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)",
"generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "silk"
version = "0.3.3"
dependencies = [
"bincode 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ring 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
"sha2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"sha2-asm 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "0.12.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "time"
version = "0.1.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "typenum"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "untrusted"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0fd1479b7c29641adbd35ff3b5c293922d696a92f25c8c975da3e0acbc87258f"
"checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef"
"checksum bincode 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bda13183df33055cbb84b847becce220d392df502ebe7a4a78d7021771ed94d0"
"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf"
"checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab"
"checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40"
"checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23"
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
"checksum chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c20ebe0b2b08b0aeddba49c609fe7957ba2e33449882cb186a180bc60682fa9"
"checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3"
"checksum crossbeam-epoch 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "59796cc6cbbdc6bb319161349db0c3250ec73ec7fcb763a51065ec4e2e158552"
"checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9"
"checksum digest 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "00a49051fef47a72c9623101b19bd71924a45cca838826caae3eaa4d00772603"
"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
"checksum either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "740178ddf48b1a9e878e6d6509a1442a2d42fd2928aae8e7a6f8a36fb01981b3"
"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
"checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb"
"checksum generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fceb69994e330afed50c93524be68c42fa898c2d9fd4ee8da03bd7363acd26f2"
"checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d"
"checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c"
"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d"
"checksum libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "f54263ad99207254cf58b5f701ecb432c717445ea2ee8af387334bdd1a03fdff"
"checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3"
"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2"
"checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e"
"checksum num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f8d26da319fb45674985c78f1d1caf99aa4941f785d384a2ae36d0740bc3e2fe"
"checksum num-iter 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "4b226df12c5a59b63569dd57fafb926d91b385dfce33d8074a412411b689d593"
"checksum num-traits 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3c2bd9b9d21e48e956b763c9f37134dc62d9e95da6edb3f672cacb6caf3cd3"
"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
"checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0"
"checksum quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eca14c727ad12702eb4b6bfb5a232287dcf8385cb8ca83a3eeaf6519c44c408"
"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
"checksum rayon 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b614fe08b6665cb9a231d07ac1364b0ef3cb3698f1239ee0c4c3a88a524f54c8"
"checksum rayon 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "485541959c8ecc49865526fe6c4de9653dd6e60d829d6edf0be228167b60372d"
"checksum rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d24ad214285a7729b174ed6d3bcfcb80177807f959d95fafd5bfc5c4f201ac8"
"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd"
"checksum ring 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6f7d28b30a72c01b458428e0ae988d4149c20d902346902be881e3edc4bb325c"
"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27"
"checksum serde 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)" = "e928fecdb00fe608c96f83a012633383564e730962fc7a0b79225a6acf056798"
"checksum serde_derive 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)" = "95f666a2356d87ce4780ea15b14b13532785579a5cad2dcba5292acc75f6efe2"
"checksum serde_derive_internals 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1fc848d073be32cd982380c06587ea1d433bc1a4c4a111de07ec2286a3ddade8"
"checksum serde_json 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "57781ed845b8e742fc2bf306aba8e3b408fe8c366b900e3769fbc39f49eb8b39"
"checksum sha2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7daca11f2fdb8559c4f6c588386bed5e2ad4b6605c1442935a7f08144a918688"
"checksum sha2-asm 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e319010fd740857efd4428b8ee1b74f311aeb0fda1ece174a9bad6741182d26"
"checksum syn 0.12.13 (registry+https://github.com/rust-lang/crates.io-index)" = "517f6da31bc53bf080b9a77b29fbd0ff8da2f5a2ebd24c73c2238274a94ac7cb"
"checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098"
"checksum typenum 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13a99dc6780ef33c78780b826cf9d2a78840b72cae9474de4bcaf9051e60ebbd"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f392d7819dbe58833e26872f5f6f0d68b7bbbe90fc3667e98731c4a15ad9a7ae"
"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@ -1,51 +1,50 @@
[package]
name = "silk"
description = "A silky smooth implementation of the Loom architecture"
version = "0.3.3"
documentation = "https://docs.rs/silk"
name = "solana"
description = "High Performance Blockchain"
version = "0.4.0"
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 <aeyakovenko@gmail.com>",
"Greg Fitzgerald <garious@gmail.com>",
"Anatoly Yakovenko <anatoly@solana.co>",
"Greg Fitzgerald <greg@solana.co>",
]
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 = []
asm = ["sha2-asm"]
ipv6 = []
[dependencies]
rayon = "1.0.0"
sha2 = "0.7.0"
sha2-asm = {version="0.3", optional=true}
generic-array = { version = "0.9.0", default-features = false, features = ["serde"] }
serde = "1.0.27"
serde_derive = "1.0.27"
@ -54,3 +53,5 @@ ring = "0.12.1"
untrusted = "0.5.1"
bincode = "1.0.0"
chrono = { version = "0.4.0", features = ["serde"] }
log = "^0.4.1"
matches = "^0.1.6"

View File

@ -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&trade; 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&trade; is a new architecture for a high performance blockchain. It aims to support
over 700 thousand transactions per second on a gigabit network.
Running the demo
===
@ -31,57 +24,51 @@ $ curl https://sh.rustup.rs -sSf | sh
$ source $HOME/.cargo/env
```
Install the silk executables:
```bash
$ cargo install silk
```
The testnode server is initialized with a transaction log from stdin and
generates new log entries on stdout. To create the input log, we'll need
to create *the mint* and use it to generate a *genesis log*. It's done in
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
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 1000000000 | cargo run --release --bin solana-mint | tee mint.json
$ cat mint.json | cargo run --release --bin solana-genesis | tee genesis.log
```
Now you can start the server:
```bash
$ cat genesis.log | silk-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 log.
the JSON configuration file here, not the genesis ledger.
```bash
$ cat mint.json | silk-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 transaction log. You should
Now kill the server with Ctrl-C, and take a look at the ledger. You should
see something similar to:
```json
{"num_hashes":27,"id":[0, "..."],"event":"Tick"}
{"num_hashes":3,"id":[67, "..."],"event":{"Transaction":{"asset":42}}}
{"num_hashes":3,"id":[67, "..."],"event":{"Transaction":{"tokens":42}}}
{"num_hashes":27,"id":[0, "..."],"event":"Tick"}
```
Now restart the server from where we left off. Pass it both the genesis log, and
the transaction log.
Now restart the server from where we left off. Pass it both the genesis ledger, and
the transaction ledger.
```bash
$ cat genesis.log transactions0.log | silk-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
previous round, and so no additional transactions are added.
```bash
$ cat mint.json | silk-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.
@ -103,8 +90,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
@ -128,5 +115,5 @@ $ rustup install nightly
Run the benchmarks:
```bash
$ cargo +nightly bench --features="asm,unstable"
$ cargo +nightly bench --features="unstable"
```

15
doc/consensus.msc Normal file
View File

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

View File

@ -1,27 +1,27 @@
The Historian
===
Create a *Historian* and send it *events* to generate an *event log*, where each log *entry*
Create a *Historian* and send it *events* to generate an *event log*, where each *entry*
is tagged with the historian's latest *hash*. Then ensure the order of events was not tampered
with by verifying each entry's hash can be generated from the hash in the previous entry:
![historian](https://user-images.githubusercontent.com/55449/36950845-459bdb58-1fb9-11e8-850e-894586f3729b.png)
```rust
extern crate silk;
extern crate solana;
use silk::historian::Historian;
use silk::log::{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;
fn create_log(hist: &Historian<Hash>) -> Result<(), SendError<Event<Hash>>> {
fn create_ledger(hist: &Historian<Hash>) -> Result<(), SendError<Event<Hash>>> {
sleep(Duration::from_millis(15));
let asset = Hash::default();
let tokens = 42;
let keypair = generate_keypair();
let event0 = Event::new_claim(get_pubkey(&keypair), asset, sign_claim_data(&asset, &keypair));
let event0 = Event::new_claim(get_pubkey(&keypair), tokens, sign_claim_data(&tokens, &keypair));
hist.sender.send(event0)?;
sleep(Duration::from_millis(10));
Ok(())
@ -30,7 +30,7 @@ fn create_log(hist: &Historian<Hash>) -> Result<(), SendError<Event<Hash>>> {
fn main() {
let seed = Hash::default();
let hist = Historian::new(&seed, Some(10));
create_log(&hist).expect("send error");
create_ledger(&hist).expect("send error");
drop(hist.sender);
let entries: Vec<Entry<Hash>> = hist.receiver.iter().collect();
for entry in &entries {
@ -42,11 +42,11 @@ fn main() {
}
```
Running the program should produce a log similar to:
Running the program should produce a ledger similar to:
```rust
Entry { num_hashes: 0, id: [0, ...], event: Tick }
Entry { num_hashes: 3, id: [67, ...], event: Transaction { asset: [37, ...] } }
Entry { num_hashes: 3, id: [67, ...], event: Transaction { tokens: 42 } }
Entry { num_hashes: 3, id: [123, ...], event: Tick }
```

View File

@ -1,17 +1,17 @@
msc {
client,historian,logger;
client,historian,recorder;
logger=>historian [ label = "e0 = Entry{id: h0, n: 0, event: Tick}" ] ;
logger=>logger [ label = "h1 = hash(h0)" ] ;
logger=>logger [ label = "h2 = hash(h1)" ] ;
recorder=>historian [ label = "e0 = Entry{id: h0, n: 0, event: Tick}" ] ;
recorder=>recorder [ label = "h1 = hash(h0)" ] ;
recorder=>recorder [ label = "h2 = hash(h1)" ] ;
client=>historian [ label = "Transaction(d0)" ] ;
historian=>logger [ label = "Transaction(d0)" ] ;
logger=>logger [ label = "h3 = hash(h2 + d0)" ] ;
logger=>historian [ label = "e1 = Entry{id: hash(h3), n: 3, event: Transaction(d0)}" ] ;
logger=>logger [ label = "h4 = hash(h3)" ] ;
logger=>logger [ label = "h5 = hash(h4)" ] ;
logger=>logger [ label = "h6 = hash(h5)" ] ;
logger=>historian [ label = "e2 = Entry{id: h6, n: 3, event: Tick}" ] ;
historian=>recorder [ label = "Transaction(d0)" ] ;
recorder=>recorder [ label = "h3 = hash(h2 + d0)" ] ;
recorder=>historian [ label = "e1 = Entry{id: hash(h3), n: 3, event: Transaction(d0)}" ] ;
recorder=>recorder [ label = "h4 = hash(h3)" ] ;
recorder=>recorder [ label = "h5 = hash(h4)" ] ;
recorder=>recorder [ label = "h6 = hash(h5)" ] ;
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)" ] ;

View File

@ -1,18 +1,22 @@
//! 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` 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
//! already been signed and verified.
use hash::Hash;
use chrono::prelude::*;
use entry::Entry;
use event::Event;
use transaction::{Condition, Transaction};
use signature::{KeyPair, PublicKey, Signature};
use hash::Hash;
use historian::Historian;
use mint::Mint;
use historian::{reserve_signature, Historian};
use std::sync::mpsc::SendError;
use plan::{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 chrono::prelude::*;
use std::sync::mpsc::SendError;
use transaction::Transaction;
#[derive(Debug, PartialEq, Eq)]
pub enum AccountingError {
@ -24,25 +28,32 @@ pub enum AccountingError {
pub type Result<T> = result::Result<T, AccountingError>;
/// Commit funds to the 'to' party.
fn complete_transaction(balances: &mut HashMap<PublicKey, i64>, plan: &Plan) {
if let Plan::Pay(ref payment) = *plan {
*balances.entry(payment.to).or_insert(0) += payment.tokens;
}
}
pub struct Accountant {
pub historian: Historian,
pub balances: HashMap<PublicKey, i64>,
pub first_id: Hash,
pub last_id: Hash,
pending: HashMap<Signature, Transaction<i64>>,
pending: HashMap<Signature, Plan>,
time_sources: HashSet<PublicKey>,
last_time: DateTime<Utc>,
}
impl Accountant {
/// Create an Accountant using an existing ledger.
pub fn new_from_entries<I>(entries: I, ms_per_tick: Option<u64>) -> Self
where
I: IntoIterator<Item = Entry>,
{
let mut entries = entries.into_iter();
// The first item in the log is required to be an entry with zero num_hashes,
// which implies its id can be used as the log's seed.
// 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;
@ -51,146 +62,106 @@ impl Accountant {
historian: hist,
balances: HashMap::new(),
first_id: start_hash,
last_id: start_hash,
pending: HashMap::new(),
time_sources: HashSet::new(),
last_time: Utc.timestamp(0, 0),
};
// The second item in the log 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
// transfer to oneself.
let entry1 = entries.next().unwrap();
acc.process_verified_event(&entry1.event, true).unwrap();
acc.process_verified_event(&entry1.events[0], true).unwrap();
for entry in entries {
acc.process_verified_event(&entry.event, false).unwrap();
for event in entry.events {
acc.process_verified_event(&event, false).unwrap();
}
}
acc
}
/// Create an Accountant with only a Mint. Typically used by unit tests.
pub fn new(mint: &Mint, ms_per_tick: Option<u64>) -> Self {
Self::new_from_entries(mint.create_entries(), ms_per_tick)
}
pub fn sync(self: &mut Self) -> Hash {
while let Ok(entry) = self.historian.receiver.try_recv() {
self.last_id = entry.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
}
self.last_id
}
fn is_deposit(allow_deposits: bool, from: &PublicKey, to: &PublicKey) -> bool {
allow_deposits && from == to
}
pub fn process_transaction(self: &mut Self, tr: Transaction<i64>) -> Result<()> {
if !tr.verify() {
return Err(AccountingError::InvalidTransfer);
}
if self.get_balance(&tr.from).unwrap_or(0) < tr.asset {
/// 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, false)?;
if let Err(SendError(_)) = self.historian.sender.send(Event::Transaction(tr)) {
if let Err(SendError(_)) = self.historian
.sender
.send(Signal::Event(Event::Transaction(tr)))
{
return Err(AccountingError::SendError);
}
Ok(())
}
/// Commit funds to the 'to' party.
fn complete_transaction(self: &mut Self, tr: &Transaction<i64>) {
if self.balances.contains_key(&tr.to) {
if let Some(x) = self.balances.get_mut(&tr.to) {
*x += tr.asset;
}
} else {
self.balances.insert(tr.to, tr.asset);
/// 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)
}
/// Return funds to the 'from' party.
fn cancel_transaction(self: &mut Self, tr: &Transaction<i64>) {
if let Some(x) = self.balances.get_mut(&tr.from) {
*x += tr.asset;
}
}
// TODO: Move this to transaction.rs
fn all_satisfied(&self, conds: &[Condition]) -> bool {
let mut satisfied = true;
for cond in conds {
if let &Condition::Timestamp(dt) = cond {
if dt > self.last_time {
satisfied = false;
}
} else {
satisfied = false;
}
}
satisfied
}
/// Process a Transaction that has already been verified.
fn process_verified_transaction(
self: &mut Self,
tr: &Transaction<i64>,
tr: &Transaction,
allow_deposits: bool,
) -> Result<()> {
if !reserve_signature(&mut self.historian.signatures, &tr.sig) {
if !self.historian.reserve_signature(&tr.sig) {
return Err(AccountingError::InvalidTransferSignature);
}
if !tr.unless_any.is_empty() {
// TODO: Check to see if the transaction is expired.
}
if !Self::is_deposit(allow_deposits, &tr.from, &tr.to) {
if !Self::is_deposit(allow_deposits, &tr.from, &tr.plan) {
if let Some(x) = self.balances.get_mut(&tr.from) {
*x -= tr.asset;
*x -= tr.tokens;
}
}
if !self.all_satisfied(&tr.if_all) {
self.pending.insert(tr.sig, tr.clone());
return Ok(());
let mut plan = tr.plan.clone();
plan.apply_witness(&Witness::Timestamp(self.last_time));
if plan.is_complete() {
complete_transaction(&mut self.balances, &plan);
} else {
self.pending.insert(tr.sig, plan);
}
self.complete_transaction(tr);
Ok(())
}
/// Process a Witness Signature that has already been verified.
fn process_verified_sig(&mut self, from: PublicKey, tx_sig: Signature) -> Result<()> {
let mut cancel = false;
if let Some(tr) = self.pending.get(&tx_sig) {
// Cancel:
// if Signature(from) is in unless_any, return funds to tx.from, and remove the tx from this map.
// TODO: Use find().
for cond in &tr.unless_any {
if let Condition::Signature(pubkey) = *cond {
if from == pubkey {
cancel = true;
break;
}
}
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());
e.remove_entry();
}
}
};
if cancel {
if let Some(tr) = self.pending.remove(&tx_sig) {
self.cancel_transaction(&tr);
}
}
// Process Multisig:
// otherwise, if "Signature(from) is in if_all, remove it. If that causes that list
// to be empty, add the asset to to, and remove the tx from this map.
Ok(())
}
/// Process a Witness Timestamp that has already been verified.
fn process_verified_timestamp(&mut self, from: PublicKey, dt: DateTime<Utc>) -> Result<()> {
// If this is the first timestamp we've seen, it probably came from the genesis block,
// so we'll trust it.
@ -205,92 +176,85 @@ impl Accountant {
} else {
return Ok(());
}
// TODO: Lookup pending Transaction waiting on time, signed by a whitelisted PublicKey.
// Expire:
// if a Timestamp after this DateTime is in unless_any, return funds to tx.from,
// and remove the tx from this map.
// Check to see if any timelocked transactions can be completed.
let mut completed = vec![];
for (key, tr) in &self.pending {
for cond in &tr.if_all {
if let Condition::Timestamp(dt) = *cond {
if self.last_time >= dt {
if tr.if_all.len() == 1 {
completed.push(*key);
}
}
}
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);
completed.push(key.clone());
}
// TODO: Add this in once we start removing constraints
//if tr.if_all.is_empty() {
// // TODO: Remove tr from pending
// self.complete_transaction(tr);
//}
}
for key in completed {
if let Some(tr) = self.pending.remove(&key) {
self.complete_transaction(&tr);
}
self.pending.remove(&key);
}
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::Tick => Ok(()),
Event::Transaction(ref tr) => self.process_verified_transaction(tr, allow_deposits),
Event::Signature { from, tx_sig, .. } => self.process_verified_sig(from, tx_sig),
Event::Timestamp { from, dt, .. } => self.process_verified_timestamp(from, dt),
}
}
/// 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,
keypair: &KeyPair,
to: PublicKey,
last_id: Hash,
) -> Result<Signature> {
let tr = Transaction::new(keypair, to, n, self.last_id);
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`
/// 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,
keypair: &KeyPair,
to: PublicKey,
dt: DateTime<Utc>,
last_id: Hash,
) -> Result<Signature> {
let tr = Transaction::new_on_date(keypair, to, dt, n, self.last_id);
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<i64> {
self.balances.get(pubkey).map(|x| *x)
self.balances.get(pubkey).cloned()
}
}
#[cfg(test)]
mod tests {
use super::*;
use recorder::ExitReason;
use signature::KeyPairUtil;
use logger::ExitReason;
#[test]
fn test_accountant() {
let alice = Mint::new(10_000);
let bob_pubkey = KeyPair::new().pubkey();
let mut acc = Accountant::new(&alice, Some(2));
acc.transfer(1_000, &alice.keypair(), bob_pubkey).unwrap();
acc.transfer(1_000, &alice.keypair(), bob_pubkey, alice.seed())
.unwrap();
assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_000);
acc.transfer(500, &alice.keypair(), bob_pubkey).unwrap();
acc.transfer(500, &alice.keypair(), bob_pubkey, alice.seed())
.unwrap();
assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 1_500);
drop(acc.historian.sender);
@ -305,9 +269,10 @@ mod tests {
let alice = Mint::new(11_000);
let mut acc = Accountant::new(&alice, Some(2));
let bob_pubkey = KeyPair::new().pubkey();
acc.transfer(1_000, &alice.keypair(), bob_pubkey).unwrap();
acc.transfer(1_000, &alice.keypair(), bob_pubkey, alice.seed())
.unwrap();
assert_eq!(
acc.transfer(10_001, &alice.keypair(), bob_pubkey),
acc.transfer(10_001, &alice.keypair(), bob_pubkey, alice.seed()),
Err(AccountingError::InsufficientFunds)
);
@ -322,13 +287,38 @@ mod tests {
);
}
#[test]
fn test_overspend_attack() {
let alice = Mint::new(1);
let mut acc = Accountant::new(&alice, None);
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()),
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.log_transaction(tr.clone()),
Err(AccountingError::InvalidTransfer)
);
}
#[test]
fn test_transfer_to_newb() {
let alice = Mint::new(10_000);
let mut acc = Accountant::new(&alice, Some(2));
let alice_keypair = alice.keypair();
let bob_pubkey = KeyPair::new().pubkey();
acc.transfer(500, &alice_keypair, bob_pubkey).unwrap();
acc.transfer(500, &alice_keypair, bob_pubkey, alice.seed())
.unwrap();
assert_eq!(acc.get_balance(&bob_pubkey).unwrap(), 500);
drop(acc.historian.sender);
@ -345,7 +335,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)
acc.transfer_on_date(1, &alice_keypair, bob_pubkey, dt, alice.seed())
.unwrap();
// Alice's balance will be zero because all funds are locked up.
@ -374,7 +364,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)
acc.transfer_on_date(1, &alice_keypair, bob_pubkey, dt, alice.seed())
.unwrap();
assert_eq!(acc.get_balance(&alice.pubkey()), Some(0));
@ -388,7 +378,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)
let sig = acc.transfer_on_date(1, &alice_keypair, bob_pubkey, dt, alice.seed())
.unwrap();
// Alice's balance will be zero because all funds are locked up.

View File

@ -1,24 +1,55 @@
use std::io;
use accountant::Accountant;
use transaction::Transaction;
use signature::PublicKey;
use hash::Hash;
use entry::Entry;
use std::net::UdpSocket;
use bincode::{deserialize, serialize};
//! 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.
pub struct AccountantSkel {
use accountant::Accountant;
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::net::{SocketAddr, UdpSocket};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex};
use std::thread::{spawn, JoinHandle};
use std::time::Duration;
use streamer;
use transaction::Transaction;
use rayon::prelude::*;
pub struct AccountantSkel<W: Write + Send + 'static> {
pub acc: Accountant,
pub last_id: Hash,
writer: W,
}
#[cfg_attr(feature = "cargo-clippy", allow(large_enum_variant))]
#[derive(Serialize, Deserialize, Debug)]
pub enum Request {
Transaction(Transaction<i64>),
Transaction(Transaction),
GetBalance { key: PublicKey },
GetEntries { last_id: Hash },
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.into_par_iter().filter({ |x| x.0.verify() }).collect()
}
#[derive(Serialize, Deserialize, Debug)]
pub enum Response {
Balance { key: PublicKey, val: Option<i64> },
@ -26,15 +57,31 @@ pub enum Response {
Id { id: Hash, is_last: bool },
}
impl AccountantSkel {
pub fn new(acc: Accountant) -> Self {
AccountantSkel { acc }
impl<W: Write + Send + 'static> AccountantSkel<W> {
/// 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,
}
}
pub fn process_request(self: &mut Self, msg: Request) -> Option<Response> {
/// 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();
}
self.last_id
}
/// Process Request items sent by clients.
pub fn log_verified_request(&mut self, msg: Request) -> Option<Response> {
match msg {
Request::Transaction(tr) => {
if let Err(err) = self.acc.process_transaction(tr) {
if let Err(err) = self.acc.log_verified_transaction(tr) {
eprintln!("Transaction error: {:?}", err);
}
None
@ -43,11 +90,9 @@ impl AccountantSkel {
let val = self.acc.get_balance(&key);
Some(Response::Balance { key, val })
}
Request::GetEntries { .. } => Some(Response::Entries { entries: vec![] }),
Request::GetId { is_last } => Some(Response::Id {
id: if is_last {
self.acc.sync();
self.acc.last_id
self.sync()
} else {
self.acc.first_id
},
@ -56,20 +101,89 @@ impl AccountantSkel {
}
}
/// UDP Server that forwards messages to Accountant methods.
pub fn serve(self: &mut Self, addr: &str) -> io::Result<()> {
let socket = UdpSocket::bind(addr)?;
let mut buf = vec![0u8; 1024];
loop {
//println!("skel: Waiting for incoming packets...");
let (_sz, src) = socket.recv_from(&mut buf)?;
// TODO: Return a descriptive error message if deserialization fails.
let req = deserialize(&buf).expect("deserialize request");
if let Some(resp) = self.process_request(req) {
socket.send_to(&serialize(&resp).expect("serialize response"), &src)?;
fn process(
obj: &Arc<Mutex<AccountantSkel<W>>>,
r_reader: &streamer::Receiver,
s_responder: &streamer::Responder,
packet_recycler: &streamer::PacketRecycler,
response_recycler: &streamer::ResponseRecycler,
) -> Result<()> {
let timer = Duration::new(1, 0);
let msgs = r_reader.recv_timeout(timer)?;
let msgs_ = msgs.clone();
let rsps = streamer::allocate(response_recycler);
let rsps_ = rsps.clone();
{
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().log_verified_request(req) {
if ursps.responses.len() <= num {
ursps
.responses
.resize((num + 1) * 2, streamer::Response::default());
}
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;
}
}
ursps.responses.resize(num, streamer::Response::default());
}
s_responder.send(rsps_)?;
streamer::recycle(packet_recycler, msgs_);
Ok(())
}
/// Create a UDP microservice that forwards messages the given AccountantSkel.
/// Set `exit` to shutdown its threads.
pub fn serve(
obj: Arc<Mutex<AccountantSkel<W>>>,
addr: &str,
exit: Arc<AtomicBool>,
) -> Result<Vec<JoinHandle<()>>> {
let read = UdpSocket::bind(addr)?;
// make sure we are on the same interface
let mut local = read.local_addr()?;
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 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,
&r_reader,
&s_responder,
&packet_recycler,
&response_recycler,
);
if e.is_err() && exit.load(Ordering::Relaxed) {
break;
}
});
Ok(vec![t_receiver, t_responder, t_server])
}
}

View File

@ -1,37 +1,41 @@
//! 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_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.
use std::net::UdpSocket;
use std::io;
use bincode::{deserialize, serialize};
use transaction::Transaction;
use signature::{KeyPair, PublicKey, Signature};
use hash::Hash;
use entry::Entry;
use accountant_skel::{Request, Response};
use bincode::{deserialize, serialize};
use hash::Hash;
use signature::{KeyPair, PublicKey, Signature};
use std::io;
use std::net::UdpSocket;
use transaction::Transaction;
pub struct AccountantStub {
pub addr: String,
pub socket: UdpSocket,
pub last_id: Option<Hash>,
}
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(),
socket,
last_id: None,
}
}
pub fn transfer_signed(&self, tr: Transaction<i64>) -> io::Result<usize> {
/// 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<usize> {
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,
@ -44,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<Option<i64>> {
let req = Request::GetBalance { key: *pubkey };
let data = serialize(&req).expect("serialize GetBalance");
@ -58,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<Hash> {
let req = Request::GetId { is_last };
let data = serialize(&req).expect("serialize GetId");
@ -71,41 +79,13 @@ 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<Hash> {
self.get_id(true)
}
pub fn wait_on_signature(&mut self, wait_sig: &Signature) -> io::Result<()> {
let last_id = match self.last_id {
None => {
let first_id = self.get_id(false)?;
self.last_id = Some(first_id);
first_id
}
Some(last_id) => last_id,
};
let req = Request::GetEntries { last_id };
let data = serialize(&req).unwrap();
self.socket.send_to(&data, &self.addr).map(|_| ())?;
let mut buf = vec![0u8; 1024];
self.socket.recv_from(&mut buf)?;
let resp = deserialize(&buf).expect("deserialize signature");
if let Response::Entries { entries } = resp {
for Entry { id, event, .. } in entries {
self.last_id = Some(id);
if let Some(sig) = event.get_signature() {
if sig == *wait_sig {
return Ok(());
}
}
}
}
// TODO: Loop until we found it.
Ok(())
}
}
#[cfg(test)]
@ -113,27 +93,34 @@ mod tests {
use super::*;
use accountant::Accountant;
use accountant_skel::AccountantSkel;
use std::thread::{sleep, spawn};
use std::time::Duration;
use mint::Mint;
use signature::{KeyPair, KeyPairUtil};
use std::io::sink;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::thread::sleep;
use std::time::Duration;
// TODO: Figure out why this test sometimes hangs on TravisCI.
#[test]
fn test_accountant_stub() {
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, None);
let acc = Accountant::new(&alice, Some(30));
let bob_pubkey = KeyPair::new().pubkey();
spawn(move || AccountantSkel::new(acc).serve(addr).unwrap());
sleep(Duration::from_millis(30));
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();
sleep(Duration::from_millis(300));
let socket = UdpSocket::bind(send_addr).unwrap();
let mut acc = AccountantStub::new(addr, socket);
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)
let _sig = acc.transfer(500, &alice.keypair(), bob_pubkey, &last_id)
.unwrap();
acc.wait_on_signature(&sig).unwrap();
assert_eq!(acc.get_balance(&bob_pubkey).unwrap().unwrap(), 500);
exit.store(true, Ordering::Relaxed);
}
}

View File

@ -1,13 +1,16 @@
extern crate rayon;
extern crate serde_json;
extern crate silk;
extern crate solana;
use silk::accountant_stub::AccountantStub;
use silk::signature::{KeyPair, KeyPairUtil};
use silk::transaction::Transaction;
use silk::mint::Mint;
use std::time::Instant;
use std::net::UdpSocket;
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::*;
fn main() {
let addr = "127.0.0.1:8000";
@ -18,22 +21,24 @@ fn main() {
let mint_pubkey = mint.pubkey();
let socket = UdpSocket::bind(send_addr).unwrap();
let mut acc = AccountantStub::new(addr, socket);
let acc = AccountantStub::new(addr, socket);
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 = 100_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)
})
.collect();
let duration = now.elapsed();
let ns = duration.as_secs() * 1_000_000_000 + duration.subsec_nanos() as u64;
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;
println!(
@ -42,36 +47,27 @@ fn main() {
nsps / 1_000_f64
);
println!("Verify signatures...");
let now = Instant::now();
for tr in &transactions {
assert!(tr.verify());
}
let duration = now.elapsed();
let ns = duration.as_secs() * 1_000_000_000 + duration.subsec_nanos() as u64;
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();
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...",);
acc.wait_on_signature(&sig).unwrap();
let mut val = mint_balance;
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 + duration.subsec_nanos() as u64;
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, 0);
}

View File

@ -1,30 +1,30 @@
extern crate serde_json;
extern crate silk;
extern crate solana;
use silk::mint::Mint;
use silk::event::Event;
use silk::transaction::Transaction;
use silk::log::create_entries;
use silk::signature::{KeyPair, KeyPairUtil, PublicKey};
use silk::hash::Hash;
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 {
Event::Transaction(Transaction::new(&from, to, tokens, last_id))
Event::Transaction(Transaction::new(from, to, tokens, last_id))
}
fn main() {
let alice = (KeyPair::new().pubkey(), 200);
let bob = (KeyPair::new().pubkey(), 100);
let mint: Mint = serde_json::from_reader(stdin()).unwrap();
let mut entries = mint.create_entries();
let from = mint.keypair();
let seed = mint.seed();
let mut events = mint.create_events();
events.push(transfer(&from, alice, seed));
events.push(transfer(&from, bob, seed));
let alice = (KeyPair::new().pubkey(), 200);
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 create_entries(&seed, events) {
for entry in entries {
println!("{}", serde_json::to_string(&entry).unwrap());
}
}

View File

@ -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() {

View File

@ -1,22 +1,23 @@
extern crate silk;
extern crate solana;
use silk::historian::Historian;
use silk::hash::Hash;
use silk::entry::Entry;
use silk::log::verify_slice;
use silk::signature::{KeyPair, KeyPairUtil};
use silk::transaction::Transaction;
use silk::event::Event;
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;
use std::sync::mpsc::SendError;
fn create_log(hist: &Historian, seed: &Hash) -> Result<(), SendError<Event>> {
fn create_ledger(hist: &Historian, seed: &Hash) -> Result<(), SendError<Signal>> {
sleep(Duration::from_millis(15));
let keypair = KeyPair::new();
let tr = Transaction::new(&keypair, keypair.pubkey(), 42, *seed);
let event0 = Event::Transaction(tr);
hist.sender.send(event0)?;
let signal0 = Signal::Event(Event::Transaction(tr));
hist.sender.send(signal0)?;
sleep(Duration::from_millis(10));
Ok(())
}
@ -24,7 +25,7 @@ fn create_log(hist: &Historian, seed: &Hash) -> Result<(), SendError<Event>> {
fn main() {
let seed = Hash::default();
let hist = Historian::new(&seed, Some(10));
create_log(&hist, &seed).expect("send error");
create_ledger(&hist, &seed).expect("send error");
drop(hist.sender);
let entries: Vec<Entry> = hist.receiver.iter().collect();
for entry in &entries {

View File

@ -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() {

View File

@ -1,9 +1,11 @@
extern crate serde_json;
extern crate silk;
extern crate solana;
use silk::accountant_skel::AccountantSkel;
use silk::accountant::Accountant;
use std::io::{self, BufRead};
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};
fn main() {
let addr = "127.0.0.1:8000";
@ -13,7 +15,11 @@ fn main() {
.lines()
.map(|line| serde_json::from_str(&line.unwrap()).unwrap());
let acc = Accountant::new_from_entries(entries, Some(1000));
let mut skel = AccountantSkel::new(acc);
let exit = Arc::new(AtomicBool::new(false));
let skel = Arc::new(Mutex::new(AccountantSkel::new(acc, stdout())));
eprintln!("Listening on {}", addr);
skel.serve(addr).unwrap();
let threads = AccountantSkel::serve(skel, addr, exit.clone()).unwrap();
for t in threads {
t.join().expect("join");
}
}

View File

@ -1,86 +1,112 @@
use hash::{extend_and_hash, hash, Hash};
//! 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};
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,
pub id: Hash,
pub event: Event,
pub events: Vec<Event>,
}
impl Entry {
/// Creates a Entry from the number of hashes 'num_hashes' since the previous event
/// and that resulting 'id'.
/// Creates a Entry from the number of hashes `num_hashes` since the previous event
/// and that resulting `id`.
pub fn new_tick(num_hashes: u64, id: &Hash) -> Self {
Entry {
num_hashes,
id: *id,
event: Event::Tick,
events: vec![],
}
}
/// Verifies self.id is the result of hashing a 'start_hash' 'self.num_hashes' times.
/// 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 {
if !self.event.verify() {
return false;
}
self.id == next_hash(start_hash, self.num_hashes, &self.event)
self.events.par_iter().all(|event| event.verify())
&& self.id == next_hash(start_hash, self.num_hashes, &self.events)
}
}
/// 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
/// the signature.
pub fn next_hash(start_hash: &Hash, num_hashes: u64, event: &Event) -> Hash {
pub fn next_hash(start_hash: &Hash, num_hashes: u64, events: &[Event]) -> Hash {
let mut id = *start_hash;
let sig = event.get_signature();
let start_index = if sig.is_some() { 1 } else { 0 };
for _ in start_index..num_hashes {
for _ in 1..num_hashes {
id = hash(&id);
}
if let Some(sig) = sig {
id = extend_and_hash(&id, &sig);
// 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);
}
}
if !hash_data.is_empty() {
return extend_and_hash(&id, &hash_data);
}
id
}
/// Creates the next Entry 'num_hashes' after 'start_hash'.
pub fn create_entry(start_hash: &Hash, cur_hashes: u64, event: Event) -> Entry {
let sig = event.get_signature();
let num_hashes = cur_hashes + if sig.is_some() { 1 } else { 0 };
let id = next_hash(start_hash, 0, &event);
/// Creates the next Entry `num_hashes` after `start_hash`.
pub fn create_entry(start_hash: &Hash, cur_hashes: u64, events: Vec<Event>) -> Entry {
let num_hashes = cur_hashes + if events.is_empty() { 0 } else { 1 };
let id = next_hash(start_hash, 0, &events);
Entry {
num_hashes,
id,
event,
events,
}
}
/// Creates the next Tick Entry 'num_hashes' after 'start_hash'.
pub fn create_entry_mut(start_hash: &mut Hash, cur_hashes: &mut u64, event: Event) -> Entry {
let entry = create_entry(start_hash, *cur_hashes, event);
/// Creates the next Tick Entry `num_hashes` after `start_hash`.
pub fn create_entry_mut(start_hash: &mut Hash, cur_hashes: &mut u64, events: Vec<Event>) -> Entry {
let entry = create_entry(start_hash, *cur_hashes, events);
*start_hash = entry.id;
*cur_hashes = 0;
entry
}
/// Creates the next Tick Entry 'num_hashes' after 'start_hash'.
/// Creates the next Tick Entry `num_hashes` after `start_hash`.
pub fn next_tick(start_hash: &Hash, num_hashes: u64) -> Entry {
let event = Event::Tick;
Entry {
num_hashes,
id: next_hash(start_hash, num_hashes, &event),
event,
id: next_hash(start_hash, num_hashes, &[]),
events: vec![],
}
}
#[cfg(test)]
mod tests {
use super::*;
use entry::create_entry;
use event::Event;
use hash::hash;
use signature::{KeyPair, KeyPairUtil};
use transaction::Transaction;
#[test]
fn test_event_verify() {
fn test_entry_verify() {
let zero = Hash::default();
let one = hash(&zero);
assert!(Entry::new_tick(0, &zero).verify(&zero)); // base case
@ -89,6 +115,23 @@ mod tests {
assert!(!next_tick(&zero, 1).verify(&one)); // inductive step, bad
}
#[test]
fn test_event_reorder_attack() {
let zero = Hash::default();
// First, verify entries
let keypair = KeyPair::new();
let tr0 = Event::Transaction(Transaction::new(&keypair, keypair.pubkey(), 0, zero));
let tr1 = Event::Transaction(Transaction::new(&keypair, keypair.pubkey(), 1, zero));
let mut e0 = create_entry(&zero, 0, vec![tr0.clone(), tr1.clone()]);
assert!(e0.verify(&zero));
// Next, swap two 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();

View File

@ -1,19 +1,14 @@
//! The `event` crate provides the data structures for log events.
//! The `event` module handles events, which may be a `Transaction`, or a `Witness` used to process a pending
//! Transaction.
use bincode::serialize;
use chrono::prelude::*;
use signature::{KeyPair, KeyPairUtil, PublicKey, Signature, SignatureUtil};
use transaction::Transaction;
use chrono::prelude::*;
use bincode::serialize;
/// When 'event' is Tick, the event represents a simple clock tick, and exists for the
/// sole purpose of improving the performance of event log verification. A tick can
/// be generated in 'num_hashes' hashes and verified in 'num_hashes' hashes. By logging
/// a hash alongside the tick, each tick and be verified in parallel using the 'id'
/// of the preceding tick to seed its hashing.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum Event {
Tick,
Transaction(Transaction<i64>),
Transaction(Transaction),
Signature {
from: PublicKey,
tx_sig: Signature,
@ -27,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<Utc>) -> Self {
let sign_data = serialize(&dt).unwrap();
let sig = Signature::clone_from_slice(from.sign(&sign_data).as_ref());
@ -38,18 +34,18 @@ impl Event {
}
// TODO: Rename this to transaction_signature().
/// If the Event is a Transaction, return its Signature.
pub fn get_signature(&self) -> Option<Signature> {
match *self {
Event::Tick => None,
Event::Transaction(ref tr) => Some(tr.sig),
Event::Signature { .. } => None,
Event::Timestamp { .. } => None,
Event::Signature { .. } | Event::Timestamp { .. } => None,
}
}
/// 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::Tick => true,
Event::Transaction(ref tr) => tr.verify(),
Event::Signature { from, tx_sig, sig } => sig.verify(&from, &tx_sig),
Event::Timestamp { from, dt, sig } => sig.verify(&from, &serialize(&dt).unwrap()),

View File

@ -1,3 +1,5 @@
//! The `hash` module provides functions for creating SHA-256 hashes.
use generic_array::GenericArray;
use generic_array::typenum::U32;
use sha2::{Digest, Sha256};

View File

@ -1,18 +1,17 @@
//! The `historian` crate provides a microservice for generating a Proof-of-History.
//! It manages a thread containing a Proof-of-History Logger.
//! The `historian` module provides a microservice for generating a Proof of History.
//! It manages a thread containing a Proof of History Recorder.
use std::thread::{spawn, JoinHandle};
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;
use hash::{hash, Hash};
use entry::Entry;
use logger::{ExitReason, Logger};
use signature::Signature;
use event::Event;
pub struct Historian {
pub sender: SyncSender<Event>,
pub sender: SyncSender<Signal>,
pub receiver: Receiver<Entry>,
pub thread_hdl: JoinHandle<ExitReason>,
pub signatures: HashSet<Signature>,
@ -23,7 +22,7 @@ impl Historian {
let (sender, event_receiver) = sync_channel(1000);
let (entry_sender, receiver) = sync_channel(1000);
let thread_hdl =
Historian::create_logger(*start_hash, ms_per_tick, event_receiver, entry_sender);
Historian::create_recorder(*start_hash, ms_per_tick, event_receiver, entry_sender);
let signatures = HashSet::new();
Historian {
sender,
@ -33,43 +32,41 @@ impl Historian {
}
}
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_logger(
fn create_recorder(
start_hash: Hash,
ms_per_tick: Option<u64>,
receiver: Receiver<Event>,
receiver: Receiver<Signal>,
sender: SyncSender<Entry>,
) -> JoinHandle<ExitReason> {
spawn(move || {
let mut logger = Logger::new(receiver, sender, start_hash);
let mut recorder = Recorder::new(receiver, sender, start_hash);
let now = Instant::now();
loop {
if let Err(err) = logger.process_events(now, ms_per_tick) {
if let Err(err) = recorder.process_events(now, ms_per_tick) {
return err;
}
if ms_per_tick.is_some() {
logger.last_id = hash(&logger.last_id);
logger.num_hashes += 1;
recorder.hash();
}
}
})
}
}
pub fn reserve_signature(sigs: &mut HashSet<Signature>, sig: &Signature) -> bool {
if sigs.contains(sig) {
return false;
}
sigs.insert(*sig);
true
}
#[cfg(test)]
mod tests {
use super::*;
use log::*;
use event::*;
use ledger::*;
use std::thread::sleep;
use std::time::Duration;
@ -78,11 +75,11 @@ mod tests {
let zero = Hash::default();
let hist = Historian::new(&zero, None);
hist.sender.send(Event::Tick).unwrap();
hist.sender.send(Signal::Tick).unwrap();
sleep(Duration::new(0, 1_000_000));
hist.sender.send(Event::Tick).unwrap();
hist.sender.send(Signal::Tick).unwrap();
sleep(Duration::new(0, 1_000_000));
hist.sender.send(Event::Tick).unwrap();
hist.sender.send(Signal::Tick).unwrap();
let entry0 = hist.receiver.recv().unwrap();
let entry1 = hist.receiver.recv().unwrap();
@ -106,7 +103,7 @@ mod tests {
let zero = Hash::default();
let hist = Historian::new(&zero, None);
drop(hist.receiver);
hist.sender.send(Event::Tick).unwrap();
hist.sender.send(Signal::Tick).unwrap();
assert_eq!(
hist.thread_hdl.join().unwrap(),
ExitReason::SendDisconnected
@ -115,10 +112,11 @@ mod tests {
#[test]
fn test_duplicate_event_signature() {
let mut sigs = HashSet::new();
let zero = Hash::default();
let mut hist = Historian::new(&zero, None);
let sig = Signature::default();
assert!(reserve_signature(&mut sigs, &sig));
assert!(!reserve_signature(&mut sigs, &sig));
assert!(hist.reserve_signature(&sig));
assert!(!hist.reserve_signature(&sig));
}
#[test]
@ -126,15 +124,12 @@ mod tests {
let zero = Hash::default();
let hist = Historian::new(&zero, Some(20));
sleep(Duration::from_millis(30));
hist.sender.send(Event::Tick).unwrap();
hist.sender.send(Signal::Tick).unwrap();
drop(hist.sender);
let entries: Vec<Entry> = hist.receiver.iter().collect();
assert!(entries.len() > 1);
// Ensure one entry is sent back for each tick sent in.
assert_eq!(entries.len(), 1);
// Ensure the ID is not the seed, which indicates another Tick
// was logged before the one we sent.
// Ensure the ID is not the seed.
assert_ne!(entries[0].id, zero);
}
}

61
src/ledger.rs Normal file
View File

@ -0,0 +1,61 @@
//! The `ledger` module provides functions for parallel verification of the
//! Proof of History ledger.
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))
}
/// Create a vector of Ticks of length `len` from `start_hash` hash and `num_hashes`.
pub fn next_ticks(start_hash: &Hash, num_hashes: u64, len: usize) -> Vec<Entry> {
let mut id = *start_hash;
let mut ticks = vec![];
for _ in 0..len {
let entry = next_tick(&id, num_hashes);
id = entry.id;
ticks.push(entry);
}
ticks
}
#[cfg(test)]
mod tests {
use super::*;
use hash::hash;
#[test]
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
let mut bad_ticks = next_ticks(&zero, 0, 2);
bad_ticks[1].id = one;
assert!(!verify_slice(&bad_ticks, &zero)); // inductive step, bad
}
}
#[cfg(all(feature = "unstable", test))]
mod bench {
extern crate test;
use self::test::Bencher;
use ledger::*;
#[bench]
fn event_bench(bencher: &mut Bencher) {
let start_hash = Default::default();
let events = next_ticks(&start_hash, 10_000, 8);
bencher.iter(|| {
assert!(verify_slice(&events, &start_hash));
});
}
}

View File

@ -1,19 +1,24 @@
#![cfg_attr(feature = "unstable", feature(test))]
pub mod signature;
pub mod hash;
pub mod transaction;
pub mod event;
pub mod entry;
pub mod log;
pub mod mint;
pub mod logger;
pub mod historian;
pub mod accountant;
pub mod accountant_skel;
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 result;
pub mod signature;
pub mod streamer;
pub mod transaction;
extern crate bincode;
extern crate chrono;
extern crate generic_array;
#[macro_use]
extern crate log;
extern crate rayon;
extern crate ring;
extern crate serde;
@ -22,3 +27,7 @@ extern crate serde_derive;
extern crate serde_json;
extern crate sha2;
extern crate untrusted;
#[cfg(test)]
#[macro_use]
extern crate matches;

View File

@ -1,113 +0,0 @@
//! The `log` crate provides the foundational data structures for Proof-of-History,
//! an ordered log of events in time.
/// Each log 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 logged.
use hash::Hash;
use entry::{create_entry_mut, next_tick, Entry};
use event::Event;
use rayon::prelude::*;
/// Verifies the hashes and counts of a slice of events are all consistent.
pub fn verify_slice(events: &[Entry], start_hash: &Hash) -> bool {
let genesis = [Entry::new_tick(Default::default(), start_hash)];
let event_pairs = genesis.par_iter().chain(events).zip(events);
event_pairs.all(|(x0, x1)| x1.verify(&x0.id))
}
pub fn create_entries(start_hash: &Hash, events: Vec<Event>) -> Vec<Entry> {
let mut id = *start_hash;
events
.into_iter()
.map(|event| create_entry_mut(&mut id, &mut 0, event))
.collect()
}
/// Create a vector of Ticks of length 'len' from 'start_hash' hash and 'num_hashes'.
pub fn next_ticks(start_hash: &Hash, num_hashes: u64, len: usize) -> Vec<Entry> {
let mut id = *start_hash;
let mut ticks = vec![];
for _ in 0..len {
let entry = next_tick(&id, num_hashes);
id = entry.id;
ticks.push(entry);
}
ticks
}
#[cfg(test)]
mod tests {
use super::*;
use signature::{KeyPair, KeyPairUtil};
use transaction::Transaction;
use hash::hash;
#[test]
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
let mut bad_ticks = next_ticks(&zero, 0, 2);
bad_ticks[1].id = one;
assert!(!verify_slice(&bad_ticks, &zero)); // inductive step, bad
}
#[test]
fn test_reorder_attack() {
let zero = Hash::default();
// First, verify entries
let keypair = KeyPair::new();
let tr0 = Transaction::new(&keypair, keypair.pubkey(), 0, zero);
let tr1 = Transaction::new(&keypair, keypair.pubkey(), 1, zero);
let events = vec![Event::Transaction(tr0), Event::Transaction(tr1)];
let mut entries = create_entries(&zero, events);
assert!(verify_slice(&entries, &zero));
// Next, swap two events and ensure verification fails.
let event0 = entries[0].event.clone();
let event1 = entries[1].event.clone();
entries[0].event = event1;
entries[1].event = event0;
assert!(!verify_slice(&entries, &zero));
}
}
#[cfg(all(feature = "unstable", test))]
mod bench {
extern crate test;
use self::test::Bencher;
use log::*;
#[bench]
fn event_bench(bencher: &mut Bencher) {
let start_hash = Default::default();
let events = next_ticks(&start_hash, 10_000, 8);
bencher.iter(|| {
assert!(verify_slice(&events, &start_hash));
});
}
#[bench]
fn event_bench_seq(bencher: &mut Bencher) {
let start_hash = Default::default();
let events = next_ticks(&start_hash, 10_000, 8);
bencher.iter(|| {
assert!(verify_slice_seq(&events, &start_hash));
});
}
}

View File

@ -1,71 +0,0 @@
//! The `logger` crate provides an object for generating a Proof-of-History.
//! It logs 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
//! Event, the latest hash, and the number of hashes since the last event.
//! The resulting stream of entries represents ordered events in time.
use std::sync::mpsc::{Receiver, SyncSender, TryRecvError};
use std::time::{Duration, Instant};
use hash::Hash;
use entry::{create_entry_mut, Entry};
use event::Event;
use serde_json;
#[derive(Debug, PartialEq, Eq)]
pub enum ExitReason {
RecvDisconnected,
SendDisconnected,
}
pub struct Logger {
pub sender: SyncSender<Entry>,
pub receiver: Receiver<Event>,
pub last_id: Hash,
pub num_hashes: u64,
pub num_ticks: u64,
}
impl Logger {
pub fn new(receiver: Receiver<Event>, sender: SyncSender<Entry>, start_hash: Hash) -> Self {
Logger {
receiver,
sender,
last_id: start_hash,
num_hashes: 0,
num_ticks: 0,
}
}
pub fn log_event(&mut self, event: Event) -> Result<Entry, ExitReason> {
let entry = create_entry_mut(&mut self.last_id, &mut self.num_hashes, event);
println!("{}", serde_json::to_string(&entry).unwrap());
Ok(entry)
}
pub fn process_events(
&mut self,
epoch: Instant,
ms_per_tick: Option<u64>,
) -> Result<(), ExitReason> {
loop {
if let Some(ms) = ms_per_tick {
if epoch.elapsed() > Duration::from_millis((self.num_ticks + 1) * ms) {
self.log_event(Event::Tick)?;
self.num_ticks += 1;
}
}
match self.receiver.try_recv() {
Ok(event) => {
let entry = self.log_event(event)?;
self.sender
.send(entry)
.or(Err(ExitReason::SendDisconnected))?;
}
Err(TryRecvError::Empty) => return Ok(()),
Err(TryRecvError::Disconnected) => return Err(ExitReason::RecvDisconnected),
};
}
}
}

View File

@ -1,12 +1,12 @@
//! A library for generating the chain's genesis block.
//! The `mint` module is a library for generating the chain's genesis block.
use event::Event;
use transaction::Transaction;
use signature::{KeyPair, KeyPairUtil, PublicKey};
use entry::Entry;
use log::create_entries;
use entry::create_entry;
use event::Event;
use hash::{hash, Hash};
use ring::rand::SystemRandom;
use signature::{KeyPair, KeyPairUtil, PublicKey};
use transaction::Transaction;
use untrusted::Input;
#[derive(Serialize, Deserialize, Debug)]
@ -44,27 +44,29 @@ impl Mint {
pub fn create_events(&self) -> Vec<Event> {
let keypair = self.keypair();
let tr = Transaction::new(&keypair, self.pubkey(), self.tokens, self.seed());
vec![Event::Tick, Event::Transaction(tr)]
vec![Event::Transaction(tr)]
}
pub fn create_entries(&self) -> Vec<Entry> {
create_entries(&self.seed(), self.create_events())
let e0 = create_entry(&self.seed(), 0, vec![]);
let e1 = create_entry(&e0.id, 0, self.create_events());
vec![e0, e1]
}
}
#[cfg(test)]
mod tests {
use super::*;
use log::verify_slice;
use ledger::verify_slice;
use plan::Plan;
#[test]
fn test_create_events() {
let mut events = Mint::new(100).create_events().into_iter();
assert_eq!(events.next().unwrap(), Event::Tick);
if let Event::Transaction(tr) = events.next().unwrap() {
assert_eq!(tr.from, tr.to);
} else {
assert!(false);
if let Plan::Pay(payment) = tr.plan {
assert_eq!(tr.from, payment.to);
}
}
assert_eq!(events.next(), None);
}

175
src/plan.rs Normal file
View File

@ -0,0 +1,175 @@
//! 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.
use chrono::prelude::*;
use signature::PublicKey;
use std::mem;
pub enum Witness {
Timestamp(DateTime<Utc>),
Signature(PublicKey),
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum Condition {
Timestamp(DateTime<Utc>),
Signature(PublicKey),
}
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,
(&Condition::Timestamp(ref dt), &Witness::Timestamp(ref last_time)) => dt <= last_time,
_ => false,
}
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Payment {
pub tokens: i64,
pub to: PublicKey,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum Plan {
Pay(Payment),
After(Condition, Payment),
Race((Condition, Payment), (Condition, Payment)),
}
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<Utc>, 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<Utc>,
from: PublicKey,
tokens: i64,
to: PublicKey,
) -> Self {
Plan::Race(
(Condition::Timestamp(dt), Payment { tokens, to }),
(Condition::Signature(from), Payment { tokens, to: from }),
)
}
/// Return true if the spending plan requires no additional Witnesses.
pub fn is_complete(&self) -> bool {
match *self {
Plan::Pay(_) => true,
_ => false,
}
}
/// 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) => {
payment.tokens == spendable_tokens
}
Plan::Race(ref a, ref b) => {
a.1.tokens == spendable_tokens && b.1.tokens == spendable_tokens
}
}
}
/// Apply a witness to the spending plan to see if the plan can be reduced.
/// If so, modify the plan in-place.
pub fn apply_witness(&mut self, witness: &Witness) {
let new_payment = match *self {
Plan::After(ref cond, ref payment) if cond.is_satisfied(witness) => Some(payment),
Plan::Race((ref cond, ref payment), _) if cond.is_satisfied(witness) => Some(payment),
Plan::Race(_, (ref cond, ref payment)) if cond.is_satisfied(witness) => Some(payment),
_ => None,
}.cloned();
if let Some(payment) = new_payment {
mem::replace(self, Plan::Pay(payment));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_signature_satisfied() {
let sig = PublicKey::default();
assert!(Condition::Signature(sig).is_satisfied(&Witness::Signature(sig)));
}
#[test]
fn test_timestamp_satisfied() {
let dt1 = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10);
let dt2 = Utc.ymd(2014, 11, 14).and_hms(10, 9, 8);
assert!(Condition::Timestamp(dt1).is_satisfied(&Witness::Timestamp(dt1)));
assert!(Condition::Timestamp(dt1).is_satisfied(&Witness::Timestamp(dt2)));
assert!(!Condition::Timestamp(dt2).is_satisfied(&Witness::Timestamp(dt1)));
}
#[test]
fn test_verify_plan() {
let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10);
let from = PublicKey::default();
let to = PublicKey::default();
assert!(Plan::new_payment(42, to).verify(42));
assert!(Plan::new_authorized_payment(from, 42, to).verify(42));
assert!(Plan::new_future_payment(dt, 42, to).verify(42));
assert!(Plan::new_cancelable_future_payment(dt, from, 42, to).verify(42));
}
#[test]
fn test_authorized_payment() {
let from = PublicKey::default();
let to = PublicKey::default();
let mut plan = Plan::new_authorized_payment(from, 42, to);
plan.apply_witness(&Witness::Signature(from));
assert_eq!(plan, Plan::new_payment(42, to));
}
#[test]
fn test_future_payment() {
let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10);
let to = PublicKey::default();
let mut plan = Plan::new_future_payment(dt, 42, to);
plan.apply_witness(&Witness::Timestamp(dt));
assert_eq!(plan, Plan::new_payment(42, to));
}
#[test]
fn test_cancelable_future_payment() {
let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10);
let from = PublicKey::default();
let to = PublicKey::default();
let mut plan = Plan::new_cancelable_future_payment(dt, from, 42, to);
plan.apply_witness(&Witness::Timestamp(dt));
assert_eq!(plan, Plan::new_payment(42, to));
let mut plan = Plan::new_cancelable_future_payment(dt, from, 42, to);
plan.apply_witness(&Witness::Signature(from));
assert_eq!(plan, Plan::new_payment(42, from));
}
}

89
src/recorder.rs Normal file
View File

@ -0,0 +1,89 @@
//! 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
//! Event, the latest hash, and the number of hashes since the last event.
//! The resulting stream of entries represents ordered events in time.
use entry::{create_entry_mut, Entry};
use event::Event;
use hash::{hash, Hash};
use std::mem;
use std::sync::mpsc::{Receiver, SyncSender, TryRecvError};
use std::time::{Duration, Instant};
#[cfg_attr(feature = "cargo-clippy", allow(large_enum_variant))]
pub enum Signal {
Tick,
Event(Event),
}
#[derive(Debug, PartialEq, Eq)]
pub enum ExitReason {
RecvDisconnected,
SendDisconnected,
}
pub struct Recorder {
sender: SyncSender<Entry>,
receiver: Receiver<Signal>,
last_hash: Hash,
events: Vec<Event>,
num_hashes: u64,
num_ticks: u64,
}
impl Recorder {
pub fn new(receiver: Receiver<Signal>, sender: SyncSender<Entry>, start_hash: Hash) -> Self {
Recorder {
receiver,
sender,
last_hash: start_hash,
events: vec![],
num_hashes: 0,
num_ticks: 0,
}
}
pub fn hash(&mut self) {
self.last_hash = hash(&self.last_hash);
self.num_hashes += 1;
}
pub fn record_entry(&mut self) -> Result<(), ExitReason> {
let events = mem::replace(&mut self.events, vec![]);
let entry = create_entry_mut(&mut self.last_hash, &mut self.num_hashes, events);
self.sender
.send(entry)
.or(Err(ExitReason::SendDisconnected))?;
Ok(())
}
pub fn process_events(
&mut self,
epoch: Instant,
ms_per_tick: Option<u64>,
) -> Result<(), ExitReason> {
loop {
if let Some(ms) = ms_per_tick {
if epoch.elapsed() > Duration::from_millis((self.num_ticks + 1) * ms) {
self.record_entry()?;
self.num_ticks += 1;
}
}
match self.receiver.try_recv() {
Ok(signal) => match signal {
Signal::Tick => {
self.record_entry()?;
}
Signal::Event(event) => {
self.events.push(event);
}
},
Err(TryRecvError::Empty) => return Ok(()),
Err(TryRecvError::Disconnected) => return Err(ExitReason::RecvDisconnected),
};
}
}
}

125
src/result.rs Normal file
View File

@ -0,0 +1,125 @@
//! The `result` module exposes a Result type that propagates one of many different Error types.
use bincode;
use serde_json;
use std;
use std::any::Any;
#[derive(Debug)]
pub enum Error {
IO(std::io::Error),
JSON(serde_json::Error),
AddrParse(std::net::AddrParseError),
JoinError(Box<Any + Send + 'static>),
RecvError(std::sync::mpsc::RecvError),
RecvTimeoutError(std::sync::mpsc::RecvTimeoutError),
Serialize(std::boxed::Box<bincode::ErrorKind>),
SendError,
Services,
}
pub type Result<T> = std::result::Result<T, Error>;
impl std::convert::From<std::sync::mpsc::RecvError> for Error {
fn from(e: std::sync::mpsc::RecvError) -> Error {
Error::RecvError(e)
}
}
impl std::convert::From<std::sync::mpsc::RecvTimeoutError> for Error {
fn from(e: std::sync::mpsc::RecvTimeoutError) -> Error {
Error::RecvTimeoutError(e)
}
}
impl<T> std::convert::From<std::sync::mpsc::SendError<T>> for Error {
fn from(_e: std::sync::mpsc::SendError<T>) -> Error {
Error::SendError
}
}
impl std::convert::From<Box<Any + Send + 'static>> for Error {
fn from(e: Box<Any + Send + 'static>) -> Error {
Error::JoinError(e)
}
}
impl std::convert::From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Error {
Error::IO(e)
}
}
impl std::convert::From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Error {
Error::JSON(e)
}
}
impl std::convert::From<std::net::AddrParseError> for Error {
fn from(e: std::net::AddrParseError) -> Error {
Error::AddrParse(e)
}
}
impl std::convert::From<std::boxed::Box<bincode::ErrorKind>> for Error {
fn from(e: std::boxed::Box<bincode::ErrorKind>) -> Error {
Error::Serialize(e)
}
}
#[cfg(test)]
mod tests {
use result::Error;
use result::Result;
use serde_json;
use std::io;
use std::io::Write;
use std::net::SocketAddr;
use std::sync::mpsc::RecvError;
use std::sync::mpsc::RecvTimeoutError;
use std::sync::mpsc::channel;
use std::thread;
fn addr_parse_error() -> Result<SocketAddr> {
let r = "12fdfasfsafsadfs".parse()?;
Ok(r)
}
fn join_error() -> Result<()> {
let r = thread::spawn(|| panic!("hi")).join()?;
Ok(r)
}
fn json_error() -> Result<()> {
let r = serde_json::from_slice("=342{;;;;:}".as_bytes())?;
Ok(r)
}
fn send_error() -> Result<()> {
let (s, r) = channel();
drop(r);
s.send(())?;
Ok(())
}
#[test]
fn from_test() {
assert_matches!(addr_parse_error(), Err(Error::AddrParse(_)));
assert_matches!(Error::from(RecvError {}), Error::RecvError(_));
assert_matches!(
Error::from(RecvTimeoutError::Timeout),
Error::RecvTimeoutError(_)
);
assert_matches!(send_error(), Err(Error::SendError));
assert_matches!(join_error(), Err(Error::JoinError(_)));
let ioe = io::Error::new(io::ErrorKind::NotFound, "hi");
assert_matches!(Error::from(ioe), Error::IO(_));
}
#[test]
fn fmt_test() {
write!(io::sink(), "{:?}", addr_parse_error()).unwrap();
write!(io::sink(), "{:?}", Error::from(RecvError {})).unwrap();
write!(io::sink(), "{:?}", Error::from(RecvTimeoutError::Timeout)).unwrap();
write!(io::sink(), "{:?}", send_error()).unwrap();
write!(io::sink(), "{:?}", join_error()).unwrap();
write!(io::sink(), "{:?}", json_error()).unwrap();
write!(
io::sink(),
"{:?}",
Error::from(io::Error::new(io::ErrorKind::NotFound, "hi"))
).unwrap();
}
}

View File

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

474
src/streamer.rs Normal file
View File

@ -0,0 +1,474 @@
//! 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::atomic::{AtomicBool, Ordering};
use std::sync::mpsc;
use std::sync::{Arc, Mutex, RwLock};
use std::thread::{spawn, JoinHandle};
use std::time::Duration;
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<Packet>,
}
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<Response>,
}
impl Default for Responses {
fn default() -> Responses {
Responses {
responses: vec![Response::default(); NUM_RESP],
}
}
}
pub type SharedPackets = Arc<RwLock<Packets>>;
pub type PacketRecycler = Arc<Mutex<Vec<SharedPackets>>>;
pub type Receiver = mpsc::Receiver<SharedPackets>;
pub type Sender = mpsc::Sender<SharedPackets>;
pub type SharedResponses = Arc<RwLock<Responses>>;
pub type ResponseRecycler = Arc<Mutex<Vec<SharedResponses>>>;
pub type Responder = mpsc::Sender<SharedResponses>;
pub type ResponseReceiver = mpsc::Receiver<SharedResponses>;
impl Packets {
fn run_read_from(&mut self, socket: &UdpSocket) -> Result<usize> {
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<T>(recycler: &Arc<Mutex<Vec<Arc<RwLock<T>>>>>) -> Arc<RwLock<T>>
where
T: Default,
{
let mut gc = recycler.lock().expect("lock");
gc.pop()
.unwrap_or_else(|| Arc::new(RwLock::new(Default::default())))
}
pub fn recycle<T>(recycler: &Arc<Mutex<Vec<Arc<RwLock<T>>>>>, msgs: Arc<RwLock<T>>)
where
T: Default,
{
let mut gc = recycler.lock().expect("lock");
gc.push(msgs);
}
fn recv_loop(
sock: &UdpSocket,
exit: &Arc<AtomicBool>,
recycler: &PacketRecycler,
channel: &Sender,
) -> Result<()> {
loop {
let msgs = allocate(recycler);
let msgs_ = msgs.clone();
loop {
match msgs.write().unwrap().read_from(sock) {
Ok(()) => {
channel.send(msgs_)?;
break;
}
Err(_) => {
if exit.load(Ordering::Relaxed) {
recycle(recycler, msgs_);
return Ok(());
}
}
}
}
}
}
pub fn receiver(
sock: UdpSocket,
exit: Arc<AtomicBool>,
recycler: PacketRecycler,
channel: Sender,
) -> Result<JoinHandle<()>> {
let timer = Duration::new(1, 0);
sock.set_read_timeout(Some(timer))?;
Ok(spawn(move || {
let _ = recv_loop(&sock, &exit, &recycler, &channel);
()
}))
}
fn recv_send(sock: &UdpSocket, recycler: &ResponseRecycler, r: &ResponseReceiver) -> 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_);
Ok(())
}
pub fn responder(
sock: UdpSocket,
exit: Arc<AtomicBool>,
recycler: ResponseRecycler,
r: ResponseReceiver,
) -> JoinHandle<()> {
spawn(move || loop {
if recv_send(&sock, &recycler, &r).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};
fn producer(
addr: &SocketAddr,
recycler: PacketRecycler,
exit: Arc<AtomicBool>,
) -> JoinHandle<()> {
let send = UdpSocket::bind("0.0.0.0:0").unwrap();
let msgs = allocate(&recycler);
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.set_addr(&addr);
}
spawn(move || loop {
if exit.load(Ordering::Relaxed) {
return;
}
let mut num = 0;
for p in msgs_.read().unwrap().packets.iter() {
let a = p.meta.get_addr();
send.send_to(&p.data[..p.meta.size], &a).unwrap();
num += 1;
}
assert_eq!(num, 10);
})
}
fn sink(
recycler: PacketRecycler,
exit: Arc<AtomicBool>,
rvs: Arc<Mutex<usize>>,
r: Receiver,
) -> JoinHandle<()> {
spawn(move || loop {
if exit.load(Ordering::Relaxed) {
return;
}
let timer = Duration::new(1, 0);
match r.recv_timeout(timer) {
Ok(msgs) => {
let msgs_ = msgs.clone();
*rvs.lock().unwrap() += msgs.read().unwrap().packets.len();
recycle(&recycler, msgs_);
}
_ => (),
}
})
}
fn run_streamer_bench() -> Result<()> {
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 (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 rvs = Arc::new(Mutex::new(0));
let t_sink = sink(recycler.clone(), exit.clone(), rvs.clone(), r_reader);
let start = SystemTime::now();
let start_val = *rvs.lock().unwrap();
sleep(Duration::new(5, 0));
let elapsed = start.elapsed().unwrap();
let end_val = *rvs.lock().unwrap();
let time = elapsed.as_secs() * 10000000000 + elapsed.subsec_nanos() as u64;
let ftime = (time as f64) / 10000000000f64;
let fcount = (end_val - start_val) as f64;
println!("performance: {:?}", fcount / ftime);
exit.store(true, Ordering::Relaxed);
t_reader.join()?;
t_producer1.join()?;
t_producer2.join()?;
t_producer3.join()?;
t_sink.join()?;
Ok(())
}
#[bench]
pub fn streamer_bench(_bench: &mut Bencher) {
run_streamer_bench().unwrap();
}
}
#[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::time::Duration;
use streamer::{allocate, receiver, responder, Packet, Packets, Receiver, Response, Responses,
PACKET_SIZE};
fn get_msgs(r: Receiver, num: &mut usize) {
for _t in 0..5 {
let timer = Duration::new(1, 0);
match r.recv_timeout(timer) {
Ok(m) => *num += m.read().unwrap().packets.len(),
e => println!("error {:?}", e),
}
if *num == 10 {
break;
}
}
}
#[cfg(ipv6)]
#[test]
pub fn streamer_send_test_ipv6() {
let read = UdpSocket::bind("[::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 (s_reader, r_reader) = channel();
let t_receiver = receiver(read, exit.clone(), 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() {
w.data[0] = i as u8;
w.size = PACKET_SIZE;
w.set_addr(&addr);
assert_eq!(w.get_addr(), addr);
}
s_responder.send(msgs).expect("send");
let mut num = 0;
get_msgs(r_reader, &mut num);
assert_eq!(num, 10);
exit.store(true, Ordering::Relaxed);
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();
}
#[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("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 (s_reader, r_reader) = channel();
let t_receiver = receiver(read, exit.clone(), packet_recycler.clone(), s_reader).unwrap();
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;
w.meta.size = PACKET_SIZE;
w.meta.set_addr(&addr);
assert_eq!(w.meta.get_addr(), addr);
}
s_responder.send(msgs).expect("send");
let mut num = 0;
get_msgs(r_reader, &mut num);
assert_eq!(num, 10);
exit.store(true, Ordering::Relaxed);
t_receiver.join().expect("join");
t_responder.join().expect("join");
}
}

View File

@ -1,36 +1,30 @@
//! The `transaction` crate provides functionality for creating log transactions.
//! The `transaction` module provides functionality for creating log transactions.
use signature::{KeyPair, KeyPairUtil, PublicKey, Signature, SignatureUtil};
use serde::Serialize;
use bincode::serialize;
use hash::Hash;
use chrono::prelude::*;
use rayon::prelude::*;
use hash::Hash;
use plan::{Condition, Payment, Plan};
use signature::{KeyPair, KeyPairUtil, PublicKey, Signature, SignatureUtil};
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum Condition {
Timestamp(DateTime<Utc>),
Signature(PublicKey),
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Transaction<T> {
pub struct Transaction {
pub from: PublicKey,
pub to: PublicKey,
pub if_all: Vec<Condition>,
pub unless_any: Vec<Condition>,
pub asset: T,
pub plan: Plan,
pub tokens: i64,
pub last_id: Hash,
pub sig: Signature,
}
impl<T: Serialize> Transaction<T> {
pub fn new(from_keypair: &KeyPair, to: PublicKey, asset: T, last_id: Hash) -> Self {
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 });
let mut tr = Transaction {
from: from_keypair.pubkey(),
to,
if_all: vec![],
unless_any: vec![],
asset,
from,
plan,
tokens,
last_id,
sig: Signature::default(),
};
@ -38,20 +32,23 @@ impl<T: Serialize> Transaction<T> {
tr
}
/// Create and sign a postdated Transaction. Used for unit-testing.
pub fn new_on_date(
from_keypair: &KeyPair,
to: PublicKey,
dt: DateTime<Utc>,
asset: T,
tokens: i64,
last_id: Hash,
) -> Self {
let from = from_keypair.pubkey();
let plan = Plan::Race(
(Condition::Timestamp(dt), Payment { tokens, to }),
(Condition::Signature(from), Payment { tokens, to: from }),
);
let mut tr = Transaction {
from,
to,
if_all: vec![Condition::Timestamp(dt)],
unless_any: vec![Condition::Signature(from)],
asset,
plan,
tokens,
last_id,
sig: Signature::default(),
};
@ -60,38 +57,46 @@ impl<T: Serialize> Transaction<T> {
}
fn get_sign_data(&self) -> Vec<u8> {
serialize(&(
&self.from,
&self.to,
&self.if_all,
&self.unless_any,
&self.asset,
&self.last_id,
)).unwrap()
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.sig.verify(&self.from, &self.get_sign_data()) && self.plan.verify(self.tokens)
}
}
/// 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::*;
use bincode::{deserialize, serialize};
use hash::hash;
#[test]
fn test_claim() {
let keypair = KeyPair::new();
let asset = hash(b"hello, world");
let zero = Hash::default();
let tr0 = Transaction::new(&keypair, keypair.pubkey(), asset, zero);
let tr0 = Transaction::new(&keypair, keypair.pubkey(), 42, zero);
assert!(tr0.verify());
}
@ -101,24 +106,25 @@ mod tests {
let keypair0 = KeyPair::new();
let keypair1 = KeyPair::new();
let pubkey1 = keypair1.pubkey();
let asset = hash(b"hello, world");
let tr0 = Transaction::new(&keypair0, pubkey1, asset, zero);
let tr0 = Transaction::new(&keypair0, pubkey1, 42, zero);
assert!(tr0.verify());
}
#[test]
fn test_serialize_claim() {
let plan = Plan::Pay(Payment {
tokens: 0,
to: Default::default(),
});
let claim0 = Transaction {
from: Default::default(),
to: Default::default(),
if_all: Default::default(),
unless_any: Default::default(),
asset: 0u8,
plan,
tokens: 0,
last_id: Default::default(),
sig: Default::default(),
};
let buf = serialize(&claim0).unwrap();
let claim1: Transaction<u8> = deserialize(&buf).unwrap();
let claim1: Transaction = deserialize(&buf).unwrap();
assert_eq!(claim1, claim0);
}
@ -127,9 +133,9 @@ mod tests {
let zero = Hash::default();
let keypair = KeyPair::new();
let pubkey = keypair.pubkey();
let mut tr = Transaction::new(&keypair, pubkey, hash(b"hello, world"), zero);
let mut tr = Transaction::new(&keypair, pubkey, 42, zero);
tr.sign(&keypair);
tr.asset = hash(b"goodbye cruel world"); // <-- attack!
tr.tokens = 1_000_000; // <-- attack!
assert!(!tr.verify());
}
@ -140,9 +146,46 @@ mod tests {
let thief_keypair = KeyPair::new();
let pubkey1 = keypair1.pubkey();
let zero = Hash::default();
let mut tr = Transaction::new(&keypair0, pubkey1, hash(b"hello, world"), zero);
let mut tr = Transaction::new(&keypair0, pubkey1, 42, zero);
tr.sign(&keypair0);
tr.to = thief_keypair.pubkey(); // <-- attack!
if let Plan::Pay(ref mut payment) = tr.plan {
payment.to = thief_keypair.pubkey(); // <-- attack!
};
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));
});
}
}