diff --git a/Cargo.lock b/Cargo.lock index 33dec36dc816c..41754216434f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,7 +15,7 @@ dependencies = [ "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-timer 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -396,7 +396,7 @@ dependencies = [ [[package]] name = "datastore" version = "0.1.0" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "base64 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "chashmap 2.2.1 (git+https://github.com/redox-os/tfs)", @@ -529,11 +529,6 @@ dependencies = [ "untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "edit-distance" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "either" version = "1.5.0" @@ -660,28 +655,6 @@ dependencies = [ "serde 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "ethkey" -version = "0.3.0" -source = "git+https://github.com/paritytech/parity.git?rev=202c54d42398fc4b49d67ffbf9070522e38f9360#202c54d42398fc4b49d67ffbf9070522e38f9360" -dependencies = [ - "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "edit-distance 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "eth-secp256k1 0.5.7 (git+https://github.com/paritytech/rust-secp256k1)", - "ethcore-crypto 0.1.0 (git+https://github.com/paritytech/parity.git?rev=202c54d42398fc4b49d67ffbf9070522e38f9360)", - "ethereum-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "mem 0.1.0 (git+https://github.com/paritytech/parity.git?rev=202c54d42398fc4b49d67ffbf9070522e38f9360)", - "parity-wordlist 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.4.2 (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.70 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", - "tiny-keccak 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "etrace" version = "0.2.8" @@ -912,11 +885,6 @@ dependencies = [ "unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "integer-encoding" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "integer-sqrt" version = "0.1.0" @@ -936,14 +904,6 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "itertools" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "itoa" version = "0.4.1" @@ -1112,27 +1072,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libp2p" version = "0.1.0" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "libp2p-dns 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "libp2p-floodsub 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "libp2p-identify 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "libp2p-kad 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "libp2p-mplex 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "libp2p-peerstore 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "libp2p-ping 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "libp2p-ratelimit 0.1.1 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "libp2p-relay 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "libp2p-secio 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "libp2p-tcp-transport 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "libp2p-transport-timeout 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "libp2p-uds 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "libp2p-websocket 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "libp2p-yamux 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "libp2p-dns 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "libp2p-floodsub 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "libp2p-identify 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "libp2p-kad 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "libp2p-mplex 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "libp2p-peerstore 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "libp2p-ping 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "libp2p-ratelimit 0.1.1 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "libp2p-relay 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "libp2p-secio 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "libp2p-tcp-transport 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "libp2p-transport-timeout 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "libp2p-uds 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "libp2p-websocket 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "libp2p-yamux 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "stdweb 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-codec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-current-thread 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1142,33 +1102,35 @@ dependencies = [ [[package]] name = "libp2p-core" version = "0.1.0" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "bs58 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "multihash 0.8.1-pre (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "multistream-select 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "multihash 0.8.1-pre (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "multistream-select 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "parking_lot 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "protobuf 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rw-stream-sink 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "rw-stream-sink 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "smallvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "libp2p-dns" version = "0.1.0" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "tokio-dns-unofficial 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1176,19 +1138,19 @@ dependencies = [ [[package]] name = "libp2p-floodsub" version = "0.1.0" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "bs58 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "parking_lot 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "protobuf 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-codec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "unsigned-varint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1197,15 +1159,15 @@ dependencies = [ [[package]] name = "libp2p-identify" version = "0.1.0" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "libp2p-peerstore 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "libp2p-peerstore 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "parking_lot 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "protobuf 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-codec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1216,20 +1178,20 @@ dependencies = [ [[package]] name = "libp2p-kad" version = "0.1.0" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "bigint 4.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "bs58 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "datastore 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "datastore 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "libp2p-identify 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "libp2p-ping 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "libp2p-identify 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "libp2p-ping 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "parking_lot 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "protobuf 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1243,12 +1205,12 @@ dependencies = [ [[package]] name = "libp2p-mplex" version = "0.1.0" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-codec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1259,13 +1221,13 @@ dependencies = [ [[package]] name = "libp2p-peerstore" version = "0.1.0" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "bs58 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "datastore 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "datastore 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1274,14 +1236,14 @@ dependencies = [ [[package]] name = "libp2p-ping" version = "0.1.0" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "multistream-select 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "multistream-select 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "parking_lot 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-codec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1291,27 +1253,27 @@ dependencies = [ [[package]] name = "libp2p-ratelimit" version = "0.1.1" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "aio-limited 0.1.0 (git+https://github.com/paritytech/aio-limited.git)", "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "libp2p-relay" version = "0.1.0" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "libp2p-peerstore 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "libp2p-peerstore 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "protobuf 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-codec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1322,19 +1284,19 @@ dependencies = [ [[package]] name = "libp2p-secio" version = "0.1.0" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "asn1_der 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "eth-secp256k1 0.5.7 (git+https://github.com/paritytech/rust-secp256k1)", "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "protobuf 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", "ring 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", - "rw-stream-sink 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "rw-stream-sink 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1342,12 +1304,12 @@ dependencies = [ [[package]] name = "libp2p-tcp-transport" version = "0.1.0" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "tk-listen 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-tcp 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1356,10 +1318,10 @@ dependencies = [ [[package]] name = "libp2p-transport-timeout" version = "0.1.0" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-timer 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1367,25 +1329,25 @@ dependencies = [ [[package]] name = "libp2p-uds" version = "0.1.0" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "tokio-uds 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "libp2p-websocket" version = "0.1.0" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", - "rw-stream-sink 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", + "rw-stream-sink 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "stdweb 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "websocket 0.20.3 (git+https://github.com/tomaka/rust-websocket?branch=send)", @@ -1394,11 +1356,11 @@ dependencies = [ [[package]] name = "libp2p-yamux" version = "0.1.0" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1450,11 +1412,6 @@ name = "matches" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "mem" -version = "0.1.0" -source = "git+https://github.com/paritytech/parity.git?rev=202c54d42398fc4b49d67ffbf9070522e38f9360#202c54d42398fc4b49d67ffbf9070522e38f9360" - [[package]] name = "memchr" version = "2.0.1" @@ -1545,19 +1502,19 @@ dependencies = [ [[package]] name = "multiaddr" version = "0.3.0" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "bs58 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "integer-encoding 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "multihash 0.8.1-pre (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "multihash 0.8.1-pre (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "serde 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", + "unsigned-varint 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "multihash" version = "0.8.1-pre" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "sha1 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1567,7 +1524,7 @@ dependencies = [ [[package]] name = "multistream-select" version = "0.1.0" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1683,7 +1640,7 @@ dependencies = [ [[package]] name = "parity-bytes" version = "0.1.0" -source = "git+https://github.com/paritytech/parity-common.git#613d1420f91cc5660c1d9983447ce32b41fcfe28" +source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "parity-wasm" @@ -1693,16 +1650,6 @@ dependencies = [ "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "parity-wordlist" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "itertools 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "parking_lot" version = "0.4.8" @@ -1737,7 +1684,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2167,7 +2114,7 @@ dependencies = [ "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.43 (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.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2352,7 +2299,7 @@ dependencies = [ [[package]] name = "rw-stream-sink" version = "0.1.0" -source = "git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346#304e9c72c88bc97824f2734dc19d1b5f4556d346" +source = "git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4#2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4" dependencies = [ "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2550,7 +2497,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "smallvec" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2632,6 +2579,7 @@ dependencies = [ "substrate-extrinsic-pool 0.1.0", "substrate-network 0.1.0", "substrate-network-libp2p 0.1.0", + "substrate-primitives 0.1.0", "substrate-runtime-primitives 0.1.0", "substrate-service 0.2.2", "substrate-telemetry 0.2.2", @@ -2706,7 +2654,7 @@ dependencies = [ "hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.6.2 (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.70 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2802,22 +2750,21 @@ dependencies = [ "assert_matches 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-io 1.12.0 (git+https://github.com/paritytech/parity.git?rev=202c54d42398fc4b49d67ffbf9070522e38f9360)", - "ethcore-logger 1.12.0 (git+https://github.com/paritytech/parity.git?rev=202c54d42398fc4b49d67ffbf9070522e38f9360)", "ethereum-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "ethkey 0.3.0 (git+https://github.com/paritytech/parity.git?rev=202c54d42398fc4b49d67ffbf9070522e38f9360)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "libp2p 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)", + "libp2p 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-bytes 0.1.0 (git+https://github.com/paritytech/parity-common.git)", + "parity-bytes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-timer 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "unsigned-varint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3375,7 +3322,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-fs 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-reactor 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3407,7 +3354,7 @@ dependencies = [ "mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)", "scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-reactor 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-timer 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3419,7 +3366,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -3435,7 +3382,7 @@ dependencies = [ [[package]] name = "tokio-executor" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3487,7 +3434,7 @@ dependencies = [ "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -3522,7 +3469,7 @@ dependencies = [ "log 0.4.5 (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)", - "tokio-executor 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -3533,7 +3480,7 @@ dependencies = [ "crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -3718,6 +3665,14 @@ dependencies = [ "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "unsigned-varint" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "unsigned-varint" version = "0.2.1" @@ -3988,11 +3943,10 @@ dependencies = [ "checksum crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "677d453a17e8bd2b913fa38e8b9cf04bcdbb5be790aa294f2389661d72036015" "checksum crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda" "checksum ctrlc 1.1.1 (git+https://github.com/paritytech/rust-ctrlc.git)" = "" -"checksum datastore 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" +"checksum datastore 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" "checksum difference 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3304d19798a8e067e48d8e69b2c37f0b5e9b4e462504ad9e27e9f3fce02bba8" "checksum digest 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3cae2388d706b52f2f2f9afe280f9d768be36544bd71d1b8120cb34ea6450b55" "checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab" -"checksum edit-distance 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3bd26878c3d921f89797a4e1a1711919f999a9f6946bb6f5a4ffda126d297b7e" "checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0" "checksum elastic-array 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "88d4851b005ef16de812ea9acdb7bece2f0a40dd86c07b85631d7dafa54537bb" "checksum env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3ddf21e73e016298f5cb37d6ef8e8da8e39f91f9ec8b0df44b7deb16a9f8cd5b" @@ -4005,7 +3959,6 @@ dependencies = [ "checksum ethcore-logger 1.12.0 (git+https://github.com/paritytech/parity.git?rev=202c54d42398fc4b49d67ffbf9070522e38f9360)" = "" "checksum ethereum-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c48729b8aea8aedb12cf4cb2e5cef439fdfe2dda4a89e47eeebd15778ef53b6" "checksum ethereum-types-serialize 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ac59a21a9ce98e188f3dace9eb67a6c4a3c67ec7fbc7218cb827852679dc002" -"checksum ethkey 0.3.0 (git+https://github.com/paritytech/parity.git?rev=202c54d42398fc4b49d67ffbf9070522e38f9360)" = "" "checksum etrace 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "5a3eb49b4ae7e88cc23caa812e8072c9f83a3e202e0b789ff4f9319cf796d8ca" "checksum exit-future 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9aa7b56cef68c4182db7212dece19cc9f6e2916cf9412e57e6cea53ec02f316d" "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" @@ -4033,11 +3986,9 @@ dependencies = [ "checksum hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)" = "368cb56b2740ebf4230520e2b90ebb0461e69034d85d1945febd9b3971426db2" "checksum hyper 0.11.27 (registry+https://github.com/rust-lang/crates.io-index)" = "34a590ca09d341e94cddf8e5af0bbccde205d5fbc2fa3c09dd67c7f85cea59d7" "checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d" -"checksum integer-encoding 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "26746cbc2e680af687e88d717f20ff90079bd10fc984ad57d277cd0e37309fa5" "checksum integer-sqrt 0.1.0 (git+https://github.com/paritytech/integer-sqrt-rs.git)" = "" "checksum interleaved-ordered 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "141340095b15ed7491bd3d4ced9d20cebfb826174b6bb03386381f62b01e3d77" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" -"checksum itertools 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4833d6978da405305126af4ac88569b5d71ff758581ce5a987dbfa3755f694fc" "checksum itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c069bbec61e1ca5a596166e55dfe4773ff745c3d16b700013bcaff9a6df2c682" "checksum jsonrpc-core 8.0.2 (git+https://github.com/paritytech/jsonrpc.git)" = "" "checksum jsonrpc-http-server 8.0.1 (git+https://github.com/paritytech/jsonrpc.git)" = "" @@ -4056,30 +4007,29 @@ dependencies = [ "checksum lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e6412c5e2ad9584b0b8e979393122026cdd6d2a80b933f890dcd694ddbe73739" "checksum lazycell 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a6f08839bc70ef4a3fe1d566d5350f519c5912ea86be0df1740a7d247c7fc0ef" "checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" -"checksum libp2p 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" -"checksum libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" -"checksum libp2p-dns 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" -"checksum libp2p-floodsub 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" -"checksum libp2p-identify 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" -"checksum libp2p-kad 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" -"checksum libp2p-mplex 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" -"checksum libp2p-peerstore 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" -"checksum libp2p-ping 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" -"checksum libp2p-ratelimit 0.1.1 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" -"checksum libp2p-relay 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" -"checksum libp2p-secio 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" -"checksum libp2p-tcp-transport 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" -"checksum libp2p-transport-timeout 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" -"checksum libp2p-uds 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" -"checksum libp2p-websocket 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" -"checksum libp2p-yamux 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" +"checksum libp2p 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" +"checksum libp2p-core 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" +"checksum libp2p-dns 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" +"checksum libp2p-floodsub 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" +"checksum libp2p-identify 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" +"checksum libp2p-kad 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" +"checksum libp2p-mplex 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" +"checksum libp2p-peerstore 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" +"checksum libp2p-ping 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" +"checksum libp2p-ratelimit 0.1.1 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" +"checksum libp2p-relay 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" +"checksum libp2p-secio 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" +"checksum libp2p-tcp-transport 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" +"checksum libp2p-transport-timeout 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" +"checksum libp2p-uds 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" +"checksum libp2p-websocket 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" +"checksum libp2p-yamux 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" "checksum linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70fb39025bc7cdd76305867c4eccf2f2dcf6e9a57f5b21a93e1c2d86cd03ec9e" "checksum local-encoding 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e1ceb20f39ff7ae42f3ff9795f3986b1daad821caaa1e1732a0944103a5a1a66" "checksum lock_api 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "949826a5ccf18c1b3a7c3d57692778d21768b79e46eb9dd07bfc4c2160036c54" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f" "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" -"checksum mem 0.1.0 (git+https://github.com/paritytech/parity.git?rev=202c54d42398fc4b49d67ffbf9070522e38f9360)" = "" "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" "checksum memory_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882" @@ -4089,9 +4039,9 @@ dependencies = [ "checksum mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)" = "6d771e3ef92d58a8da8df7d6976bfca9371ed1de6619d9d5a5ce5b1f29b85bfe" "checksum mio-uds 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "84c7b5caa3a118a6e34dbac36504503b1e8dc5835e833306b9d6af0e05929f79" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" -"checksum multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" -"checksum multihash 0.8.1-pre (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" -"checksum multistream-select 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" +"checksum multiaddr 0.3.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" +"checksum multihash 0.8.1-pre (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" +"checksum multistream-select 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" "checksum names 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef320dab323286b50fb5cdda23f61c796a72a89998ab565ca32525c5c556f2da" "checksum nan-preserving-float 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34d4f00fcc2f4c9efa8cc971db0da9e28290e28e97af47585e48691ef10ff31f" "checksum native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f74dbadc8b43df7864539cedb7bc91345e532fdd913cfdc23ad94f4d2d40fbc0" @@ -4104,9 +4054,8 @@ dependencies = [ "checksum openssl 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)" = "a3605c298474a3aa69de92d21139fb5e2a81688d308262359d85cdd0d12a7985" "checksum openssl-sys 0.9.33 (registry+https://github.com/rust-lang/crates.io-index)" = "d8abc04833dcedef24221a91852931df2f63e3369ae003134e70aff3645775cc" "checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" -"checksum parity-bytes 0.1.0 (git+https://github.com/paritytech/parity-common.git)" = "" +"checksum parity-bytes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa5168b4cf41f3835e4bc6ffb32f51bc9365dc50cb351904595b3931d917fd0c" "checksum parity-wasm 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e1c91199d14bd5b78ecade323d4a891d094799749c1b9e82d9c590c2e2849a40" -"checksum parity-wordlist 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d0dec124478845b142f68b446cbee953d14d4b41f1bc0425024417720dce693" "checksum parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e" "checksum parking_lot 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4d05f1349491390b1730afba60bb20d55761bef489a954546b58b4b34e1e2ac" "checksum parking_lot 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "901d6514273469bb17380c1ac3f51fb3ce54be1f960e51a6f04901eba313ab8d" @@ -4153,7 +4102,7 @@ dependencies = [ "checksum rustc-hex 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d2b03280c2813907a030785570c577fb27d3deec8da4c18566751ade94de0ace" "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" "checksum rustc_version 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a54aa04a10c68c1c4eacb4337fd883b435997ede17a9385784b990777686b09a" -"checksum rw-stream-sink 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=304e9c72c88bc97824f2734dc19d1b5f4556d346)" = "" +"checksum rw-stream-sink 0.1.0 (git+https://github.com/libp2p/rust-libp2p?rev=2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4)" = "" "checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" "checksum schannel 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "85fd9df495640643ad2d00443b3d78aae69802ad488debab4f1dd52fc1806ade" "checksum scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28" @@ -4179,7 +4128,7 @@ dependencies = [ "checksum smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c8cbcd6df1e117c2210e13ab5109635ad68a929fcbb8964dc965b76cb5ee013" "checksum smallvec 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f90c5e5fe535e48807ab94fc611d323935f39d4660c52b26b96446a7b33aef10" "checksum smallvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1347484b6f8bc4b32a9323d9800b6d934376391002ad9c528cc659fe8afc08ee" -"checksum smallvec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "26df3bb03ca5eac2e64192b723d51f56c1b1e0860e7c766281f4598f181acdc8" +"checksum smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "153ffa32fd170e9944f7e0838edf824a754ec4c1fc64746fcc9fe1f8fa602e5d" "checksum snappy-sys 0.1.0 (git+https://github.com/paritytech/rust-snappy)" = "" "checksum stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "15132e0e364248108c5e2c02e3ab539be8d6f5d52a01ca9bbf27ed657316f02b" "checksum stdweb 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e" @@ -4203,7 +4152,7 @@ dependencies = [ "checksum tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "aeeffbbb94209023feaef3c196a41cbcdafa06b4a6f893f68779bb5e53796f71" "checksum tokio-current-thread 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9f785265962bde425bf3b77dd6abac6674b8c6d5e8831427383aa9c56c5210e1" "checksum tokio-dns-unofficial 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bb9bf62ca2c53bf2f2faec3e48a98b6d8c9577c27011cb0203a4beacdc8ab328" -"checksum tokio-executor 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8cac2a7883ff3567e9d66bb09100d09b33d90311feca0206c7ca034bc0c55113" +"checksum tokio-executor 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "84823b932d566bc3c6aa644df4ca36cb38593c50b7db06011fd4e12e31e4047e" "checksum tokio-fs 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "76766830bbf9a2d5bfb50c95350d56a2e79e2c80f675967fff448bc615899708" "checksum tokio-io 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8d6cc2de7725863c86ac71b0b9068476fec50834f055a243558ef1655bbd34cb" "checksum tokio-proto 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fbb47ae81353c63c487030659494b295f6cb6576242f907f203473b191b0389" @@ -4234,6 +4183,7 @@ dependencies = [ "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +"checksum unsigned-varint 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5c1441164e5da61f00acd15f5a9e61939693c2c6e8b9fae36a220b82de7e212" "checksum unsigned-varint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5fb8abc4b7d8158bdfbbaaccc35331ed3c30c2673e99000d7ae665a2eb6576f4" "checksum untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f392d7819dbe58833e26872f5f6f0d68b7bbbe90fc3667e98731c4a15ad9a7ae" "checksum url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f808aadd8cfec6ef90e4a14eb46f24511824d1ac596b9682703c87056c8678b7" diff --git a/substrate/cli/Cargo.toml b/substrate/cli/Cargo.toml index 716057903c833..e9beab4c2febf 100644 --- a/substrate/cli/Cargo.toml +++ b/substrate/cli/Cargo.toml @@ -27,6 +27,7 @@ substrate-extrinsic-pool = { path = "../../substrate/extrinsic-pool" } substrate-network = { path = "../../substrate/network" } substrate-network-libp2p = { path = "../../substrate/network-libp2p" } substrate-runtime-primitives = { path = "../../substrate/runtime/primitives" } +substrate-primitives = { path = "../../substrate/primitives" } substrate-service = { path = "../../substrate/service" } substrate-telemetry = { path = "../../substrate/telemetry" } names = "0.11.0" diff --git a/substrate/cli/src/lib.rs b/substrate/cli/src/lib.rs index 5d6a589a84173..bb86d26331b60 100644 --- a/substrate/cli/src/lib.rs +++ b/substrate/cli/src/lib.rs @@ -37,6 +37,7 @@ extern crate substrate_network_libp2p as network_libp2p; extern crate substrate_runtime_primitives as runtime_primitives; extern crate substrate_extrinsic_pool; extern crate substrate_service as service; +extern crate substrate_primitives as primitives; #[macro_use] extern crate slog; // needed until we can reexport `slog_info` from `substrate_telemetry` #[macro_use] @@ -62,12 +63,14 @@ use service::{ FactoryGenesis, PruningMode, ChainSpec, }; use network::NonReservedPeerMode; +use primitives::H256; use std::io::{Write, Read, stdin, stdout}; use std::iter; use std::fs::File; use std::net::{Ipv4Addr, SocketAddr}; use std::path::{Path, PathBuf}; +use std::str::FromStr; use names::{Generator, Name}; use regex::Regex; @@ -301,8 +304,8 @@ where ]; config.network.public_addresses = Vec::new(); config.network.client_version = config.client_id(); - config.network.use_secret = match matches.value_of("node-key").map(|s| s.parse()) { - Some(Ok(secret)) => Some(secret), + config.network.use_secret = match matches.value_of("node-key").map(H256::from_str) { + Some(Ok(secret)) => Some(secret.into()), Some(Err(err)) => return Err(format!("Error parsing node key: {}", err).into()), None => None, }; diff --git a/substrate/network-libp2p/Cargo.toml b/substrate/network-libp2p/Cargo.toml index 7836706cb1c67..a0aa8c78761e0 100644 --- a/substrate/network-libp2p/Cargo.toml +++ b/substrate/network-libp2p/Cargo.toml @@ -11,9 +11,7 @@ bytes = "0.4" error-chain = { version = "0.12", default-features = false } fnv = "1.0" futures = "0.1" -libp2p = { git = "https://github.com/libp2p/rust-libp2p", rev = "304e9c72c88bc97824f2734dc19d1b5f4556d346", default-features = false, features = ["libp2p-secio", "libp2p-secio-secp256k1"] } -ethcore-io = { git = "https://github.com/paritytech/parity.git", rev = "202c54d42398fc4b49d67ffbf9070522e38f9360" } -ethkey = { git = "https://github.com/paritytech/parity.git", rev = "202c54d42398fc4b49d67ffbf9070522e38f9360" } +libp2p = { git = "https://github.com/libp2p/rust-libp2p", rev = "2a7a48b4962cd01095ed634f7d0fb4dd2bddb7e4", default-features = false, features = ["libp2p-secio", "libp2p-secio-secp256k1"] } ethereum-types = "0.3" parking_lot = "0.5" libc = "0.2" @@ -22,13 +20,13 @@ rand = "0.5.0" serde = "1.0.70" serde_derive = "1.0.70" serde_json = "1.0.24" +smallvec = "0.6.5" tokio = "0.1" +tokio-executor = "0.1" tokio-io = "0.1" tokio-timer = "0.2" unsigned-varint = { version = "0.2.1", features = ["codec"] } [dev-dependencies] assert_matches = "1.2" -parity-bytes = { git = "https://github.com/paritytech/parity-common.git" } -ethcore-io = { git = "https://github.com/paritytech/parity.git", rev = "202c54d42398fc4b49d67ffbf9070522e38f9360" } -ethcore-logger = { git = "https://github.com/paritytech/parity.git", rev = "202c54d42398fc4b49d67ffbf9070522e38f9360" } +parity-bytes = "0.1" diff --git a/substrate/network-libp2p/src/custom_proto.rs b/substrate/network-libp2p/src/custom_proto.rs index 72807f21e8f2e..fdb8e4b587d59 100644 --- a/substrate/network-libp2p/src/custom_proto.rs +++ b/substrate/network-libp2p/src/custom_proto.rs @@ -233,6 +233,12 @@ where C: AsyncRead + AsyncWrite + Send + 'static, // TODO: 'static :-/ pub struct RegisteredProtocols(pub Vec>); impl RegisteredProtocols { + /// Returns the number of protocols. + #[inline] + pub fn len(&self) -> usize { + self.0.len() + } + /// Finds a protocol in the list by its id. pub fn find_protocol(&self, protocol: ProtocolId) -> Option<&RegisteredProtocol> { diff --git a/substrate/network-libp2p/src/error.rs b/substrate/network-libp2p/src/error.rs index d095858b12203..9de1b9d43fee0 100644 --- a/substrate/network-libp2p/src/error.rs +++ b/substrate/network-libp2p/src/error.rs @@ -16,8 +16,6 @@ use std::{io, net, fmt}; use libc::{ENFILE, EMFILE}; -use io::IoError; -use ethkey; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum DisconnectReason @@ -82,10 +80,6 @@ impl fmt::Display for DisconnectReason { } error_chain! { - foreign_links { - SocketIo(IoError) #[doc = "Socket IO error."]; - } - errors { #[doc = "Error concerning the network address parsing subsystem."] AddressParse { @@ -171,17 +165,6 @@ impl From for Error { } } -impl From for Error { - fn from(_err: ethkey::Error) -> Self { - ErrorKind::Auth.into() - } -} - -impl From for Error { - fn from(_err: ethkey::crypto::Error) -> Self { - ErrorKind::Auth.into() - } -} impl From for Error { fn from(_err: net::AddrParseError) -> Self { ErrorKind::AddressParse.into() } diff --git a/substrate/network-libp2p/src/lib.rs b/substrate/network-libp2p/src/lib.rs index 59d056b969689..a5e95e8792f44 100644 --- a/substrate/network-libp2p/src/lib.rs +++ b/substrate/network-libp2p/src/lib.rs @@ -14,6 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . +// tag::description[] +//! TODO: Missing doc +// end::description[] + #![recursion_limit="128"] #![type_length_limit = "268435456"] @@ -21,20 +25,21 @@ extern crate parking_lot; extern crate fnv; extern crate futures; extern crate tokio; +extern crate tokio_executor; extern crate tokio_io; extern crate tokio_timer; -extern crate ethkey; extern crate libc; +#[macro_use] extern crate libp2p; extern crate rand; extern crate serde; #[macro_use] extern crate serde_derive; extern crate serde_json; +extern crate smallvec; extern crate bytes; extern crate unsigned_varint; -extern crate ethcore_io as io; extern crate ethereum_types; #[macro_use] @@ -44,17 +49,25 @@ extern crate log; #[cfg(test)] #[macro_use] extern crate assert_matches; +use libp2p::PeerId; + pub use connection_filter::{ConnectionFilter, ConnectionDirection}; -pub use io::TimerToken; pub use error::{Error, ErrorKind, DisconnectReason}; pub use libp2p::{Multiaddr, multiaddr::AddrComponent}; pub use traits::*; +pub type TimerToken = usize; + +// TODO: remove as it is unused ; however modifying `network` causes a clusterfuck of dependencies +// resolve errors at the moment mod connection_filter; mod custom_proto; mod error; -mod network_state; +mod node_handler; +mod secret; mod service; +mod service_task; +mod swarm; mod timeouts; mod topology; mod traits; @@ -64,8 +77,19 @@ pub use service::NetworkService; /// Check if node url is valid pub fn validate_node_url(url: &str) -> Result<(), Error> { - match url.parse::() { + match url.parse::() { Ok(_) => Ok(()), Err(_) => Err(ErrorKind::InvalidNodeId.into()), } } + +/// Parses a string address and returns the component, if valid. +pub(crate) fn parse_str_addr(addr_str: &str) -> Result<(PeerId, Multiaddr), Error> { + let mut addr: Multiaddr = addr_str.parse().map_err(|_| ErrorKind::AddressParse)?; + let who = match addr.pop() { + Some(AddrComponent::P2P(key)) => + PeerId::from_multihash(key).map_err(|_| ErrorKind::AddressParse)?, + _ => return Err(ErrorKind::AddressParse.into()), + }; + Ok((who, addr)) +} diff --git a/substrate/network-libp2p/src/network_state.rs b/substrate/network-libp2p/src/network_state.rs deleted file mode 100644 index e06735f647fc5..0000000000000 --- a/substrate/network-libp2p/src/network_state.rs +++ /dev/null @@ -1,953 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// This file is part of Substrate. - -// Substrate is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Substrate is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Substrate. If not, see . - -use bytes::Bytes; -use fnv::{FnvHashMap, FnvHashSet}; -use futures::sync::mpsc; -use libp2p::core::{multiaddr::ToMultiaddr, Multiaddr, AddrComponent, Endpoint, UniqueConnec}; -use libp2p::core::{UniqueConnecState, PeerId, PublicKey}; -use libp2p::kad::KadConnecController; -use libp2p::ping::Pinger; -use libp2p::secio; -use {Error, ErrorKind, NetworkConfiguration, NonReservedPeerMode}; -use {NodeIndex, ProtocolId, SessionInfo}; -use parking_lot::{Mutex, RwLock}; -use rand::{self, Rng}; -use topology::{DisconnectReason, NetTopology}; -use std::fs; -use std::io::{Error as IoError, ErrorKind as IoErrorKind, Read, Write}; -use std::path::Path; -use std::sync::atomic; -use std::time::{Duration, Instant}; - -// File where the peers are stored. -const NODES_FILE: &str = "nodes.json"; -// File where the private key is stored. -const SECRET_FILE: &str = "secret"; -// Duration during which a peer is disabled. -const PEER_DISABLE_DURATION: Duration = Duration::from_secs(5 * 60); - -// Common struct shared throughout all the components of the service. -pub struct NetworkState { - /// Contains the information about the network. - topology: RwLock, - - /// Active connections. - connections: RwLock, - - /// Maximum incoming peers. - max_incoming_peers: u32, - /// Maximum outgoing peers. - max_outgoing_peers: u32, - - /// If true, only reserved peers can connect. - reserved_only: atomic::AtomicBool, - /// List of the IDs of the reserved peers. - reserved_peers: RwLock>, - - /// Each node we discover gets assigned a new unique ID. This ID increases linearly. - next_node_index: atomic::AtomicUsize, - - /// List of the IDs of the disabled peers. These peers will see their - /// connections refused. Includes the time when the disabling expires. - disabled_nodes: Mutex>, - - /// Local private key. - local_private_key: secio::SecioKeyPair, - /// Local public key. - local_public_key: PublicKey, -} - -struct Connections { - /// For each libp2p peer ID, the ID of the peer in the API we expose. - /// Also corresponds to the index in `info_by_peer`. - peer_by_nodeid: FnvHashMap, - - /// For each peer ID, information about our connection to this peer. - info_by_peer: FnvHashMap, -} - -struct PeerConnectionInfo { - /// A list of protocols, and the potential corresponding connection. - /// The `UniqueConnec` contains a sender and the protocol version. - /// The sender can be used to transmit data for the remote. Note that the - /// packet_id has to be inside the `Bytes`. - protocols: Vec<(ProtocolId, UniqueConnec<(mpsc::UnboundedSender, u8)>)>, - - /// The Kademlia connection to this node. - kad_connec: UniqueConnec, - - /// The ping connection to this node. - ping_connec: UniqueConnec, - - /// Id of the peer. - id: PeerId, - - /// True if this connection was initiated by us. `None` if we're not connected. - /// Note that it is theoretically possible that we dial the remote at the - /// same time they dial us, in which case the protocols may be dispatched - /// between both connections, and in which case the value here will be racy. - originated: Option, - - /// Latest known ping duration. - ping: Option, - - /// The client version of the remote, or `None` if not known. - client_version: Option, - - /// The multiaddresses of the remote, or `None` if not known. - remote_addresses: Vec, - - /// The local multiaddress used to communicate with the remote, or `None` - /// if not known. - // TODO: never filled ; also shouldn't be an `Option` - local_address: Option, -} - -/// Simplified, POD version of PeerConnectionInfo. -#[derive(Debug, Clone)] -pub struct PeerInfo { - /// Id of the peer. - pub id: PeerId, - - /// True if this connection was initiated by us. - /// Note that it is theoretically possible that we dial the remote at the - /// same time they dial us, in which case the protocols may be dispatched - /// between both connections, and in which case the value here will be racy. - pub originated: bool, - - /// Latest known ping duration. - pub ping: Option, - - /// The client version of the remote, or `None` if not known. - pub client_version: Option, - - /// The multiaddress of the remote. - pub remote_address: Option, - - /// The local multiaddress used to communicate with the remote, or `None` - /// if not known. - pub local_address: Option, -} - -impl<'a> From<&'a PeerConnectionInfo> for PeerInfo { - fn from(i: &'a PeerConnectionInfo) -> PeerInfo { - PeerInfo { - id: i.id.clone(), - originated: i.originated.unwrap_or(true), - ping: i.ping, - client_version: i.client_version.clone(), - remote_address: i.remote_addresses.get(0).map(|a| a.clone()), - local_address: i.local_address.clone(), - } - } -} - -impl NetworkState { - pub fn new(config: &NetworkConfiguration) -> Result { - // Private and public keys configuration. - let local_private_key = obtain_private_key(&config)?; - let local_public_key = local_private_key.to_public_key(); - - // Build the storage for peers, including the bootstrap nodes. - let mut topology = if let Some(ref path) = config.net_config_path { - let path = Path::new(path).join(NODES_FILE); - debug!(target: "sub-libp2p", "Initializing peer store for JSON file {:?}", path); - NetTopology::from_file(path) - } else { - debug!(target: "sub-libp2p", "No peers file configured ; peers won't be saved"); - NetTopology::memory() - }; - - let reserved_peers = { - let mut reserved_peers = FnvHashSet::with_capacity_and_hasher( - config.reserved_nodes.len(), - Default::default() - ); - for peer in config.reserved_nodes.iter() { - let (id, _) = parse_and_add_to_topology(peer, &mut topology)?; - reserved_peers.insert(id); - } - RwLock::new(reserved_peers) - }; - - let expected_max_peers = config.max_peers as usize + config.reserved_nodes.len(); - - Ok(NetworkState { - topology: RwLock::new(topology), - max_outgoing_peers: config.min_peers, - max_incoming_peers: config.max_peers.saturating_sub(config.min_peers), - connections: RwLock::new(Connections { - peer_by_nodeid: FnvHashMap::with_capacity_and_hasher(expected_max_peers, Default::default()), - info_by_peer: FnvHashMap::with_capacity_and_hasher(expected_max_peers, Default::default()), - }), - reserved_only: atomic::AtomicBool::new(config.non_reserved_mode == NonReservedPeerMode::Deny), - reserved_peers, - next_node_index: atomic::AtomicUsize::new(0), - disabled_nodes: Mutex::new(Default::default()), - local_private_key, - local_public_key, - }) - } - - /// Returns the private key of the local node. - pub fn local_private_key(&self) -> &secio::SecioKeyPair { - &self.local_private_key - } - - /// Returns the public key of the local node. - pub fn local_public_key(&self) -> &PublicKey { - &self.local_public_key - } - - /// Returns a list of peers and addresses which we should try connect to. - /// - /// Because of expiration and back-off mechanisms, this list can change - /// by itself over time. The `Instant` that is returned corresponds to - /// the earlier known time when a new entry will be added automatically to - /// the list. - pub fn outgoing_connections_to_attempt(&self) -> (Vec<(PeerId, Multiaddr)>, Instant) { - // TODO: handle better - let connections = self.connections.read(); - - let num_to_attempt = if self.reserved_only.load(atomic::Ordering::Relaxed) { - 0 - } else { - let num_open_custom_connections = num_open_custom_connections(&connections, &self.reserved_peers.read()); - self.max_outgoing_peers.saturating_sub(num_open_custom_connections.unreserved_outgoing) - }; - - let topology = self.topology.read(); - let (list, change) = topology.addrs_to_attempt(); - let list = list - .filter(|&(peer, _)| { - // Filter out peers which we are already connected to. - let cur = match connections.peer_by_nodeid.get(peer) { - Some(e) => e, - None => return true - }; - - let infos = match connections.info_by_peer.get(&cur) { - Some(i) => i, - None => return true - }; - - !infos.protocols.iter().any(|(_, conn)| conn.is_alive()) - }) - .take(num_to_attempt as usize) - .map(|(addr, peer)| (addr.clone(), peer.clone())) - .collect(); - (list, change) - } - - /// Returns true if we are connected to any peer at all. - pub fn has_connected_peer(&self) -> bool { - !self.connections.read().peer_by_nodeid.is_empty() - } - - /// Get a list of all connected peers by id. - pub fn connected_peers(&self) -> Vec { - self.connections.read().peer_by_nodeid.values().cloned().collect() - } - - /// Returns true if the given `NodeIndex` is valid. - /// - /// `NodeIndex`s are never reused, so once this function returns `false` it - /// will never return `true` again for the same `NodeIndex`. - pub fn is_peer_connected(&self, peer: NodeIndex) -> bool { - self.connections.read().info_by_peer.contains_key(&peer) - } - - /// Reports the ping of the peer. Returned later by `session_info()`. - /// No-op if the `who` is not valid/expired. - pub fn report_ping_duration(&self, who: NodeIndex, ping: Duration) { - let mut connections = self.connections.write(); - let info = match connections.info_by_peer.get_mut(&who) { - Some(info) => info, - None => return, - }; - info.ping = Some(ping); - } - - /// If we're connected to a peer with the given protocol, returns - /// information about the connection. Otherwise, returns `None`. - pub fn session_info(&self, peer: NodeIndex, protocol: ProtocolId) -> Option { - let connections = self.connections.read(); - let info = match connections.info_by_peer.get(&peer) { - Some(info) => info, - None => return None, - }; - - let protocol_version = match info.protocols.iter().find(|&(ref p, _)| p == &protocol) { - Some(&(_, ref unique_connec)) => - if let Some(val) = unique_connec.poll() { - val.1 as u32 - } else { - return None - } - None => return None, - }; - - Some(SessionInfo { - id: None, // TODO: ???? what to do??? wrong format! - client_version: info.client_version.clone().take().unwrap_or(String::new()), - protocol_version, - capabilities: Vec::new(), // TODO: list of supported protocols ; hard - peer_capabilities: Vec::new(), // TODO: difference with `peer_capabilities`? - ping: info.ping, - originated: info.originated.unwrap_or(true), - remote_address: info.remote_addresses.get(0).map(|a| a.to_string()).unwrap_or_default(), - local_address: info.local_address.as_ref().map(|a| a.to_string()) - .unwrap_or(String::new()), - }) - } - - /// If we're connected to a peer with the given protocol, returns the - /// protocol version. Otherwise, returns `None`. - pub fn protocol_version(&self, peer: NodeIndex, protocol: ProtocolId) -> Option { - let connections = self.connections.read(); - let peer = match connections.info_by_peer.get(&peer) { - Some(peer) => peer, - None => return None, - }; - - peer.protocols.iter() - .find(|p| p.0 == protocol) - .and_then(|p| p.1.poll()) - .map(|(_, version)| version) - } - - /// Equivalent to `session_info(peer).map(|info| info.client_version)`. - pub fn peer_client_version(&self, peer: NodeIndex, protocol: ProtocolId) -> Option { - // TODO: implement more directly, without going through `session_info` - self.session_info(peer, protocol) - .map(|info| info.client_version) - } - - /// Adds an address discovered by Kademlia. - /// Note that we don't have to be connected to a peer to add an address. - /// If `connectable` is `true`, that means we have a hint from a remote that this node can be - /// connected to. - pub fn add_kad_discovered_addr(&self, node_id: &PeerId, addr: Multiaddr, connectable: bool) { - self.topology.write().add_kademlia_discovered_addr(node_id, addr, connectable) - } - - /// Returns the known multiaddresses of a peer. - /// - /// The boolean associated to each address indicates whether we're connected to it. - pub fn addrs_of_peer(&self, node_id: &PeerId) -> Vec<(Multiaddr, bool)> { - let topology = self.topology.read(); - // Note: I have no idea why, but fusing the two lines below fails the - // borrow check - let out: Vec<_> = topology - .addrs_of_peer(node_id).map(|(a, c)| (a.clone(), c)).collect(); - out - } - - /// Sets information about a peer. - /// - /// No-op if the node index is invalid. - pub fn set_node_info( - &self, - node_index: NodeIndex, - client_version: String - ) { - let mut connections = self.connections.write(); - let infos = match connections.info_by_peer.get_mut(&node_index) { - Some(i) => i, - None => return - }; - - infos.client_version = Some(client_version); - } - - /// Adds a peer to the internal peer store. - /// Returns an error if the peer address is invalid. - pub fn add_bootstrap_peer(&self, peer: &str) -> Result<(PeerId, Multiaddr), Error> { - parse_and_add_to_topology(peer, &mut self.topology.write()) - } - - /// Adds a reserved peer to the list of reserved peers. - /// Returns an error if the peer address is invalid. - pub fn add_reserved_peer(&self, peer: &str) -> Result<(), Error> { - let (id, _) = parse_and_add_to_topology(peer, &mut self.topology.write())?; - self.reserved_peers.write().insert(id); - Ok(()) - } - - /// Removes the peer from the list of reserved peers. If we're in reserved mode, drops any - /// active connection to this peer. - /// Returns an error if the peer address is invalid. - pub fn remove_reserved_peer(&self, peer: &str) -> Result<(), Error> { - let (id, _) = parse_and_add_to_topology(peer, &mut self.topology.write())?; - self.reserved_peers.write().remove(&id); - - // Dropping the peer if we're in reserved mode. - if self.reserved_only.load(atomic::Ordering::SeqCst) { - let mut connections = self.connections.write(); - if let Some(who) = connections.peer_by_nodeid.remove(&id) { - connections.info_by_peer.remove(&who); - // TODO: use drop_peer instead - } - } - - Ok(()) - } - - /// Set the non-reserved peer mode. - pub fn set_non_reserved_mode(&self, mode: NonReservedPeerMode) { - match mode { - NonReservedPeerMode::Accept => - self.reserved_only.store(false, atomic::Ordering::SeqCst), - NonReservedPeerMode::Deny => - // TODO: drop existing peers? - self.reserved_only.store(true, atomic::Ordering::SeqCst), - } - } - - /// Reports that we tried to connect to the given address but failed. - /// - /// This decreases the chance this address will be tried again in the future. - #[inline] - pub fn report_failed_to_connect(&self, addr: &Multiaddr) { - trace!(target: "sub-libp2p", "Failed to connect to {:?}", addr); - self.topology.write().report_failed_to_connect(addr); - } - - /// Returns the `NodeIndex` corresponding to a node id, or assigns a `NodeIndex` if none - /// exists. - /// - /// Returns an error if this node is on the list of disabled/banned nodes.. - pub fn assign_node_index( - &self, - node_id: &PeerId - ) -> Result { - // Check whether node is disabled. - // TODO: figure out the locking strategy here to avoid possible deadlocks - // TODO: put disabled_nodes in connections? - let mut disabled_nodes = self.disabled_nodes.lock(); - if let Some(timeout) = disabled_nodes.get(node_id).cloned() { - if timeout > Instant::now() { - debug!(target: "sub-libp2p", "Refusing peer {:?} because it is disabled", node_id); - return Err(IoError::new(IoErrorKind::ConnectionRefused, "peer is disabled")); - } else { - disabled_nodes.remove(node_id); - } - } - drop(disabled_nodes); - - let mut connections = self.connections.write(); - let connections = &mut *connections; - let peer_by_nodeid = &mut connections.peer_by_nodeid; - let info_by_peer = &mut connections.info_by_peer; - - let who = *peer_by_nodeid.entry(node_id.clone()).or_insert_with(|| { - let new_id = self.next_node_index.fetch_add(1, atomic::Ordering::Relaxed); - trace!(target: "sub-libp2p", "Creating new peer #{:?} for {:?}", new_id, node_id); - - info_by_peer.insert(new_id, PeerConnectionInfo { - protocols: Vec::new(), // TODO: Vec::with_capacity(num_registered_protocols), - kad_connec: UniqueConnec::empty(), - ping_connec: UniqueConnec::empty(), - id: node_id.clone(), - originated: None, - ping: None, - client_version: None, - local_address: None, - remote_addresses: Vec::with_capacity(1), - }); - - new_id - }); - - Ok(who) - } - - /// Notifies that we're connected to a node through an address. - /// - /// Returns an error if we refuse the connection. - /// - /// Note that is it legal to connection multiple times to the same node id through different - /// addresses and endpoints. - pub fn report_connected( - &self, - node_index: NodeIndex, - addr: &Multiaddr, - endpoint: Endpoint - ) -> Result<(), IoError> { - let mut connections = self.connections.write(); - - // TODO: double locking in this function ; although this has been reviewed to not deadlock - // as of the writing of this code, it is possible that a later change that isn't carefully - // reviewed triggers one - - if endpoint == Endpoint::Listener { - let stats = num_open_custom_connections(&connections, &self.reserved_peers.read()); - if stats.unreserved_incoming >= self.max_incoming_peers { - debug!(target: "sub-libp2p", "Refusing incoming connection from {} because we \ - reached max incoming peers", addr); - return Err(IoError::new(IoErrorKind::ConnectionRefused, - "maximum incoming peers reached")); - } - } - - let infos = match connections.info_by_peer.get_mut(&node_index) { - Some(i) => i, - None => return Ok(()) - }; - - if !infos.remote_addresses.iter().any(|a| a == addr) { - infos.remote_addresses.push(addr.clone()); - } - - if infos.originated.is_none() { - infos.originated = Some(endpoint == Endpoint::Dialer); - } - - self.topology.write().report_connected(addr, &infos.id); - - Ok(()) - } - - /// Returns the node id from a node index. - /// - /// Returns `None` if the node index is invalid. - pub fn node_id_from_index( - &self, - node_index: NodeIndex - ) -> Option { - let mut connections = self.connections.write(); - let infos = match connections.info_by_peer.get_mut(&node_index) { - Some(i) => i, - None => return None - }; - Some(infos.id.clone()) - } - - /// Obtains the `UniqueConnec` corresponding to the Kademlia connection to a peer. - /// - /// Returns `None` if the node index is invalid. - pub fn kad_connection( - &self, - node_index: NodeIndex - ) -> Option> { - let mut connections = self.connections.write(); - let infos = match connections.info_by_peer.get_mut(&node_index) { - Some(i) => i, - None => return None - }; - Some(infos.kad_connec.clone()) - } - - /// Obtains the `UniqueConnec` corresponding to the Ping connection to a peer. - /// - /// Returns `None` if the node index is invalid. - pub fn ping_connection( - &self, - node_index: NodeIndex - ) -> Option> { - let mut connections = self.connections.write(); - let infos = match connections.info_by_peer.get_mut(&node_index) { - Some(i) => i, - None => return None - }; - Some(infos.ping_connec.clone()) - } - - /// Cleans up inactive connections and returns a list of - /// connections to ping and identify. - pub fn cleanup_and_prepare_updates( - &self - ) -> Vec { - self.topology.write().cleanup(); - - let mut connections = self.connections.write(); - let connections = &mut *connections; - let peer_by_nodeid = &mut connections.peer_by_nodeid; - let info_by_peer = &mut connections.info_by_peer; - - let mut ret = Vec::with_capacity(info_by_peer.len()); - info_by_peer.retain(|&who, infos| { - // Remove the peer if neither Kad nor any protocol is alive. - if !infos.kad_connec.is_alive() && - !infos.protocols.iter().any(|(_, conn)| conn.is_alive()) - { - peer_by_nodeid.remove(&infos.id); - trace!(target: "sub-libp2p", "Cleaning up expired peer \ - #{:?} ({:?})", who, infos.id); - return false; - } - - if let Some(addr) = infos.remote_addresses.get(0) { - ret.push(PeriodicUpdate { - node_index: who, - peer_id: infos.id.clone(), - address: addr.clone(), - pinger: infos.ping_connec.clone(), - identify: infos.client_version.is_none(), - }); - } - true - }); - ret - } - - /// Obtains the `UniqueConnec` corresponding to a custom protocol connection to a peer. - /// - /// Returns `None` if the node index is invalid. - pub fn custom_proto( - &self, - node_index: NodeIndex, - protocol_id: ProtocolId, - ) -> Option, u8)>> { - let mut connections = self.connections.write(); - let infos = match connections.info_by_peer.get_mut(&node_index) { - Some(i) => i, - None => return None - }; - - if let Some((_, ref uconn)) = infos.protocols.iter().find(|&(prot, _)| prot == &protocol_id) { - return Some(uconn.clone()) - } - - let unique_connec = UniqueConnec::empty(); - infos.protocols.push((protocol_id.clone(), unique_connec.clone())); - Some(unique_connec) - } - - /// Sends some data to the given peer, using the sender that was passed - /// to the `UniqueConnec` of `custom_proto`. - pub fn send(&self, who: NodeIndex, protocol: ProtocolId, message: Bytes) -> Result<(), Error> { - if let Some(peer) = self.connections.read().info_by_peer.get(&who) { - let sender = peer.protocols.iter().find(|elem| elem.0 == protocol) - .and_then(|e| e.1.poll()) - .map(|e| e.0); - if let Some(sender) = sender { - sender.unbounded_send(message) - .map_err(|err| ErrorKind::Io(IoError::new(IoErrorKind::Other, err)))?; - Ok(()) - } else { - // We are connected to this peer, but not with the current - // protocol. - debug!(target: "sub-libp2p", - "Tried to send message to peer {} for which we aren't connected with the requested protocol", - who - ); - return Err(ErrorKind::PeerNotFound.into()) - } - } else { - debug!(target: "sub-libp2p", "Tried to send message to invalid peer ID {}", who); - return Err(ErrorKind::PeerNotFound.into()) - } - } - - /// Get the info on a peer, if there's an active connection. - pub fn peer_info(&self, who: NodeIndex) -> Option { - self.connections.read().info_by_peer.get(&who).map(Into::into) - } - - /// Reports that an attempt to make a low-level ping of the peer failed. - pub fn report_ping_failed(&self, who: NodeIndex) { - self.drop_peer(who); - } - - /// Disconnects a peer, if a connection exists (ie. drops the Kademlia - /// controller, and the senders that were stored in the `UniqueConnec` of - /// `custom_proto`). - pub fn drop_peer(&self, who: NodeIndex) { - let mut connections = self.connections.write(); - if let Some(peer_info) = connections.info_by_peer.remove(&who) { - trace!(target: "sub-libp2p", "Destroying peer #{} {:?} ; kademlia = {:?} ; num_protos = {:?}", - who, - peer_info.id, - peer_info.kad_connec.is_alive(), - peer_info.protocols.iter().filter(|c| c.1.is_alive()).count()); - let old = connections.peer_by_nodeid.remove(&peer_info.id); - debug_assert_eq!(old, Some(who)); - for addr in &peer_info.remote_addresses { - self.topology.write().report_disconnected(addr, - DisconnectReason::ClosedGracefully); // TODO: wrong reason - } - } - } - - /// Disconnects all the peers. - /// This destroys all the Kademlia controllers and the senders that were - /// stored in the `UniqueConnec` of `custom_proto`. - pub fn disconnect_all(&self) { - let mut connec = self.connections.write(); - *connec = Connections { - info_by_peer: FnvHashMap::with_capacity_and_hasher( - connec.peer_by_nodeid.capacity(), Default::default()), - peer_by_nodeid: FnvHashMap::with_capacity_and_hasher( - connec.peer_by_nodeid.capacity(), Default::default()), - }; - } - - /// Disables a peer for `PEER_DISABLE_DURATION`. This adds the peer to the - /// list of disabled peers, and drops any existing connections if - /// necessary (ie. drops the sender that was stored in the `UniqueConnec` - /// of `custom_proto`). - pub fn ban_peer(&self, who: NodeIndex, reason: &str) { - // TODO: what do we do if the peer is reserved? - // TODO: same logging as in drop_peer - let mut connections = self.connections.write(); - let peer_info = if let Some(peer_info) = connections.info_by_peer.remove(&who) { - if let &Some(ref client_version) = &peer_info.client_version { - info!(target: "network", "Peer {} (version: {}, addresses: {:?}) disabled. {}", who, client_version, peer_info.remote_addresses, reason); - } else { - info!(target: "network", "Peer {} (addresses: {:?}) disabled. {}", who, peer_info.remote_addresses, reason); - } - let old = connections.peer_by_nodeid.remove(&peer_info.id); - debug_assert_eq!(old, Some(who)); - peer_info - } else { - return - }; - - drop(connections); - let timeout = Instant::now() + PEER_DISABLE_DURATION; - self.disabled_nodes.lock().insert(peer_info.id.clone(), timeout); - } - - /// Flushes the caches to the disk. - /// - /// This is done in an atomical way, so that an error doesn't corrupt - /// anything. - pub fn flush_caches_to_disk(&self) -> Result<(), IoError> { - match self.topology.read().flush_to_disk() { - Ok(()) => { - debug!(target: "sub-libp2p", "Flushed JSON peer store to disk"); - Ok(()) - } - Err(err) => { - warn!(target: "sub-libp2p", "Failed to flush changes to JSON peer store: {}", err); - Err(err) - } - } - } -} - -impl Drop for NetworkState { - fn drop(&mut self) { - let _ = self.flush_caches_to_disk(); - } -} - -/// Periodic update that should be performed by the user of the network state. -pub struct PeriodicUpdate { - /// Index of the node in the network state. - pub node_index: NodeIndex, - /// Id of the peer. - pub peer_id: PeerId, - /// Address of the node to ping. - pub address: Multiaddr, - /// Object that allows pinging the node. - pub pinger: UniqueConnec, - /// The node should be identified as well. - pub identify: bool, -} - -struct OpenCustomConnectionsNumbers { - /// Total number of open and pending connections. - pub total: u32, - /// Unreserved incoming number of open and pending connections. - pub unreserved_incoming: u32, - /// Unreserved outgoing number of open and pending connections. - pub unreserved_outgoing: u32, -} - -/// Returns the number of open and pending connections with -/// custom protocols. -fn num_open_custom_connections(connections: &Connections, reserved_peers: &FnvHashSet) -> OpenCustomConnectionsNumbers { - let filtered = connections - .info_by_peer - .values() - .filter(|info| - info.protocols.iter().any(|&(_, ref connec)| - match connec.state() { - UniqueConnecState::Pending | UniqueConnecState::Full => true, - _ => false - } - ) - ); - - let mut total: u32 = 0; - let mut unreserved_incoming: u32 = 0; - let mut unreserved_outgoing: u32 = 0; - - for info in filtered { - total += 1; - let node_is_reserved = reserved_peers.contains(&info.id); - if !node_is_reserved { - if !info.originated.unwrap_or(true) { - unreserved_incoming += 1; - } else { - unreserved_outgoing += 1; - } - } - } - - OpenCustomConnectionsNumbers { - total, - unreserved_incoming, - unreserved_outgoing, - } -} - -/// Parses an address of the form `/ip4/x.x.x.x/tcp/x/p2p/xxxxxx`, and adds it -/// to the given topology. Returns the corresponding peer ID and multiaddr. -fn parse_and_add_to_topology( - addr_str: &str, - topology: &mut NetTopology -) -> Result<(PeerId, Multiaddr), Error> { - - let mut addr = addr_str.to_multiaddr().map_err(|_| ErrorKind::AddressParse)?; - let who = match addr.pop() { - Some(AddrComponent::P2P(key)) => - PeerId::from_multihash(key).map_err(|_| ErrorKind::AddressParse)?, - _ => return Err(ErrorKind::AddressParse.into()), - }; - - topology.add_bootstrap_addr(&who, addr.clone()); - Ok((who, addr)) -} - -/// Obtains or generates the local private key using the configuration. -fn obtain_private_key(config: &NetworkConfiguration) - -> Result { - if let Some(ref secret) = config.use_secret { - // Key was specified in the configuration. - secio::SecioKeyPair::secp256k1_raw_key(&secret[..]) - .map_err(|err| IoError::new(IoErrorKind::InvalidData, err)) - - } else { - if let Some(ref path) = config.net_config_path { - fs::create_dir_all(Path::new(path))?; - - // Try fetch the key from a the file containing th esecret. - let secret_path = Path::new(path).join(SECRET_FILE); - match load_private_key_from_file(&secret_path) { - Ok(s) => Ok(s), - Err(err) => { - // Failed to fetch existing file ; generate a new key - trace!(target: "sub-libp2p", - "Failed to load existing secret key file {:?}, generating new key ; err = {:?}", - secret_path, - err - ); - Ok(gen_key_and_try_write_to_file(&secret_path)) - } - } - - } else { - // No path in the configuration, nothing we can do except generate - // a new key. - let mut key: [u8; 32] = [0; 32]; - rand::rngs::EntropyRng::new().fill(&mut key); - Ok(secio::SecioKeyPair::secp256k1_raw_key(&key) - .expect("randomly-generated key with correct len should always be valid")) - } - } -} - -/// Tries to load a private key from a file located at the given path. -fn load_private_key_from_file

(path: P) - -> Result - where P: AsRef - { - fs::File::open(path) - .and_then(|mut file| { - // We are in 2018 and there is still no method on `std::io::Read` - // that directly returns a `Vec`. - let mut buf = Vec::new(); - file.read_to_end(&mut buf).map(|_| buf) - }) - .and_then(|content| - secio::SecioKeyPair::secp256k1_raw_key(&content) - .map_err(|err| IoError::new(IoErrorKind::InvalidData, err)) - ) -} - -/// Generates a new secret key and tries to write it to the given file. -/// Doesn't error if we couldn't open or write to the file. -fn gen_key_and_try_write_to_file

(path: P) -> secio::SecioKeyPair - where P: AsRef { - let raw_key: [u8; 32] = rand::rngs::EntropyRng::new().gen(); - let secio_key = secio::SecioKeyPair::secp256k1_raw_key(&raw_key) - .expect("randomly-generated key with correct len should always be valid"); - - // And store the newly-generated key in the file if possible. - // Errors that happen while doing so are ignored. - match open_priv_key_file(&path) { - Ok(mut file) => - match file.write_all(&raw_key) { - Ok(()) => (), - Err(err) => warn!(target: "sub-libp2p", - "Failed to write secret key in file {:?} ; err = {:?}", - path.as_ref(), - err - ), - }, - Err(err) => warn!(target: "sub-libp2p", - "Failed to store secret key in file {:?} ; err = {:?}", - path.as_ref(), - err - ), - } - - secio_key -} - -/// Opens a file containing a private key in write mode. -#[cfg(unix)] -fn open_priv_key_file

(path: P) -> Result - where P: AsRef -{ - use std::os::unix::fs::OpenOptionsExt; - fs::OpenOptions::new() - .write(true) - .create_new(true) - .mode(256 | 128) // 0o600 in decimal - .open(path) -} -/// Opens a file containing a private key in write mode. -#[cfg(not(unix))] -fn open_priv_key_file

(path: P) -> Result - where P: AsRef -{ - fs::OpenOptions::new() - .write(true) - .create_new(true) - .open(path) -} - -#[cfg(test)] -mod tests { - use libp2p::core::PublicKey; - use network_state::NetworkState; - - #[test] - fn refuse_disabled_peer() { - let state = NetworkState::new(&Default::default()).unwrap(); - let example_peer = PublicKey::Rsa(vec![1, 2, 3, 4]).into_peer_id(); - - let who = state.assign_node_index(&example_peer).unwrap(); - state.ban_peer(who, "Just a test"); - - assert!(state.assign_node_index(&example_peer).is_err()); - } -} diff --git a/substrate/network-libp2p/src/node_handler.rs b/substrate/network-libp2p/src/node_handler.rs new file mode 100644 index 0000000000000..56d5b41cd1331 --- /dev/null +++ b/substrate/network-libp2p/src/node_handler.rs @@ -0,0 +1,796 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +use bytes::Bytes; +use custom_proto::{RegisteredProtocols, RegisteredProtocolOutput}; +use futures::{prelude::*, future, task}; +use libp2p::core::{ConnectionUpgrade, Endpoint, PeerId, PublicKey, upgrade}; +use libp2p::kad::{KadConnecConfig, KadFindNodeRespond, KadIncomingRequest, KadConnecController}; +use libp2p::{identify, ping}; +use parking_lot::Mutex; +use std::error::Error; +use std::io::{Error as IoError, ErrorKind as IoErrorKind}; +use std::sync::Arc; +use std::time::{Duration, Instant}; +use tokio_io::{AsyncRead, AsyncWrite}; +use tokio_timer::{Delay, Interval, Timeout, timeout::Error as TimeoutError}; +use {Multiaddr, PacketId, ProtocolId}; + +/// Duration after which we consider that a ping failed. +const PING_TIMEOUT: Duration = Duration::from_secs(30); +/// After a ping succeeded, wait this long before the next ping. +const DELAY_TO_NEXT_PING: Duration = Duration::from_secs(15); +/// Period at which we identify the remote. +const PERIOD_IDENTIFY: Duration = Duration::from_secs(5 * 60); +/// Delay between the moment we connect and the first time we ping. +const DELAY_TO_FIRST_PING: Duration = Duration::from_secs(5); +/// Delay between the moment we connect and the first time we identify. +const DELAY_TO_FIRST_IDENTIFY: Duration = Duration::from_secs(2); + +/// This struct handles the open substreams of a specific node. +/// +/// It doesn't handle opening the substreams, but only what to do with substreams that have been +/// opened. +/// +/// The node will be pinged at a regular interval to determine whether it's still alive. We will +/// also regularly query the remote for identification information, for statistics purposes. +pub struct NodeHandler { + /// List of registered custom protocols. + registered_custom: Arc>, + /// Substreams open for "custom" protocols (eg. dot). + custom_protocols_substreams: Vec>, + + /// Substream open for Kademlia, if any. + kademlia_substream: Option<(KadConnecController, Box + Send>)>, + + /// Substream open for sending pings, if any. + ping_out_substream: Option<(ping::Pinger, Box + Send>)>, + /// Active pinging attempt. Includes the moment when we started the ping. + active_ping_out: Option<(Instant, Box>> + Send>)>, + /// Substreams open for receiving pings. + ping_in_substreams: Vec + Send>>, + /// Future that fires when we need to ping the node again. + /// + /// Every time we receive a pong, we reset the timer to the next time. + next_ping: Delay, + + /// Substreams for sending back our identify info to the remote. + /// + /// This is in an `Arc` in order to avoid borrowing issues with the future. + identify_send_back: Arc + Send>>>>, + /// Stream that fires when we need to identify the node again. + next_identify: Interval, + + /// Substreams being upgraded on the listening side. + upgrades_in_progress_listen: Vec, Error = IoError> + Send>>, + /// Substreams being upgraded on the dialing side. Contrary to `upgrades_in_progress_listen`, + /// these have a known purpose. + upgrades_in_progress_dial: Vec<(UpgradePurpose, Box, Error = IoError> + Send>)>, + /// The substreams we want to open. + queued_dial_upgrades: Vec, + /// Number of outbound substreams that the user should open. + /// While this is non-zero, polling the handler will produce `OutboundSubstreamRequested`. + num_out_user_must_open: usize, + + /// Task to notify if we add an element to one of the lists from the public API. + to_notify: Option, +} + +/// Purpose of an upgrade in progress on the dialing side. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum UpgradePurpose { + Custom(ProtocolId), + Kad, + Identify, + Ping, +} + +/// Event that can happen on the `NodeHandler`. +pub enum NodeEvent { + /// The node has been determined to be unresponsive. + Unresponsive, + + /// The node works but we can't do anything useful with it. + Useless, + + /// Started pinging the remote. This can be used to print a diagnostic message in the logs. + PingStart, + + /// The node has successfully responded to a ping. + PingSuccess(Duration), + + /// Opened a custom protocol with the remote. + CustomProtocolOpen { + /// Identifier of the protocol. + protocol_id: ProtocolId, + /// Version of the protocol that has been opened. + version: u8, + }, + + /// Closed a custom protocol with the remote. + CustomProtocolClosed { + /// Identifier of the protocol. + protocol_id: ProtocolId, + /// Reason why the substream closed. If `Ok`, then it's a graceful exit (EOF). + result: Result<(), IoError>, + }, + + /// Receives a message on a custom protocol substream. + CustomMessage { + /// Protocol which generated the message. + protocol_id: ProtocolId, + /// Identifier of the packet. + packet_id: u8, + /// Data that has been received. + data: Bytes, + }, + + /// We obtained identification information from the remote + Identified { + /// Information of the remote. + info: identify::IdentifyInfo, + /// Address the remote observes us as. + observed_addr: Multiaddr, + }, + + /// The remote wants us to send back identification information. + /// + /// The `IdentificationRequest` object should be used to send the information. + IdentificationRequest(IdentificationRequest), + + /// The emitter wants a new outbound substream to be opened. + /// + /// In the future, the user should answer that request by calling `inject_substream` with + /// `endpoint` set to `Dialer`. + /// If multiple such events are produced, the user should open a new substream once per event. + OutboundSubstreamRequested, + + /// Opened a Kademlia substream with the node. + KadOpen(KadConnecController), + + /// The remote wants us to answer a Kademlia `FIND_NODE` request. + /// + /// The `responder` should be used to answer that query. + // TODO: this API with the "responder" is bad, but changing it requires modifications in libp2p + KadFindNode { + /// The value being searched. + searched: PeerId, + /// Object to use to respond to the request. + responder: KadFindNodeRespond, + }, + + /// The Kademlia substream has been closed. + /// + /// The parameter contains the reason why it has been closed. `Ok` means that it's been closed + /// gracefully. + KadClosed(Result<(), IoError>), + + /// An error happened while upgrading a substream. + /// + /// This can be used to print a diagnostic message. + SubstreamUpgradeFail(IoError), +} + +/// The remote wants us to send back information. +pub struct IdentificationRequest { + /// Where to store the future that sends back the information. + identify_send_back: Arc + Send>>>>, + /// Object that sends back the information. + sender: identify::IdentifySender, + /// Protocol names that we support, to send back. + protocols: Vec, +} + +impl IdentificationRequest { + /// Responds to the request. + /// + /// - `local_key` must contain our local public key. + /// - `listen_addrs` must contain the list of addresses we're listening on (preferably after + /// NAT traversal). + /// - `remote_addr` must be the address of the remote from our local point of view. + /// + pub fn respond( + self, + local_key: PublicKey, + listen_addrs: Vec, + remote_addr: &Multiaddr + ) where TSubstream: AsyncRead + AsyncWrite + Send + 'static { + // TODO: what to return for `protocol_version` and `agent_version`? + let sender = self.sender.send( + identify::IdentifyInfo { + public_key: local_key, + protocol_version: concat!("substrate/", env!("CARGO_PKG_VERSION")).to_owned(), + agent_version: concat!("substrate/", env!("CARGO_PKG_VERSION")).to_owned(), + listen_addrs, + protocols: self.protocols, + }, + remote_addr + ); + + self.identify_send_back.lock().push(sender); + } +} + +/// Ideally we would have a method on `NodeHandler` that builds this type, but in practice it's a +/// bit tedious to express, even with the `impl Trait` syntax. +/// Therefore we simply use a macro instead. +macro_rules! listener_upgrade { + ($self:expr) => ( + upgrade::or(upgrade::or(upgrade::or( + upgrade::map((*$self.registered_custom).clone(), move |c| FinalUpgrade::Custom(c)), + upgrade::map(KadConnecConfig::new(), move |(c, s)| FinalUpgrade::Kad(c, s))), + upgrade::map(ping::Ping, move |p| FinalUpgrade::from(p))), + upgrade::map(identify::IdentifyProtocolConfig, move |i| FinalUpgrade::from(i))) + // TODO: meh for cloning a Vec here + ) +} + +impl NodeHandler +where TSubstream: AsyncRead + AsyncWrite + Send + 'static, + TUserData: Clone + Send + 'static, +{ + /// Creates a new node handler. + #[inline] + pub fn new(registered_custom: Arc>) -> Self { + let registered_custom_len = registered_custom.len(); + let queued_dial_upgrades = registered_custom.0 + .iter() + .map(|proto| UpgradePurpose::Custom(proto.id())) + .collect(); + + NodeHandler { + custom_protocols_substreams: Vec::with_capacity(registered_custom_len), + kademlia_substream: None, + identify_send_back: Arc::new(Mutex::new(Vec::with_capacity(1))), + ping_in_substreams: Vec::with_capacity(1), + ping_out_substream: None, + active_ping_out: None, + registered_custom, + upgrades_in_progress_listen: Vec::with_capacity(registered_custom_len + 3), + upgrades_in_progress_dial: Vec::with_capacity(registered_custom_len + 3), + next_ping: Delay::new(Instant::now() + DELAY_TO_FIRST_PING), + next_identify: Interval::new(Instant::now() + DELAY_TO_FIRST_IDENTIFY, PERIOD_IDENTIFY), + queued_dial_upgrades, + num_out_user_must_open: registered_custom_len, + to_notify: None, + } + } + + /// Closes the node and returns all the events that should be produced by gracefully closing + /// everything. + // TODO: stronger return type + pub fn close(self) -> Vec> { + let mut events = Vec::new(); + + if let Some(_) = self.kademlia_substream { + events.push(NodeEvent::KadClosed(Ok(()))); + } + + for proto in self.custom_protocols_substreams { + events.push(NodeEvent::CustomProtocolClosed { + protocol_id: proto.protocol_id, + result: Ok(()), + }); + } + + events + } + + /// Sends a message on a custom protocol substream. + pub fn send_custom_message( + &mut self, + protocol: ProtocolId, + packet_id: PacketId, + data: Vec, + ) { + debug_assert!(self.registered_custom.has_protocol(protocol), + "invalid protocol id requested in the API of the libp2p networking"); + let proto = match self.custom_protocols_substreams.iter().find(|p| p.protocol_id == protocol) { + Some(proto) => proto, + None => return, // TODO: diagnostic message? + }; + + let mut message = Bytes::with_capacity(1 + data.len()); + message.extend_from_slice(&[packet_id]); + message.extend_from_slice(&data); + + // TODO: report error? + let _ = proto.outgoing.unbounded_send(message); + } + + /// Injects a substream that has been successfully opened with this node. + /// + /// If `endpoint` is `Listener`, the remote opened the substream. If `endpoint` is `Dialer`, + /// our node opened it. + pub fn inject_substream(&mut self, substream: TSubstream, endpoint: Endpoint) { + // For listeners, propose all the possible upgrades. + if endpoint == Endpoint::Listener { + let listener_upgrade = listener_upgrade!(self); + // TODO: shouldn't be future::empty() ; requires a change in libp2p + let upgrade = upgrade::apply(substream, listener_upgrade, Endpoint::Listener, future::empty()) + .map(|(out, _)| out); + self.upgrades_in_progress_listen.push(Box::new(upgrade) as Box<_>); + // Since we pushed to `upgrades_in_progress_listen`, we have to notify the task. + if let Some(task) = self.to_notify.take() { + task.notify(); + } + return; + } + + // If we're the dialer, we have to decide which upgrade we want. + let purpose = if self.queued_dial_upgrades.is_empty() { + error!(target: "sub-libp2p", "Logic error: opened an outgoing substream \ + with no purpose"); + return; + } else { + self.queued_dial_upgrades.remove(0) + }; + + match purpose { + UpgradePurpose::Custom(id) => { + let wanted = if let Some(proto) = self.registered_custom.find_protocol(id) { + // TODO: meh for cloning + upgrade::map(proto.clone(), move |c| FinalUpgrade::Custom(c)) + } else { + error!(target: "sub-libp2p", "Logic error: wrong custom protocol id for \ + opened substream"); + return; + }; + + // TODO: shouldn't be future::empty() ; requires a change in libp2p + let upgrade = upgrade::apply(substream, wanted, Endpoint::Dialer, future::empty()) + .map(|(out, _)| out); + self.upgrades_in_progress_dial.push((purpose, Box::new(upgrade) as Box<_>)); + } + UpgradePurpose::Kad => { + let wanted = upgrade::map(KadConnecConfig::new(), move |(c, s)| FinalUpgrade::Kad(c, s)); + // TODO: shouldn't be future::empty() ; requires a change in libp2p + let upgrade = upgrade::apply(substream, wanted, Endpoint::Dialer, future::empty::()) + .map(|(out, _)| out); + self.upgrades_in_progress_dial.push((purpose, Box::new(upgrade) as Box<_>)); + } + UpgradePurpose::Identify => { + let wanted = upgrade::map(identify::IdentifyProtocolConfig, move |i| FinalUpgrade::from(i)); + // TODO: shouldn't be future::empty() ; requires a change in libp2p + let upgrade = upgrade::apply(substream, wanted, Endpoint::Dialer, future::empty()) + .map(|(out, _)| out); + self.upgrades_in_progress_dial.push((purpose, Box::new(upgrade) as Box<_>)); + } + UpgradePurpose::Ping => { + let wanted = upgrade::map(ping::Ping, move |p| FinalUpgrade::from(p)); + // TODO: shouldn't be future::empty() ; requires a change in libp2p + let upgrade = upgrade::apply(substream, wanted, Endpoint::Dialer, future::empty::()) + .map(|(out, _): (FinalUpgrade, _)| out); + self.upgrades_in_progress_dial.push((purpose, Box::new(upgrade) as Box<_>)); + } + }; + + // Since we pushed to `upgrades_in_progress_dial`, we have to notify the task. + if let Some(task) = self.to_notify.take() { + task.notify(); + } + } + + /// If we have a Kademlia substream open, returns a copy of the controller. Otherwise, the node + /// will try to open a Kademlia substream and produce a `KadOpen` event containing the + /// controller. + pub fn open_kademlia(&mut self) -> Option { + if let Some((ref ctrl, _)) = self.kademlia_substream { + Some(ctrl.clone()) + } else if self.has_upgrade_purpose(&UpgradePurpose::Kad) { + // We are currently upgrading a substream to Kademlia ; nothing more to do except wait. + None + } else { + // Opening a new substream for Kademlia. + self.queued_dial_upgrades.push(UpgradePurpose::Kad); + self.num_out_user_must_open += 1; + None + } + } + + /// Returns true if we are currently upgrading to the given protocol. + fn has_upgrade_purpose(&self, purpose: &UpgradePurpose) -> bool { + self.upgrades_in_progress_dial.iter().any(|&(ref p, _)| p == purpose) || + self.queued_dial_upgrades.iter().any(|p| p == purpose) + } + + /// Cancels a dialing upgrade in progress. + /// + /// Useful when the listener opened the protocol we wanted. + fn cancel_dial_upgrade(&mut self, purpose: &UpgradePurpose) { + self.upgrades_in_progress_dial.retain(|&(purp, _)| &purp != purpose); + self.queued_dial_upgrades.retain(|u| u != purpose); + } + + /// Returns the names of the protocols that we supporitt. + fn supported_protocol_names(&self) -> Vec { + let list = listener_upgrade!(self); + ConnectionUpgrade::>::protocol_names(&list) + .filter_map(|(n, _)| String::from_utf8(n.to_vec()).ok()) + .collect() + } + + /// Inject a fully negotiated substream into the state. + /// + /// Optionally produces an event to dispatch. + fn inject_fully_negotiated( + &mut self, + upgrade: FinalUpgrade + ) -> Option> { + match upgrade { + FinalUpgrade::IdentifyListener(sender) => + Some(NodeEvent::IdentificationRequest(IdentificationRequest { + sender, + identify_send_back: self.identify_send_back.clone(), + protocols: self.supported_protocol_names(), + })), + FinalUpgrade::IdentifyDialer(info, observed_addr) => { + self.cancel_dial_upgrade(&UpgradePurpose::Identify); + Some(NodeEvent::Identified { info, observed_addr }) + }, + FinalUpgrade::PingDialer(pinger, ping_process) => { + self.cancel_dial_upgrade(&UpgradePurpose::Ping); + // We always open the ping substream for a reason, which is to immediately ping. + self.ping_out_substream = Some((pinger, ping_process)); + if self.ping_remote() { + Some(NodeEvent::PingStart) + } else { + None + } + }, + FinalUpgrade::PingListener(ping_listener) => { + self.ping_in_substreams.push(ping_listener); + None + }, + FinalUpgrade::Kad(controller, stream) => { + // Remove all upgrades in the progress for Kademlia. + self.cancel_dial_upgrade(&UpgradePurpose::Kad); + // Refuse the substream if we already have Kademlia substream open. + if self.kademlia_substream.is_none() { + self.kademlia_substream = Some((controller.clone(), stream)); + Some(NodeEvent::KadOpen(controller)) + } else { + None + } + }, + FinalUpgrade::Custom(proto) => { + self.cancel_dial_upgrade(&UpgradePurpose::Custom(proto.protocol_id)); + if self.custom_protocols_substreams.iter().any(|p| p.protocol_id == proto.protocol_id) { + // Skipping protocol that's already open. + return None; + } + + let event = NodeEvent::CustomProtocolOpen { + protocol_id: proto.protocol_id, + version: proto.protocol_version, + }; + + self.custom_protocols_substreams.push(proto); + Some(event) + }, + } + } + + /// Start the process of identifying the remote. + fn identify_remote(&mut self) { + if !self.has_upgrade_purpose(&UpgradePurpose::Identify) { + self.queued_dial_upgrades.push(UpgradePurpose::Identify); + self.num_out_user_must_open += 1; + } + } + + /// Start the process of pinging the remote. + /// + /// Doesn't do anything if a ping attempt is already in progress. + /// + /// Returns true if this actually starts a ping, false is this just opens a substream or does + /// nothing. + fn ping_remote(&mut self) -> bool { + // Ignore if we are already actively pinging. + if self.active_ping_out.is_some() { + return false; + } + + // If we have a ping open, ping it! + if let Some((ref mut pinger, _)) = self.ping_out_substream { + let future = Timeout::new(pinger.ping(), PING_TIMEOUT); + self.active_ping_out = Some((Instant::now(), Box::new(future) as Box<_>)); + return true; + } + + // Otherwise, ensure we have an upgrade for a ping substream in queue. + if !self.has_upgrade_purpose(&UpgradePurpose::Ping) { + self.queued_dial_upgrades.push(UpgradePurpose::Ping); + self.num_out_user_must_open += 1; + } + + false + } + + /// Polls the upgrades in progress. + fn poll_upgrades_in_progress(&mut self) -> Poll>, IoError> { + // Continue negotiation of newly-opened substreams on the listening side. + // We remove each element from `upgrades_in_progress_listen` one by one and add them back + // if not ready. + for n in (0 .. self.upgrades_in_progress_listen.len()).rev() { + let mut in_progress = self.upgrades_in_progress_listen.swap_remove(n); + match in_progress.poll() { + Ok(Async::Ready(upgrade)) => { + if let Some(event) = self.inject_fully_negotiated(upgrade) { + return Ok(Async::Ready(Some(event))); + } + }, + Ok(Async::NotReady) => { + self.upgrades_in_progress_listen.push(in_progress); + }, + Err(err) => { + return Ok(Async::Ready(Some(NodeEvent::SubstreamUpgradeFail(err)))); + }, + } + } + + // Continue negotiation of newly-opened substreams. + // We remove each element from `upgrades_in_progress_dial` one by one and add them back if + // not ready. + for n in (0 .. self.upgrades_in_progress_dial.len()).rev() { + let (purpose, mut in_progress) = self.upgrades_in_progress_dial.swap_remove(n); + match in_progress.poll() { + Ok(Async::Ready(upgrade)) => { + if let Some(event) = self.inject_fully_negotiated(upgrade) { + return Ok(Async::Ready(Some(event))); + } + }, + Ok(Async::NotReady) => + self.upgrades_in_progress_dial.push((purpose, in_progress)), + Err(err) => { + // TODO: dispatch depending on actual error ; right now we assume that + // error == not supported, which is not necessarily true in theory + if let UpgradePurpose::Custom(_) = purpose { + return Ok(Async::Ready(Some(NodeEvent::Useless))); + } else { + let msg = format!("While upgrading to {:?}: {:?}", purpose, err); + let err = IoError::new(IoErrorKind::Other, msg); + return Ok(Async::Ready(Some(NodeEvent::SubstreamUpgradeFail(err)))); + } + }, + } + } + + Ok(Async::NotReady) + } + + /// Polls the upgrades in progress. + fn poll_custom_protocols(&mut self) -> Poll>, IoError> { + // Poll for messages on the custom protocol stream. + for n in (0 .. self.custom_protocols_substreams.len()).rev() { + let mut custom_proto = self.custom_protocols_substreams.swap_remove(n); + match custom_proto.incoming.poll() { + Ok(Async::NotReady) => self.custom_protocols_substreams.push(custom_proto), + Ok(Async::Ready(Some((packet_id, data)))) => { + let protocol_id = custom_proto.protocol_id; + self.custom_protocols_substreams.push(custom_proto); + return Ok(Async::Ready(Some(NodeEvent::CustomMessage { + protocol_id, + packet_id, + data, + }))); + }, + Ok(Async::Ready(None)) => { + // Trying to reopen the protocol. + self.queued_dial_upgrades.push(UpgradePurpose::Custom(custom_proto.protocol_id)); + self.num_out_user_must_open += 1; + return Ok(Async::Ready(Some(NodeEvent::CustomProtocolClosed { + protocol_id: custom_proto.protocol_id, + result: Ok(()), + }))) + }, + Err(err) => { + // Trying to reopen the protocol. + self.queued_dial_upgrades.push(UpgradePurpose::Custom(custom_proto.protocol_id)); + self.num_out_user_must_open += 1; + return Ok(Async::Ready(Some(NodeEvent::CustomProtocolClosed { + protocol_id: custom_proto.protocol_id, + result: Err(err), + }))) + }, + } + } + + Ok(Async::NotReady) + } + + /// Polls the open Kademlia substream, if any. + fn poll_kademlia(&mut self) -> Poll>, IoError> { + // Poll for Kademlia events. + if let Some((controller, mut stream)) = self.kademlia_substream.take() { + match stream.poll() { + Ok(Async::Ready(Some(KadIncomingRequest::FindNode { searched, responder }))) => { + return Ok(Async::Ready(Some(NodeEvent::KadFindNode { searched, responder }))); + }, + // We don't care about Kademlia pings, they are unused. + Ok(Async::Ready(Some(KadIncomingRequest::PingPong))) => {}, + Ok(Async::NotReady) => self.kademlia_substream = Some((controller, stream)), + Ok(Async::Ready(None)) => return Ok(Async::Ready(Some(NodeEvent::KadClosed(Ok(()))))), + Err(err) => return Ok(Async::Ready(Some(NodeEvent::KadClosed(Err(err))))), + } + } + + Ok(Async::NotReady) + } + + /// Polls the ping substreams. + fn poll_ping(&mut self) -> Poll>, IoError> { + // Poll for answering pings. + for n in (0 .. self.ping_in_substreams.len()).rev() { + let mut ping = self.ping_in_substreams.swap_remove(n); + match ping.poll() { + Ok(Async::Ready(())) => {}, + Ok(Async::NotReady) => self.ping_in_substreams.push(ping), + Err(err) => warn!(target: "sub-libp2p", "Remote ping substream errored: {:?}", err), + } + } + + // Poll the ping substream. + // TODO: the pinging API would benefit from some improvements on the side of libp2p. + if let Some((pinger, mut future)) = self.ping_out_substream.take() { + match future.poll() { + Ok(Async::Ready(())) => {}, + Ok(Async::NotReady) => self.ping_out_substream = Some((pinger, future)), + Err(_) => {}, + } + } + + // Poll the active ping attempt. + if let Some((started, mut ping_attempt)) = self.active_ping_out.take() { + match ping_attempt.poll() { + Ok(Async::Ready(())) => { + self.next_ping.reset(Instant::now() + DELAY_TO_NEXT_PING); + return Ok(Async::Ready(Some(NodeEvent::PingSuccess(started.elapsed())))); + }, + Ok(Async::NotReady) => self.active_ping_out = Some((started, ping_attempt)), + Err(_) => return Ok(Async::Ready(Some(NodeEvent::Unresponsive))), + } + } + + // Poll the future that fires when we need to ping the node again. + match self.next_ping.poll() { + Ok(Async::NotReady) => {}, + Ok(Async::Ready(())) => { + // We reset `next_ping` to a very long time in the future so that we can poll + // it again without having an accident. + self.next_ping.reset(Instant::now() + Duration::from_secs(5 * 60)); + if self.ping_remote() { + return Ok(Async::Ready(Some(NodeEvent::PingStart))); + } + }, + Err(err) => { + warn!(target: "sub-libp2p", "Ping timer errored: {:?}", err); + return Err(IoError::new(IoErrorKind::Other, err)); + } + } + + Ok(Async::NotReady) + } + + /// Polls the identify substreams. + fn poll_identify(&mut self) -> Poll>, IoError> { + // Poll the future that fires when we need to identify the node again. + loop { + match self.next_identify.poll() { + Ok(Async::NotReady) => break, + Ok(Async::Ready(Some(_))) => self.identify_remote(), + Ok(Async::Ready(None)) => { + warn!(target: "sub-libp2p", "Identify timer closed unexpectedly"); + return Ok(Async::Ready(None)); + } + Err(err) => { + warn!(target: "sub-libp2p", "Identify timer errored: {:?}", err); + return Err(IoError::new(IoErrorKind::Other, err)); + } + } + } + + // Poll for sending identify information to the remote. + let mut identify_send_back = self.identify_send_back.lock(); + for n in (0 .. identify_send_back.len()).rev() { + let mut id_send_back = identify_send_back.swap_remove(n); + match id_send_back.poll() { + Ok(Async::Ready(())) => {}, + Ok(Async::NotReady) => identify_send_back.push(id_send_back), + Err(err) => warn!(target: "sub-libp2p", "Sending back identify info errored: {:?}", err), + } + } + + Ok(Async::NotReady) + } +} + +impl Stream for NodeHandler +where TSubstream: AsyncRead + AsyncWrite + Send + 'static, + TUserData: Clone + Send + 'static, +{ + type Item = NodeEvent; + type Error = IoError; + + fn poll(&mut self) -> Poll, Self::Error> { + // Request new outbound substreams from the user if necessary. + if self.num_out_user_must_open >= 1 { + self.num_out_user_must_open -= 1; + return Ok(Async::Ready(Some(NodeEvent::OutboundSubstreamRequested))); + } + + match self.poll_upgrades_in_progress()? { + Async::Ready(value) => return Ok(Async::Ready(value)), + Async::NotReady => (), + }; + + match self.poll_custom_protocols()? { + Async::Ready(value) => return Ok(Async::Ready(value)), + Async::NotReady => (), + }; + + match self.poll_kademlia()? { + Async::Ready(value) => return Ok(Async::Ready(value)), + Async::NotReady => (), + }; + + match self.poll_ping()? { + Async::Ready(value) => return Ok(Async::Ready(value)), + Async::NotReady => (), + }; + + match self.poll_identify()? { + Async::Ready(value) => return Ok(Async::Ready(value)), + Async::NotReady => (), + }; + + // Nothing happened. Register our task to be notified and return. + self.to_notify = Some(task::current()); + Ok(Async::NotReady) + } +} + +/// Enum of all the possible protocols our service handles. +enum FinalUpgrade { + Kad(KadConnecController, Box + Send>), + IdentifyListener(identify::IdentifySender), + IdentifyDialer(identify::IdentifyInfo, Multiaddr), + PingDialer(ping::Pinger, Box + Send>), + PingListener(Box + Send>), + Custom(RegisteredProtocolOutput), +} + +impl From for FinalUpgrade { + fn from(out: ping::PingOutput) -> Self { + match out { + ping::PingOutput::Ponger(processing) => + FinalUpgrade::PingListener(processing), + ping::PingOutput::Pinger { pinger, processing } => + FinalUpgrade::PingDialer(pinger, processing), + } + } +} + +impl From> for FinalUpgrade { + fn from(out: identify::IdentifyOutput) -> Self { + match out { + identify::IdentifyOutput::RemoteInfo { info, observed_addr } => + FinalUpgrade::IdentifyDialer(info, observed_addr), + identify::IdentifyOutput::Sender { sender } => + FinalUpgrade::IdentifyListener(sender), + } + } +} diff --git a/substrate/network-libp2p/src/secret.rs b/substrate/network-libp2p/src/secret.rs new file mode 100644 index 0000000000000..26d800bdf919a --- /dev/null +++ b/substrate/network-libp2p/src/secret.rs @@ -0,0 +1,132 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +use libp2p::secio; +use rand::{self, Rng}; +use std::fs; +use std::io::{Error as IoError, ErrorKind as IoErrorKind, Read, Write}; +use std::path::Path; +use NetworkConfiguration; + +// File where the private key is stored. +const SECRET_FILE: &str = "secret"; + +/// Obtains or generates the local private key using the configuration. +pub(crate) fn obtain_private_key( + config: &NetworkConfiguration +) -> Result { + if let Some(ref secret) = config.use_secret { + // Key was specified in the configuration. + secio::SecioKeyPair::secp256k1_raw_key(&secret[..]) + .map_err(|err| IoError::new(IoErrorKind::InvalidData, err)) + + } else { + if let Some(ref path) = config.net_config_path { + fs::create_dir_all(Path::new(path))?; + + // Try fetch the key from a the file containing the secret. + let secret_path = Path::new(path).join(SECRET_FILE); + match load_private_key_from_file(&secret_path) { + Ok(s) => Ok(s), + Err(err) => { + // Failed to fetch existing file ; generate a new key + trace!(target: "sub-libp2p", + "Failed to load existing secret key file {:?}, generating new key ; err = {:?}", + secret_path, + err + ); + Ok(gen_key_and_try_write_to_file(&secret_path)) + } + } + + } else { + // No path in the configuration, nothing we can do except generate + // a new key. + let mut key: [u8; 32] = [0; 32]; + rand::rngs::EntropyRng::new().fill(&mut key); + Ok(secio::SecioKeyPair::secp256k1_raw_key(&key) + .expect("randomly-generated key with correct len should always be valid")) + } + } +} + +/// Tries to load a private key from a file located at the given path. +fn load_private_key_from_file

(path: P) + -> Result + where P: AsRef { + fs::File::open(path) + .and_then(|mut file| { + // We are in 2018 and there is still no method on `std::io::Read` + // that directly returns a `Vec`. + let mut buf = Vec::new(); + file.read_to_end(&mut buf).map(|_| buf) + }) + .and_then(|content| + secio::SecioKeyPair::secp256k1_raw_key(&content) + .map_err(|err| IoError::new(IoErrorKind::InvalidData, err)) + ) +} + +/// Generates a new secret key and tries to write it to the given file. +/// Doesn't error if we couldn't open or write to the file. +fn gen_key_and_try_write_to_file

(path: P) -> secio::SecioKeyPair + where P: AsRef { + let raw_key: [u8; 32] = rand::rngs::EntropyRng::new().gen(); + let secio_key = secio::SecioKeyPair::secp256k1_raw_key(&raw_key) + .expect("randomly-generated key with correct len should always be valid"); + + // And store the newly-generated key in the file if possible. + // Errors that happen while doing so are ignored. + match open_priv_key_file(&path) { + Ok(mut file) => + match file.write_all(&raw_key) { + Ok(()) => (), + Err(err) => warn!(target: "sub-libp2p", + "Failed to write secret key in file {:?} ; err = {:?}", + path.as_ref(), + err + ), + }, + Err(err) => warn!(target: "sub-libp2p", + "Failed to store secret key in file {:?} ; err = {:?}", + path.as_ref(), + err + ), + } + + secio_key +} + +/// Opens a file containing a private key in write mode. +#[cfg(unix)] +fn open_priv_key_file

(path: P) -> Result + where P: AsRef { + use std::os::unix::fs::OpenOptionsExt; + fs::OpenOptions::new() + .write(true) + .create_new(true) + .mode(256 | 128) // 0o600 in decimal + .open(path) +} +/// Opens a file containing a private key in write mode. +#[cfg(not(unix))] +fn open_priv_key_file

(path: P) -> Result + where P: AsRef { + fs::OpenOptions::new() + .write(true) + .create_new(true) + .open(path) +} diff --git a/substrate/network-libp2p/src/service.rs b/substrate/network-libp2p/src/service.rs index bd36e2ff528d5..e8befa459e84b 100644 --- a/substrate/network-libp2p/src/service.rs +++ b/substrate/network-libp2p/src/service.rs @@ -14,87 +14,93 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -use bytes::Bytes; -use {Error, ErrorKind, NetworkConfiguration, NetworkProtocolHandler}; -use {NonReservedPeerMode, NetworkContext, Severity, NodeIndex, ProtocolId}; -use parking_lot::RwLock; -use libp2p; -use libp2p::multiaddr::{AddrComponent, Multiaddr}; -use libp2p::kad::{KadSystem, KadConnecConfig, KadSystemConfig}; -use libp2p::kad::{KadIncomingRequest, KadConnecController, KadPeer}; -use libp2p::kad::{KadConnectionType, KadQueryEvent}; -use libp2p::identify::{IdentifyInfo, IdentifyOutput, IdentifySender}; -use libp2p::identify::{IdentifyProtocolConfig}; -use libp2p::core::{upgrade, Transport, MuxedTransport, ConnectionUpgrade}; -use libp2p::core::{Endpoint, PeerId as PeerstorePeerId, PublicKey}; -use libp2p::core::{SwarmController, UniqueConnecState}; -use libp2p::ping; -use libp2p::transport_timeout::TransportTimeout; +use fnv::FnvHashMap; +use parking_lot::Mutex; +use libp2p::core::{nodes::swarm::ConnectedPoint, Endpoint, Multiaddr, PeerId as PeerstorePeerId}; use {PacketId, SessionInfo, TimerToken}; -use rand; +use service_task::ServiceEvent; use std::io::{Error as IoError, ErrorKind as IoErrorKind}; -use std::iter; -use std::net::SocketAddr; use std::sync::Arc; use std::sync::mpsc as sync_mpsc; use std::thread; -use std::time::{Duration, Instant}; -use futures::{future, Future, stream, Stream, select_all}; +use std::time::Duration; +use futures::{prelude::*, Future, stream, Stream, select_all}; use futures::sync::{mpsc, oneshot}; use tokio::runtime::current_thread; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_timer::{Interval, Timeout}; +use {Error, ErrorKind, NetworkConfiguration, NetworkProtocolHandler, parse_str_addr}; +use {NonReservedPeerMode, NetworkContext, Severity, NodeIndex, ProtocolId}; use custom_proto::{RegisteredProtocol, RegisteredProtocols}; -use custom_proto::RegisteredProtocolOutput; -use network_state::{NetworkState, PeriodicUpdate}; +use service_task::start_service; use timeouts; -use transport; /// IO Service with networking. pub struct NetworkService { - shared: Arc, + /// Information about peers. + peer_infos: Arc>>, + + /// Use this channel to send a timeout request to the background thread's + /// events loop. After the timeout, elapsed, it will call `timeout` on the + /// `NetworkProtocolHandler`. + timeouts_register_tx: mpsc::UnboundedSender<(Duration, (Arc, ProtocolId, TimerToken))>, - /// Holds the networking-running background thread alive. The `Option` is - /// only set to `None` in the destructor. - /// Sending a message on the channel will trigger the end of the - /// background thread. We can then wait on the join handle. + /// Sender for messages to send to the background thread. + msg_tx: mpsc::UnboundedSender, + + /// Sender for messages to the backgound service task, and handle for the background thread. + /// Dropping the sender should close the task and the thread. bg_thread: Option<(oneshot::Sender<()>, thread::JoinHandle<()>)>, -} -/// Common struct shared throughout all the components of the service. -struct Shared { /// Original configuration of the service. config: NetworkConfiguration, - /// Contains the state of the network. - network_state: NetworkState, + /// List of registered protocols. + registered_custom: Arc>>, +} - /// Kademlia system. Contains the DHT. - kad_system: KadSystem, +/// Known information about a peer. +struct PeerInfos { + /// Id of the peer. + id: PeerstorePeerId, - /// Configuration for the Kademlia upgrade. - kad_upgrade: KadConnecConfig, + /// List of open custom protocols, and their version. + protocols: Vec<(ProtocolId, u8)>, - /// List of protocols available on the network. It is a logic error to - /// remove protocols from this list, and the code may assume that protocols - /// stay at the same index forever. - protocols: RegisteredProtocols>, + /// Note that it is theoretically possible that we dial the remote at the + /// same time they dial us, in which case the protocols may be dispatched + /// between both connections, and in which case the value here will be racy. + endpoint: Endpoint, - /// Use this channel to send a timeout request to the background thread's - /// events loop. After the timeout, elapsed, it will call `timeout` on the - /// `NetworkProtocolHandler`. This can be closed if the background thread - /// is not running. The sender will be overwritten every time we start - /// the service. - timeouts_register_tx: mpsc::UnboundedSender<(Duration, (Arc, ProtocolId, TimerToken))>, + /// Latest known ping duration. + ping: Option, + + /// The client version of the remote, or `None` if not known. + client_version: Option, - /// Original address from the configuration, after being adjusted by the `Transport`. - // TODO: because we create the `Shared` before starting to listen, this - // has to be set later ; sort this out - original_listened_addr: RwLock>, + /// The multiaddresses of the remote, or `None` if not known. + remote_address: Option, - /// Contains the addresses we known about ourselves. - listened_addrs: RwLock>, + /// The local multiaddress used to communicate with the remote, or `None` + /// if not known. + // TODO: never filled ; also shouldn't be an `Option` + local_address: Option, +} + +/// Message to send to the service task. +#[derive(Debug, Clone)] +enum MsgToBgThread { + /// Call `add_reserved_peer` on the network service. + AddReservedPeer(PeerstorePeerId, Multiaddr), + /// Call `remove_reserved_peer` on the network service. + RemoveReservedPeer(PeerstorePeerId, Multiaddr), + /// Call `set_non_reserved_mode` on the network service. + SetNonReserved(NonReservedPeerMode), + /// Call `send_custom_message` on the network service. + SendCustomMessage(NodeIndex, ProtocolId, PacketId, Vec), + /// Call `drop_peer` on the network service. + DropNode(NodeIndex), + /// Call `ban_peer` on the network service. + BanNode(NodeIndex), } impl NetworkService { @@ -106,60 +112,36 @@ impl NetworkService { config: NetworkConfiguration, protocols: Vec<(Arc, ProtocolId, &[(u8, u8)])> ) -> Result { - let network_state = NetworkState::new(&config)?; - - let local_peer_id = network_state.local_public_key().clone() - .into_peer_id(); - for mut addr in config.listen_addresses.iter().cloned() { - addr.append(AddrComponent::P2P(local_peer_id.clone().into())); - info!(target: "sub-libp2p", "Local node address is: {}", addr); - } - - let kad_system = KadSystem::without_init(KadSystemConfig { - parallelism: 3, - local_peer_id: local_peer_id.clone(), - kbuckets_timeout: Duration::from_secs(600), - request_timeout: Duration::from_secs(10), - known_initial_peers: iter::empty(), - }); + // Start by creating the protocols list. + let registered_custom = Arc::new(RegisteredProtocols(protocols.into_iter() + .map(|(handler, protocol, versions)| + RegisteredProtocol::new(handler.clone(), protocol, versions)) + .collect())); - // Channel we use to signal success or failure of the bg thread - // initialization process. let (init_tx, init_rx) = sync_mpsc::channel(); - // Channel the main thread uses to signal the bg thread that it - // should stop let (close_tx, close_rx) = oneshot::channel(); let (timeouts_register_tx, timeouts_register_rx) = mpsc::unbounded(); - - let listened_addrs = config.public_addresses.clone(); - - let shared = Arc::new(Shared { - network_state, - protocols: RegisteredProtocols(protocols.into_iter() - .map(|(handler, protocol, versions)| - RegisteredProtocol::new(handler.clone(), protocol, versions)) - .collect() - ), - kad_system, - kad_upgrade: KadConnecConfig::new(), - config, - timeouts_register_tx, - original_listened_addr: RwLock::new(Vec::new()), - listened_addrs: RwLock::new(listened_addrs), - }); + let peer_infos = Arc::new(Mutex::new(Default::default())); + let timeouts_register_tx_clone = timeouts_register_tx.clone(); + let (msg_tx, msg_rx) = mpsc::unbounded(); + let registered_custom_clone = registered_custom.clone(); + let config_clone = config.clone(); + let peer_infos_clone = peer_infos.clone(); + let msg_tx_clone = msg_tx.clone(); // Initialize all the protocols now. - // TODO: what about failure to initialize? we can't uninitialize a protocol // TODO: remove this `initialize` method eventually, as it's only used for timers - for protocol in shared.protocols.0.iter() { + for protocol in registered_custom.0.iter() { protocol.custom_data().initialize(&NetworkContextImpl { - inner: shared.clone(), - protocol: protocol.id().clone(), + peer_infos: peer_infos.clone(), + registered_custom: registered_custom.clone(), + msg_tx: msg_tx.clone(), + timeouts_register_tx: timeouts_register_tx.clone(), + protocol: protocol.id(), current_peer: None, }); } - let shared_clone = shared.clone(); let join_handle = thread::spawn(move || { // Tokio runtime that is going to run everything in this thread. let mut runtime = match current_thread::Runtime::new() { @@ -170,7 +152,15 @@ impl NetworkService { } }; - let fut = match init_thread(shared_clone, timeouts_register_rx, close_rx) { + let fut = match init_thread( + config_clone, + registered_custom_clone, + peer_infos_clone, + timeouts_register_tx_clone, + timeouts_register_rx, + msg_tx_clone, + msg_rx + ) { Ok(future) => { debug!(target: "sub-libp2p", "Successfully started networking service"); let _ = init_tx.send(Ok(())); @@ -182,54 +172,67 @@ impl NetworkService { } }; + let fut = fut.map_err(|_| ()) + .select(close_rx.then(|_| Ok(()))) + .map(|_| ()).map_err(|_| ()); match runtime.block_on(fut) { Ok(()) => debug!(target: "sub-libp2p", "libp2p future finished"), - Err(err) => error!(target: "sub-libp2p", "error while running libp2p: {:?}", err), + Err(err) => error!(target: "sub-libp2p", "Error while running libp2p: {:?}", err), } }); init_rx.recv().expect("libp2p background thread panicked")?; Ok(NetworkService { - shared, + config, + peer_infos, + timeouts_register_tx, + msg_tx, bg_thread: Some((close_tx, join_handle)), + registered_custom, }) } /// Returns network configuration. + // TODO: is this method really necessary? we could remove the `config` field if not pub fn config(&self) -> &NetworkConfiguration { - &self.shared.config + &self.config } pub fn external_url(&self) -> Option { - // TODO: in the context of libp2p, it is hard to define what an external + None + // TODO: + /*// TODO: in the context of libp2p, it is hard to define what an external // URL is, as different nodes can have multiple different ways to // reach us self.shared.original_listened_addr.read().get(0) .map(|addr| format!("{}/p2p/{}", addr, self.shared.kad_system.local_peer_id().to_base58()) - ) + )*/ } /// Get a list of all connected peers by id. pub fn connected_peers(&self) -> Vec { - self.shared.network_state.connected_peers() + self.peer_infos.lock().keys().cloned().collect() } /// Try to add a reserved peer. pub fn add_reserved_peer(&self, peer: &str) -> Result<(), Error> { - // TODO: try to dial the peer? - self.shared.network_state.add_reserved_peer(peer) + let (peer_id, addr) = parse_str_addr(peer)?; + let _ = self.msg_tx.unbounded_send(MsgToBgThread::AddReservedPeer(peer_id, addr)); + Ok(()) } /// Try to remove a reserved peer. pub fn remove_reserved_peer(&self, peer: &str) -> Result<(), Error> { - self.shared.network_state.remove_reserved_peer(peer) + let (peer_id, addr) = parse_str_addr(peer)?; + let _ = self.msg_tx.unbounded_send(MsgToBgThread::RemoveReservedPeer(peer_id, addr)); + Ok(()) } /// Set the non-reserved peer mode. pub fn set_non_reserved_mode(&self, mode: NonReservedPeerMode) { - self.shared.network_state.set_non_reserved_mode(mode) + let _ = self.msg_tx.unbounded_send(MsgToBgThread::SetNonReserved(mode)); } /// Executes action in the network context @@ -242,12 +245,15 @@ impl NetworkService { pub fn with_context_eval(&self, protocol: ProtocolId, action: F) -> Option where F: FnOnce(&NetworkContext) -> T { - if !self.shared.protocols.has_protocol(protocol) { + if !self.registered_custom.has_protocol(protocol) { return None } Some(action(&NetworkContextImpl { - inner: self.shared.clone(), + peer_infos: self.peer_infos.clone(), + registered_custom: self.registered_custom.clone(), + msg_tx: self.msg_tx.clone(), + timeouts_register_tx: self.timeouts_register_tx.clone(), protocol: protocol.clone(), current_peer: None, })) @@ -256,282 +262,45 @@ impl NetworkService { impl Drop for NetworkService { fn drop(&mut self) { - if let Some((close_tx, join)) = self.bg_thread.take() { - let _ = close_tx.send(()); + if let Some((sender, join)) = self.bg_thread.take() { + drop(sender); if let Err(e) = join.join() { warn!(target: "sub-libp2p", "error while waiting on libp2p background thread: {:?}", e); } } - - debug_assert!(!self.shared.network_state.has_connected_peer()); - } -} - -#[derive(Clone)] -struct NetworkContextImpl { - inner: Arc, - protocol: ProtocolId, - current_peer: Option, -} - -impl NetworkContext for NetworkContextImpl { - fn send(&self, peer: NodeIndex, packet_id: PacketId, data: Vec) { - self.send_protocol(self.protocol, peer, packet_id, data) - } - - fn send_protocol( - &self, - protocol: ProtocolId, - peer: NodeIndex, - packet_id: PacketId, - data: Vec - ) { - debug_assert!(self.inner.protocols.has_protocol(protocol), - "invalid protocol id requested in the API of the libp2p networking"); - // TODO: could be "optimized" by building `message` only after checking the validity of - // the peer, but that's probably not worth the effort - let mut message = Bytes::with_capacity(1 + data.len()); - message.extend_from_slice(&[packet_id]); - message.extend_from_slice(&data); - if self.inner.network_state.send(peer, protocol, message).is_err() { - debug!(target: "sub-libp2p", "Sending to peer {} failed. Dropping.", peer); - self.inner.network_state.drop_peer(peer); - } - } - - fn respond(&self, packet_id: PacketId, data: Vec) { - if let Some(peer) = self.current_peer { - self.send_protocol(self.protocol, peer, packet_id, data) - } else { - panic!("respond() called outside of a received message"); - } - } - - fn report_peer(&self, peer: NodeIndex, reason: Severity) { - if let Some(info) = self.inner.network_state.peer_info(peer) { - if let Some(client_version) = info.client_version { - info!(target: "sub-libp2p", - "Peer {} ({:?} {}) reported by client: {}", - peer, - info.remote_address, - client_version, - reason - ); - } else { - info!(target: "sub-libp2p", "Peer {} reported by client: {}", peer, reason); - } - } - match reason { - Severity::Bad(reason) => self.inner.network_state.ban_peer(peer, reason), - Severity::Useless(_) => self.inner.network_state.drop_peer(peer), - Severity::Timeout => self.inner.network_state.drop_peer(peer), - } - } - - fn is_expired(&self) -> bool { - if let Some(current_peer) = self.current_peer { - !self.inner.network_state.is_peer_connected(current_peer) - } else { - // TODO: is this correct? - true - } - } - - fn register_timer(&self, token: usize, duration: Duration) - -> Result<(), Error> { - let handler = self.inner.protocols - .find_protocol(self.protocol) - .ok_or(ErrorKind::BadProtocol)? - .custom_data() - .clone(); - self.inner.timeouts_register_tx - .unbounded_send((duration, (handler, self.protocol, token))) - .map_err(|err| ErrorKind::Io(IoError::new(IoErrorKind::Other, err)))?; - Ok(()) - } - - fn peer_client_version(&self, peer: NodeIndex) -> String { - // Devp2p returns "unknown" on unknown peer ID, so we do the same. - self.inner.network_state.peer_client_version(peer, self.protocol) - .unwrap_or_else(|| "unknown".to_string()) - } - - fn session_info(&self, peer: NodeIndex) -> Option { - self.inner.network_state.session_info(peer, self.protocol) - } - - fn protocol_version(&self, protocol: ProtocolId, peer: NodeIndex) -> Option { - self.inner.network_state.protocol_version(peer, protocol) - } - - fn subprotocol_name(&self) -> ProtocolId { - self.protocol.clone() } } /// Builds the main `Future` for the network service. -/// -/// - `timeouts_register_rx` should receive newly-registered timeouts. -/// - `close_rx` should be triggered when we want to close the network. fn init_thread( - shared: Arc, + config: NetworkConfiguration, + registered_custom: Arc>>, + peers: Arc>>, + timeouts_register_tx: mpsc::UnboundedSender< + (Duration, (Arc, ProtocolId, TimerToken)) + >, timeouts_register_rx: mpsc::UnboundedReceiver< (Duration, (Arc, ProtocolId, TimerToken)) >, - close_rx: oneshot::Receiver<()> + msg_tx: mpsc::UnboundedSender, + mut msg_rx: mpsc::UnboundedReceiver, ) -> Result, Error> { - // Build the transport layer. - let transport = { - let base = transport::build_transport( - shared.network_state.local_private_key().clone() - ); - - let base = base.map_err_dial({ - let shared = shared.clone(); - move |err, addr| { - trace!(target: "sub-libp2p", "Failed to dial {}: {:?}", addr, err); - shared.network_state.report_failed_to_connect(&addr); - err - } - }); - - let shared = shared.clone(); - Transport::and_then(base, move |(peer_id, stream), endpoint, remote_addr| { - remote_addr.and_then(move |remote_addr| { - if &peer_id == shared.kad_system.local_peer_id() { - // TODO: this happens very frequently for now and floods the logs - //warn!(target: "sub-libp2p", "Refusing connection from our local peer id"); - return Err(IoErrorKind::ConnectionRefused.into()) - } - - // TODO: there's a possible race condition here if `cleanup_and_prepare_updates` is - // called between `assign_node_index` and one of `kad_connec`, `unique_connec`, - // etc. ; in practice though, it is very unlikely to happen - let node_index = shared.network_state.assign_node_index(&peer_id)?; - shared.network_state.report_connected(node_index, &remote_addr, endpoint)?; - let out = TransportOutput { - socket: stream, - node_index, - original_addr: remote_addr.clone(), - }; - Ok((out, future::ok(remote_addr))) - }) - }) - }; - - // Build the swarm. The swarm is the single entry point where successfully - // negotiated protocols arrive. - let (swarm_controller, swarm_events) = { - let upgraded_transport = transport.clone() - .and_then({ - let shared = shared.clone(); - move |out, endpoint, client_addr| { - let node_index = out.node_index; - let original_addr = out.original_addr; - let listener_upgrade = upgrade::or(upgrade::or(upgrade::or( - upgrade::map(shared.kad_upgrade.clone(), move |(c, f)| FinalUpgrade::Kad(node_index, c, f)), - upgrade::map(IdentifyProtocolConfig, move |id| FinalUpgrade::from((node_index, id, original_addr)))), - upgrade::map(ping::Ping, move |out| FinalUpgrade::from((node_index, out)))), - upgrade::map(DelayedProtosList(shared), move |c| FinalUpgrade::Custom(node_index, c))); - upgrade::apply(out.socket, listener_upgrade, endpoint, client_addr) - } - }); - let shared = shared.clone(); - - libp2p::core::swarm( - upgraded_transport, - move |upgrade, _client_addr| - listener_handle(shared.clone(), upgrade) - ) - }; - - // Listen on multiaddresses. - for addr in &shared.config.listen_addresses { - match swarm_controller.listen_on(addr.clone()) { - Ok(new_addr) => { - debug!(target: "sub-libp2p", "Libp2p listening on {}", new_addr); - shared.original_listened_addr.write().push(new_addr.clone()); - }, - Err(_) => { - warn!(target: "sub-libp2p", "Can't listen on {}, protocol not supported", addr); - return Err(ErrorKind::BadProtocol.into()) - }, - } - } - - // Explicitely connect to _all_ the boostrap nodes as a temporary measure. - for bootnode in shared.config.boot_nodes.iter() { - match shared.network_state.add_bootstrap_peer(bootnode) { - Ok((who, addr)) => { - trace!(target: "sub-libp2p", "Dialing bootnode {:?} through {}", who, addr); - shared.kad_system.update_kbuckets(who.clone()); - for proto in shared.protocols.0.clone().into_iter() { - open_peer_custom_proto( - shared.clone(), - transport.clone(), - addr.clone(), - Some(who.clone()), - proto, - &swarm_controller - ) - } - }, - Err(Error(ErrorKind::AddressParse, _)) => { - // fallback: trying with IP:Port - let multi = match bootnode.parse::() { - Ok(SocketAddr::V4(socket)) => - format!("/ip4/{}/tcp/{}", socket.ip(), socket.port()).parse::(), - Ok(SocketAddr::V6(socket)) => - format!("/ip6/{}/tcp/{}", socket.ip(), socket.port()).parse::(), - _ => { - warn!(target: "sub-libp2p", "Not a valid Bootnode Address {:}", bootnode); - continue; - } - }; - - if let Ok(addr) = multi { - trace!(target: "sub-libp2p", "Missing NodeIndex for Bootnode {:}. Querying", bootnode); - for proto in shared.protocols.0.clone().into_iter() { - open_peer_custom_proto( - shared.clone(), - transport.clone(), - addr.clone(), - None, - proto, - &swarm_controller - ) - } - } else { - warn!(target: "sub-libp2p", "Not a valid Bootnode Address {:}", bootnode); - continue; - } - }, - Err(err) => warn!(target:"sub-libp2p", "Couldn't parse Bootnode Address: {}", err), - } - } - - let outgoing_connections = Interval::new(Instant::now(), Duration::from_secs(1)) - .map_err(|err| IoError::new(IoErrorKind::Other, err)) - .for_each({ - let shared = shared.clone(); - let transport = transport.clone(); - let swarm_controller = swarm_controller.clone(); - move |_| { - connect_to_nodes(shared.clone(), transport.clone(), &swarm_controller); - Ok(()) - } - }); - // Build the timeouts system for the `register_timeout` function. // (note: this has nothing to do with socket timeouts) let timeouts = timeouts::build_timeouts_stream(timeouts_register_rx) .for_each({ - let shared = shared.clone(); + let peers = peers.clone(); + let msg_tx = msg_tx.clone(); + let registered_custom = registered_custom.clone(); + let timeouts_register_tx = timeouts_register_tx.clone(); move |(handler, protocol_id, timer_token)| { handler.timeout(&NetworkContextImpl { - inner: shared.clone(), + peer_infos: peers.clone(), + registered_custom: registered_custom.clone(), + msg_tx: msg_tx.clone(), protocol: protocol_id, current_peer: None, + timeouts_register_tx: timeouts_register_tx.clone(), }, timer_token); Ok(()) } @@ -541,894 +310,266 @@ fn init_thread( val }); - // Start the process of periodically discovering nodes to connect to. - let discovery = start_kademlia_discovery(shared.clone(), - transport.clone(), swarm_controller.clone()); + // Start the main service. + let mut service = start_service(config, registered_custom.clone())?; + + let service_stream = stream::poll_fn(move || { + loop { + match msg_rx.poll() { + Ok(Async::NotReady) => break, + Ok(Async::Ready(None)) => return Ok(Async::Ready(None)), + Ok(Async::Ready(Some(MsgToBgThread::AddReservedPeer(peer_id, addr)))) => { + service.add_reserved_peer(peer_id, addr); + }, + Ok(Async::Ready(Some(MsgToBgThread::RemoveReservedPeer(peer_id, addr)))) => { + service.remove_reserved_peer(peer_id, addr); + }, + Ok(Async::Ready(Some(MsgToBgThread::SetNonReserved(mode)))) => { + service.set_non_reserved_mode(mode); + }, + Ok(Async::Ready(Some(MsgToBgThread::SendCustomMessage(node_index, protocol_id, packet_id, data)))) => { + service.send_custom_message(node_index, protocol_id, packet_id, data); + }, + Ok(Async::Ready(Some(MsgToBgThread::DropNode(node_index)))) => { + service.drop_node(node_index); + }, + Ok(Async::Ready(Some(MsgToBgThread::BanNode(node_index)))) => { + service.ban_node(node_index); + }, + Err(()) => unreachable!("An unbounded receiver never errors"), + } + } + + service.poll() + }) + .for_each(move |event| { + macro_rules! ctxt { + ($protocol:expr, $node_index:expr) => ( + NetworkContextImpl { + peer_infos: peers.clone(), + registered_custom: registered_custom.clone(), + msg_tx: msg_tx.clone(), + timeouts_register_tx: timeouts_register_tx.clone(), + protocol: $protocol, + current_peer: Some($node_index), + } + ); + } - // Start the process of pinging the active nodes on the network. - let periodic = start_periodic_updates(shared.clone(), transport, swarm_controller); + match event { + ServiceEvent::NewNode { node_index, peer_id, endpoint } => { + let remote_address = match endpoint { + ConnectedPoint::Dialer { ref address } => Some(address.clone()), + ConnectedPoint::Listener { .. } => None, + }; + peers.lock().insert(node_index, PeerInfos { + id: peer_id, + protocols: Vec::new(), + endpoint: endpoint.into(), + ping: None, + client_version: None, + remote_address, + local_address: None, // TODO: fill + }); + }, + ServiceEvent::NodeClosed { node_index, closed_custom_protocols } => { + let old = peers.lock().remove(&node_index); + debug_assert!(old.is_some()); + for protocol in closed_custom_protocols { + registered_custom.find_protocol(protocol) + .expect("Invalid protocol ID") + .custom_data() + .disconnected(&ctxt!(protocol, node_index), &node_index); + } + }, + ServiceEvent::PingDuration(node_index, ping) => + peers.lock().get_mut(&node_index) + .expect("State mismatch in the network service") + .ping = Some(ping), + ServiceEvent::NodeInfos { node_index, client_version } => + peers.lock().get_mut(&node_index) + .expect("State mismatch in the network service") + .client_version = Some(client_version), + ServiceEvent::NodeAddress { node_index, address } => + peers.lock().get_mut(&node_index) + .expect("State mismatch in the network service") + .remote_address = Some(address), + ServiceEvent::OpenedCustomProtocol { node_index, protocol, version } => { + peers.lock().get_mut(&node_index) + .expect("State inconsistency in service") + .protocols + .push((protocol, version)); + registered_custom.find_protocol(protocol) + .expect("Invalid protocol ID") + .custom_data() + .connected(&ctxt!(protocol, node_index), &node_index) + }, + ServiceEvent::ClosedCustomProtocol { node_index, protocol } => { + peers.lock().get_mut(&node_index) + .expect("State inconsistency in service") + .protocols + .retain(|&(ref p, _)| p != &protocol); + registered_custom.find_protocol(protocol) + .expect("Invalid protocol ID") + .custom_data() + .disconnected(&ctxt!(protocol, node_index), &node_index) + }, + ServiceEvent::CustomMessage { node_index, protocol_id, packet_id, data } => { + registered_custom.find_protocol(protocol_id) + .expect("Invalid protocol ID") + .custom_data() + .read(&ctxt!(protocol_id, node_index), &node_index, packet_id, &data) + }, + }; + Ok(()) + }); + // Merge all futures into one. let futures: Vec>> = vec![ - Box::new(swarm_events.for_each(|_| Ok(()))), - Box::new(discovery), - Box::new(periodic), - Box::new(outgoing_connections), + Box::new(service_stream), Box::new(timeouts), - Box::new(close_rx.map_err(|err| IoError::new(IoErrorKind::Other, err))), ]; Ok( select_all(futures) .and_then(move |_| { - debug!(target: "sub-libp2p", "Networking ended ; disconnecting all peers"); - shared.network_state.disconnect_all(); + debug!(target: "sub-libp2p", "Networking ended"); Ok(()) }) .map_err(|(r, _, _)| r) ) } -/// Output of the common transport layer. -struct TransportOutput { - socket: S, - node_index: NodeIndex, - original_addr: Multiaddr, -} - -/// Enum of all the possible protocols our service handles. -enum FinalUpgrade { - Kad(NodeIndex, KadConnecController, Box + Send>), - /// The remote identification system, and the multiaddress we see the remote as. - IdentifyListener(NodeIndex, IdentifySender, Multiaddr), - /// The remote information about the address they see us as. - IdentifyDialer(NodeIndex, IdentifyInfo, Multiaddr), - PingDialer(NodeIndex, ping::Pinger, Box + Send>), - PingListener(NodeIndex, Box + Send>), - /// `Custom` means anything not in the core libp2p and is handled - /// by `CustomProtoConnectionUpgrade`. - Custom(NodeIndex, RegisteredProtocolOutput>), -} - -impl From<(NodeIndex, ping::PingOutput)> for FinalUpgrade { - fn from((node_index, out): (NodeIndex, ping::PingOutput)) -> FinalUpgrade { - match out { - ping::PingOutput::Ponger(processing) => - FinalUpgrade::PingListener(node_index, processing), - ping::PingOutput::Pinger { pinger, processing } => - FinalUpgrade::PingDialer(node_index, pinger, processing), - } - } -} - -impl From<(NodeIndex, IdentifyOutput, Multiaddr)> for FinalUpgrade { - fn from((node_index, out, addr): (NodeIndex, IdentifyOutput, Multiaddr)) -> FinalUpgrade { - match out { - IdentifyOutput::RemoteInfo { info, observed_addr } => - FinalUpgrade::IdentifyDialer(node_index, info, observed_addr), - IdentifyOutput::Sender { sender } => - FinalUpgrade::IdentifyListener(node_index, sender, addr), - } - } +#[derive(Clone)] +struct NetworkContextImpl { + peer_infos: Arc>>, + msg_tx: mpsc::UnboundedSender, + protocol: ProtocolId, + current_peer: Option, + registered_custom: Arc>>, + /// Clone of `NetworkService::timeouts_register_tx`. + timeouts_register_tx: mpsc::UnboundedSender<(Duration, (Arc, ProtocolId, TimerToken))>, } -/// Called whenever we successfully open a multistream with a remote. -fn listener_handle<'a, C>( - shared: Arc, - upgrade: FinalUpgrade, -) -> Box + Send + 'a> - where C: AsyncRead + AsyncWrite + Send + 'a { - match upgrade { - FinalUpgrade::Kad(node_index, controller, kademlia_stream) => { - trace!(target: "sub-libp2p", "Opened kademlia substream with #{:?}", node_index); - match handle_kademlia_connection(shared, node_index, controller, kademlia_stream) { - Ok(fut) => Box::new(fut) as Box<_>, - Err(err) => Box::new(future::err(err)) as Box<_>, - } - }, - - FinalUpgrade::IdentifyListener(node_index, sender, original_addr) => { - trace!(target: "sub-libp2p", "Sending back identification info to #{}", node_index); - sender.send( - IdentifyInfo { - public_key: shared.network_state.local_public_key().clone(), - protocol_version: concat!("substrate/", - env!("CARGO_PKG_VERSION")).to_owned(), // TODO: ? - agent_version: concat!("substrate/", - env!("CARGO_PKG_VERSION")).to_owned(), - listen_addrs: shared.listened_addrs.read().clone(), - protocols: Vec::new(), // TODO: protocols_to_report, - }, - &original_addr - ) - }, - - FinalUpgrade::IdentifyDialer(node_index, info, observed_addr) => { - process_identify_info(&shared, node_index, &info, &observed_addr); - Box::new(future::ok(())) - }, - - FinalUpgrade::PingListener(node_index, future) => { - trace!(target: "sub-libp2p", "Received ping substream from #{}", node_index); - future - }, - - FinalUpgrade::PingDialer(node_index, pinger, future) => { - let ping_connec = match shared.network_state.ping_connection(node_index) { - Some(p) => p, - None => return Box::new(future::ok(())) as Box<_> - }; - trace!(target: "sub-libp2p", "Successfully opened ping substream with #{}", node_index); - let fut = ping_connec.tie_or_passthrough(pinger, future); - Box::new(fut) as Box<_> - }, - - FinalUpgrade::Custom(node_index, custom_proto_out) => { - // A "custom" protocol is one that is part of substrate and not part of libp2p. - let shared = shared.clone(); - let fut = handle_custom_connection(shared, node_index, custom_proto_out); - Box::new(fut) as Box<_> - }, +impl NetworkContext for NetworkContextImpl { + fn send(&self, peer: NodeIndex, packet_id: PacketId, data: Vec) { + self.send_protocol(self.protocol, peer, packet_id, data) } -} - -/// Handles a newly-opened Kademlia connection. -fn handle_kademlia_connection( - shared: Arc, - node_index: NodeIndex, - controller: KadConnecController, - kademlia_stream: Box + Send> -) -> Result, IoError> { - let kad_connec = match shared.network_state.kad_connection(node_index) { - Some(kad) => kad, - None => return Err(IoError::new(IoErrorKind::Other, "node no longer exists")), - }; - - let node_id = match shared.network_state.node_id_from_index(node_index) { - Some(id) => id, - None => return Err(IoError::new(IoErrorKind::Other, "node no longer exists")), - }; - - let node_id2 = node_id.clone(); - let future = future::loop_fn(kademlia_stream, move |kademlia_stream| { - let shared = shared.clone(); - let next = kademlia_stream - .into_future() - .map_err(|(err, _)| err); - Timeout::new(next, Duration::from_secs(20)) - .map_err(|err| - // TODO: improve the error reporting here, but tokio-timer's API is bad - IoError::new(IoErrorKind::Other, err) - ) - .and_then(move |(req, rest)| { - match req { - Some(KadIncomingRequest::FindNode { searched, responder }) => { - let resp = build_kademlia_response(&shared, &searched); - trace!(target: "sub-libp2p", "Responding to Kad {:?} with {:?}", searched, resp); - responder.respond(resp) - }, - Some(KadIncomingRequest::PingPong) => (), - None => return Ok(future::Loop::Break(())) - } - Ok(future::Loop::Continue(rest)) - }) - }).then(move |val| { - trace!(target: "sub-libp2p", "Closed Kademlia connection with #{} {:?} => {:?}", node_index, node_id2, val); - val - }); - - Ok(kad_connec.tie_or_passthrough(controller, future)) -} - -/// When a remote performs a `FIND_NODE` Kademlia request for `searched`, -/// this function builds the response to send back. -fn build_kademlia_response( - shared: &Arc, - searched: &PeerstorePeerId -) -> Vec { - shared.kad_system - .known_closest_peers(searched) - .map(move |who| { - if who == *shared.kad_system.local_peer_id() { - KadPeer { - node_id: who.clone(), - multiaddrs: shared.listened_addrs.read().clone(), - connection_ty: KadConnectionType::Connected, - } - } else { - let mut addrs = shared.network_state.addrs_of_peer(&who); - let connected = addrs.iter().any(|&(_, conn)| conn); - // The Kademlia protocol of libp2p doesn't allow specifying which address is valid - // and which is outdated, therefore in order to stay honest towards the network - // we only report the addresses we're connected to if we're connected to any. - if connected { - addrs = addrs.into_iter() - .filter_map(|(a, c)| if c { Some((a, c)) } else { None }) - .collect(); - } - - KadPeer { - node_id: who.clone(), - multiaddrs: addrs.into_iter().map(|(a, _)| a).collect(), - connection_ty: if connected { - KadConnectionType::Connected - } else { - KadConnectionType::NotConnected - }, - } - } - }) - // TODO: we really want to remove nodes with no multiaddress from - // the results, but a flaw in the Kad protocol of libp2p makes it - // impossible to return empty results ; therefore we must at least - // return ourselves - .filter(|p| p.node_id == *shared.kad_system.local_peer_id() || - !p.multiaddrs.is_empty()) - .take(20) - .collect::>() -} - -/// Handles a newly-opened connection to a remote with a custom protocol -/// (eg. `/substrate/dot/0`). -/// Returns a future that corresponds to when the handling is finished. -fn handle_custom_connection( - shared: Arc, - node_index: NodeIndex, - custom_proto_out: RegisteredProtocolOutput> -) -> Box + Send> { - let handler = custom_proto_out.custom_data; - let protocol_id = custom_proto_out.protocol_id; - - // Determine the ID of this peer, or drop the connection if the peer is disabled, - // if we reached `max_peers`, or a similar reason. - // TODO: is there a better way to refuse connections than to drop the - // newly-opened substream? should we refuse the connection - // beforehand? - let unique_connec = match shared.network_state.custom_proto( - node_index, - protocol_id, + fn send_protocol( + &self, + protocol: ProtocolId, + peer: NodeIndex, + packet_id: PacketId, + data: Vec ) { - Some(c) => c, - None => return Box::new(future::err(IoErrorKind::Other.into())) as Box<_>, - }; - - if let UniqueConnecState::Full = unique_connec.state() { - debug!(target: "sub-libp2p", - "Interrupting connection attempt to #{} with {:?} because we're already connected", - node_index, - custom_proto_out.protocol_id - ); - return Box::new(future::ok(())) as Box<_> + let msg = MsgToBgThread::SendCustomMessage(peer, protocol, packet_id, data); + let _ = self.msg_tx.unbounded_send(msg); } - struct ProtoDisconnectGuard { - inner: Arc, - who: NodeIndex, - handler: Arc, - protocol: ProtocolId, - print_log_message: bool, + fn respond(&self, packet_id: PacketId, data: Vec) { + if let Some(peer) = self.current_peer { + self.send_protocol(self.protocol, peer, packet_id, data) + } else { + panic!("respond() called outside of a received message"); + } } - impl Drop for ProtoDisconnectGuard { - fn drop(&mut self) { - if self.print_log_message { + fn report_peer(&self, node_index: NodeIndex, reason: Severity) { + let peer_infos = self.peer_infos.lock(); + if let Some(info) = peer_infos.get(&node_index) { + if let Some(ref client_version) = info.client_version { info!(target: "sub-libp2p", - "Node {:?} with peer ID {} through protocol {:?} disconnected", - self.inner.network_state.node_id_from_index(self.who), - self.who, - self.protocol + "Peer {:?} ({:?} {}) reported by client: {}", + info.id, + info.remote_address, + client_version, + reason ); + } else { + info!(target: "sub-libp2p", "Peer {:?} reported by client: {}", info.id, reason); } - self.handler.disconnected(&NetworkContextImpl { - inner: self.inner.clone(), - protocol: self.protocol, - current_peer: Some(self.who), - }, &self.who); - - // When any custom protocol drops, we drop the peer entirely. - // TODO: is this correct? - self.inner.network_state.drop_peer(self.who); } - } - - let mut dc_guard = ProtoDisconnectGuard { - inner: shared.clone(), - who: node_index, - handler: handler.clone(), - protocol: protocol_id, - print_log_message: true, - }; - - let fut = custom_proto_out.incoming - .for_each({ - let shared = shared.clone(); - let handler = handler.clone(); - move |(packet_id, data)| { - if let Some(id) = shared.network_state.node_id_from_index(node_index) { - shared.kad_system.update_kbuckets(id); - } - handler.read(&NetworkContextImpl { - inner: shared.clone(), - protocol: protocol_id, - current_peer: Some(node_index.clone()), - }, &node_index, packet_id, &data); - Ok(()) - } - }); - let val = (custom_proto_out.outgoing, custom_proto_out.protocol_version); - let final_fut = unique_connec.tie_or_stop(val, fut) - .then(move |val| { - info!(target: "sub-libp2p", "Finishing future for proto {:?} with {:?} => {:?}", - protocol_id, node_index, val); - // Makes sure that `dc_guard` is kept alive until here. - dc_guard.print_log_message = false; - drop(dc_guard); - val + let _ = self.msg_tx.unbounded_send(match reason { + Severity::Bad(_) => MsgToBgThread::BanNode(node_index), + Severity::Useless(_) => MsgToBgThread::DropNode(node_index), + Severity::Timeout => MsgToBgThread::DropNode(node_index), }); - - debug!(target: "sub-libp2p", - "Successfully connected to {:?} (peer id #{}) with protocol {:?} version {}", - shared.network_state.node_id_from_index(node_index), - node_index, - protocol_id, - custom_proto_out.protocol_version - ); - - handler.connected(&NetworkContextImpl { - inner: shared.clone(), - protocol: protocol_id, - current_peer: Some(node_index), - }, &node_index); - - Box::new(final_fut) as Box<_> -} - -/// Randomly discovers peers to connect to. -/// This works by running a round at a regular interval, and skipping if we -/// reached `min_peers`. When we are over `min_peers`, we stop trying to dial -/// nodes and only accept incoming connections. -fn start_kademlia_discovery( - shared: Arc, - transport: T, - swarm_controller: SwarmController + Send>> -) -> Box + Send> - where T: MuxedTransport> + Clone + Send + 'static, - T::Dial: Send, - T::MultiaddrFuture: Send + 'static, - T::Listener: Send, - T::ListenerUpgrade: Send, - T::Incoming: Send, - T::IncomingUpgrade: Send, - To: AsyncRead + AsyncWrite + Send + 'static, - St: MuxedTransport> + Clone + Send + 'static, - St::Dial: Send, - St::MultiaddrFuture: Send, - St::Listener: Send, - St::ListenerUpgrade: Send, - St::Incoming: Send, - St::IncomingUpgrade: Send, - C: Send + 'static { - let kad_init = shared.kad_system.perform_initialization({ - let shared = shared.clone(); - let transport = transport.clone(); - let swarm_controller = swarm_controller.clone(); - move |who| - obtain_kad_connection( - shared.clone(), - who.clone(), - transport.clone(), - swarm_controller.clone() - ) - }); - - // We perform a random Kademlia query at a regular interval. - let discovery = Interval::new(Instant::now(), Duration::from_secs(32)) - // TODO: add a timeout to the lookups? - .map_err(|err| IoError::new(IoErrorKind::Other, err)) - .for_each({ - let shared = shared.clone(); - let transport = transport.clone(); - let swarm_controller = swarm_controller.clone(); - move |_| { - let _ = shared.network_state.flush_caches_to_disk(); - perform_kademlia_query(shared.clone(), transport.clone(), swarm_controller.clone()) - } - }); - - let final_future = kad_init - .select(discovery) - .map_err(|(err, _)| err) - .and_then(|(_, rest)| rest); - - // Note that we use a Box in order to speed compilation time. - Box::new(final_future) as Box + Send> -} - -/// Performs a kademlia request to a random node. -/// Note that we don't actually care about the results, so the future -/// produces `()`. -fn perform_kademlia_query( - shared: Arc, - transport: T, - swarm_controller: SwarmController + Send>> -) -> Box + Send> - where T: MuxedTransport> + Clone + Send + 'static, - T::MultiaddrFuture: Send + 'static, - T::Dial: Send, - T::Listener: Send, - T::ListenerUpgrade: Send, - T::Incoming: Send, - T::IncomingUpgrade: Send, - To: AsyncRead + AsyncWrite + Send + 'static, - St: MuxedTransport> + Send + Clone + 'static, - St::Dial: Send, - St::MultiaddrFuture: Send, - St::Listener: Send, - St::ListenerUpgrade: Send, - St::Incoming: Send, - St::IncomingUpgrade: Send, - C: Send + 'static { - // Query the node IDs that are closest to a random ID. - // Note that the randomness doesn't have to be secure, as this only - // influences which nodes we end up being connected to. - let random_key = PublicKey::Ed25519((0 .. 32) - .map(|_| -> u8 { rand::random() }).collect()); - let random_peer_id = random_key.into_peer_id(); - trace!(target: "sub-libp2p", "Start kademlia discovery for {:?}", random_peer_id); - - let future = shared.clone() - .kad_system - .find_node(random_peer_id, { - let shared = shared.clone(); - let transport = transport.clone(); - let swarm_controller = swarm_controller.clone(); - move |who| obtain_kad_connection(shared.clone(), who.clone(), - transport.clone(), swarm_controller.clone()) - }) - .filter_map(move |event| - match event { - KadQueryEvent::PeersReported(peers) => { - for peer in peers { - let connected = match peer.connection_ty { - KadConnectionType::NotConnected => false, - KadConnectionType::Connected => true, - KadConnectionType::CanConnect => true, - KadConnectionType::CannotConnect => continue, - }; - - for addr in peer.multiaddrs { - shared.network_state.add_kad_discovered_addr( - &peer.node_id, - addr, - connected - ); - } - } - None - }, - KadQueryEvent::Finished(_) => Some(()), - } - ) - .into_future() - .map_err(|(err, _)| err) - .map(|_| ()); - - // Note that we use a `Box` in order to speed up compilation. - Box::new(future) as Box + Send> -} - -/// Connects to additional nodes, if necessary. -fn connect_to_nodes( - shared: Arc, - base_transport: T, - swarm_controller: &SwarmController + Send>> -) - where T: MuxedTransport> + Clone + Send + 'static, - T::MultiaddrFuture: Send + 'static, - T::Dial: Send, - T::Listener: Send, - T::ListenerUpgrade: Send, - T::Incoming: Send, - T::IncomingUpgrade: Send, - To: AsyncRead + AsyncWrite + Send + 'static, - St: MuxedTransport> + Clone + Send + 'static, - St::Dial: Send, - St::MultiaddrFuture: Send, - St::Listener: Send, - St::ListenerUpgrade: Send, - St::Incoming: Send, - St::IncomingUpgrade: Send, - C: Send + 'static { - let (addrs, _will_change) = shared.network_state.outgoing_connections_to_attempt(); - - for (peer, addr) in addrs.into_iter() { - // Try to dial that node for each registered protocol. Since dialing - // upgrades the connection to use multiplexing, dialing multiple times - // should automatically open multiple substreams. - for proto in shared.protocols.0.clone().into_iter() { - open_peer_custom_proto( - shared.clone(), - base_transport.clone(), - addr.clone(), - Some(peer.clone()), - proto, - swarm_controller - ) - } } -} - -/// Dials the given address for the given protocol and using the given `swarm_controller`. -/// -/// This function *always* performs a dial, and doesn't check whether we already have an existing -/// connection to the remote. This is expected to be checked by the caller. -/// -/// The dialing will fail if the obtained peer ID doesn't match the expected ID. This is an -/// opinionated decision, as we could just let the new connection through. But we decide not to. -/// If `None` is passed for the expected peer ID, we always accept the connection. -fn open_peer_custom_proto( - shared: Arc, - base_transport: T, - addr: Multiaddr, - expected_peer_id: Option, - proto: RegisteredProtocol>, - swarm_controller: &SwarmController + Send>> -) - where T: MuxedTransport> + Clone + Send + 'static, - T::MultiaddrFuture: Send + 'static, - T::Dial: Send, - T::Listener: Send, - T::ListenerUpgrade: Send, - T::Incoming: Send, - T::IncomingUpgrade: Send, - To: AsyncRead + AsyncWrite + Send + 'static, - St: MuxedTransport> + Clone + Send + 'static, - St::Dial: Send, - St::MultiaddrFuture: Send, - St::Listener: Send, - St::ListenerUpgrade: Send, - St::Incoming: Send, - St::IncomingUpgrade: Send, - C: Send + 'static, -{ - let proto_id = proto.id(); - - let with_proto = base_transport - .and_then(move |out, endpoint, client_addr| { - let node_index = out.node_index; - upgrade::apply(out.socket, proto, endpoint, client_addr) - .map(move |(custom, client_addr)| - ((node_index, FinalUpgrade::Custom(node_index, custom)), client_addr)) - }); - - let with_timeout = TransportTimeout::new(with_proto, Duration::from_secs(20)); - - if let Some(expected_peer_id) = expected_peer_id { - let expected_node_index = match shared.network_state.assign_node_index(&expected_peer_id) { - Ok(i) => i, - Err(_) => return, - }; - - let unique_connec = match shared.network_state.custom_proto(expected_node_index, proto_id) { - Some(uc) => uc, - None => return, - }; - - let with_peer_check = with_timeout - .and_then(move |(node_index, custom), _, client_addr| { - if node_index == expected_node_index { - future::ok((custom, client_addr)) - } else { - future::err(IoError::new(IoErrorKind::ConnectionRefused, "Peer id mismatch")) - } - }); - - let with_disconnect = with_peer_check - .map_err({ - let shared = shared.clone(); - move |err| { - // If we fail to reach the dot protocol, drop the peer altogether. - shared.network_state.drop_peer(expected_node_index); - err - } - }); - - trace!(target: "sub-libp2p", - "Opening connection to {:?} through {} with proto {:?}", - expected_peer_id, - addr, - proto_id - ); - let _ = unique_connec.dial(swarm_controller, &addr, with_disconnect); - - } else { - let trans = with_timeout.map(|(_, out), _| out); - if let Err(addr) = swarm_controller.dial(addr, trans) { - debug!(target: "sub-libp2p", "Failed to dial {:?}", addr); - } + fn is_expired(&self) -> bool { + let peer_infos = self.peer_infos.lock(); + let current_peer = self.current_peer.as_ref() + .expect("Called is_expired outside of a context"); + !peer_infos.contains_key(current_peer) } -} - -/// Obtain a Kademlia connection to the given peer. -fn obtain_kad_connection( - shared: Arc, - who: PeerstorePeerId, - transport: T, - swarm_controller: SwarmController + Send>> -) -> Box + Send> - where T: MuxedTransport> + Clone + Send + 'static, - T::MultiaddrFuture: Send + 'static, - T::Dial: Send, - T::Listener: Send, - T::ListenerUpgrade: Send, - T::Incoming: Send, - T::IncomingUpgrade: Send, - To: AsyncRead + AsyncWrite + Send + 'static, - St: MuxedTransport> + Clone + Send + 'static, - St::Dial: Send, - St::MultiaddrFuture: Send, - St::Listener: Send, - St::ListenerUpgrade: Send, - St::Incoming: Send, - St::IncomingUpgrade: Send, - C: Send + 'static { - let kad_upgrade = shared.kad_upgrade.clone(); - let transport = transport - .and_then(move |out, endpoint, client_addr| { - let node_index = out.node_index; - upgrade::apply(out.socket, kad_upgrade.clone(), endpoint, client_addr) - .map(move |((ctrl, fut), addr)| (FinalUpgrade::Kad(node_index, ctrl, fut), addr)) - }); - // This function consists in trying all the addresses we know one by one until we find - // one that works. - // - // This `future` returns a Kad controller, or an error if all dialing attempts failed. - let future = stream::iter_ok(shared.network_state.addrs_of_peer(&who)) - .and_then(move |addr| { - let node_index = shared.network_state.assign_node_index(&who)?; - let kad = match shared.network_state.kad_connection(node_index) { - Some(kad) => kad, - None => return Err(IoError::new(IoErrorKind::Other, "node no longer exists")), - }; - Ok((kad, addr)) - }) - .and_then(move |(unique_connec, addr)| { - unique_connec.dial(&swarm_controller, &addr.0, transport.clone()) - }) - .then(|result| -> Result<_, ()> { Ok(result.ok()) }) - .filter_map(|result| result) - .into_future() - .map_err(|_| -> IoError { unreachable!("all items always succeed") }) - .and_then(|(kad, _)| kad.ok_or_else(|| IoErrorKind::ConnectionRefused.into())); - - // Note that we use a Box in order to speed up compilation. - Box::new(future) as Box + Send> -} - -/// Processes the identification information that we received about a node. -fn process_identify_info( - shared: &Shared, - node_index: NodeIndex, - info: &IdentifyInfo, - observed_addr: &Multiaddr, -) { - trace!(target: "sub-libp2p", "Received identification info from #{}", node_index); - - shared.network_state.set_node_info(node_index, info.agent_version.clone()); - - for original_listened_addr in &*shared.original_listened_addr.read() { - // TODO: we're using a hack here ; ideally we would call `nat_traversal` on our - // `Transport` ; but that's complicated to pass around ; we could put it in a `Box` in - // `Shared`, but since our transport doesn't implement `Send` (libp2p doesn't implement - // `Send` on modifiers), we can't. Instead let's just recreate a transport locally every - // time. - let transport = libp2p::tcp::TcpConfig::new(); - if let Some(mut ext_addr) = transport.nat_traversal(original_listened_addr, &observed_addr) { - let mut listened_addrs = shared.listened_addrs.write(); - if !listened_addrs.iter().any(|a| a == &ext_addr) { - trace!(target: "sub-libp2p", - "NAT traversal: remote observes us as {}; registering {} as one of our own addresses", - observed_addr, - ext_addr - ); - listened_addrs.push(ext_addr.clone()); - ext_addr.append(AddrComponent::P2P(shared.kad_system - .local_peer_id().clone().into())); - info!(target: "sub-libp2p", "New external node address: {}", ext_addr); - } - } + fn register_timer(&self, token: usize, duration: Duration) + -> Result<(), Error> { + let handler = self.registered_custom + .find_protocol(self.protocol) + .ok_or(ErrorKind::BadProtocol)? + .custom_data() + .clone(); + self.timeouts_register_tx + .unbounded_send((duration, (handler, self.protocol, token))) + .map_err(|err| ErrorKind::Io(IoError::new(IoErrorKind::Other, err)))?; + Ok(()) } - for addr in info.listen_addrs.iter() { - if let Some(node_id) = shared.network_state.node_id_from_index(node_index) { - shared.network_state.add_kad_discovered_addr(&node_id, addr.clone(), true); - } + fn peer_client_version(&self, peer: NodeIndex) -> String { + // Devp2p returns "unknown" on unknown peer ID, so we do the same. + // TODO: implement more directly, without going through `session_info` + self.session_info(peer) + .map(|info| info.client_version) + .unwrap_or_else(|| "unknown".to_string()) } -} - -/// Returns a future that regularly pings every peer we're connected to. -/// If a peer doesn't respond after a while, we disconnect it. -fn start_periodic_updates( - shared: Arc, - transport: T, - swarm_controller: SwarmController + Send>> -) -> Box + Send> - where T: MuxedTransport> + Clone + Send + 'static, - T::MultiaddrFuture: Send + 'static, - T::Dial: Send, - T::Listener: Send, - T::ListenerUpgrade: Send, - T::Incoming: Send, - T::IncomingUpgrade: Send, - To: AsyncRead + AsyncWrite + Send + 'static, - St: MuxedTransport> + Clone + Send + 'static, - St::Dial: Send, - St::MultiaddrFuture: Send, - St::Listener: Send, - St::ListenerUpgrade: Send, - St::Incoming: Send, - St::IncomingUpgrade: Send, - C: Send + 'static { - let ping_transport = transport.clone() - .and_then(move |out, endpoint, client_addr| { - let node_index = out.node_index; - upgrade::apply(out.socket, ping::Ping, endpoint, client_addr) - .map(move |(stream, addr)| (FinalUpgrade::from((node_index, stream)), addr)) - }); - - let identify_transport = transport - .and_then(move |out, endpoint, client_addr| { - let node_index = out.node_index; - upgrade::apply(out.socket, IdentifyProtocolConfig, endpoint, client_addr) - .map(move |(id, addr)| { - let fin = match id { - IdentifyOutput::RemoteInfo { info, observed_addr } => - FinalUpgrade::IdentifyDialer(node_index, info, observed_addr), - IdentifyOutput::Sender { .. } => unreachable!("can't reach that on the dialer side"), - }; - (fin, addr) - }) - }); - - let fut = Interval::new(Instant::now() + Duration::from_secs(5), Duration::from_secs(30)) - .map_err(|err| IoError::new(IoErrorKind::Other, err)) - .for_each(move |_| periodic_updates( - shared.clone(), - ping_transport.clone(), - identify_transport.clone(), - &swarm_controller - )) - .then(|val| { - warn!(target: "sub-libp2p", "Periodic updates stream has stopped: {:?}", val); - val - }); - - // Note that we use a Box in order to speed compilation time. - Box::new(fut) as Box + Send> -} - -/// Pings all the nodes we're connected to and disconnects any node that -/// doesn't respond. Identifies nodes that need to be identified. Returns -/// a `Future` when all the pings have either suceeded or timed out. -fn periodic_updates( - shared: Arc, - ping_transport: Tp, - identify_transport: Tid, - swarm_controller: &SwarmController + Send>> -) -> Box + Send> - where Tp: MuxedTransport> + Clone + Send + 'static, - Tp::MultiaddrFuture: Send + 'static, - Tp::Dial: Send, - Tp::MultiaddrFuture: Send, - Tp::Listener: Send, - Tp::ListenerUpgrade: Send, - Tp::Incoming: Send, - Tp::IncomingUpgrade: Send, - Tid: MuxedTransport> + Clone + Send + 'static, - Tid::MultiaddrFuture: Send + 'static, - Tid::Dial: Send, - Tid::MultiaddrFuture: Send, - Tid::Listener: Send, - Tid::ListenerUpgrade: Send, - Tid::Incoming: Send, - Tid::IncomingUpgrade: Send, - St: MuxedTransport> + Clone + Send + 'static, - St::Dial: Send, - St::MultiaddrFuture: Send, - St::Listener: Send, - St::ListenerUpgrade: Send, - St::Incoming: Send, - St::IncomingUpgrade: Send, - C: Send + 'static { - trace!(target: "sub-libp2p", "Periodic update cycle"); - - let mut ping_futures = Vec::new(); - for PeriodicUpdate { node_index, peer_id, address, pinger, identify } in - shared.network_state.cleanup_and_prepare_updates() { - let shared = shared.clone(); + fn session_info(&self, peer: NodeIndex) -> Option { + let peer_infos = self.peer_infos.lock(); + let info = match peer_infos.get(&peer) { + Some(info) => info, + None => return None, + }; - let fut = pinger - .dial(&swarm_controller, &address, ping_transport.clone()) - .and_then(move |mut p| { - trace!(target: "sub-libp2p", "Pinging peer #{} aka. {:?}", node_index, peer_id); - p.ping() - .map(move |()| peer_id) - .map_err(|err| IoError::new(IoErrorKind::Other, err)) - }); - let ping_start_time = Instant::now(); - let fut = Timeout::new_at(fut, ping_start_time + Duration::from_secs(30)) - .then(move |val| - match val { - Err(err) => { - trace!(target: "sub-libp2p", "Error while pinging #{:?} => {:?}", node_index, err); - shared.network_state.report_ping_failed(node_index); - // Return Ok, otherwise we would close the ping service - Ok(()) - }, - Ok(who) => { - let elapsed = ping_start_time.elapsed(); - trace!(target: "sub-libp2p", "Pong from #{:?} in {:?}", who, elapsed); - shared.network_state.report_ping_duration(node_index, elapsed); - Ok(()) - } - } - ); - ping_futures.push(fut); + let protocol_id = self.protocol; + let protocol_version = match info.protocols.iter().find(|&(ref p, _)| p == &protocol_id) { + Some(&(_, vers)) => vers, + None => return None, + }; - if identify { - // Ignore dialing errors, as identifying is only about diagnostics. - trace!(target: "sub-libp2p", "Attempting to identify #{}", node_index); - let _ = swarm_controller.dial(address, identify_transport.clone()); - } + Some(SessionInfo { + id: None, // TODO: ???? what to do??? wrong format! + client_version: info.client_version.clone().take().unwrap_or(String::new()), + protocol_version: From::from(protocol_version), + capabilities: Vec::new(), // TODO: list of supported protocols ; hard + peer_capabilities: Vec::new(), // TODO: difference with `peer_capabilities`? + ping: info.ping, + originated: info.endpoint == Endpoint::Dialer, + remote_address: info.remote_address.as_ref().map(|a| a.to_string()).unwrap_or_default(), + local_address: info.local_address.as_ref().map(|a| a.to_string()) + .unwrap_or(String::new()), + }) } - let future = future::loop_fn(ping_futures, |ping_futures| { - if ping_futures.is_empty() { - let fut = future::ok(future::Loop::Break(())); - return future::Either::A(fut) - } - - let fut = future::select_all(ping_futures) - .map(|((), _, rest)| future::Loop::Continue(rest)) - .map_err(|(err, _, _)| err); - future::Either::B(fut) - }); - - // Note that we use a Box in order to speed up compilation. - Box::new(future) as Box + Send> -} + fn protocol_version(&self, protocol: ProtocolId, peer: NodeIndex) -> Option { + let peer_infos = self.peer_infos.lock(); + let info = match peer_infos.get(&peer) { + Some(info) => info, + None => return None, + }; -/// Since new protocols are added after the networking starts, we have to load the protocols list -/// in a lazy way. This is what this wrapper does. -#[derive(Clone)] -struct DelayedProtosList(Arc); -// `Maf` is short for `MultiaddressFuture` -impl ConnectionUpgrade for DelayedProtosList -where C: AsyncRead + AsyncWrite + Send + 'static, // TODO: 'static :-/ - Maf: Future + Send + 'static, // TODO: 'static :( -{ - type NamesIter = > as ConnectionUpgrade>::NamesIter; - type UpgradeIdentifier = > as ConnectionUpgrade>::UpgradeIdentifier; + let protocol_version = match info.protocols.iter().find(|&(ref p, _)| p == &protocol) { + Some(&(_, vers)) => vers, + None => return None, + }; - fn protocol_names(&self) -> Self::NamesIter { - ConnectionUpgrade::::protocol_names(&self.0.protocols) + Some(protocol_version) } - type Output = > as ConnectionUpgrade>::Output; - type MultiaddrFuture = > as ConnectionUpgrade>::MultiaddrFuture; - type Future = > as ConnectionUpgrade>::Future; - - #[inline] - fn upgrade(self, socket: C, id: Self::UpgradeIdentifier, endpoint: Endpoint, - remote_addr: Maf) -> Self::Future - { - self.0.protocols - .clone() - .upgrade(socket, id, endpoint, remote_addr) + fn subprotocol_name(&self) -> ProtocolId { + self.protocol.clone() } } diff --git a/substrate/network-libp2p/src/service_task.rs b/substrate/network-libp2p/src/service_task.rs new file mode 100644 index 0000000000000..3d996a5f393fa --- /dev/null +++ b/substrate/network-libp2p/src/service_task.rs @@ -0,0 +1,961 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +use bytes::Bytes; +use custom_proto::RegisteredProtocols; +use fnv::{FnvHashMap, FnvHashSet}; +use futures::{prelude::*, task, Stream}; +use futures::sync::{oneshot, mpsc}; +use libp2p::{Multiaddr, PeerId}; +use libp2p::core::{Endpoint, PublicKey}; +use libp2p::core::nodes::swarm::ConnectedPoint; +use libp2p::kad::{KadSystem, KadSystemConfig, KadConnecController, KadPeer}; +use libp2p::kad::{KadConnectionType, KadQueryEvent}; +use parking_lot::Mutex; +use rand; +use secret::obtain_private_key; +use std::io::{Error as IoError, ErrorKind as IoErrorKind}; +use std::iter; +use std::net::SocketAddr; +use std::path::Path; +use std::sync::Arc; +use std::time::{Duration, Instant}; +use swarm::{self, Swarm, SwarmEvent}; +use topology::{DisconnectReason, NetTopology}; +use tokio_timer::{Delay, Interval}; +use {Error, ErrorKind, NetworkConfiguration, NetworkProtocolHandler, NodeIndex, parse_str_addr}; +use {NonReservedPeerMode, PacketId, ProtocolId}; + +// File where the network topology is stored. +const NODES_FILE: &str = "nodes.json"; +// Duration during which a peer is disabled. +const PEER_DISABLE_DURATION: Duration = Duration::from_secs(5 * 60); + +/// Starts the substrate libp2p service. +/// +/// Returns a stream that must be polled regularly in order for the networking to function. +pub fn start_service( + config: NetworkConfiguration, + registered_custom: Arc>>, +) -> Result { + // Private and public keys configuration. + let local_private_key = obtain_private_key(&config)?; + let local_public_key = local_private_key.to_public_key(); + let local_peer_id = local_public_key.clone().into_peer_id(); + + // Build the swarm. + let mut swarm = swarm::start_swarm(registered_custom, local_private_key)?; + + // Listen on multiaddresses. + for addr in &config.listen_addresses { + match swarm.listen_on(addr.clone()) { + Ok(new_addr) => debug!(target: "sub-libp2p", "Libp2p listening on {}", new_addr), + Err(_) => { + warn!(target: "sub-libp2p", "Can't listen on {}, protocol not supported", addr); + return Err(ErrorKind::BadProtocol.into()) + }, + } + } + + // Register the external addresses provided by the user. + for addr in &config.public_addresses { + swarm.add_external_address(addr.clone()); + } + + // Initialize the topology of the network. + let mut topology = if let Some(ref path) = config.net_config_path { + let path = Path::new(path).join(NODES_FILE); + debug!(target: "sub-libp2p", "Initializing peer store for JSON file {:?}", path); + NetTopology::from_file(path) + } else { + debug!(target: "sub-libp2p", "No peers file configured ; peers won't be saved"); + NetTopology::memory() + }; + + // Create the Kademlia system, containing the kbuckets. + let kad_system = KadSystem::without_init(KadSystemConfig { + parallelism: 3, + local_peer_id, + kbuckets_timeout: Duration::from_secs(600), + request_timeout: Duration::from_secs(10), + known_initial_peers: iter::empty(), + }); + + // Add the bootstrap nodes to the topology and connect to them. + for bootnode in config.boot_nodes.iter() { + match parse_str_addr(bootnode) { + Ok((peer_id, addr)) => { + topology.add_bootstrap_addr(&peer_id, addr.clone()); + kad_system.update_kbuckets(peer_id.clone()); + if let Err(_) = swarm.ensure_connection(peer_id, addr) { + warn!(target: "sub-libp2p", "Failed to dial boot node: {}", bootnode); + } + }, + Err(_) => { + // If the format of the bootstrap node is not a multiaddr, try to parse it as + // a `SocketAddr`. This corresponds to the format `IP:PORT`. + let addr = match bootnode.parse::() { + Ok(SocketAddr::V4(socket)) => multiaddr![IP4(*socket.ip()), TCP(socket.port())], + Ok(SocketAddr::V6(socket)) => multiaddr![IP6(*socket.ip()), TCP(socket.port())], + _ => { + warn!(target: "sub-libp2p", "Not a valid bootnode address: {}", bootnode); + continue; + } + }; + + debug!(target: "sub-libp2p", "Dialing {} with no peer id", addr); + if let Err(addr) = swarm.dial(addr) { + warn!(target: "sub-libp2p", "Bootstrap address not supported: {}", addr); + } + }, + } + } + + // Initialize the reserved peers. + let mut reserved_peers = FnvHashSet::default(); + for reserved in config.reserved_nodes.iter() { + match parse_str_addr(reserved) { + Ok((peer_id, addr)) => { + reserved_peers.insert(peer_id.clone()); + topology.add_bootstrap_addr(&peer_id, addr.clone()); + if let Err(_) = swarm.ensure_connection(peer_id, addr) { + warn!(target: "sub-libp2p", "Failed to dial reserved node: {}", reserved); + } + }, + Err(_) => + // TODO: also handle the `IP:PORT` format ; however we need to somehow add the + // reserved ID to `reserved_peers` at some point + warn!(target: "sub-libp2p", "Not a valid reserved node address: {}", reserved), + } + } + + debug!(target: "sub-libp2p", "Topology started with {} entries", topology.num_peers()); + + let (kad_new_ctrl_req_tx, kad_new_ctrl_req_rx) = mpsc::unbounded(); + + Ok(Service { + swarm, + max_incoming_connections: config.max_peers.saturating_sub(config.min_peers) as usize, + max_outgoing_connections: config.min_peers as usize, + topology, + nodes_addresses: Default::default(), + disabled_peers: Default::default(), + reserved_peers, + reserved_only: config.non_reserved_mode == NonReservedPeerMode::Deny, + kad_system, + kad_pending_ctrls: Default::default(), + kad_new_ctrl_req_tx, + kad_new_ctrl_req_rx, + kad_queries: Vec::with_capacity(1), + next_connect_to_nodes: Delay::new(Instant::now()), + next_kad_random_query: Interval::new(Instant::now() + Duration::from_secs(5), Duration::from_secs(45)), + cleanup: Interval::new_interval(Duration::from_secs(60)), + injected_events: Vec::new(), + to_notify: None, + }) +} + +/// Event produced by the service. +pub enum ServiceEvent { + /// We have successfully connected to a new node. + NewNode { + /// Index that was attributed for this node. Will be used for all further interaction with + /// it. + node_index: NodeIndex, + /// Public key of the node as a peer id. + peer_id: PeerId, + /// Whether we dialed the node or if it came to us. Should be used only for statistics + /// purposes. + endpoint: ConnectedPoint, + }, + + /// Closed connection to a node. + /// + /// It is guaranteed that this node has been opened with a `NewNode` event beforehand. However + /// not all `ClosedCustomProtocol` events have been dispatched. + NodeClosed { + /// Index of the node. + node_index: NodeIndex, + /// List of custom protocols that were still open. + closed_custom_protocols: Vec, + }, + + /// Report the duration of the ping for the given node. + PingDuration(NodeIndex, Duration), + + /// Report the address of a node if it was not previously known. + NodeAddress { + /// Index of the node. + node_index: NodeIndex, + /// Address of the node. + address: Multiaddr, + }, + + /// Report information about the node. + NodeInfos { + /// Index of the node. + node_index: NodeIndex, + /// The client version. Note that it can be anything and should not be trusted. + client_version: String, + }, + + /// A custom protocol substream has been opened with a node. + OpenedCustomProtocol { + /// Index of the node. + node_index: NodeIndex, + /// Protocol that has been opened. + protocol: ProtocolId, + /// Version of the protocol that was opened. + version: u8, + }, + + /// A custom protocol substream has been closed. + ClosedCustomProtocol { + /// Index of the node. + node_index: NodeIndex, + /// Protocol that has been closed. + protocol: ProtocolId, + }, + + /// Receives a message on a custom protocol stream. + CustomMessage { + /// Index of the node. + node_index: NodeIndex, + /// Protocol which generated the message. + protocol_id: ProtocolId, + /// Identifier of the packet. + packet_id: u8, + /// Data that has been received. + data: Bytes, + }, +} + +/// Network service. Must be polled regularly in order for the networking to work. +pub struct Service { + /// Stream of events of the swarm. + swarm: Swarm>, + + /// Maximum number of incoming non-reserved connections, taken from the config. + max_incoming_connections: usize, + + /// Maximum number of outgoing non-reserved connections, taken from the config. + max_outgoing_connections: usize, + + /// For each node we're connected to, its address if known. + nodes_addresses: FnvHashMap, + + /// If true, only reserved peers can connect. + reserved_only: bool, + + /// List of the IDs of the reserved peers. + reserved_peers: FnvHashSet, + + /// List of the IDs of disabled peers, and when the ban expires. + /// Purged at a regular interval. + disabled_peers: FnvHashMap, + + /// Topology of the network. + topology: NetTopology, + + /// Handles the Kademlia queries. + // TODO: put the kbuckets in the topology instead + kad_system: KadSystem, + + /// List of Kademlia controller we want to open. + /// + /// A clone of tihs `Arc` is stored in each Kademlia query stream. + // TODO: use a better container? + kad_pending_ctrls: Arc>>>>, + + /// Sender whenever we inserted an entry in `kad_pending_ctrls`, so that we can process it. + kad_new_ctrl_req_tx: mpsc::UnboundedSender, + /// Receiver side of `kad_new_ctrl_req_tx`. + kad_new_ctrl_req_rx: mpsc::UnboundedReceiver, + + /// Active Kademlia queries. + kad_queries: Vec>, Error = IoError> + Send>>, + + /// Future that will fire when we need to connect to new nodes. + next_connect_to_nodes: Delay, + + /// Stream that fires when we need to perform the next Kademlia query. + next_kad_random_query: Interval, + + /// Stream that fires when we need to cleanup and flush the topology, and cleanup the disabled + /// peers. + cleanup: Interval, + + /// Events to produce on the Stream. + injected_events: Vec, + + /// Task to notify when elements are added to `injected_events`. + to_notify: Option, +} + +impl Service { + /// Try to add a reserved peer. + pub fn add_reserved_peer(&mut self, peer_id: PeerId, addr: Multiaddr) { + self.reserved_peers.insert(peer_id.clone()); + self.topology.add_bootstrap_addr(&peer_id, addr.clone()); + let _ = self.swarm.ensure_connection(peer_id, addr); + } + + /// Try to remove a reserved peer. + // TODO: remove `_addr` parameter? + pub fn remove_reserved_peer(&mut self, peer_id: PeerId, _addr: Multiaddr) { + self.reserved_peers.remove(&peer_id); + if self.reserved_only { + if let Some(node_index) = self.swarm.latest_node_by_peer_id(&peer_id) { + self.drop_node(node_index); + } + } + } + + /// Set the non-reserved peer mode. + pub fn set_non_reserved_mode(&mut self, mode: NonReservedPeerMode) { + self.reserved_only = mode == NonReservedPeerMode::Deny; + if self.reserved_only { + // Disconnect the nodes that are not reserved. + let to_disconnect: Vec = self.swarm + .nodes() + .filter(|&n| { + let peer_id = self.swarm.peer_id_of_node(n) + .expect("Logic error ; invalid node index which we just retreived"); + !self.reserved_peers.contains(peer_id) + }) + .collect(); + for node_index in to_disconnect { + self.drop_node(node_index); + } + } else { + self.connect_to_nodes(); + } + } + + /// Sends a message to a peer using the custom protocol. + // TODO: report invalid node index or protocol? + pub fn send_custom_message( + &mut self, + node_index: NodeIndex, + protocol: ProtocolId, + packet_id: PacketId, + data: Vec + ) { + self.swarm.send_custom_message(node_index, protocol, packet_id, data) + } + + /// Disconnects a peer and bans it for a little while. + /// + /// Same as `drop_node`, except that the same peer will not be able to reconnect later. + #[inline] + pub fn ban_node(&mut self, node_index: NodeIndex) { + self.drop_node_inner(node_index, Some(PEER_DISABLE_DURATION)); + } + + /// Disconnects a peer. + /// + /// This is asynchronous and will not immediately close the peer. + /// Corresponding closing events will be generated once the closing actually happens. + #[inline] + pub fn drop_node(&mut self, node_index: NodeIndex) { + self.drop_node_inner(node_index, None); + } + + /// Common implementation of `drop_node` and `ban_node`. + fn drop_node_inner(&mut self, node_index: NodeIndex, disable_duration: Option) { + let peer_id = match self.swarm.peer_id_of_node(node_index) { + Some(pid) => pid.clone(), + None => return, // TODO: report? + }; + + // Kill the node from the swarm, and inject an event about it. + let closed_custom_protocols = self.swarm.drop_node(node_index) + .expect("Checked right above that node is valid"); + self.injected_events.push(ServiceEvent::NodeClosed { + node_index, + closed_custom_protocols, + }); + + if let Some(addr) = self.nodes_addresses.remove(&node_index) { + let reason = if disable_duration.is_some() { + DisconnectReason::Banned + } else { + DisconnectReason::ClosedGracefully + }; + + self.topology.report_disconnected(&addr, reason); + } + + if let Some(disable_duration) = disable_duration { + let timeout = Instant::now() + disable_duration; + self.disabled_peers.insert(peer_id, timeout); + } + + self.connect_to_nodes(); + } + + /// Counts the number of non-reserved ingoing connections. + fn num_ingoing_connections(&self) -> usize { + self.swarm.nodes() + .filter(|&i| self.swarm.node_endpoint(i) == Some(Endpoint::Listener) && + !self.reserved_peers.contains(&self.swarm.peer_id_of_node(i).unwrap())) + .count() + } + + /// Counts the number of non-reserved outgoing connections. + fn num_outgoing_connections(&self) -> usize { + self.swarm.nodes() + .filter(|&i| self.swarm.node_endpoint(i) == Some(Endpoint::Dialer) && + !self.reserved_peers.contains(&self.swarm.peer_id_of_node(i).unwrap())) + .count() + } + + /// Updates the attempted connections to nodes. + /// + /// Also updates `next_connect_to_nodes` with the earliest known moment when we need to + /// update connections again. + fn connect_to_nodes(&mut self) { + // Make sure we are connected or connecting to all the reserved nodes. + for reserved in self.reserved_peers.iter() { + let addrs = self.topology.addrs_of_peer(&reserved); + for (addr, _) in addrs { + let _ = self.swarm.ensure_connection(reserved.clone(), addr.clone()); + } + } + + // Counter of number of connections to open, decreased when we open one. + let mut num_to_open = self.max_outgoing_connections - self.num_outgoing_connections(); + + let (to_try, will_change) = self.topology.addrs_to_attempt(); + for (peer_id, addr) in to_try { + if num_to_open == 0 { + break; + } + + if self.disabled_peers.contains_key(&peer_id) { + continue; + } + + // TODO: it is possible that we are connected to this peer, but the topology + // doesn't know about that because we don't know its multiaddress yet + // TODO: after some changes in libp2p, we can avoid this situation and also remove + // the `num_to_open` variable + match self.swarm.ensure_connection(peer_id.clone(), addr.clone()) { + Ok(true) => (), + Ok(false) => num_to_open -= 1, + Err(_) => () + } + } + + self.next_connect_to_nodes.reset(will_change); + } + + /// Starts a random Kademlia query in order to fill the topology. + /// + /// Query the node IDs that are closest to a random ID. + /// Note that the randomness doesn't have to be secure, as this only influences which nodes we + /// end up being connected to. + fn perform_kad_random_query(&mut self) { + let random_key = PublicKey::Ed25519((0 .. 32) + .map(|_| -> u8 { rand::random() }).collect()); + let random_peer_id = random_key.into_peer_id(); + debug!(target: "sub-libp2p", "Start random Kademlia query for {:?}", random_peer_id); + + let kad_pending_ctrls = self.kad_pending_ctrls.clone(); + let kad_new_ctrl_req_tx = self.kad_new_ctrl_req_tx.clone(); + let stream = self.kad_system + .find_node(random_peer_id, move |who| { + let (tx, rx) = oneshot::channel(); + let mut kad_pending_ctrls = kad_pending_ctrls.lock(); + kad_pending_ctrls.entry(who.clone()).or_insert(Vec::new()).push(tx); + let _ = kad_new_ctrl_req_tx.unbounded_send(who.clone()); + rx.map_err(|_| IoError::new(IoErrorKind::Other, "Couldn't reach peer")) + }); + + self.kad_queries.push(Box::new(stream)); + } + + /// If a remote performs a `FIND_NODE` Kademlia request for `searched`, this function builds + /// the response to send back. + fn build_kademlia_response(&self, searched: &PeerId) -> Vec { + self.kad_system + .known_closest_peers(searched) + .map(|who| { + if who == *self.kad_system.local_peer_id() { + KadPeer { + node_id: who.clone(), + multiaddrs: self.swarm.external_addresses().cloned().collect(), + connection_ty: KadConnectionType::Connected, + } + } else { + let mut addrs = self.topology.addrs_of_peer(&who) + .map(|(a, c)| (a.clone(), c)) + .collect::>(); + let connected = addrs.iter().any(|&(_, conn)| conn); + // The Kademlia protocol of libp2p doesn't allow specifying which address is valid + // and which is outdated, therefore in order to stay honest towards the network + // we only report the addresses we're connected to if we're connected to any. + if connected { + addrs.retain(|&(_, connected)| connected); + } + + KadPeer { + node_id: who.clone(), + multiaddrs: addrs.into_iter().map(|(a, _)| a).collect(), + connection_ty: if connected { + KadConnectionType::Connected + } else { + KadConnectionType::NotConnected + }, + } + } + }) + // TODO: we really want to remove nodes with no multiaddress from + // the results, but a flaw in the Kad protocol of libp2p makes it + // impossible to return empty results ; therefore we must at least + // return ourselves + .filter(|p| p.node_id == *self.kad_system.local_peer_id() || !p.multiaddrs.is_empty()) + .take(20) + .collect::>() + } + + /// Adds a list of peers to the network topology. + fn add_discovered_peers(&mut self, list: impl IntoIterator) { + for peer in list { + let connected = match peer.connection_ty { + KadConnectionType::NotConnected => false, + KadConnectionType::Connected => true, + KadConnectionType::CanConnect => true, + KadConnectionType::CannotConnect => continue, + }; + + self.topology.add_kademlia_discovered_addrs( + &peer.node_id, + peer.multiaddrs.iter().map(|a| (a.clone(), connected)) + ); + } + + // Potentially connect to the newly-discovered nodes. + // TODO: only do so if the topology reports that something new has been added + self.connect_to_nodes(); + } + + /// Handles the swarm opening a connection to the given peer. + /// + /// Returns the `NewNode` event to produce. + /// + /// > **Note**: Must be called from inside `poll()`, otherwise it will panic. + fn handle_connection( + &mut self, + node_index: NodeIndex, + peer_id: PeerId, + endpoint: ConnectedPoint + ) -> Option { + // Reject connections to our own node, which can happen if the DHT contains `127.0.0.1` + // for example. + if &peer_id == self.kad_system.local_peer_id() { + debug!(target: "sub-libp2p", "Rejected connection to/from ourself: {:?}", endpoint); + assert_eq!(self.swarm.drop_node(node_index), Ok(Vec::new())); + if let ConnectedPoint::Dialer { ref address } = endpoint { + self.topology.report_failed_to_connect(address); + } + return None; + } + + // Reject non-reserved nodes if we're in reserved mode. + if self.reserved_only { + if !self.reserved_peers.contains(&peer_id) { + debug!(target: "sub-libp2p", "Rejected non-reserved peer {:?}", peer_id); + assert_eq!(self.swarm.drop_node(node_index), Ok(Vec::new())); + if let ConnectedPoint::Dialer { ref address } = endpoint { + self.topology.report_failed_to_connect(address); + } + return None; + } + } + + // Reject connections from disabled peers. + if let Some(expires) = self.disabled_peers.get(&peer_id) { + if expires > &Instant::now() { + info!(target: "sub-libp2p", "Rejected connection from disabled peer: {:?}", peer_id); + assert_eq!(self.swarm.drop_node(node_index), Ok(Vec::new())); + if let ConnectedPoint::Dialer { ref address } = endpoint { + self.topology.report_failed_to_connect(address); + } + return None; + } + } + + match endpoint { + ConnectedPoint::Listener { ref listen_addr } => { + if self.num_ingoing_connections() < self.max_incoming_connections { + debug!(target: "sub-libp2p", "Connected to {:?} on listener {}", + peer_id, listen_addr); + } else { + info!(target: "sub-libp2p", "Rejected incoming peer {:?} because we are full", peer_id); + assert_eq!(self.swarm.drop_node(node_index), Ok(Vec::new())); + return None; + } + }, + ConnectedPoint::Dialer { ref address } => { + if self.num_outgoing_connections() < self.max_outgoing_connections { + debug!(target: "sub-libp2p", "Connected to {:?} through {}", peer_id, address); + self.topology.report_connected(address, &peer_id); + self.nodes_addresses.insert(node_index, address.clone()); + } else { + debug!(target: "sub-libp2p", "Rejected dialed peer {:?} because we are full", peer_id); + assert_eq!(self.swarm.drop_node(node_index), Ok(Vec::new())); + return None; + } + }, + }; + + if let Err(_) = self.swarm.accept_node(node_index) { + error!(target: "sub-libp2p", "accept_node returned an error"); + } + + // If we're waiting for a Kademlia substream for this peer id, open one. + let kad_pending_ctrls = self.kad_pending_ctrls.lock(); + if kad_pending_ctrls.contains_key(&peer_id) { + let res = self.swarm.open_kademlia(node_index); + debug_assert!(res.is_ok()); + } + drop(kad_pending_ctrls); + + self.kad_system.update_kbuckets(peer_id.clone()); + + Some(ServiceEvent::NewNode { + node_index, + peer_id, + endpoint + }) + } + + /// Processes an event received by the swarm. + /// + /// Optionally returns an event to report back to the outside. + /// + /// > **Note**: Must be called from inside `poll()`, otherwise it will panic. + fn process_network_event( + &mut self, + event: SwarmEvent + ) -> Option { + match event { + SwarmEvent::NodePending { node_index, peer_id, endpoint } => + if let Some(event) = self.handle_connection(node_index, peer_id, endpoint) { + Some(event) + } else { + None + }, + SwarmEvent::NodeClosed { node_index, peer_id, closed_custom_protocols } => { + debug!(target: "sub-libp2p", "Connection to {:?} closed gracefully", peer_id); + if let Some(addr) = self.nodes_addresses.get(&node_index) { + self.topology.report_disconnected(addr, DisconnectReason::ClosedGracefully); + } + self.connect_to_nodes(); + Some(ServiceEvent::NodeClosed { + node_index, + closed_custom_protocols, + }) + }, + SwarmEvent::DialFail { address, error } => { + debug!(target: "sub-libp2p", "Failed to dial address {}: {:?}", address, error); + self.topology.report_failed_to_connect(&address); + self.connect_to_nodes(); + None + }, + SwarmEvent::NodeAddress { node_index, address } => { + let peer_id = self.swarm.peer_id_of_node(node_index) + .expect("Inconsistent state ; got NodeAddress event about non-existing node"); + self.topology.report_connected(&address, &peer_id); + self.nodes_addresses.insert(node_index, address.clone()); + Some(ServiceEvent::NodeAddress { + node_index, + address, + }) + }, + SwarmEvent::UnresponsiveNode { node_index } => { + let closed_custom_protocols = self.swarm.drop_node(node_index) + .expect("Got UnresponsiveNode event about a non-existing node"); + if let Some(addr) = self.nodes_addresses.remove(&node_index) { + self.topology.report_disconnected(&addr, DisconnectReason::ClosedGracefully); + } + Some(ServiceEvent::NodeClosed { + node_index, + closed_custom_protocols, + }) + }, + SwarmEvent::UselessNode { node_index } => { + let peer_id = self.swarm.peer_id_of_node(node_index) + .expect("Got UselessNode event about non-existing node") + .clone(); + let closed_custom_protocols = self.swarm.drop_node(node_index) + .expect("Got UselessNode event about a non-existing node"); + self.topology.report_useless(&peer_id); + if let Some(addr) = self.nodes_addresses.remove(&node_index) { + self.topology.report_disconnected(&addr, DisconnectReason::ClosedGracefully); + } + Some(ServiceEvent::NodeClosed { + node_index, + closed_custom_protocols, + }) + }, + SwarmEvent::PingDuration(node_index, ping) => { + let peer_id = self.swarm.peer_id_of_node(node_index) + .expect("Got PingDuration event about non-existing node"); + self.kad_system.update_kbuckets(peer_id.clone()); + Some(ServiceEvent::PingDuration(node_index, ping)) + }, + SwarmEvent::NodeInfos { node_index, client_version, listen_addrs } => { + let peer_id = self.swarm.peer_id_of_node(node_index) + .expect("Inconsistent state ; got NodeAddress event about non-existing node"); + // TODO: wrong function name + self.topology.add_kademlia_discovered_addrs( + peer_id, + listen_addrs.into_iter().map(|a| (a, false)) + ); + Some(ServiceEvent::NodeInfos { + node_index, + client_version, + }) + }, + SwarmEvent::KadFindNode { node_index, searched, responder } => { + let peer_id = self.swarm.peer_id_of_node(node_index) + .expect("Got KadFindNode event about non-existing node"); + let response = self.build_kademlia_response(&searched); + self.kad_system.update_kbuckets(peer_id.clone()); + responder.respond(response); + None + }, + SwarmEvent::KadOpen { node_index, controller } => { + let peer_id = self.swarm.peer_id_of_node(node_index) + .expect("Got KadOpen event about non-existing node"); + trace!(target: "sub-libp2p", "Opened Kademlia substream with {:?}", peer_id); + self.kad_system.update_kbuckets(peer_id.clone()); + if let Some(list) = self.kad_pending_ctrls.lock().remove(&peer_id) { + for tx in list { + let _ = tx.send(controller.clone()); + } + } + None + }, + SwarmEvent::KadClosed { .. } => { + None + }, + SwarmEvent::OpenedCustomProtocol { node_index, protocol, version } => + Some(ServiceEvent::OpenedCustomProtocol { + node_index, + protocol, + version, + }), + SwarmEvent::ClosedCustomProtocol { node_index, protocol } => + Some(ServiceEvent::ClosedCustomProtocol { + node_index, + protocol, + }), + SwarmEvent::CustomMessage { node_index, protocol_id, packet_id, data } => { + let peer_id = self.swarm.peer_id_of_node(node_index) + .expect("Got CustomMessage event about non-existing node"); + self.kad_system.update_kbuckets(peer_id.clone()); + Some(ServiceEvent::CustomMessage { + node_index, + protocol_id, + packet_id, + data, + }) + }, + } + } + + /// Handles a Kademlia query requesting a Kademlia controller with the given peer. + fn handle_kad_ctrl_request(&mut self, peer_id: PeerId) { + if let Some(node_index) = self.swarm.latest_node_by_peer_id(&peer_id) { + if let Err(_) = self.swarm.open_kademlia(node_index) { + self.kad_pending_ctrls.lock().remove(&peer_id); + } + } else { + let addrs = self.topology.addrs_of_peer(&peer_id); + let mut one_worked = false; + for (addr, _) in addrs { + if let Ok(_) = self.swarm.ensure_connection(peer_id.clone(), addr.clone()) { + one_worked = true; + } + } + if !one_worked { + debug!(target: "sub-libp2p", "Couldn't open Kad substream with {:?} \ + because no address is known", peer_id); + // Closing the senders in order to generate errors on the Kad query. + self.kad_pending_ctrls.lock().remove(&peer_id); + } + } + } + + /// Polls for what happened on the main network side. + fn poll_swarm(&mut self) -> Poll, IoError> { + loop { + match self.swarm.poll() { + Ok(Async::Ready(Some(event))) => + if let Some(event) = self.process_network_event(event) { + return Ok(Async::Ready(Some(event))); + } + Ok(Async::NotReady) => return Ok(Async::NotReady), + Ok(Async::Ready(None)) => unreachable!("The Swarm stream never ends"), + // TODO: this `Err` contains a `Void` ; remove variant when Rust allows that + Err(_) => unreachable!("The Swarm stream never errors"), + } + } + } + + /// Polls the Kademlia system. + fn poll_kademlia(&mut self) -> Poll, IoError> { + // Polls the active Kademlia queries. + // We remove each element from `kad_queries` one by one and add them back if not ready. + for n in (0 .. self.kad_queries.len()).rev() { + let mut query = self.kad_queries.swap_remove(n); + loop { + match query.poll() { + Ok(Async::Ready(Some(KadQueryEvent::PeersReported(list)))) => + self.add_discovered_peers(list), + // We don't actually care about the results + Ok(Async::Ready(Some(KadQueryEvent::Finished(_out)))) => break, + Ok(Async::Ready(None)) => break, + Ok(Async::NotReady) => { + self.kad_queries.push(query); + break; + }, + Err(err) => { + warn!(target: "sub-libp2p", "Kademlia query failed: {:?}", err); + break; + }, + } + } + } + + // Poll the future that fires when we need to perform a random Kademlia query. + loop { + match self.kad_new_ctrl_req_rx.poll() { + Ok(Async::NotReady) => break, + Ok(Async::Ready(Some(peer_id))) => self.handle_kad_ctrl_request(peer_id), + Ok(Async::Ready(None)) => unreachable!("The tx is in self"), + Err(()) => unreachable!("An UnboundedReceiver never errors"), + } + } + + // Poll the future that fires when we need to perform a random Kademlia query. + loop { + match self.next_kad_random_query.poll() { + Ok(Async::NotReady) => break, + Ok(Async::Ready(Some(_))) => self.perform_kad_random_query(), + Ok(Async::Ready(None)) => { + warn!(target: "sub-libp2p", "Kad query timer closed unexpectedly"); + return Ok(Async::Ready(None)); + } + Err(err) => { + warn!(target: "sub-libp2p", "Kad query timer errored: {:?}", err); + return Err(IoError::new(IoErrorKind::Other, err)); + } + } + } + + Ok(Async::NotReady) + } + + // Polls the future that fires when we need to refresh our connections. + fn poll_next_connect_refresh(&mut self) -> Poll, IoError> { + loop { + match self.next_connect_to_nodes.poll() { + Ok(Async::Ready(())) => self.connect_to_nodes(), + Ok(Async::NotReady) => return Ok(Async::NotReady), + Err(err) => { + warn!(target: "sub-libp2p", "Connect to nodes timer errored: {:?}", err); + return Err(IoError::new(IoErrorKind::Other, err)); + } + } + } + } + + /// Polls the stream that fires when we need to cleanup and flush the topology. + fn poll_cleanup(&mut self) -> Poll, IoError> { + loop { + match self.cleanup.poll() { + Ok(Async::NotReady) => return Ok(Async::NotReady), + Ok(Async::Ready(Some(_))) => { + debug!(target: "sub-libp2p", "Cleaning and flushing topology"); + self.topology.cleanup(); + if let Err(err) = self.topology.flush_to_disk() { + warn!(target: "sub-libp2p", "Failed to flush topology: {:?}", err); + } + let now = Instant::now(); + self.disabled_peers.retain(move |_, v| *v < now); + debug!(target: "sub-libp2p", "Topology now contains {} nodes", + self.topology.num_peers()); + }, + Ok(Async::Ready(None)) => { + warn!(target: "sub-libp2p", "Topology flush stream ended unexpectedly"); + return Ok(Async::Ready(None)); + } + Err(err) => { + warn!(target: "sub-libp2p", "Topology flush stream errored: {:?}", err); + return Err(IoError::new(IoErrorKind::Other, err)); + } + } + } + } +} + +impl Drop for Service { + fn drop(&mut self) { + if let Err(err) = self.topology.flush_to_disk() { + warn!(target: "sub-libp2p", "Failed to flush topology: {:?}", err); + } + } +} + +impl Stream for Service { + type Item = ServiceEvent; + type Error = IoError; + + fn poll(&mut self) -> Poll, Self::Error> { + if !self.injected_events.is_empty() { + return Ok(Async::Ready(Some(self.injected_events.remove(0)))); + } + + match self.poll_swarm()? { + Async::Ready(value) => return Ok(Async::Ready(value)), + Async::NotReady => (), + } + + match self.poll_kademlia()? { + Async::Ready(value) => return Ok(Async::Ready(value)), + Async::NotReady => (), + } + + match self.poll_next_connect_refresh()? { + Async::Ready(value) => return Ok(Async::Ready(value)), + Async::NotReady => (), + } + + match self.poll_cleanup()? { + Async::Ready(value) => return Ok(Async::Ready(value)), + Async::NotReady => (), + } + + // The only way we reach this is if we went through all the `NotReady` paths above, + // ensuring the current task is registered everywhere. + self.to_notify = Some(task::current()); + Ok(Async::NotReady) + } +} diff --git a/substrate/network-libp2p/src/swarm.rs b/substrate/network-libp2p/src/swarm.rs new file mode 100644 index 0000000000000..7ae1adc56f8ad --- /dev/null +++ b/substrate/network-libp2p/src/swarm.rs @@ -0,0 +1,1080 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +use bytes::Bytes; +use custom_proto::RegisteredProtocols; +use fnv::FnvHashMap; +use futures::{prelude::*, Stream, sync::mpsc, task}; +use libp2p::{Multiaddr, multiaddr::AddrComponent, PeerId}; +use libp2p::core::{muxing, Endpoint, PublicKey}; +use libp2p::core::nodes::node::Substream; +use libp2p::core::nodes::swarm::{ConnectedPoint, Swarm as Libp2pSwarm}; +use libp2p::core::nodes::swarm::{SwarmEvent as Libp2pSwarmEvent, Peer as SwarmPeer}; +use libp2p::core::transport::boxed::Boxed; +use libp2p::kad::{KadConnecController, KadFindNodeRespond}; +use libp2p::secio; +use node_handler::{NodeEvent, NodeHandler, IdentificationRequest}; +use std::io::{Error as IoError, ErrorKind as IoErrorKind}; +use std::mem; +use std::sync::Arc; +use std::time::Duration; +use tokio_executor; +use transport; +use {Error, NodeIndex, PacketId, ProtocolId}; + +/// Starts a swarm. +/// +/// Returns a stream that must be polled regularly in order for the networking to function. +pub fn start_swarm( + registered_custom: Arc>, + local_private_key: secio::SecioKeyPair, +) -> Result, Error> { + // Private and public keys. + let local_public_key = local_private_key.to_public_key(); + let local_peer_id = local_public_key.clone().into_peer_id(); + + // Build the transport layer. This is what allows us to listen or to reach nodes. + let transport = transport::build_transport(local_private_key); + + // Build the underlying libp2p swarm. + let swarm = Libp2pSwarm::new(transport); + + let (node_tasks_events_tx, node_tasks_events_rx) = mpsc::unbounded(); + Ok(Swarm { + swarm, + local_public_key, + local_peer_id, + listening_addrs: Vec::new(), + registered_custom, + node_by_peer: Default::default(), + nodes_info: Default::default(), + next_node_index: 0, + node_tasks_events_rx, + node_tasks_events_tx, + tasks_to_spawn: Vec::new(), + to_notify: None, + }) +} + +/// Event produced by the swarm. +pub enum SwarmEvent { + /// We have successfully connected to a node. + /// + /// The node is in pending node, and should be accepted by calling `accept_node(node_index)` + /// or denied by calling `drop_node(node_index)`. + NodePending { + /// Index of the node. + node_index: NodeIndex, + /// Public key of the node as a peer id. + peer_id: PeerId, + /// Whether we dialed the node or if it came to us. + endpoint: ConnectedPoint, + }, + + /// Closed connection to a node. + /// + /// It is guaranteed that this node has been opened with a `NewNode` event beforehand. However + /// not all `ClosedCustomProtocol` events have been dispatched. + NodeClosed { + /// Index of the node. + node_index: NodeIndex, + /// Peer id we were connected to. + peer_id: PeerId, + /// List of custom protocols that were still open. + closed_custom_protocols: Vec, + }, + + /// Failed to dial an address. + DialFail { + /// Address that failed. + address: Multiaddr, + /// Reason why we failed. + error: IoError, + }, + + /// Report the duration of the ping for the given node. + PingDuration(NodeIndex, Duration), + + /// Report the address of a node if it was not previously known. + NodeAddress { + /// Index of the node. + node_index: NodeIndex, + /// Address of the node. + address: Multiaddr, + }, + + /// Report information about the node. + NodeInfos { + /// Index of the node. + node_index: NodeIndex, + /// The client version. Note that it can be anything and should not be trusted. + client_version: String, + /// Multiaddresses the node is listening on. + listen_addrs: Vec, + }, + + /// A custom protocol substream has been opened with a node. + OpenedCustomProtocol { + /// Index of the node. + node_index: NodeIndex, + /// Protocol that has been opened. + protocol: ProtocolId, + /// Version of the protocol that was opened. + version: u8, + }, + + /// A custom protocol substream has been closed. + ClosedCustomProtocol { + /// Index of the node. + node_index: NodeIndex, + /// Protocol that has been closed. + protocol: ProtocolId, + }, + + /// Receives a message on a custom protocol stream. + CustomMessage { + /// Index of the node. + node_index: NodeIndex, + /// Protocol which generated the message. + protocol_id: ProtocolId, + /// Identifier of the packet. + packet_id: u8, + /// Data that has been received. + data: Bytes, + }, + + /// The node has been determined to be unresponsive. + UnresponsiveNode { + /// Index of the node. + node_index: NodeIndex, + }, + + /// The node works but we can't do anything useful with it. + UselessNode { + /// Index of the node. + node_index: NodeIndex, + }, + + /// Opened a Kademlia substream with the node. + // TODO: the controller API is bad, but we need to make changes in libp2p to improve that + KadOpen { + /// Index of the node. + node_index: NodeIndex, + /// The Kademlia controller. Allows making queries. + controller: KadConnecController, + }, + + /// The remote wants us to answer a Kademlia `FIND_NODE` request. + /// + /// The `responder` should be used to answer that query. + // TODO: this API with the "responder" is bad, but changing it requires modifications in libp2p + KadFindNode { + /// Index of the node that wants an answer. + node_index: NodeIndex, + /// The value being searched. + searched: PeerId, + /// Object to use to respond to the request. + responder: KadFindNodeRespond, + }, + + /// A Kademlia substream has been closed. + KadClosed { + /// Index of the node. + node_index: NodeIndex, + /// Reason why it has been closed. `Ok` means that it's been closed gracefully. + result: Result<(), IoError>, + }, +} + +/// Network swarm. Must be polled regularly in order for the networking to work. +pub struct Swarm { + /// Stream of events of the swarm. + swarm: Libp2pSwarm, Muxer, ()>, + + /// Public key of the local node. + local_public_key: PublicKey, + + /// Peer id of the local node. + local_peer_id: PeerId, + + /// Addresses we know we're listening on. Only includes NAT traversed addresses. + listening_addrs: Vec, + + /// List of registered custom protocols. + registered_custom: Arc>, + + /// For each peer id, the corresponding node index. + /// This is filled when the swarm receives a node, and is emptied when the node handler closes. + /// + /// Note that we may temporarily have multiple node indices pointing to the same peer ID. This + /// hash map only contains the latest node for each given peer. + /// Nodes that are not in this list should all be in the closing state. + node_by_peer: FnvHashMap, + + /// All the nodes tasks. Must be maintained consistent with `node_by_peer`. + /// + /// # How it works + /// + /// First, the `swarm` generates events about connected nodes. This creates an entry in + /// `nodes_info` and `node_by_peer`, where the node is in pending mode. Accepting the node + /// spawns a background task and puts a sender in `nodes_info`. + /// + /// If the `swarm` tells us that a node is closed, or if the user wants to drop a peer, we + /// destroy that sender, which tells the background task that it needs to stop. + /// + /// Once the background task stops, we remove the entries in `node_by_peer` and `nodes_info`. + /// + /// In order to maintain a consistent state, at no point we should close the sender without + /// removing the peer from the nework first (removing a peer from the network is + /// instantaneous), and at no point should we remove entries before the background task is + /// stopped. + nodes_info: FnvHashMap, + + /// Next key to use when we insert a new entry in `nodes_info`. + next_node_index: NodeIndex, + + /// Events received by the node tasks. If `None`, means that the task finished for this node. + node_tasks_events_rx: mpsc::UnboundedReceiver<(NodeIndex, Option>>)>, + + /// Sending side of `node_tasks_events_rx`. Meant to be cloned and sent to background tasks. + node_tasks_events_tx: mpsc::UnboundedSender<(NodeIndex, Option>>)>, + + /// List of tasks to spawn when we're in a polling context. + tasks_to_spawn: Vec>, + + /// Task to notify when an element is added to `tasks_to_spawn`. + to_notify: Option, +} + +/// Local information about a peer. +struct NodeInfo { + /// The peer id. Must be maintained consistent with the rest of the state. + peer_id: PeerId, + + /// Whether we opened the connection or the remote opened it. + endpoint: Endpoint, + + /// State of the node. + state: NodeState, + + /// List of custom protocol substreams that are open. + open_protocols: Vec, +} + +/// State of the node. +enum NodeState { + /// The node is waiting to be accepted or denied. + /// Contains both ends of a channel, so that we can start appending messages that will be + /// processed once the node gets accepted. + Pending(mpsc::UnboundedSender, mpsc::UnboundedReceiver), + + /// The node is active. The sender can be used to dispatch messages to the background task. + /// Destroying the sender will close the background task. + Accepted(mpsc::UnboundedSender), + + /// The node is closing. We dropped the sender, and thus. + Closing, + + /// The node has been closed by calling `drop_node`. Same as `closing`, except that we must + /// must not generate any event about this node anymore. + Closed, +} + +impl NodeState { + /// Returns the inner sender, if any. + #[inline] + fn as_sender(&mut self) -> Option<&mut mpsc::UnboundedSender> { + match *self { + NodeState::Pending(ref mut tx, _) => Some(tx), + NodeState::Accepted(ref mut tx) => Some(tx), + NodeState::Closing => None, + NodeState::Closed => None, + } + } + + /// Returns `true` for `NodeState::Closed`. + #[inline] + fn is_closed(&self) -> bool { + match *self { + NodeState::Pending(_, _) => false, + NodeState::Accepted(_) => false, + NodeState::Closing => false, + NodeState::Closed => true, + } + } + + /// Switches the state to `Closing`, unless we're already closing or closed. + #[inline] + fn close_if_necessary(&mut self) { + match *self { + NodeState::Pending(_, _) | NodeState::Accepted(_) => (), + NodeState::Closing | NodeState::Closed => return, + }; + + *self = NodeState::Closing; + } +} + +/// Message from the service to one of the background node tasks. +enum OutToTaskMsg { + /// Must call `inject_substream()` on the node handler. + InjectSubstream { + substream: Substream, + endpoint: Endpoint, + }, + + /// Must call `open_kademlia()` on the node handler. + OpenKademlia, + + /// Must call `send_custom_message()` on the node handler. + SendCustomMessage { + protocol: ProtocolId, + packet_id: PacketId, + data: Vec, + }, +} + +type Muxer = muxing::StreamMuxerBox; + +impl Swarm + where TUserData: Clone + Send + Sync + 'static { + /// Start listening on a multiaddr. + #[inline] + pub fn listen_on(&mut self, addr: Multiaddr) -> Result { + match self.swarm.listen_on(addr) { + Ok(mut addr) => { + addr.append(AddrComponent::P2P(self.local_peer_id.clone().into())); + info!(target: "sub-libp2p", "Local node address is: {}", addr); + Ok(addr) + }, + Err(addr) => Err(addr) + } + } + + /// Adds an external address. Sent to other nodes when they query it. + #[inline] + pub fn add_external_address(&mut self, addr: Multiaddr) { + self.listening_addrs.push(addr); + } + + /// Returns an iterator to our known external addresses. + #[inline] + pub fn external_addresses(&self) -> impl Iterator { + self.listening_addrs.iter() + } + + /// Returns all the nodes that are currently active. + #[inline] + pub fn nodes<'a>(&'a self) -> impl Iterator + 'a { + self.nodes_info.keys().cloned() + } + + /// Returns the latest node connected to this peer ID. + #[inline] + pub fn latest_node_by_peer_id(&self, peer_id: &PeerId) -> Option { + self.node_by_peer.get(peer_id).map(|&i| i) + } + + /// Endpoint of the node. + /// + /// Returns `None` if the index is invalid. + #[inline] + pub fn node_endpoint(&self, node_index: NodeIndex) -> Option { + self.nodes_info.get(&node_index).map(|i| i.endpoint) + } + + /// Sends a message to a peer using the custom protocol. + // TODO: report invalid node index or protocol? + pub fn send_custom_message( + &mut self, + node_index: NodeIndex, + protocol: ProtocolId, + packet_id: PacketId, + data: Vec + ) { + if let Some(info) = self.nodes_info.get_mut(&node_index) { + if let Some(ref mut sender) = info.state.as_sender() { + let msg = OutToTaskMsg::SendCustomMessage { protocol, packet_id, data }; + let _ = sender.unbounded_send(msg); + } + } + } + + /// Returns the peer id of a node we're connected to. + #[inline] + pub fn peer_id_of_node(&self, node_index: NodeIndex) -> Option<&PeerId> { + self.nodes_info.get(&node_index).map(|i| &i.peer_id) + } + + /// If we're not already dialing the given peer, start dialing it and return false. + /// If we're dialing, adds the address to the queue of addresses to try (if not already) and + /// return false. + /// If we're already connected, do nothing and return true. + /// + /// Returns an error if the address is not supported. + pub fn ensure_connection(&mut self, peer_id: PeerId, addr: Multiaddr) -> Result { + match self.swarm.peer(peer_id.clone()) { + SwarmPeer::Connected(_) => Ok(true), + SwarmPeer::PendingConnect(mut peer) => { + peer.append_multiaddr_attempt(addr); + Ok(false) + }, + SwarmPeer::NotConnected(peer) => { + trace!(target: "sub-libp2p", "Starting to connect to {:?} through {}", + peer_id, addr); + match peer.connect(addr) { + Ok(_) => Ok(false), + Err(_) => Err(()), + } + }, + } + } + + /// Start dialing an address, not knowing which peer ID to expect. + #[inline] + pub fn dial(&mut self, addr: Multiaddr) -> Result<(), Multiaddr> { + self.swarm.dial(addr) + } + + /// After receiving a `NodePending` event, you should call either `accept_node` or `drop_node` + /// with the specified index. + /// + /// Returns an error if the node index is invalid, or if it was already accepted. + #[inline] + pub fn accept_node(&mut self, node_index: NodeIndex) -> Result<(), ()> { + let info = match self.nodes_info.get_mut(&node_index) { + Some(i) => i, + None => return Err(()), + }; + + let out_commands_rx = match mem::replace(&mut info.state, NodeState::Closing) { + NodeState::Pending(tx, rx) => { + info.state = NodeState::Accepted(tx); + rx + }, + other => { + info.state = other; + return Err(()) + }, + }; + + self.tasks_to_spawn.push(NodeTask { + node_index, + handler: Some(NodeHandler::new(self.registered_custom.clone())), + out_commands_rx, + node_tasks_events_tx: self.node_tasks_events_tx.clone(), + }); + + if let Some(to_notify) = self.to_notify.take() { + to_notify.notify(); + } + + Ok(()) + } + + /// Disconnects a peer. + /// + /// If the peer is connected, this disconnects it. + /// If the peer hasn't been accepted yet, this immediately drops it. + /// + /// Returns the list of custom protocol substreams that were opened. + #[inline] + pub fn drop_node(&mut self, node_index: NodeIndex) -> Result, ()> { + let mut must_remove = false; + + let ret = { + let info = match self.nodes_info.get_mut(&node_index) { + Some(i) => i, + None => { + error!(target: "sub-libp2p", "Trying to close non-existing node #{}", node_index); + return Err(()); + }, + }; + + if let Some(connected) = self.swarm.peer(info.peer_id.clone()).as_connected() { + connected.close(); + } + + // If we don't have a background task yet, remove the entry immediately. + if let NodeState::Pending(_, _) = info.state { + must_remove = true; + Vec::new() + } else { + // There may be events pending on the rx side about this node, so we switch it to + // the `Closed` state in order to know not emit any more event about it. + info.state = NodeState::Closed; + info.open_protocols.clone() + } + }; + + if must_remove { + let info = self.nodes_info.remove(&node_index) + .expect("We checked the key a few lines above"); + self.node_by_peer.remove(&info.peer_id); + } + + Ok(ret) + } + + /// Opens a Kademlia substream with the given node. A `KadOpen` event will later be produced + /// for the given node. + /// + /// If a Kademlia substream is already open, also produces a `KadOpen` event. + /// + /// Returns an error if the node index is invalid. + pub fn open_kademlia(&mut self, node_index: NodeIndex) -> Result<(), ()> { + if let Some(info) = self.nodes_info.get_mut(&node_index) { + if let Some(ref mut sender) = info.state.as_sender() { + let _ = sender.unbounded_send(OutToTaskMsg::OpenKademlia); + Ok(()) + } else { + Err(()) + } + } else { + Err(()) + } + } + + /// Adds an address the given peer observes us as. + fn add_observed_addr(&mut self, peer_id: &PeerId, observed_addr: &Multiaddr) { + for mut addr in self.swarm.nat_traversal(observed_addr) { + // Ignore addresses we already know about. + if self.listening_addrs.iter().any(|a| a == &addr) { + continue; + } + + debug!(target: "sub-libp2p", + "NAT traversal: {:?} observes us as {}; registering {} as one of our own addresses", + peer_id, + observed_addr, + addr + ); + + self.listening_addrs.push(addr.clone()); + addr.append(AddrComponent::P2P(self.local_peer_id.clone().into())); + info!(target: "sub-libp2p", "New external node address: {}", addr); + } + } + + /// Responds to an answer to send back identification information. + fn respond_to_identify_request( + &mut self, + requester: &PeerId, + responder: IdentificationRequest> + ) { + let peer = match self.swarm.peer(requester.clone()).as_connected() { + Some(p) => p, + None => { + debug!(target: "sub-libp2p", "Ignoring identify request from {:?} because we are \ + disconnected", requester); + return; + } + }; + + if let Some(address) = peer.multiaddr() { + trace!(target: "sub-libp2p", "Responding to identify request from {:?}", requester); + responder.respond( + self.local_public_key.clone(), + self.listening_addrs.clone(), + &address, + ); + } else { + // TODO: we have the problem that we don't know the address of the remote instantly + // even though we could ; making this properly requires a lot of changes in libp2p + debug!(target: "sub-libp2p", "Ignoring identify request from {:?} because we its \ + address is not known yet", requester); + } + } + + /// Handles the swarm opening a connection to the given peer. + /// + /// Returns the `NewNode` event to produce. + /// + /// > **Note**: Must be called from inside `poll()`, otherwise it will panic. This method + /// > shouldn't be made public because of this requirement. + fn handle_connection( + &mut self, + peer_id: PeerId, + endpoint: ConnectedPoint + ) -> Option { + let (tx, rx) = mpsc::unbounded(); + + // Assign the node index. + let node_index = self.next_node_index.clone(); + self.next_node_index += 1; + self.node_by_peer.insert(peer_id.clone(), node_index); + self.nodes_info.insert(node_index, NodeInfo { + peer_id: peer_id.clone(), + endpoint: match endpoint { + ConnectedPoint::Listener { .. } => Endpoint::Listener, + ConnectedPoint::Dialer { .. } => Endpoint::Dialer, + }, + state: NodeState::Pending(tx, rx), + open_protocols: Vec::new(), + }); + + Some(SwarmEvent::NodePending { + node_index, + peer_id, + endpoint + }) + } + + /// Handles a swarm event about a newly-opened substream for the given peer. + /// + /// Dispatches the substream to the corresponding task. + fn handle_new_substream( + &mut self, + peer_id: PeerId, + substream: Substream, + endpoint: Endpoint, + ) { + let node_index = match self.node_by_peer.get(&peer_id) { + Some(i) => *i, + None => { + error!(target: "sub-libp2p", "Logic error: new substream for closed node"); + return + }, + }; + + let info = match self.nodes_info.get_mut(&node_index) { + Some(i) => i, + None => { + error!(target: "sub-libp2p", "Logic error: new substream for closed node"); + return + }, + }; + + if let Some(ref mut sender) = info.state.as_sender() { + let _ = sender.unbounded_send(OutToTaskMsg::InjectSubstream { + substream, + endpoint, + }); + } else { + error!(target: "sub-libp2p", "Logic error: no background task for {:?}", peer_id); + } + } + + /// Processes an event received by the swarm. + /// + /// Optionally returns an event to report back to the outside. + /// + /// > **Note**: Must be called from inside `poll()`, otherwise it will panic. This method + /// > shouldn't be made public because of this requirement. + fn process_network_event( + &mut self, + event: Libp2pSwarmEvent, Muxer, ()> + ) -> Option { + match event { + Libp2pSwarmEvent::Connected { peer_id, endpoint } => + if let Some(event) = self.handle_connection(peer_id, endpoint) { + return Some(event); + }, + Libp2pSwarmEvent::Replaced { peer_id, endpoint, .. } => { + let node_index = *self.node_by_peer.get(&peer_id).expect("State inconsistency"); + self.nodes_info.get_mut(&node_index).expect("State inconsistency") + .state.close_if_necessary(); + if let Some(event) = self.handle_connection(peer_id, endpoint) { + return Some(event); + } + }, + Libp2pSwarmEvent::NodeClosed { peer_id, .. } => { + debug!(target: "sub-libp2p", "Connection to {:?} closed gracefully", peer_id); + let node_index = *self.node_by_peer.get(&peer_id).expect("State inconsistency"); + self.nodes_info.get_mut(&node_index).expect("State inconsistency") + .state.close_if_necessary(); + }, + Libp2pSwarmEvent::NodeError { peer_id, error, .. } => { + debug!(target: "sub-libp2p", "Closing {:?} because of error: {:?}", peer_id, error); + let node_index = *self.node_by_peer.get(&peer_id).expect("State inconsistency"); + self.nodes_info.get_mut(&node_index).expect("State inconsistency") + .state.close_if_necessary(); + }, + Libp2pSwarmEvent::DialError { multiaddr, error, .. } => + return Some(SwarmEvent::DialFail { + address: multiaddr, + error, + }), + Libp2pSwarmEvent::UnknownPeerDialError { multiaddr, error } => + return Some(SwarmEvent::DialFail { + address: multiaddr, + error, + }), + Libp2pSwarmEvent::PublicKeyMismatch { + actual_peer_id, + multiaddr, + expected_peer_id, + .. + } => { + debug!(target: "sub-libp2p", "When dialing {:?} through {}, public key mismatch, \ + actual = {:?}", expected_peer_id, multiaddr, actual_peer_id); + return Some(SwarmEvent::DialFail { + address: multiaddr, + error: IoError::new(IoErrorKind::Other, "Public key mismatch"), + }); + }, + Libp2pSwarmEvent::ListenerClosed { listen_addr, result, .. } => { + warn!(target: "sub-libp2p", "Listener closed for {}: {:?}", listen_addr, result); + if self.swarm.listeners().count() == 0 { + warn!(target: "sub-libp2p", "No listener left"); + } + }, + Libp2pSwarmEvent::NodeMultiaddr { peer_id, address: Ok(address) } => { + trace!(target: "sub-libp2p", "Determined the multiaddr of {:?} => {}", + peer_id, address); + if let Some(&node_index) = self.node_by_peer.get(&peer_id) { + return Some(SwarmEvent::NodeAddress { + node_index, + address, + }); + } else { + error!(target: "sub-libp2p", "Logic error: no index for {:?}", peer_id); + } + }, + Libp2pSwarmEvent::NodeMultiaddr { peer_id, address: Err(err) } => + trace!(target: "sub-libp2p", "Error when determining the multiaddr of {:?} => {:?}", + peer_id, err), + Libp2pSwarmEvent::IncomingConnection { listen_addr } => + trace!(target: "sub-libp2p", "Incoming connection on listener {}", listen_addr), + Libp2pSwarmEvent::IncomingConnectionError { listen_addr, error } => + trace!(target: "sub-libp2p", "Incoming connection on listener {} errored: {:?}", + listen_addr, error), + Libp2pSwarmEvent::InboundSubstream { peer_id, substream } => + self.handle_new_substream(peer_id, substream, Endpoint::Listener), + Libp2pSwarmEvent::OutboundSubstream { peer_id, substream, .. } => + self.handle_new_substream(peer_id, substream, Endpoint::Dialer), + Libp2pSwarmEvent::InboundClosed { .. } => {}, + Libp2pSwarmEvent::OutboundClosed { .. } => {}, + } + + None + } + + /// Polls for what happened on the main network side. + fn poll_network(&mut self) -> Poll, IoError> { + loop { + match self.swarm.poll() { + Ok(Async::Ready(Some(event))) => + if let Some(event) = self.process_network_event(event) { + return Ok(Async::Ready(Some(event))); + } + Ok(Async::NotReady) => return Ok(Async::NotReady), + Ok(Async::Ready(None)) => unreachable!("The Swarm stream never ends"), + // TODO: this `Err` contains a `Void` ; remove variant when Rust allows that + Err(_) => unreachable!("The Swarm stream never errors"), + } + } + } + + /// Polls for what happened on the background node tasks. + fn poll_node_tasks(&mut self) -> Poll, IoError> { + loop { + match self.node_tasks_events_rx.poll() { + Ok(Async::NotReady) => return Ok(Async::NotReady), + Ok(Async::Ready(Some((node_index, event)))) => + if let Some(event) = self.handle_node_event(node_index, event) { + return Ok(Async::Ready(Some(event))); + }, + Ok(Async::Ready(None)) => unreachable!("The tx is in self so the rx never closes"), + Err(()) => unreachable!("An UnboundedReceiver never errors"), + } + } + } + + /// Processes an event obtained by a node background task. + /// + /// If the `event` is `None`, that means that the background task finished. + /// + /// Optionally returns an event that the service must emit. + /// + /// > **Note**: The event **must** have been produced by a background task, otherwise state + /// > inconsistencies will likely happen. + fn handle_node_event( + &mut self, + node_index: NodeIndex, + event: Option>> + ) -> Option { + // Obtain the peer id and whether the node has been closed earlier. + // If the node has been closed, do not generate any additional event about it. + let (peer_id, node_is_closed) = { + let info = self.nodes_info.get_mut(&node_index) + .expect("Handlers are created when we fill nodes_info, and nodes_info is cleared \ + only when a background task ends"); + (info.peer_id.clone(), info.state.is_closed()) + }; + + match event { + None => { + let _info = self.nodes_info.remove(&node_index).expect("Inconsistent state"); + // It is possible that the entry in `node_by_peer` doesn't match our node, if a new + // node was created with the same peer. + if self.node_by_peer.get(&peer_id) == Some(&node_index) { + self.node_by_peer.remove(&peer_id); + } + + // Only generate an event if `drop_node` hasn't been called with this node index. + if !node_is_closed { + Some(SwarmEvent::NodeClosed { + node_index, + peer_id, + closed_custom_protocols: Vec::new(), + }) + } else { + None + } + }, + Some(NodeEvent::Unresponsive) => { + debug!(target: "sub-libp2p", "Node {:?} is unresponsive", peer_id); + if !node_is_closed { + Some(SwarmEvent::UnresponsiveNode { node_index }) + } else { + None + } + }, + Some(NodeEvent::Useless) => { + debug!(target: "sub-libp2p", "Node {:?} is useless", peer_id); + if !node_is_closed { + Some(SwarmEvent::UselessNode { node_index }) + } else { + None + } + }, + Some(NodeEvent::PingStart) => { + trace!(target: "sub-libp2p", "Pinging {:?}", peer_id); + None + }, + Some(NodeEvent::PingSuccess(ping)) => { + trace!(target: "sub-libp2p", "Pong from {:?} in {:?}", peer_id, ping); + if !node_is_closed { + Some(SwarmEvent::PingDuration(node_index, ping)) + } else { + None + } + }, + Some(NodeEvent::Identified { info, observed_addr }) => { + self.add_observed_addr(&peer_id, &observed_addr); + trace!(target: "sub-libp2p", "Client version of {:?}: {:?}", peer_id, info.agent_version); + if !info.agent_version.contains("substrate") { + info!(target: "sub-libp2p", "Connected to non-substrate node {:?}: {}", + peer_id, info.agent_version); + } + + if !node_is_closed { + Some(SwarmEvent::NodeInfos { + node_index, + client_version: info.agent_version, + listen_addrs: info.listen_addrs, + }) + } else { + None + } + }, + Some(NodeEvent::IdentificationRequest(request)) => { + self.respond_to_identify_request(&peer_id, request); + None + }, + Some(NodeEvent::KadFindNode { searched, responder }) => { + if !node_is_closed { + Some(SwarmEvent::KadFindNode { node_index, searched, responder }) + } else { + None + } + }, + Some(NodeEvent::KadOpen(ctrl)) => { + trace!(target: "sub-libp2p", "Opened Kademlia substream with {:?}", peer_id); + if !node_is_closed { + Some(SwarmEvent::KadOpen { node_index, controller: ctrl }) + } else { + None + } + }, + Some(NodeEvent::KadClosed(result)) => { + trace!(target: "sub-libp2p", "Closed Kademlia substream with {:?}: {:?}", peer_id, result); + if !node_is_closed { + Some(SwarmEvent::KadClosed { node_index, result }) + } else { + None + } + }, + Some(NodeEvent::OutboundSubstreamRequested) => { + if let Some(mut peer) = self.swarm.peer(peer_id.clone()).as_connected() { + peer.open_substream(()); + } else { + error!(target: "sub-libp2p", "Inconsistent state in the service task"); + } + None + }, + Some(NodeEvent::CustomProtocolOpen { protocol_id, version }) => { + trace!(target: "sub-libp2p", "Opened custom protocol with {:?}", peer_id); + self.nodes_info.get_mut(&node_index).expect("Inconsistent state") + .open_protocols.push(protocol_id); + if !node_is_closed { + Some(SwarmEvent::OpenedCustomProtocol { + node_index, + protocol: protocol_id, + version, + }) + } else { + None + } + }, + Some(NodeEvent::CustomProtocolClosed { protocol_id, result }) => { + trace!(target: "sub-libp2p", "Closed custom protocol with {:?}: {:?}", peer_id, result); + self.nodes_info.get_mut(&node_index).expect("Inconsistent state") + .open_protocols.retain(|p| p != &protocol_id); + if !node_is_closed { + Some(SwarmEvent::ClosedCustomProtocol { + node_index, + protocol: protocol_id, + }) + } else { + None + } + }, + Some(NodeEvent::CustomMessage { protocol_id, packet_id, data }) => { + if !node_is_closed { + Some(SwarmEvent::CustomMessage { + node_index, + protocol_id, + packet_id, + data, + }) + } else { + None + } + }, + Some(NodeEvent::SubstreamUpgradeFail(err)) => { + debug!(target: "sub-libp2p", "Error while negotiating final protocol \ + with {:?}: {:?}", peer_id, err); + None + }, + } + } +} + +impl Stream for Swarm + where TUserData: Clone + Send + Sync + 'static { + type Item = SwarmEvent; + type Error = IoError; + + fn poll(&mut self) -> Poll, Self::Error> { + for task in self.tasks_to_spawn.drain(..) { + tokio_executor::spawn(task); + } + + match self.poll_network()? { + Async::Ready(value) => return Ok(Async::Ready(value)), + Async::NotReady => (), + } + + match self.poll_node_tasks()? { + Async::Ready(value) => return Ok(Async::Ready(value)), + Async::NotReady => (), + } + + // The only way we reach this is if we went through all the `NotReady` paths above, + // ensuring the current task is registered everywhere. + self.to_notify = Some(task::current()); + Ok(Async::NotReady) + } +} + +/// Wraps around a `NodeHandler` and adds communication with the outside through channels. +struct NodeTask { + node_index: NodeIndex, + handler: Option, TUserData>>, + out_commands_rx: mpsc::UnboundedReceiver, + node_tasks_events_tx: mpsc::UnboundedSender<(NodeIndex, Option>>)>, +} + +impl NodeTask +where TUserData: Clone + Send + Sync + 'static +{ + fn handle_out_command(&mut self, command: OutToTaskMsg) { + match command { + OutToTaskMsg::InjectSubstream { substream, endpoint } => + if let Some(handler) = self.handler.as_mut() { + handler.inject_substream(substream, endpoint); + } else { + error!(target: "sub-libp2p", "Received message after handler is closed"); + }, + OutToTaskMsg::OpenKademlia => + if let Some(handler) = self.handler.as_mut() { + if let Some(ctrl) = handler.open_kademlia() { + let event = NodeEvent::KadOpen(ctrl); + let _ = self.node_tasks_events_tx.unbounded_send((self.node_index, Some(event))); + } + } else { + error!(target: "sub-libp2p", "Received message after handler is closed"); + }, + OutToTaskMsg::SendCustomMessage { protocol, packet_id, data } => + if let Some(handler) = self.handler.as_mut() { + handler.send_custom_message(protocol, packet_id, data); + } else { + error!(target: "sub-libp2p", "Received message after handler is closed"); + }, + } + } +} + +impl Future for NodeTask +where TUserData: Clone + Send + Sync + 'static +{ + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + // Poll for commands sent from the service. + loop { + match self.out_commands_rx.poll() { + Ok(Async::NotReady) => break, + Ok(Async::Ready(Some(command))) => self.handle_out_command(command), + Ok(Async::Ready(None)) => { + if let Some(handler) = self.handler.take() { + for event in handler.close() { + let _ = self.node_tasks_events_tx.unbounded_send((self.node_index, Some(event))); + } + } + let _ = self.node_tasks_events_tx.unbounded_send((self.node_index, None)); + return Ok(Async::Ready(())) + }, + Err(_) => unreachable!("An UnboundedReceiver never errors"), + } + } + + // Poll events from the node. + loop { + match self.handler.as_mut().map(|h| h.poll()).unwrap_or(Ok(Async::Ready(None))) { + Ok(Async::Ready(event)) => { + let finished = event.is_none(); + let _ = self.node_tasks_events_tx.unbounded_send((self.node_index, event)); + // If the node's events stream ends, end the task as well. + if finished { + return Ok(Async::Ready(())); + } + }, + Ok(Async::NotReady) => break, + Err(err) => { + warn!(target: "sub-libp2p", "Error in node handler: {:?}", err); + return Ok(Async::Ready(())); + } + } + } + + // If we reach here, that means nothing is ready. + Ok(Async::NotReady) + } +} diff --git a/substrate/network-libp2p/src/topology.rs b/substrate/network-libp2p/src/topology.rs index 7d86f03f809be..b77d045d873d2 100644 --- a/substrate/network-libp2p/src/topology.rs +++ b/substrate/network-libp2p/src/topology.rs @@ -43,6 +43,8 @@ const KADEMLIA_DISCOVERY_EXPIRATION: Duration = Duration::from_secs(2 * 3600); const EXPIRATION_PUSH_BACK_CONNEC: Duration = Duration::from_secs(2 * 3600); /// Initial score that a bootstrap node receives when registered. const BOOTSTRAP_NODE_SCORE: u32 = 100; +/// Score modifier to apply on a peer that has been determined to be useless. +const USELESS_PEER_SCORE_CHANGE: i32 = -9; /// Time to live of a boostrap node. This only applies if you start the node later *without* /// that bootstrap node configured anymore. const BOOTSTRAP_NODE_EXPIRATION: Duration = Duration::from_secs(24 * 3600); @@ -106,9 +108,16 @@ impl NetTopology { }; let file = fs::File::create(path)?; + // TODO: the capacity of the BufWriter is kind of arbitrary ; decide better serialize(BufWriter::with_capacity(1024 * 1024, file), &self.store) } + /// Returns the number of peers in the topology. + #[inline] + pub fn num_peers(&self) -> usize { + self.store.len() + } + /// Perform a cleanup pass, removing all obsolete addresses and peers. /// /// This should be done from time to time. @@ -163,18 +172,29 @@ impl NetTopology { let mut instant = now + Duration::from_secs(3600); let mut addrs_out = Vec::new(); - for (peer, info) in &self.store { + let mut peer_addrs = Vec::new(); + + 'peer_loop: for (peer, info) in &self.store { + peer_addrs.clear(); + for addr in &info.addrs { let (score, is_connected) = addr.score_and_is_connected(); + if is_connected { + continue 'peer_loop; + } if score == 0 || addr.expires < now_systime { continue; } - if !is_connected && addr.back_off_until > now { + if addr.back_off_until > now { instant = cmp::min(instant, addr.back_off_until); continue; } - addrs_out.push(((peer, &addr.addr), score)); + peer_addrs.push(((peer, &addr.addr), score)); + } + + for val in peer_addrs.drain(..) { + addrs_out.push(val); } } @@ -217,43 +237,43 @@ impl NetTopology { } } - /// Adds an address discovered through the Kademlia DHT. + /// Adds addresses discovered through the Kademlia DHT. /// - /// This address is not necessarily valid and should expire after a TTL. + /// The addresses are not necessarily valid and should expire after a TTL. /// - /// If `connectable` is true, that means we have some sort of hint that this node can - /// be reached. - pub fn add_kademlia_discovered_addr( + /// For each address, incorporates a boolean. If true, that means we have some sort of hint + /// that this address can be reached. + pub fn add_kademlia_discovered_addrs( &mut self, peer_id: &PeerId, - addr: Multiaddr, - connectable: bool - ) { + addrs: I, + ) where I: Iterator { + let mut addrs: Vec<_> = addrs.collect(); let now_systime = SystemTime::now(); let now = Instant::now(); let peer = peer_access(&mut self.store, peer_id); - let mut found = false; peer.addrs.retain(|a| { if a.expires < now_systime && !a.is_connected() { return false; } - if a.addr == addr { - found = true; + if let Some(pos) = addrs.iter().position(|&(ref addr, _)| addr == &a.addr) { + addrs.remove(pos); } true }); - if !found { + if !addrs.is_empty() { trace!( target: "sub-libp2p", - "Peer store: adding address {} for {:?} (connectable hint: {:?})", - addr, + "Peer store: adding addresses {:?} for {:?}", + addrs, peer_id, - connectable ); + } + for (addr, connectable) in addrs { let initial_score = if connectable { KADEMLIA_DISCOVERY_INITIAL_SCORE_CONNECTABLE } else { @@ -325,6 +345,7 @@ impl NetTopology { pub fn report_disconnected(&mut self, addr: &Multiaddr, reason: DisconnectReason) { let score_diff = match reason { DisconnectReason::ClosedGracefully => -1, + DisconnectReason::Banned => -1, }; for info in self.store.values_mut() { @@ -353,18 +374,34 @@ impl NetTopology { for a in info.addrs.iter_mut() { if &a.addr == addr { a.adjust_score(SCORE_DIFF_ON_FAILED_TO_CONNECT); + trace!(target: "sub-libp2p", "Back off for {} = {:?}", addr, a.next_back_off); a.back_off_until = Instant::now() + a.next_back_off; a.next_back_off = cmp::min(a.next_back_off * FAIL_BACKOFF_MULTIPLIER, MAX_BACKOFF); } } } } + + /// Indicates the peer store that the given peer is useless. + /// + /// This decreases the scores of the addresses of that peer. + pub fn report_useless(&mut self, peer: &PeerId) { + for (peer_in_store, info_in_store) in self.store.iter_mut() { + if peer == peer_in_store { + for addr in info_in_store.addrs.iter_mut() { + addr.adjust_score(USELESS_PEER_SCORE_CHANGE); + } + } + } + } } /// Reason why we disconnected from a peer. pub enum DisconnectReason { /// The disconnection was graceful. ClosedGracefully, + /// The peer has been banned. + Banned, } fn peer_access<'a>(store: &'a mut FnvHashMap, peer: &PeerId) -> &'a mut PeerInfo { @@ -527,6 +564,7 @@ fn try_load(path: impl AsRef) -> FnvHashMap { } let mut file = match fs::File::open(path) { + // TODO: the capacity of the BufReader is kind of arbitrary ; decide better Ok(f) => BufReader::with_capacity(1024 * 1024, f), Err(err) => { warn!(target: "sub-libp2p", "Failed to open peer storage file: {:?}", err); diff --git a/substrate/network-libp2p/src/traits.rs b/substrate/network-libp2p/src/traits.rs index de18f083c559d..5e74508c26322 100644 --- a/substrate/network-libp2p/src/traits.rs +++ b/substrate/network-libp2p/src/traits.rs @@ -20,10 +20,9 @@ use std::iter; use std::net::Ipv4Addr; use std::str; use std::time::Duration; -use io::TimerToken; +use TimerToken; use libp2p::{multiaddr::AddrComponent, Multiaddr}; use error::Error; -use ethkey::Secret; use ethereum_types::H512; /// Protocol handler level packet id @@ -37,6 +36,9 @@ pub type NodeId = H512; /// Local (temporary) peer session ID. pub type NodeIndex = usize; +/// secio secret key; +pub type Secret = [u8; 32]; + /// Shared session information #[derive(Debug, Clone)] pub struct SessionInfo { @@ -148,7 +150,7 @@ impl NetworkConfiguration { max_peers: 50, reserved_nodes: Vec::new(), non_reserved_mode: NonReservedPeerMode::Accept, - client_version: "Parity-network".into(), + client_version: "Parity-network".into(), // TODO: meh } } diff --git a/substrate/network-libp2p/src/transport.rs b/substrate/network-libp2p/src/transport.rs index b34a720b7d379..cb091540da943 100644 --- a/substrate/network-libp2p/src/transport.rs +++ b/substrate/network-libp2p/src/transport.rs @@ -15,36 +15,32 @@ // along with Substrate. If not, see . use libp2p::{self, PeerId, Transport, mplex, secio, yamux}; -use libp2p::core::{either, upgrade, transport::BoxedMuxed}; +use libp2p::core::{either, upgrade, transport::boxed::Boxed, muxing::StreamMuxerBox}; use libp2p::transport_timeout::TransportTimeout; use std::time::Duration; use std::usize; -use tokio_io::{AsyncRead, AsyncWrite}; /// Builds the transport that serves as a common ground for all connections. pub fn build_transport( local_private_key: secio::SecioKeyPair -) -> BoxedMuxed<(PeerId, impl AsyncRead + AsyncWrite)> { +) -> Boxed<(PeerId, StreamMuxerBox)> { let mut mplex_config = mplex::MplexConfig::new(); mplex_config.max_buffer_len_behaviour(mplex::MaxBufferBehaviour::Block); mplex_config.max_buffer_len(usize::MAX); let base = libp2p::CommonTransport::new() - .with_upgrade(secio::SecioConfig { - key: local_private_key, - }) + .with_upgrade(secio::SecioConfig::new(local_private_key)) .and_then(move |out, endpoint, client_addr| { let upgrade = upgrade::or( upgrade::map(yamux::Config::default(), either::EitherOutput::First), upgrade::map(mplex_config, either::EitherOutput::Second), ); - let key = out.remote_key; - let upgrade = upgrade::map(upgrade, move |muxer| (key, muxer)); + let peer_id = out.remote_key.into_peer_id(); + let upgrade = upgrade::map(upgrade, move |muxer| (peer_id, muxer)); upgrade::apply(out.stream, upgrade, endpoint, client_addr) }) - .into_connection_reuse() - .map(|(key, substream), _| (key.into_peer_id(), substream)); + .map(|(id, muxer), _| (id, StreamMuxerBox::new(muxer))); TransportTimeout::new(base, Duration::from_secs(20)) - .boxed_muxed() + .boxed() } diff --git a/substrate/network-libp2p/tests/tests.rs b/substrate/network-libp2p/tests/tests.rs index a74772171950c..b388d0f4a10f4 100644 --- a/substrate/network-libp2p/tests/tests.rs +++ b/substrate/network-libp2p/tests/tests.rs @@ -16,10 +16,7 @@ extern crate parking_lot; extern crate parity_bytes; -extern crate ethcore_io as io; -extern crate ethcore_logger; extern crate substrate_network_libp2p; -extern crate ethkey; use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering}; use std::sync::Arc; @@ -28,8 +25,7 @@ use std::time::*; use parking_lot::Mutex; use parity_bytes::Bytes; use substrate_network_libp2p::*; -use ethkey::{Random, Generator}; -use io::TimerToken; +use TimerToken; pub struct TestProtocol { drop_session: bool, @@ -102,9 +98,7 @@ fn net_service() { #[test] #[ignore] // TODO: how is this test even supposed to work? fn net_disconnect() { - let key1 = Random.generate().unwrap(); let mut config1 = NetworkConfiguration::new_local(); - config1.use_secret = Some(key1.secret().clone()); config1.boot_nodes = vec![ ]; let handler1 = Arc::new(TestProtocol::new(false)); let service1 = NetworkService::new(config1, vec![(handler1.clone(), *b"tst", &[(42u8, 1), (43u8, 1)])]).unwrap();