Compare commits
123 Commits
Author | SHA1 | Date | |
---|---|---|---|
e683c34a89 | |||
54e4f75081 | |||
9f256f0929 | |||
ef169a6652 | |||
eaec25f940 | |||
6a87d8975c | |||
b8cf5f9427 | |||
2f1e585446 | |||
f9309b46aa | |||
22f5985f1b | |||
c59c38e50e | |||
232e1bb8a3 | |||
1fbb34620c | |||
89f5b803c9 | |||
55179101cd | |||
132495b1fc | |||
a03d7bf5cd | |||
3bf225e85f | |||
cc2bb290c4 | |||
878ca8c5c5 | |||
4bc41d81ee | |||
f6ca176fc8 | |||
0bec360a31 | |||
04f30710c5 | |||
98c0a2af87 | |||
9db42c1769 | |||
849bced602 | |||
27f29019ef | |||
8642a41f2b | |||
bf902ef5bc | |||
7656b55c22 | |||
7d3d4b9443 | |||
15c093c5e2 | |||
116166f62d | |||
26b19dde75 | |||
c8ddc68f13 | |||
7c9681007c | |||
13206e4976 | |||
2f18302d32 | |||
ddb21d151d | |||
c64a9fb456 | |||
ee19b4f86e | |||
14239e584f | |||
112aecf6eb | |||
c1783d77d7 | |||
f089abb3c5 | |||
8e551f5e32 | |||
290960c3b5 | |||
62af09adbe | |||
e39c0b34e5 | |||
8ad90807ee | |||
533b3170a7 | |||
7732f3f5fb | |||
f52f02a434 | |||
4d7d4d673e | |||
9a437f0d38 | |||
c385f8bb6e | |||
fa44be2a9d | |||
117ab0c141 | |||
7488d19ae6 | |||
60524ad5f2 | |||
fad7ff8bf0 | |||
383d445ba1 | |||
803dcb0800 | |||
fde320e2f2 | |||
8ea97141ea | |||
9f232bac58 | |||
8295cc11c0 | |||
70f80adb9a | |||
9a7cac1e07 | |||
c584a25ec9 | |||
bff32bf7bc | |||
d0e7450389 | |||
4da89ac8a9 | |||
f7032f7d9a | |||
7c7e3931a0 | |||
6be3d62d89 | |||
6f509a8a1e | |||
4379fabf16 | |||
6b66e1a077 | |||
c11a3e0fdc | |||
3418033c55 | |||
caa9a846ed | |||
8ee76bcea0 | |||
47325cbe01 | |||
e0c8417297 | |||
9238ee9572 | |||
64af37e0cd | |||
9f9b79f30b | |||
265f41887f | |||
4f09e5d04c | |||
434f321336 | |||
f4e0d1be58 | |||
e5bae0604b | |||
e7da083c31 | |||
367c32dabe | |||
e054238af6 | |||
e8faf6d59a | |||
baa4ea3cd8 | |||
75ef0f0329 | |||
65185c0011 | |||
eb94613d7d | |||
67f4f4fb49 | |||
a7ecf4ac4c | |||
45765b625a | |||
aa0a184ebe | |||
069f9f0d5d | |||
c82b520ea8 | |||
9d6e5bde4a | |||
0eb3669fbf | |||
30449b6054 | |||
f5f71a19b8 | |||
0135971769 | |||
8579795c40 | |||
9d77fd7eec | |||
8c40d1bd72 | |||
7a0bc7d888 | |||
1e07014f86 | |||
49281b24e5 | |||
a8b1980de4 | |||
b8cd5f0482 | |||
cc9f0788aa | |||
209910299d |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,3 @@
|
||||
|
||||
Cargo.lock
|
||||
/target/
|
||||
**/*.rs.bk
|
||||
|
@ -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
482
Cargo.lock
generated
@ -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"
|
33
Cargo.toml
33
Cargo.toml
@ -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"
|
||||
|
61
README.md
61
README.md
@ -1,25 +1,18 @@
|
||||
[](https://crates.io/crates/silk)
|
||||
[](https://docs.rs/silk)
|
||||
[](https://travis-ci.org/loomprotocol/silk)
|
||||
[](https://codecov.io/gh/loomprotocol/silk)
|
||||
[](https://crates.io/crates/solana)
|
||||
[](https://docs.rs/solana)
|
||||
[](https://travis-ci.org/solana-labs/solana)
|
||||
[](https://codecov.io/gh/solana-labs/solana)
|
||||
|
||||
Disclaimer
|
||||
===
|
||||
|
||||
All claims, content, designs, algorithms, estimates, roadmaps, specifications, and performance measurements described in this project are done with the author's best effort. It is up to the reader to check and validate their accuracy and truthfulness. Furthermore nothing in this project constitutes a solicitation for investment.
|
||||
|
||||
Silk, a silky smooth implementation of the Loom specification
|
||||
Solana: High Performance Blockchain
|
||||
===
|
||||
|
||||
Loom™ is a new architecture for a high performance blockchain. Its white paper boasts a theoretical
|
||||
throughput of 710k transactions per second on a 1 gbps network. The specification is implemented
|
||||
in two git repositories. Research is performed in the loom repository. That work drives the
|
||||
Loom specification forward. This repository, on the other hand, aims to implement the specification
|
||||
as-is. We care a great deal about quality, clarity and short learning curve. We avoid the use
|
||||
of `unsafe` Rust and write tests for *everything*. Optimizations are only added when
|
||||
corresponding benchmarks are also added that demonstrate real performance boosts. We expect the
|
||||
feature set here will always be a ways behind the loom repo, but that this is an implementation
|
||||
you can take to the bank, literally.
|
||||
Solana™ is a new architecture for a high performance blockchain. It aims to support
|
||||
over 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
15
doc/consensus.msc
Normal 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" ] ;
|
||||
}
|
@ -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:
|
||||
|
||||

|
||||
|
||||
```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 }
|
||||
```
|
||||
|
||||
|
@ -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)" ] ;
|
||||
|
@ -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.
|
||||
|
@ -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])
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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 {
|
||||
|
@ -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() {
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
105
src/entry.rs
105
src/entry.rs
@ -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();
|
||||
|
24
src/event.rs
24
src/event.rs
@ -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()),
|
||||
|
@ -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};
|
||||
|
@ -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
61
src/ledger.rs
Normal 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));
|
||||
});
|
||||
}
|
||||
}
|
27
src/lib.rs
27
src/lib.rs
@ -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;
|
||||
|
113
src/log.rs
113
src/log.rs
@ -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));
|
||||
});
|
||||
}
|
||||
}
|
@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
26
src/mint.rs
26
src/mint.rs
@ -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
175
src/plan.rs
Normal 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
89
src/recorder.rs
Normal 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
125
src/result.rs
Normal 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();
|
||||
}
|
||||
}
|
@ -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
474
src/streamer.rs
Normal 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");
|
||||
}
|
||||
}
|
@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user