[Beta] Backports (#7756)

* Filter-out nodes.json (#7716)

* Filter-out nodes.json

* network: sort node table nodes by failure ratio

* network: fix node table tests

* network: fit node failure percentage into buckets of 5%

* network: consider number of attempts in sorting of node table

* network: fix node table grumbles

* Fix client not being dropped on shutdown (#7695)

* parity: wait for client to drop on shutdown

* parity: fix grumbles in shutdown wait

* parity: increase shutdown timeouts

* Wrap --help output to 120 characters (#7626)

* Update Clap dependency and remove workarounds

* WIP

* Remove line breaks in help messages for now

* Multiple values can only be separated by commas (closes #7428)

* Grumbles; refactor repeating code; add constant

* Use a single Wrapper rather than allocate a new one for each call

* Wrap --help to 120 characters rather than 100 characters
This commit is contained in:
André Silva 2018-01-31 20:45:23 +00:00 committed by Afri Schoedon
parent 582fa8ce45
commit a42d780d02
11 changed files with 399 additions and 222 deletions

38
Cargo.lock generated
View File

@ -25,6 +25,11 @@ name = "ansi_term"
version = "0.9.0" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ansi_term"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "app_dirs" name = "app_dirs"
version = "1.1.1" version = "1.1.1"
@ -163,6 +168,11 @@ name = "bitflags"
version = "0.9.1" version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "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]] [[package]]
name = "bloomable" name = "bloomable"
version = "0.1.0" version = "0.1.0"
@ -234,15 +244,14 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "2.26.2" version = "2.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -663,6 +672,8 @@ dependencies = [
"rlp 0.2.1", "rlp 0.2.1",
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"snappy 0.1.0 (git+https://github.com/paritytech/rust-snappy)", "snappy 0.1.0 (git+https://github.com/paritytech/rust-snappy)",
@ -1907,7 +1918,7 @@ version = "1.9.1"
dependencies = [ dependencies = [
"ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.26.2 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.29.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ctrlc 1.1.1 (git+https://github.com/paritytech/rust-ctrlc.git)", "ctrlc 1.1.1 (git+https://github.com/paritytech/rust-ctrlc.git)",
"daemonize 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "daemonize 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"dir 0.1.0", "dir 0.1.0",
@ -1966,6 +1977,8 @@ dependencies = [
"serde 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)",
"toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2505,7 +2518,7 @@ dependencies = [
name = "pwasm-run-test" name = "pwasm-run-test"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap 2.26.2 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.29.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-bigint 0.2.1", "ethcore-bigint 0.2.1",
"ethcore-logger 1.9.0", "ethcore-logger 1.9.0",
"ethjson 0.1.0", "ethjson 0.1.0",
@ -3113,10 +3126,9 @@ dependencies = [
[[package]] [[package]]
name = "textwrap" name = "textwrap"
version = "0.8.0" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -3477,7 +3489,7 @@ version = "0.1.0"
source = "git+https://github.com/paritytech/wasm-utils#3d59f7ca0661317bc66894a26b2a5a319fa5d229" source = "git+https://github.com/paritytech/wasm-utils#3d59f7ca0661317bc66894a26b2a5a319fa5d229"
dependencies = [ dependencies = [
"byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.26.2 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.29.1 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
@ -3556,6 +3568,7 @@ dependencies = [
"checksum adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6cbd0b9af8587c72beadc9f72d35b9fbb070982c9e6203e46e93f10df25f8f45" "checksum adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6cbd0b9af8587c72beadc9f72d35b9fbb070982c9e6203e46e93f10df25f8f45"
"checksum advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a" "checksum advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a"
"checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699" "checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699"
"checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455"
"checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
"checksum app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7d1c0d48a81bbb13043847f957971f4d87c81542d80ece5e84ba3cba4058fd4" "checksum app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7d1c0d48a81bbb13043847f957971f4d87c81542d80ece5e84ba3cba4058fd4"
"checksum arrayvec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1c0250693b17316353df525fb088da32a8c18f84eb65d113dde31f5a76ed17b6" "checksum arrayvec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1c0250693b17316353df525fb088da32a8c18f84eb65d113dde31f5a76ed17b6"
@ -3574,6 +3587,7 @@ dependencies = [
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
"checksum bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1370e9fc2a6ae53aea8b7a5110edbd08836ed87c88736dfabccade1c2b44bff4" "checksum bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1370e9fc2a6ae53aea8b7a5110edbd08836ed87c88736dfabccade1c2b44bff4"
"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf"
"checksum bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f421095d2a76fc24cd3fb3f912b90df06be7689912b1bdb423caefae59c258d" "checksum bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f421095d2a76fc24cd3fb3f912b90df06be7689912b1bdb423caefae59c258d"
"checksum bn 0.4.4 (git+https://github.com/paritytech/bn)" = "<none>" "checksum bn 0.4.4 (git+https://github.com/paritytech/bn)" = "<none>"
"checksum byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff81738b726f5d099632ceaffe7fb65b90212e8dce59d518729e7e8634032d3d" "checksum byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff81738b726f5d099632ceaffe7fb65b90212e8dce59d518729e7e8634032d3d"
@ -3581,7 +3595,7 @@ dependencies = [
"checksum cc 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7db2f146208d7e0fbee761b09cd65a7f51ccc38705d4e7262dad4d73b12a76b1" "checksum cc 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7db2f146208d7e0fbee761b09cd65a7f51ccc38705d4e7262dad4d73b12a76b1"
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
"checksum cid 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "34aa7da06f10541fbca6850719cdaa8fa03060a5d2fb33840f149cf8133a00c7" "checksum cid 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "34aa7da06f10541fbca6850719cdaa8fa03060a5d2fb33840f149cf8133a00c7"
"checksum clap 2.26.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3451e409013178663435d6f15fdb212f14ee4424a3d74f979d081d0a66b6f1f2" "checksum clap 2.29.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8f4a2b3bb7ef3c672d7c13d15613211d5a6976b6892c598b0fcb5d40765f19c2"
"checksum coco 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c06169f5beb7e31c7c67ebf5540b8b472d23e3eade3b2ec7d1f5b504a85f91bd" "checksum coco 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c06169f5beb7e31c7c67ebf5540b8b472d23e3eade3b2ec7d1f5b504a85f91bd"
"checksum conv 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" "checksum conv 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299"
"checksum cookie 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d53b80dde876f47f03cda35303e368a79b91c70b0d65ecba5fd5280944a08591" "checksum cookie 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d53b80dde876f47f03cda35303e368a79b91c70b0d65ecba5fd5280944a08591"
@ -3785,7 +3799,7 @@ dependencies = [
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
"checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1" "checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1"
"checksum term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b6b55df3198cc93372e85dd2ed817f0e38ce8cc0f22eb32391bfad9c4bf209" "checksum term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b6b55df3198cc93372e85dd2ed817f0e38ce8cc0f22eb32391bfad9c4bf209"
"checksum textwrap 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df8e08afc40ae3459e4838f303e465aa50d823df8d7f83ca88108f6d3afe7edd" "checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
"checksum thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1697c4b57aeeb7a536b647165a2825faddffb1d3bad386d507709bd51a90bb14" "checksum thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1697c4b57aeeb7a536b647165a2825faddffb1d3bad386d507709bd51a90bb14"
"checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865" "checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865"
"checksum time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d788d3aa77bc0ef3e9621256885555368b47bd495c13dd2e7413c89f845520" "checksum time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d788d3aa77bc0ef3e9621256885555368b47bd495c13dd2e7413c89f845520"

View File

@ -12,6 +12,8 @@ env_logger = "0.4"
rustc-hex = "1.0" rustc-hex = "1.0"
docopt = "0.8" docopt = "0.8"
clap = "2" clap = "2"
term_size = "0.3"
textwrap = "0.9"
time = "0.1" time = "0.1"
num_cpus = "1.2" num_cpus = "1.2"
number_prefix = "0.2" number_prefix = "0.2"

View File

@ -257,12 +257,7 @@ usage! {
ARG arg_mode: (String) = "last", or |c: &Config| otry!(c.parity).mode.clone(), ARG arg_mode: (String) = "last", or |c: &Config| otry!(c.parity).mode.clone(),
"--mode=[MODE]", "--mode=[MODE]",
"Set the operating mode. MODE can be one of: "Set the operating mode. MODE can be one of: last - Uses the last-used mode, active if none; active - Parity continuously syncs the chain; passive - Parity syncs initially, then sleeps and wakes regularly to resync; dark - Parity syncs only when the RPC is active; offline - Parity doesn't sync.",
last - Uses the last-used mode, active if none.
active - Parity continuously syncs the chain.
passive - Parity syncs initially, then sleeps and wakes regularly to resync.
dark - Parity syncs only when the RPC is active.
offline - Parity doesn't sync.",
ARG arg_mode_timeout: (u64) = 300u64, or |c: &Config| otry!(c.parity).mode_timeout.clone(), ARG arg_mode_timeout: (u64) = 300u64, or |c: &Config| otry!(c.parity).mode_timeout.clone(),
"--mode-timeout=[SECS]", "--mode-timeout=[SECS]",
@ -274,19 +269,11 @@ usage! {
ARG arg_auto_update: (String) = "critical", or |c: &Config| otry!(c.parity).auto_update.clone(), ARG arg_auto_update: (String) = "critical", or |c: &Config| otry!(c.parity).auto_update.clone(),
"--auto-update=[SET]", "--auto-update=[SET]",
"Set a releases set to automatically update and install. "Set a releases set to automatically update and install. SET can be one of: all - All updates in the our release track; critical - Only consensus/security updates; none - No updates will be auto-installed.",
all - All updates in the our release track.
critical - Only consensus/security updates.
none - No updates will be auto-installed.",
ARG arg_release_track: (String) = "current", or |c: &Config| otry!(c.parity).release_track.clone(), ARG arg_release_track: (String) = "current", or |c: &Config| otry!(c.parity).release_track.clone(),
"--release-track=[TRACK]", "--release-track=[TRACK]",
"Set which release track we should use for updates. "Set which release track we should use for updates. TRACK can be one of: stable - Stable releases; beta - Beta releases; nightly - Nightly releases (unstable); testing - Testing releases (do not use); current - Whatever track this executable was released on.",
stable - Stable releases.
beta - Beta releases.
nightly - Nightly releases (unstable).
testing - Testing releases (do not use).
current - Whatever track this executable was released on",
ARG arg_chain: (String) = "foundation", or |c: &Config| otry!(c.parity).chain.clone(), ARG arg_chain: (String) = "foundation", or |c: &Config| otry!(c.parity).chain.clone(),
"--chain=[CHAIN]", "--chain=[CHAIN]",
@ -311,8 +298,7 @@ usage! {
["Convenience options"] ["Convenience options"]
FLAG flag_unsafe_expose: (bool) = false, or |c: &Config| otry!(c.misc).unsafe_expose, FLAG flag_unsafe_expose: (bool) = false, or |c: &Config| otry!(c.misc).unsafe_expose,
"--unsafe-expose", "--unsafe-expose",
"All servers will listen on external interfaces and will be remotely accessible. It's equivalent with setting the following: --{{ws,jsonrpc,ui,ipfs,secret_store,stratum}}-interface=all --*-hosts=all "All servers will listen on external interfaces and will be remotely accessible. It's equivalent with setting the following: --{{ws,jsonrpc,ui,ipfs,secret_store,stratum}}-interface=all --*-hosts=all This option is UNSAFE and should be used with great care!",
This option is UNSAFE and should be used with great care!",
ARG arg_config: (String) = "$BASE/config.toml", or |_| None, ARG arg_config: (String) = "$BASE/config.toml", or |_| None,
"-c, --config=[CONFIG]", "-c, --config=[CONFIG]",
@ -498,7 +484,7 @@ usage! {
ARG arg_ws_hosts: (String) = "none", or |c: &Config| otry!(c.websockets).hosts.as_ref().map(|vec| vec.join(",")), ARG arg_ws_hosts: (String) = "none", or |c: &Config| otry!(c.websockets).hosts.as_ref().map(|vec| vec.join(",")),
"--ws-hosts=[HOSTS]", "--ws-hosts=[HOSTS]",
"List of allowed Host header values. This option will validate the Host header sent by the browser, it is additional security against some attack vectors. Special options: \"all\", \"none\",.", "List of allowed Host header values. This option will validate the Host header sent by the browser, it is additional security against some attack vectors. Special options: \"all\", \"none\".",
["API and console options IPC"] ["API and console options IPC"]
FLAG flag_no_ipc: (bool) = false, or |c: &Config| otry!(c.ipc).disable.clone(), FLAG flag_no_ipc: (bool) = false, or |c: &Config| otry!(c.ipc).disable.clone(),
@ -582,7 +568,7 @@ usage! {
ARG arg_secretstore_path: (String) = "$BASE/secretstore", or |c: &Config| otry!(c.secretstore).path.clone(), ARG arg_secretstore_path: (String) = "$BASE/secretstore", or |c: &Config| otry!(c.secretstore).path.clone(),
"--secretstore-path=[PATH]", "--secretstore-path=[PATH]",
"Specify directory where Secret Store should save its data..", "Specify directory where Secret Store should save its data.",
ARG arg_secretstore_secret: (Option<String>) = None, or |c: &Config| otry!(c.secretstore).self_secret.clone(), ARG arg_secretstore_secret: (Option<String>) = None, or |c: &Config| otry!(c.secretstore).self_secret.clone(),
"--secretstore-secret=[SECRET]", "--secretstore-secret=[SECRET]",
@ -671,7 +657,7 @@ usage! {
ARG arg_tx_queue_gas: (String) = "off", or |c: &Config| otry!(c.mining).tx_queue_gas.clone(), ARG arg_tx_queue_gas: (String) = "off", or |c: &Config| otry!(c.mining).tx_queue_gas.clone(),
"--tx-queue-gas=[LIMIT]", "--tx-queue-gas=[LIMIT]",
"Maximum amount of total gas for external transactions in the queue. LIMIT can be either an amount of gas or 'auto' or 'off'. 'auto' sets the limit to be 20x the current block gas limit..", "Maximum amount of total gas for external transactions in the queue. LIMIT can be either an amount of gas or 'auto' or 'off'. 'auto' sets the limit to be 20x the current block gas limit.",
ARG arg_tx_queue_strategy: (String) = "gas_price", or |c: &Config| otry!(c.mining).tx_queue_strategy.clone(), ARG arg_tx_queue_strategy: (String) = "gas_price", or |c: &Config| otry!(c.mining).tx_queue_strategy.clone(),
"--tx-queue-strategy=[S]", "--tx-queue-strategy=[S]",
@ -778,7 +764,7 @@ usage! {
ARG arg_pruning_history: (u64) = 64u64, or |c: &Config| otry!(c.footprint).pruning_history.clone(), ARG arg_pruning_history: (u64) = 64u64, or |c: &Config| otry!(c.footprint).pruning_history.clone(),
"--pruning-history=[NUM]", "--pruning-history=[NUM]",
"Set a minimum number of recent states to keep when pruning is active..", "Set a minimum number of recent states to keep when pruning is active.",
ARG arg_pruning_memory: (usize) = 32usize, or |c: &Config| otry!(c.footprint).pruning_memory.clone(), ARG arg_pruning_memory: (usize) = 32usize, or |c: &Config| otry!(c.footprint).pruning_memory.clone(),
"--pruning-memory=[MB]", "--pruning-memory=[MB]",
@ -1322,12 +1308,15 @@ mod tests {
let args = Args::parse(&["parity", "--secretstore-nodes", "abc@127.0.0.1:3333,cde@10.10.10.10:4444"]).unwrap(); let args = Args::parse(&["parity", "--secretstore-nodes", "abc@127.0.0.1:3333,cde@10.10.10.10:4444"]).unwrap();
assert_eq!(args.arg_secretstore_nodes, "abc@127.0.0.1:3333,cde@10.10.10.10:4444"); assert_eq!(args.arg_secretstore_nodes, "abc@127.0.0.1:3333,cde@10.10.10.10:4444");
// Arguments with a single value shouldn't accept multiple values let args = Args::parse(&["parity", "--password", "~/.safe/1", "--password", "~/.safe/2", "--ui-port", "8123", "ui"]).unwrap();
let args = Args::parse(&["parity", "--auto-update", "critical", "all"]);
assert!(args.is_err());
let args = Args::parse(&["parity", "--password", "~/.safe/1", "~/.safe/2"]).unwrap();
assert_eq!(args.arg_password, vec!["~/.safe/1".to_owned(), "~/.safe/2".to_owned()]); assert_eq!(args.arg_password, vec!["~/.safe/1".to_owned(), "~/.safe/2".to_owned()]);
assert_eq!(args.arg_ui_port, 8123);
assert_eq!(args.cmd_ui, true);
let args = Args::parse(&["parity", "--password", "~/.safe/1,~/.safe/2", "--ui-port", "8123", "ui"]).unwrap();
assert_eq!(args.arg_password, vec!["~/.safe/1".to_owned(), "~/.safe/2".to_owned()]);
assert_eq!(args.arg_ui_port, 8123);
assert_eq!(args.cmd_ui, true);
} }
#[test] #[test]

View File

@ -150,14 +150,20 @@ macro_rules! usage {
} }
) => { ) => {
use toml; use toml;
use std::{fs, io, process}; use std::{fs, io, process, cmp};
use std::io::{Read, Write}; use std::io::{Read, Write};
use parity_version::version; use parity_version::version;
use clap::{Arg, App, SubCommand, AppSettings, ArgMatches as ClapArgMatches, Error as ClapError, ErrorKind as ClapErrorKind}; use clap::{Arg, App, SubCommand, AppSettings, ArgSettings, Error as ClapError, ErrorKind as ClapErrorKind};
use dir::helpers::replace_home; use dir::helpers::replace_home;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::collections::HashMap; use std::collections::HashMap;
extern crate textwrap;
extern crate term_size;
use self::textwrap::{Wrapper};
const MAX_TERM_WIDTH: usize = 120;
#[cfg(test)] #[cfg(test)]
use regex::Regex; use regex::Regex;
@ -365,11 +371,23 @@ macro_rules! usage {
#[allow(unused_mut)] // subc_subc_exist may be assigned true by the macro #[allow(unused_mut)] // subc_subc_exist may be assigned true by the macro
#[allow(unused_assignments)] // Rust issue #22630 #[allow(unused_assignments)] // Rust issue #22630
pub fn print_help() -> String { pub fn print_help() -> String {
const TAB: &str = " ";
const TAB_TAB: &str = " ";
let term_width = match term_size::dimensions() {
None => MAX_TERM_WIDTH,
Some((w, _)) => {
cmp::min(w, MAX_TERM_WIDTH)
}
};
let mut help : String = include_str!("./usage_header.txt").to_owned(); let mut help : String = include_str!("./usage_header.txt").to_owned();
help.push_str("\n\n"); help.push_str("\n");
// Subcommands // Subcommands
let mut subcommands_wrapper = Wrapper::new(term_width).subsequent_indent(TAB);
help.push_str("parity [options]\n"); help.push_str("parity [options]\n");
$( $(
{ {
@ -386,11 +404,14 @@ macro_rules! usage {
)* )*
]; ];
if subc_subc_usages.is_empty() { help.push_str(&subcommands_wrapper.fill(
help.push_str(&format!("parity [options] {} {}\n", underscore_to_hyphen!(&stringify!($subc)[4..]), underscore_to_hyphen!(&stringify!($subc_subc)[stringify!($subc).len()+1..]))); format!(
} else { "parity [options] {} {} {}\n",
help.push_str(&format!("parity [options] {} {} {}\n", underscore_to_hyphen!(&stringify!($subc)[4..]), underscore_to_hyphen!(&stringify!($subc_subc)[stringify!($subc).len()+1..]), subc_subc_usages.join(" "))); underscore_to_hyphen!(&stringify!($subc)[4..]),
} underscore_to_hyphen!(&stringify!($subc_subc)[stringify!($subc).len()+1..]),
subc_subc_usages.join(" ")
).as_ref())
);
)* )*
// Print the subcommand on its own only if it has no subsubcommands // Print the subcommand on its own only if it has no subsubcommands
@ -404,22 +425,30 @@ macro_rules! usage {
)* )*
]; ];
if subc_usages.is_empty() { help.push_str(&subcommands_wrapper.fill(
help.push_str(&format!("parity [options] {}\n", underscore_to_hyphen!(&stringify!($subc)[4..]))); format!(
} else { "parity [options] {} {}\n",
help.push_str(&format!("parity [options] {} {}\n", underscore_to_hyphen!(&stringify!($subc)[4..]), subc_usages.join(" "))); underscore_to_hyphen!(&stringify!($subc)[4..]),
} subc_usages.join(" ")
).as_ref())
);
} }
} }
)* )*
help.push_str("\n");
// Arguments and flags // Arguments and flags
let args_wrapper = Wrapper::new(term_width).initial_indent(TAB_TAB).subsequent_indent(TAB_TAB);
$( $(
help.push_str("\n");
help.push_str($group_name); help.push_str(":\n"); help.push_str($group_name); help.push_str(":\n");
$( $(
help.push_str(&format!("\t{}\n\t\t{}\n", $flag_usage, $flag_help)); help.push_str(&format!("{}{}\n{}\n",
TAB, $flag_usage,
args_wrapper.fill($flag_help)
));
help.push_str("\n");
)* )*
$( $(
@ -429,10 +458,24 @@ macro_rules! usage {
if_option_vec!( if_option_vec!(
$($arg_type_tt)+, $($arg_type_tt)+,
THEN { THEN {
help.push_str(&format!("\t{}\n\t\t{} (default: {:?})\n", $arg_usage, $arg_help, {let x : inner_option_type!($($arg_type_tt)+)> = $arg_default; x})) help.push_str(&format!("{}{}\n{}\n",
TAB, $arg_usage,
args_wrapper.fill(format!(
"{} (default: {:?})",
$arg_help,
{let x : inner_option_type!($($arg_type_tt)+)> = $arg_default; x}
).as_ref())
))
} }
ELSE { ELSE {
help.push_str(&format!("\t{}\n\t\t{}{}\n", $arg_usage, $arg_help, $arg_default.map(|x: inner_option_type!($($arg_type_tt)+)| format!(" (default: {})",x)).unwrap_or("".to_owned()))) help.push_str(&format!("{}{}\n{}\n",
TAB, $arg_usage,
args_wrapper.fill(format!(
"{}{}",
$arg_help,
$arg_default.map(|x: inner_option_type!($($arg_type_tt)+)| format!(" (default: {})",x)).unwrap_or("".to_owned())
).as_ref())
))
} }
) )
} }
@ -440,14 +483,27 @@ macro_rules! usage {
if_vec!( if_vec!(
$($arg_type_tt)+, $($arg_type_tt)+,
THEN { THEN {
help.push_str(&format!("\t{}\n\t\t{} (default: {:?})\n", $arg_usage, $arg_help, {let x : $($arg_type_tt)+ = $arg_default; x})) help.push_str(&format!("{}{}\n{}\n", TAB, $arg_usage,
args_wrapper.fill(format!(
"{} (default: {:?})",
$arg_help,
{let x : $($arg_type_tt)+ = $arg_default; x}
).as_ref())
))
} }
ELSE { ELSE {
help.push_str(&format!("\t{}\n\t\t{} (default: {})\n", $arg_usage, $arg_help, $arg_default)) help.push_str(&format!("{}{}\n{}\n", TAB, $arg_usage,
args_wrapper.fill(format!(
"{} (default: {})",
$arg_help,
$arg_default
).as_ref())
))
} }
) )
} }
); );
help.push_str("\n");
)* )*
)* )*
@ -503,36 +559,6 @@ macro_rules! usage {
args args
} }
pub fn hydrate_with_globals(self: &mut Self, matches: &ClapArgMatches) -> Result<(), ClapError> {
$(
$(
self.$flag = self.$flag || matches.is_present(stringify!($flag));
)*
$(
if let some @ Some(_) = return_if_parse_error!(if_option!(
$($arg_type_tt)+,
THEN {
if_option_vec!(
$($arg_type_tt)+,
THEN { values_t!(matches, stringify!($arg), inner_option_vec_type!($($arg_type_tt)+)) }
ELSE { value_t!(matches, stringify!($arg), inner_option_type!($($arg_type_tt)+)) }
)
}
ELSE {
if_vec!(
$($arg_type_tt)+,
THEN { values_t!(matches, stringify!($arg), inner_vec_type!($($arg_type_tt)+)) }
ELSE { value_t!(matches, stringify!($arg), $($arg_type_tt)+) }
)
}
)) {
self.$arg = some;
}
)*
)*
Ok(())
}
#[allow(unused_variables)] // the submatches of arg-less subcommands aren't used #[allow(unused_variables)] // the submatches of arg-less subcommands aren't used
pub fn parse<S: AsRef<str>>(command: &[S]) -> Result<Self, ClapError> { pub fn parse<S: AsRef<str>>(command: &[S]) -> Result<Self, ClapError> {
@ -582,21 +608,31 @@ macro_rules! usage {
let matches = App::new("Parity") let matches = App::new("Parity")
.global_setting(AppSettings::VersionlessSubcommands) .global_setting(AppSettings::VersionlessSubcommands)
.global_setting(AppSettings::DisableHelpSubcommand) .global_setting(AppSettings::DisableHelpSubcommand)
.max_term_width(MAX_TERM_WIDTH)
.help(Args::print_help().as_ref()) .help(Args::print_help().as_ref())
.args(&usages.iter().map(|u| Arg::from_usage(u).use_delimiter(false).allow_hyphen_values(true)).collect::<Vec<Arg>>()) .args(&usages.iter().map(|u| {
let mut arg = Arg::from_usage(u)
.allow_hyphen_values(true) // Allow for example --allow-ips -10.0.0.0/8
.global(true) // Argument doesn't have to come before the first subcommand
.hidden(true); // Hide global arguments from the (subcommand) help messages generated by Clap
if arg.is_set(ArgSettings::Multiple) {
arg = arg.require_delimiter(true); // Multiple values can only be separated by commas, not spaces (#7428)
}
arg
}).collect::<Vec<Arg>>())
$( $(
.subcommand( .subcommand(
SubCommand::with_name(&underscore_to_hyphen!(&stringify!($subc)[4..])) SubCommand::with_name(&underscore_to_hyphen!(&stringify!($subc)[4..]))
.about($subc_help) .about($subc_help)
.args(&subc_usages.get(stringify!($subc)).unwrap().iter().map(|u| Arg::from_usage(u).use_delimiter(false).allow_hyphen_values(true)).collect::<Vec<Arg>>()) .args(&subc_usages.get(stringify!($subc)).unwrap().iter().map(|u| Arg::from_usage(u).use_delimiter(false).allow_hyphen_values(true)).collect::<Vec<Arg>>())
.args(&usages.iter().map(|u| Arg::from_usage(u).use_delimiter(false).allow_hyphen_values(true)).collect::<Vec<Arg>>()) // accept global arguments at this position
$( $(
.setting(AppSettings::SubcommandRequired) // prevent from running `parity account` .setting(AppSettings::SubcommandRequired) // prevent from running `parity account`
.subcommand( .subcommand(
SubCommand::with_name(&underscore_to_hyphen!(&stringify!($subc_subc)[stringify!($subc).len()+1..])) SubCommand::with_name(&underscore_to_hyphen!(&stringify!($subc_subc)[stringify!($subc).len()+1..]))
.about($subc_subc_help) .about($subc_subc_help)
.args(&subc_usages.get(stringify!($subc_subc)).unwrap().iter().map(|u| Arg::from_usage(u).use_delimiter(false).allow_hyphen_values(true)).collect::<Vec<Arg>>()) .args(&subc_usages.get(stringify!($subc_subc)).unwrap().iter().map(|u| Arg::from_usage(u).use_delimiter(false).allow_hyphen_values(true)).collect::<Vec<Arg>>())
.args(&usages.iter().map(|u| Arg::from_usage(u).use_delimiter(false).allow_hyphen_values(true)).collect::<Vec<Arg>>()) // accept global arguments at this position
) )
)* )*
) )
@ -605,15 +641,39 @@ macro_rules! usage {
let mut raw_args : RawArgs = Default::default(); let mut raw_args : RawArgs = Default::default();
raw_args.hydrate_with_globals(&matches)?; // Globals
$(
$(
raw_args.$flag = raw_args.$flag || matches.is_present(stringify!($flag));
)*
$(
if let some @ Some(_) = return_if_parse_error!(if_option!(
$($arg_type_tt)+,
THEN {
if_option_vec!(
$($arg_type_tt)+,
THEN { values_t!(matches, stringify!($arg), inner_option_vec_type!($($arg_type_tt)+)) }
ELSE { value_t!(matches, stringify!($arg), inner_option_type!($($arg_type_tt)+)) }
)
}
ELSE {
if_vec!(
$($arg_type_tt)+,
THEN { values_t!(matches, stringify!($arg), inner_vec_type!($($arg_type_tt)+)) }
ELSE { value_t!(matches, stringify!($arg), $($arg_type_tt)+) }
)
}
)) {
raw_args.$arg = some;
}
)*
)*
// Subcommands // Subcommands
$( $(
if let Some(submatches) = matches.subcommand_matches(&underscore_to_hyphen!(&stringify!($subc)[4..])) { if let Some(submatches) = matches.subcommand_matches(&underscore_to_hyphen!(&stringify!($subc)[4..])) {
raw_args.$subc = true; raw_args.$subc = true;
// Globals
raw_args.hydrate_with_globals(&submatches)?;
// Subcommand flags // Subcommand flags
$( $(
raw_args.$subc_flag = submatches.is_present(&stringify!($subc_flag)); raw_args.$subc_flag = submatches.is_present(&stringify!($subc_flag));
@ -643,8 +703,6 @@ macro_rules! usage {
if let Some(subsubmatches) = submatches.subcommand_matches(&underscore_to_hyphen!(&stringify!($subc_subc)[stringify!($subc).len()+1..])) { if let Some(subsubmatches) = submatches.subcommand_matches(&underscore_to_hyphen!(&stringify!($subc_subc)[stringify!($subc).len()+1..])) {
raw_args.$subc_subc = true; raw_args.$subc_subc = true;
// Globals
raw_args.hydrate_with_globals(&subsubmatches)?;
// Sub-subcommand flags // Sub-subcommand flags
$( $(
raw_args.$subc_subc_flag = subsubmatches.is_present(&stringify!($subc_subc_flag)); raw_args.$subc_subc_flag = subsubmatches.is_present(&stringify!($subc_subc_flag));

View File

@ -1,3 +1,3 @@
Parity. Ethereum Client. Parity. Ethereum Client.
By Wood/Paronyan/Kotewicz/Drwięga/Volf et al. By Wood/Paronyan/Kotewicz/Drwięga/Volf et al.
Copyright 2015, 2016, 2017 Parity Technologies (UK) Ltd Copyright 2015, 2016, 2017, 2018 Parity Technologies (UK) Ltd

View File

@ -1,6 +1,6 @@
Parity Parity
version {} version {}
Copyright 2015, 2016, 2017 Parity Technologies (UK) Ltd Copyright 2015, 2016, 2017, 2018 Parity Technologies (UK) Ltd
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it. This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. There is NO WARRANTY, to the extent permitted by law.

View File

@ -16,6 +16,8 @@
use std::fmt; use std::fmt;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use std::time::{Duration, Instant};
use std::thread;
use std::net::{TcpListener}; use std::net::{TcpListener};
use ctrlc::CtrlC; use ctrlc::CtrlC;
@ -171,8 +173,10 @@ impl ::local_store::NodeInfo for FullNodeInfo {
} }
} }
type LightClient = ::light::client::Client<::light_helpers::EpochFetch>;
// helper for light execution. // helper for light execution.
fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> Result<(bool, Option<String>), String> { fn execute_light_impl(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> Result<((bool, Option<String>), Weak<LightClient>), String> {
use light::client as light_client; use light::client as light_client;
use ethsync::{LightSyncParams, LightSync, ManageNetwork}; use ethsync::{LightSyncParams, LightSync, ManageNetwork};
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
@ -237,8 +241,9 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) ->
let service = light_client::Service::start(config, &spec, fetch, &db_dirs.client_path(algorithm), cache.clone()) let service = light_client::Service::start(config, &spec, fetch, &db_dirs.client_path(algorithm), cache.clone())
.map_err(|e| format!("Error starting light client: {}", e))?; .map_err(|e| format!("Error starting light client: {}", e))?;
let client = service.client();
let txq = Arc::new(RwLock::new(::light::transaction_queue::TransactionQueue::default())); let txq = Arc::new(RwLock::new(::light::transaction_queue::TransactionQueue::default()));
let provider = ::light::provider::LightProvider::new(service.client().clone(), txq.clone()); let provider = ::light::provider::LightProvider::new(client.clone(), txq.clone());
// start network. // start network.
// set up bootnodes // set up bootnodes
@ -275,7 +280,7 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) ->
// queue cull service. // queue cull service.
let queue_cull = Arc::new(::light_helpers::QueueCull { let queue_cull = Arc::new(::light_helpers::QueueCull {
client: service.client().clone(), client: client.clone(),
sync: light_sync.clone(), sync: light_sync.clone(),
on_demand: on_demand.clone(), on_demand: on_demand.clone(),
txq: txq.clone(), txq: txq.clone(),
@ -299,7 +304,7 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) ->
let signer_service = Arc::new(signer::new_service(&cmd.ws_conf, &cmd.ui_conf, &cmd.logger_config)); let signer_service = Arc::new(signer::new_service(&cmd.ws_conf, &cmd.ui_conf, &cmd.logger_config));
let (node_health, dapps_deps) = { let (node_health, dapps_deps) = {
let contract_client = Arc::new(::dapps::LightRegistrar { let contract_client = Arc::new(::dapps::LightRegistrar {
client: service.client().clone(), client: client.clone(),
sync: light_sync.clone(), sync: light_sync.clone(),
on_demand: on_demand.clone(), on_demand: on_demand.clone(),
}); });
@ -342,7 +347,7 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) ->
let dapps_service = dapps::service(&dapps_middleware); let dapps_service = dapps::service(&dapps_middleware);
let deps_for_rpc_apis = Arc::new(rpc_apis::LightDependencies { let deps_for_rpc_apis = Arc::new(rpc_apis::LightDependencies {
signer_service: signer_service, signer_service: signer_service,
client: service.client().clone(), client: client.clone(),
sync: light_sync.clone(), sync: light_sync.clone(),
net: light_sync.clone(), net: light_sync.clone(),
health: node_health, health: node_health,
@ -382,7 +387,7 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) ->
// the informant // the informant
let informant = Arc::new(Informant::new( let informant = Arc::new(Informant::new(
LightNodeInformantData { LightNodeInformantData {
client: service.client().clone(), client: client.clone(),
sync: light_sync.clone(), sync: light_sync.clone(),
cache: cache, cache: cache,
}, },
@ -397,26 +402,13 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) ->
let res = wait_for_exit(None, None, can_restart); let res = wait_for_exit(None, None, can_restart);
informant.shutdown(); informant.shutdown();
Ok(res) // Create a weak reference to the client so that we can wait on shutdown until it is dropped
let weak_client = Arc::downgrade(&client);
Ok((res, weak_client))
} }
pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> Result<(bool, Option<String>), String> { pub fn execute_impl(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> Result<((bool, Option<String>), Weak<Client>), String> {
if cmd.ui && cmd.dapps_conf.enabled {
// Check if Parity is already running
let addr = format!("{}:{}", cmd.ui_conf.interface, cmd.ui_conf.port);
if !TcpListener::bind(&addr as &str).is_ok() {
return open_ui(&cmd.ws_conf, &cmd.ui_conf, &cmd.logger_config).map(|_| (false, None));
}
}
// increase max number of open files
raise_fd_limit();
// run as light client.
if cmd.light {
return execute_light(cmd, can_restart, logger);
}
// load spec // load spec
let spec = cmd.spec.spec(&cmd.dirs.cache)?; let spec = cmd.spec.spec(&cmd.dirs.cache)?;
@ -855,6 +847,9 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
open_dapp(&cmd.dapps_conf, &cmd.http_conf, &dapp)?; open_dapp(&cmd.dapps_conf, &cmd.http_conf, &dapp)?;
} }
// Create a weak reference to the client so that we can wait on shutdown until it is dropped
let weak_client = Arc::downgrade(&client);
// Handle exit // Handle exit
let restart = wait_for_exit(Some(updater), Some(client), can_restart); let restart = wait_for_exit(Some(updater), Some(client), can_restart);
@ -868,7 +863,33 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
// just Arc is dropping here, to allow other reference release in its default time // just Arc is dropping here, to allow other reference release in its default time
drop(informant); drop(informant);
Ok(restart) Ok((restart, weak_client))
}
pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> Result<(bool, Option<String>), String> {
if cmd.ui && cmd.dapps_conf.enabled {
// Check if Parity is already running
let addr = format!("{}:{}", cmd.ui_conf.interface, cmd.ui_conf.port);
if !TcpListener::bind(&addr as &str).is_ok() {
return open_ui(&cmd.ws_conf, &cmd.ui_conf, &cmd.logger_config).map(|_| (false, None));
}
}
// increase max number of open files
raise_fd_limit();
fn wait<T>(res: Result<((bool, Option<String>), Weak<T>), String>) -> Result<(bool, Option<String>), String> {
res.map(|(restart, weak_client)| {
wait_for_drop(weak_client);
restart
})
}
if cmd.light {
wait(execute_light_impl(cmd, can_restart, logger))
} else {
wait(execute_impl(cmd, can_restart, logger))
}
} }
#[cfg(not(windows))] #[cfg(not(windows))]
@ -1001,3 +1022,27 @@ fn wait_for_exit(
let _ = exit.1.wait(&mut l); let _ = exit.1.wait(&mut l);
l.clone() l.clone()
} }
fn wait_for_drop<T>(w: Weak<T>) {
let sleep_duration = Duration::from_secs(1);
let warn_timeout = Duration::from_secs(60);
let max_timeout = Duration::from_secs(300);
let instant = Instant::now();
let mut warned = false;
while instant.elapsed() < max_timeout {
if w.upgrade().is_none() {
return;
}
if !warned && instant.elapsed() > warn_timeout {
warned = true;
warn!("Shutdown is taking longer than expected.");
}
thread::sleep(sleep_duration);
}
warn!("Shutdown timeout reached, exiting uncleanly.");
}

View File

@ -31,7 +31,9 @@ ethcore-logger = { path ="../../logger" }
ipnetwork = "0.12.6" ipnetwork = "0.12.6"
keccak-hash = { path = "../hash" } keccak-hash = { path = "../hash" }
snappy = { git = "https://github.com/paritytech/rust-snappy" } snappy = { git = "https://github.com/paritytech/rust-snappy" }
serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
serde_derive = "1.0"
error-chain = { version = "0.11", default-features = false } error-chain = { version = "0.11", default-features = false }
[dev-dependencies] [dev-dependencies]

View File

@ -719,7 +719,7 @@ impl Host {
let address = { let address = {
let mut nodes = self.nodes.write(); let mut nodes = self.nodes.write();
if let Some(node) = nodes.get_mut(id) { if let Some(node) = nodes.get_mut(id) {
node.last_attempted = Some(::time::now()); node.attempts += 1;
node.endpoint.address node.endpoint.address
} }
else { else {
@ -738,6 +738,7 @@ impl Host {
} }
} }
}; };
if let Err(e) = self.create_connection(socket, Some(id), io) { if let Err(e) = self.create_connection(socket, Some(id), io) {
debug!(target: "network", "Can't create connection: {:?}", e); debug!(target: "network", "Can't create connection: {:?}", e);
} }
@ -1281,4 +1282,3 @@ fn host_client_url() {
let host: Host = Host::new(config, Arc::new(NetworkStats::new()), None).unwrap(); let host: Host = Host::new(config, Arc::new(NetworkStats::new()), None).unwrap();
assert!(host.local_url().starts_with("enode://101b3ef5a4ea7a1c7928e24c4c75fd053c235d7b80c22ae5c03d145d0ac7396e2a4ffff9adee3133a7b05044a5cee08115fd65145e5165d646bde371010d803c@")); assert!(host.local_url().starts_with("enode://101b3ef5a4ea7a1c7928e24c4c75fd053c235d7b80c22ae5c03d145d0ac7396e2a4ffff9adee3133a7b05044a5cee08115fd65145e5165d646bde371010d803c@"));
} }

View File

@ -80,14 +80,16 @@ extern crate path;
extern crate ethcore_logger; extern crate ethcore_logger;
extern crate ipnetwork; extern crate ipnetwork;
extern crate keccak_hash as hash; extern crate keccak_hash as hash;
extern crate serde;
extern crate serde_json; extern crate serde_json;
extern crate snappy; extern crate snappy;
#[macro_use] #[macro_use]
extern crate error_chain; extern crate error_chain;
#[macro_use] #[macro_use]
extern crate log; extern crate log;
#[macro_use]
extern crate serde_derive;
#[cfg(test)] #[cfg(test)]
extern crate ethcore_devtools as devtools; extern crate ethcore_devtools as devtools;
@ -213,4 +215,3 @@ pub enum AllowIP {
/// Block all addresses /// Block all addresses
None, None,
} }

View File

@ -14,25 +14,20 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::mem;
use std::slice::from_raw_parts;
use std::net::{SocketAddr, ToSocketAddrs, SocketAddrV4, SocketAddrV6, Ipv4Addr, Ipv6Addr};
use std::hash::{Hash, Hasher};
use std::str::{FromStr};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fmt::{Display, Formatter}; use std::fmt::{self, Display, Formatter};
use std::path::{PathBuf}; use std::hash::{Hash, Hasher};
use std::fmt; use std::net::{SocketAddr, ToSocketAddrs, SocketAddrV4, SocketAddrV6, Ipv4Addr, Ipv6Addr};
use std::fs; use std::path::PathBuf;
use std::io::{Read, Write}; use std::str::FromStr;
use bigint::hash::*; use std::{fs, mem, slice};
use bigint::hash::H512;
use rlp::*; use rlp::*;
use time::Tm;
use error::{Error, ErrorKind}; use error::{Error, ErrorKind};
use {AllowIP, IpFilter}; use {AllowIP, IpFilter};
use discovery::{TableUpdates, NodeEntry}; use discovery::{TableUpdates, NodeEntry};
use ip_utils::*; use ip_utils::*;
use serde_json::Value; use serde_json;
/// Node public key /// Node public key
pub type NodeId = H512; pub type NodeId = H512;
@ -80,7 +75,7 @@ impl NodeEndpoint {
4 => Ok(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(addr_bytes[0], addr_bytes[1], addr_bytes[2], addr_bytes[3]), tcp_port))), 4 => Ok(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(addr_bytes[0], addr_bytes[1], addr_bytes[2], addr_bytes[3]), tcp_port))),
16 => unsafe { 16 => unsafe {
let o: *const u16 = mem::transmute(addr_bytes.as_ptr()); let o: *const u16 = mem::transmute(addr_bytes.as_ptr());
let o = from_raw_parts(o, 8); let o = slice::from_raw_parts(o, 8);
Ok(SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::new(o[0], o[1], o[2], o[3], o[4], o[5], o[6], o[7]), tcp_port, 0, 0))) Ok(SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::new(o[0], o[1], o[2], o[3], o[4], o[5], o[6], o[7]), tcp_port, 0, 0)))
}, },
_ => Err(DecoderError::RlpInconsistentLengthAndData) _ => Err(DecoderError::RlpInconsistentLengthAndData)
@ -95,7 +90,7 @@ impl NodeEndpoint {
} }
SocketAddr::V6(a) => unsafe { SocketAddr::V6(a) => unsafe {
let o: *const u8 = mem::transmute(a.ip().segments().as_ptr()); let o: *const u8 = mem::transmute(a.ip().segments().as_ptr());
rlp.append(&from_raw_parts(o, 16)); rlp.append(&slice::from_raw_parts(o, 16));
} }
}; };
rlp.append(&self.udp_port); rlp.append(&self.udp_port);
@ -143,18 +138,30 @@ pub struct Node {
pub id: NodeId, pub id: NodeId,
pub endpoint: NodeEndpoint, pub endpoint: NodeEndpoint,
pub peer_type: PeerType, pub peer_type: PeerType,
pub attempts: u32,
pub failures: u32, pub failures: u32,
pub last_attempted: Option<Tm>,
} }
const DEFAULT_FAILURE_PERCENTAGE: usize = 50;
impl Node { impl Node {
pub fn new(id: NodeId, endpoint: NodeEndpoint) -> Node { pub fn new(id: NodeId, endpoint: NodeEndpoint) -> Node {
Node { Node {
id: id, id: id,
endpoint: endpoint, endpoint: endpoint,
peer_type: PeerType::Optional, peer_type: PeerType::Optional,
attempts: 0,
failures: 0, failures: 0,
last_attempted: None, }
}
/// Returns the node's failure percentage (0..100) in buckets of 5%. If there are 0 connection attempts for this
/// node the default failure percentage is returned (50%).
pub fn failure_percentage(&self) -> usize {
if self.attempts == 0 {
DEFAULT_FAILURE_PERCENTAGE
} else {
(self.failures * 100 / self.attempts / 5 * 5) as usize
} }
} }
} }
@ -184,7 +191,7 @@ impl FromStr for Node {
id: id, id: id,
endpoint: endpoint, endpoint: endpoint,
peer_type: PeerType::Optional, peer_type: PeerType::Optional,
last_attempted: None, attempts: 0,
failures: 0, failures: 0,
}) })
} }
@ -203,6 +210,9 @@ impl Hash for Node {
} }
} }
const MAX_NODES: usize = 1024;
const NODES_FILE: &str = "nodes.json";
/// Node table backed by disk file. /// Node table backed by disk file.
pub struct NodeTable { pub struct NodeTable {
nodes: HashMap<NodeId, Node>, nodes: HashMap<NodeId, Node>,
@ -221,23 +231,37 @@ impl NodeTable {
/// Add a node to table /// Add a node to table
pub fn add_node(&mut self, mut node: Node) { pub fn add_node(&mut self, mut node: Node) {
// preserve failure counter // preserve attempts and failure counter
let failures = self.nodes.get(&node.id).map_or(0, |n| n.failures); let (attempts, failures) =
self.nodes.get(&node.id).map_or((0, 0), |n| (n.attempts, n.failures));
node.attempts = attempts;
node.failures = failures; node.failures = failures;
self.nodes.insert(node.id.clone(), node); self.nodes.insert(node.id.clone(), node);
} }
/// Returns node ids sorted by number of failures /// Returns node ids sorted by failure percentage, for nodes with the same failure percentage the absolute number of
/// failures is considered.
pub fn nodes(&self, filter: IpFilter) -> Vec<NodeId> { pub fn nodes(&self, filter: IpFilter) -> Vec<NodeId> {
let mut refs: Vec<&Node> = self.nodes.values().filter(|n| !self.useless_nodes.contains(&n.id) && n.endpoint.is_allowed(&filter)).collect(); let mut refs: Vec<&Node> = self.nodes.values()
refs.sort_by(|a, b| a.failures.cmp(&b.failures)); .filter(|n| !self.useless_nodes.contains(&n.id))
refs.iter().map(|n| n.id.clone()).collect() .filter(|n| n.endpoint.is_allowed(&filter))
.collect();
refs.sort_by(|a, b| {
a.failure_percentage().cmp(&b.failure_percentage())
.then_with(|| a.failures.cmp(&b.failures))
.then_with(|| b.attempts.cmp(&a.attempts)) // we use reverse ordering for number of attempts
});
refs.into_iter().map(|n| n.id).collect()
} }
/// Unordered list of all entries /// Unordered list of all entries
pub fn unordered_entries(&self) -> Vec<NodeEntry> { pub fn unordered_entries(&self) -> Vec<NodeEntry> {
// preserve failure counter self.nodes.values().map(|n| NodeEntry {
self.nodes.values().map(|n| NodeEntry { endpoint: n.endpoint.clone(), id: n.id.clone() }).collect() endpoint: n.endpoint.clone(),
id: n.id.clone(),
}).collect()
} }
/// Get particular node /// Get particular node
@ -270,7 +294,7 @@ impl NodeTable {
} }
} }
/// Mark as useless, no furter attempts to connect until next call to `clear_useless`. /// Mark as useless, no further attempts to connect until next call to `clear_useless`.
pub fn mark_as_useless(&mut self, id: &NodeId) { pub fn mark_as_useless(&mut self, id: &NodeId) {
self.useless_nodes.insert(id.clone()); self.useless_nodes.insert(id.clone());
} }
@ -282,77 +306,62 @@ impl NodeTable {
/// Save the nodes.json file. /// Save the nodes.json file.
pub fn save(&self) { pub fn save(&self) {
if let Some(ref path) = self.path { let mut path = match self.path {
let mut path_buf = PathBuf::from(path); Some(ref path) => PathBuf::from(path),
if let Err(e) = fs::create_dir_all(path_buf.as_path()) { None => return,
warn!("Error creating node table directory: {:?}", e); };
return; if let Err(e) = fs::create_dir_all(&path) {
}; warn!("Error creating node table directory: {:?}", e);
path_buf.push("nodes.json"); return;
let mut json = String::new(); }
json.push_str("{\n"); path.push(NODES_FILE);
json.push_str("\"nodes\": [\n"); let node_ids = self.nodes(IpFilter::default());
let node_ids = self.nodes(IpFilter::default()); let nodes = node_ids.into_iter()
for i in 0 .. node_ids.len() { .map(|id| self.nodes.get(&id).expect("self.nodes() only returns node IDs from self.nodes"))
let node = self.nodes.get(&node_ids[i]).expect("self.nodes() only returns node IDs from self.nodes"); .take(MAX_NODES)
json.push_str(&format!("\t{{ \"url\": \"{}\", \"failures\": {} }}{}\n", node, node.failures, if i == node_ids.len() - 1 {""} else {","})) .map(|node| node.clone())
} .map(Into::into)
json.push_str("]\n"); .collect();
json.push_str("}"); let table = json::NodeTable { nodes };
let mut file = match fs::File::create(path_buf.as_path()) {
Ok(file) => file, match fs::File::create(&path) {
Err(e) => { Ok(file) => {
warn!("Error creating node table file: {:?}", e); if let Err(e) = serde_json::to_writer_pretty(file, &table) {
return; warn!("Error writing node table file: {:?}", e);
} }
}; },
if let Err(e) = file.write(&json.into_bytes()) { Err(e) => {
warn!("Error writing node table file: {:?}", e); warn!("Error creating node table file: {:?}", e);
} }
} }
} }
fn load(path: Option<String>) -> HashMap<NodeId, Node> { fn load(path: Option<String>) -> HashMap<NodeId, Node> {
let mut nodes: HashMap<NodeId, Node> = HashMap::new(); let path = match path {
if let Some(path) = path { Some(path) => PathBuf::from(path).join(NODES_FILE),
let mut path_buf = PathBuf::from(path); None => return Default::default(),
path_buf.push("nodes.json"); };
let mut file = match fs::File::open(path_buf.as_path()) {
Ok(file) => file, let file = match fs::File::open(&path) {
Err(e) => { Ok(file) => file,
debug!("Error opening node table file: {:?}", e); Err(e) => {
return nodes; debug!("Error opening node table file: {:?}", e);
} return Default::default();
}; },
let mut buf = String::new(); };
match file.read_to_string(&mut buf) { let res: Result<json::NodeTable, _> = serde_json::from_reader(file);
Ok(_) => {}, match res {
Err(e) => { Ok(table) => {
warn!("Error reading node table file: {:?}", e); table.nodes.into_iter()
return nodes; .filter_map(|n| n.into_node())
} .map(|n| (n.id.clone(), n))
} .collect()
let json: Value = match ::serde_json::from_str(&buf) { },
Ok(json) => json, Err(e) => {
Err(e) => { warn!("Error reading node table file: {:?}", e);
warn!("Error parsing node table file: {:?}", e); Default::default()
return nodes; },
}
};
if let Some(list) = json.as_object().and_then(|o| o.get("nodes")).and_then(|n| n.as_array()) {
for n in list.iter().filter_map(|n| n.as_object()) {
if let Some(url) = n.get("url").and_then(|u| u.as_str()) {
if let Ok(mut node) = Node::from_str(url) {
if let Some(failures) = n.get("failures").and_then(|f| f.as_u64()) {
node.failures = failures as u32;
}
nodes.insert(node.id.clone(), node);
}
}
}
}
} }
nodes
} }
} }
@ -364,13 +373,51 @@ impl Drop for NodeTable {
/// Check if node url is valid /// Check if node url is valid
pub fn validate_node_url(url: &str) -> Option<Error> { pub fn validate_node_url(url: &str) -> Option<Error> {
use std::str::FromStr;
match Node::from_str(url) { match Node::from_str(url) {
Ok(_) => None, Ok(_) => None,
Err(e) => Some(e) Err(e) => Some(e)
} }
} }
mod json {
use super::*;
#[derive(Serialize, Deserialize)]
pub struct NodeTable {
pub nodes: Vec<Node>,
}
#[derive(Serialize, Deserialize)]
pub struct Node {
pub url: String,
pub attempts: u32,
pub failures: u32,
}
impl Node {
pub fn into_node(self) -> Option<super::Node> {
match super::Node::from_str(&self.url) {
Ok(mut node) => {
node.attempts = self.attempts;
node.failures = self.failures;
Some(node)
},
_ => None,
}
}
}
impl<'a> From<&'a super::Node> for Node {
fn from(node: &'a super::Node) -> Self {
Node {
url: format!("{}", node),
attempts: node.attempts,
failures: node.failures,
}
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -408,26 +455,42 @@ mod tests {
} }
#[test] #[test]
fn table_failure_order() { fn table_failure_percentage_order() {
let node1 = Node::from_str("enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap(); let node1 = Node::from_str("enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap();
let node2 = Node::from_str("enode://b979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap(); let node2 = Node::from_str("enode://b979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap();
let node3 = Node::from_str("enode://c979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap(); let node3 = Node::from_str("enode://c979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap();
let node4 = Node::from_str("enode://d979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap();
let id1 = H512::from_str("a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap(); let id1 = H512::from_str("a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap();
let id2 = H512::from_str("b979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap(); let id2 = H512::from_str("b979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap();
let id3 = H512::from_str("c979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap(); let id3 = H512::from_str("c979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap();
let id4 = H512::from_str("d979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap();
let mut table = NodeTable::new(None); let mut table = NodeTable::new(None);
table.add_node(node3);
table.add_node(node1); table.add_node(node1);
table.add_node(node2); table.add_node(node2);
table.add_node(node3);
table.add_node(node4);
// node 1 - failure percentage 100%
table.get_mut(&id1).unwrap().attempts = 2;
table.note_failure(&id1); table.note_failure(&id1);
table.note_failure(&id1); table.note_failure(&id1);
// node2 - failure percentage 33%
table.get_mut(&id2).unwrap().attempts = 3;
table.note_failure(&id2); table.note_failure(&id2);
// node3 - failure percentage 0%
table.get_mut(&id3).unwrap().attempts = 1;
// node4 - failure percentage 50% (default when no attempts)
let r = table.nodes(IpFilter::default()); let r = table.nodes(IpFilter::default());
assert_eq!(r[0][..], id3[..]); assert_eq!(r[0][..], id3[..]);
assert_eq!(r[1][..], id2[..]); assert_eq!(r[1][..], id2[..]);
assert_eq!(r[2][..], id1[..]); assert_eq!(r[2][..], id4[..]);
assert_eq!(r[3][..], id1[..]);
} }
#[test] #[test]
@ -441,6 +504,9 @@ mod tests {
let mut table = NodeTable::new(Some(tempdir.path().to_str().unwrap().to_owned())); let mut table = NodeTable::new(Some(tempdir.path().to_str().unwrap().to_owned()));
table.add_node(node1); table.add_node(node1);
table.add_node(node2); table.add_node(node2);
table.get_mut(&id1).unwrap().attempts = 1;
table.get_mut(&id2).unwrap().attempts = 1;
table.note_failure(&id2); table.note_failure(&id2);
} }