Remove whisper (#10855)
This commit is contained in:
parent
3f42b6178f
commit
54afb33333
72
Cargo.lock
generated
72
Cargo.lock
generated
@ -188,11 +188,6 @@ name = "bitflags"
|
|||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitflags"
|
|
||||||
version = "0.9.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@ -1716,11 +1711,6 @@ dependencies = [
|
|||||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hex"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hex-literal"
|
name = "hex-literal"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@ -2631,15 +2621,6 @@ name = "order-stat"
|
|||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ordered-float"
|
|
||||||
version = "0.5.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ordermap"
|
name = "ordermap"
|
||||||
version = "0.3.5"
|
version = "0.3.5"
|
||||||
@ -2783,7 +2764,6 @@ dependencies = [
|
|||||||
"parity-runtime 0.1.0",
|
"parity-runtime 0.1.0",
|
||||||
"parity-updater 1.12.0",
|
"parity-updater 1.12.0",
|
||||||
"parity-version 2.5.13",
|
"parity-version 2.5.13",
|
||||||
"parity-whisper 0.1.0",
|
|
||||||
"parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"pretty_assertions 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"pretty_assertions 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -3054,35 +3034,6 @@ dependencies = [
|
|||||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parity-whisper"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"ethcore-network 1.12.0",
|
|
||||||
"ethereum-types 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"ethkey 0.3.0",
|
|
||||||
"hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"jsonrpc-core 14.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"jsonrpc-derive 14.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"jsonrpc-pubsub 14.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"memzero 0.1.0",
|
|
||||||
"ordered-float 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"parity-crypto 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"rlp 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"time-utils 0.1.0",
|
|
||||||
"tiny-keccak 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parity-wordlist"
|
name = "parity-wordlist"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
@ -4825,26 +4776,6 @@ dependencies = [
|
|||||||
"webpki 0.19.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"webpki 0.19.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "whisper-cli"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"docopt 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"ethcore-network 1.12.0",
|
|
||||||
"ethcore-network-devp2p 1.12.0",
|
|
||||||
"ethkey 0.3.0",
|
|
||||||
"jsonrpc-core 14.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"jsonrpc-http-server 14.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"jsonrpc-pubsub 14.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"panic_hook 0.1.0",
|
|
||||||
"parity-whisper 0.1.0",
|
|
||||||
"rustc-hex 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
@ -4982,7 +4913,6 @@ dependencies = [
|
|||||||
"checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c"
|
"checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c"
|
||||||
"checksum bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f"
|
"checksum bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f"
|
||||||
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
|
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
|
||||||
"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
|
|
||||||
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||||
"checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab"
|
"checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab"
|
||||||
"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
|
"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
|
||||||
@ -5071,7 +5001,6 @@ dependencies = [
|
|||||||
"checksum heapsize 0.4.2 (git+https://github.com/cheme/heapsize.git?branch=ec-macfix)" = "<none>"
|
"checksum heapsize 0.4.2 (git+https://github.com/cheme/heapsize.git?branch=ec-macfix)" = "<none>"
|
||||||
"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
|
"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
|
||||||
"checksum hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120"
|
"checksum hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120"
|
||||||
"checksum hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d6a22814455d41612f41161581c2883c0c6a1c41852729b17d5ed88f01e153aa"
|
|
||||||
"checksum hex-literal 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "961de220ec9a91af2e1e5bd80d02109155695e516771762381ef8581317066e0"
|
"checksum hex-literal 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "961de220ec9a91af2e1e5bd80d02109155695e516771762381ef8581317066e0"
|
||||||
"checksum hex-literal-impl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9d4c5c844e2fee0bf673d54c2c177f1713b3d2af2ff6e666b49cb7572e6cf42d"
|
"checksum hex-literal-impl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9d4c5c844e2fee0bf673d54c2c177f1713b3d2af2ff6e666b49cb7572e6cf42d"
|
||||||
"checksum hmac 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "733e1b3ac906631ca01ebb577e9bb0f5e37a454032b9036b5eaea4013ed6f99a"
|
"checksum hmac 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "733e1b3ac906631ca01ebb577e9bb0f5e37a454032b9036b5eaea4013ed6f99a"
|
||||||
@ -5161,7 +5090,6 @@ dependencies = [
|
|||||||
"checksum ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2c49021782e5233cd243168edfa8037574afed4eba4bbaf538b3d8d1789d8c"
|
"checksum ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2c49021782e5233cd243168edfa8037574afed4eba4bbaf538b3d8d1789d8c"
|
||||||
"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
||||||
"checksum order-stat 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "efa535d5117d3661134dbf1719b6f0ffe06f2375843b13935db186cd094105eb"
|
"checksum order-stat 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "efa535d5117d3661134dbf1719b6f0ffe06f2375843b13935db186cd094105eb"
|
||||||
"checksum ordered-float 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7eb5259643245d3f292c7a146b2df53bba24d7eab159410e648eb73dc164669d"
|
|
||||||
"checksum ordermap 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a86ed3f5f244b372d6b1a00b72ef7f8876d0bc6a78a4c9985c53614041512063"
|
"checksum ordermap 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a86ed3f5f244b372d6b1a00b72ef7f8876d0bc6a78a4c9985c53614041512063"
|
||||||
"checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13"
|
"checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13"
|
||||||
"checksum parity-bytes 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0c276d76c5333b8c2579e02d49a06733a55b8282d2d9b13e8d53b6406bd7e30a"
|
"checksum parity-bytes 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0c276d76c5333b8c2579e02d49a06733a55b8282d2d9b13e8d53b6406bd7e30a"
|
||||||
|
@ -59,7 +59,6 @@ parity-runtime = { path = "util/runtime" }
|
|||||||
parity-rpc = { path = "rpc" }
|
parity-rpc = { path = "rpc" }
|
||||||
parity-updater = { path = "updater" }
|
parity-updater = { path = "updater" }
|
||||||
parity-version = { path = "util/version" }
|
parity-version = { path = "util/version" }
|
||||||
parity-whisper = { path = "whisper" }
|
|
||||||
parity-path = "0.1"
|
parity-path = "0.1"
|
||||||
dir = { path = "util/dir" }
|
dir = { path = "util/dir" }
|
||||||
panic_hook = { path = "util/panic-hook" }
|
panic_hook = { path = "util/panic-hook" }
|
||||||
@ -137,7 +136,6 @@ members = [
|
|||||||
"ethcore/wasm/run",
|
"ethcore/wasm/run",
|
||||||
"evmbin",
|
"evmbin",
|
||||||
"parity-clib",
|
"parity-clib",
|
||||||
"whisper/cli",
|
|
||||||
"util/triehash-ethereum",
|
"util/triehash-ethereum",
|
||||||
"util/keccak-hasher",
|
"util/keccak-hasher",
|
||||||
"util/patricia-trie-ethereum",
|
"util/patricia-trie-ethereum",
|
||||||
|
@ -320,10 +320,6 @@ Caching, Importing Blocks, and Block Information
|
|||||||
patricia-trie-ethereum registrar rlp_compress rlp_derive parity-runtime stats
|
patricia-trie-ethereum registrar rlp_compress rlp_derive parity-runtime stats
|
||||||
time-utils triehash-ethereum unexpected parity-version
|
time-utils triehash-ethereum unexpected parity-version
|
||||||
```
|
```
|
||||||
* Parity Whisper Protocol Implementation
|
|
||||||
```bash
|
|
||||||
parity-whisper whisper-cli
|
|
||||||
```
|
|
||||||
|
|
||||||
</p></details>
|
</p></details>
|
||||||
|
|
||||||
@ -360,7 +356,6 @@ In addition to the Parity Ethereum client, there are additional tools in this re
|
|||||||
- [evmbin](./evmbin) - Parity Ethereum EVM Implementation.
|
- [evmbin](./evmbin) - Parity Ethereum EVM Implementation.
|
||||||
- [ethstore](./accounts/ethstore) - Parity Ethereum Key Management.
|
- [ethstore](./accounts/ethstore) - Parity Ethereum Key Management.
|
||||||
- [ethkey](./accounts/ethkey) - Parity Ethereum Keys Generator.
|
- [ethkey](./accounts/ethkey) - Parity Ethereum Keys Generator.
|
||||||
- [whisper](./whisper) - Parity Ethereum Whisper-v2 PoC Implementation.
|
|
||||||
|
|
||||||
The following tool is available in a separate repository:
|
The following tool is available in a separate repository:
|
||||||
- [ethabi](https://github.com/paritytech/ethabi) - Parity Ethereum Encoding of Function Calls. [Docs here](https://crates.io/crates/ethabi)
|
- [ethabi](https://github.com/paritytech/ethabi) - Parity Ethereum Encoding of Function Calls. [Docs here](https://crates.io/crates/ethabi)
|
||||||
|
@ -218,4 +218,3 @@ _This project is a part of the Parity Ethereum toolchain._
|
|||||||
- [ethabi](https://github.com/paritytech/ethabi) - Parity Ethereum function calls encoding.
|
- [ethabi](https://github.com/paritytech/ethabi) - Parity Ethereum function calls encoding.
|
||||||
- [ethstore](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethstore) - Parity Ethereum key management.
|
- [ethstore](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethstore) - Parity Ethereum key management.
|
||||||
- [ethkey](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethkey) - Parity Ethereum keys generator.
|
- [ethkey](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethkey) - Parity Ethereum keys generator.
|
||||||
- [whisper](https://github.com/paritytech/parity-ethereum/blob/master/whisper/) - Implementation of Whisper-v2 PoC.
|
|
||||||
|
@ -337,4 +337,3 @@ _This project is a part of the Parity Ethereum toolchain._
|
|||||||
- [ethabi](https://github.com/paritytech/ethabi) - Parity Ethereum function calls encoding.
|
- [ethabi](https://github.com/paritytech/ethabi) - Parity Ethereum function calls encoding.
|
||||||
- [ethstore](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethstore) - Parity Ethereum key management.
|
- [ethstore](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethstore) - Parity Ethereum key management.
|
||||||
- [ethkey](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethkey) - Parity Ethereum keys generator.
|
- [ethkey](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethkey) - Parity Ethereum keys generator.
|
||||||
- [whisper](https://github.com/paritytech/parity-ethereum/blob/master/whisper/) - Implementation of Whisper-v2 PoC.
|
|
||||||
|
@ -214,27 +214,6 @@ impl From<light_net::Status> for PipProtocolInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration to attach alternate protocol handlers.
|
|
||||||
/// Only works when IPC is disabled.
|
|
||||||
pub struct AttachedProtocol {
|
|
||||||
/// The protocol handler in question.
|
|
||||||
pub handler: Arc<dyn NetworkProtocolHandler + Send + Sync>,
|
|
||||||
/// 3-character ID for the protocol.
|
|
||||||
pub protocol_id: ProtocolId,
|
|
||||||
/// Supported versions and their packet counts.
|
|
||||||
pub versions: &'static [(u8, u8)],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AttachedProtocol {
|
|
||||||
fn register(&self, network: &NetworkService) {
|
|
||||||
let res = network.register_protocol(self.handler.clone(), self.protocol_id, self.versions);
|
|
||||||
|
|
||||||
if let Err(e) = res {
|
|
||||||
warn!(target: "sync", "Error attaching protocol {:?}: {:?}", self.protocol_id, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A prioritized tasks run in a specialised timer.
|
/// A prioritized tasks run in a specialised timer.
|
||||||
/// Every task should be completed within a hard deadline,
|
/// Every task should be completed within a hard deadline,
|
||||||
/// if it's not it's either cancelled or split into multiple tasks.
|
/// if it's not it's either cancelled or split into multiple tasks.
|
||||||
@ -282,8 +261,6 @@ pub struct Params {
|
|||||||
pub provider: Arc<dyn crate::light::Provider>,
|
pub provider: Arc<dyn crate::light::Provider>,
|
||||||
/// Network layer configuration.
|
/// Network layer configuration.
|
||||||
pub network_config: NetworkConfiguration,
|
pub network_config: NetworkConfiguration,
|
||||||
/// Other protocols to attach.
|
|
||||||
pub attached_protos: Vec<AttachedProtocol>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ethereum network protocol handler
|
/// Ethereum network protocol handler
|
||||||
@ -294,8 +271,6 @@ pub struct EthSync {
|
|||||||
eth_handler: Arc<SyncProtocolHandler>,
|
eth_handler: Arc<SyncProtocolHandler>,
|
||||||
/// Light (pip) protocol handler
|
/// Light (pip) protocol handler
|
||||||
light_proto: Option<Arc<LightProtocol>>,
|
light_proto: Option<Arc<LightProtocol>>,
|
||||||
/// Other protocols to attach.
|
|
||||||
attached_protos: Vec<AttachedProtocol>,
|
|
||||||
/// The main subprotocol name
|
/// The main subprotocol name
|
||||||
subprotocol_name: [u8; 3],
|
subprotocol_name: [u8; 3],
|
||||||
/// Light subprotocol name.
|
/// Light subprotocol name.
|
||||||
@ -387,7 +362,6 @@ impl EthSync {
|
|||||||
light_proto: light_proto,
|
light_proto: light_proto,
|
||||||
subprotocol_name: params.config.subprotocol_name,
|
subprotocol_name: params.config.subprotocol_name,
|
||||||
light_subprotocol_name: params.config.light_subprotocol_name,
|
light_subprotocol_name: params.config.light_subprotocol_name,
|
||||||
attached_protos: params.attached_protos,
|
|
||||||
priority_tasks: Mutex::new(priority_tasks_tx),
|
priority_tasks: Mutex::new(priority_tasks_tx),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -643,11 +617,6 @@ impl ChainNotify for EthSync {
|
|||||||
)
|
)
|
||||||
.unwrap_or_else(|e| warn!("Error registering light client protocol: {:?}", e));
|
.unwrap_or_else(|e| warn!("Error registering light client protocol: {:?}", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
// register any attached protocols.
|
|
||||||
for proto in &self.attached_protos {
|
|
||||||
proto.register(&self.network)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop(&self) {
|
fn stop(&self) {
|
||||||
@ -979,15 +948,12 @@ pub struct LightSyncParams<L> {
|
|||||||
pub subprotocol_name: [u8; 3],
|
pub subprotocol_name: [u8; 3],
|
||||||
/// Other handlers to attach.
|
/// Other handlers to attach.
|
||||||
pub handlers: Vec<Arc<dyn LightHandler>>,
|
pub handlers: Vec<Arc<dyn LightHandler>>,
|
||||||
/// Other subprotocols to run.
|
|
||||||
pub attached_protos: Vec<AttachedProtocol>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Service for light synchronization.
|
/// Service for light synchronization.
|
||||||
pub struct LightSync {
|
pub struct LightSync {
|
||||||
proto: Arc<LightProtocol>,
|
proto: Arc<LightProtocol>,
|
||||||
sync: Arc<dyn SyncInfo + Sync + Send>,
|
sync: Arc<dyn SyncInfo + Sync + Send>,
|
||||||
attached_protos: Vec<AttachedProtocol>,
|
|
||||||
network: NetworkService,
|
network: NetworkService,
|
||||||
subprotocol_name: [u8; 3],
|
subprotocol_name: [u8; 3],
|
||||||
network_id: u64,
|
network_id: u64,
|
||||||
@ -1031,7 +997,6 @@ impl LightSync {
|
|||||||
Ok(LightSync {
|
Ok(LightSync {
|
||||||
proto: light_proto,
|
proto: light_proto,
|
||||||
sync: sync,
|
sync: sync,
|
||||||
attached_protos: params.attached_protos,
|
|
||||||
network: service,
|
network: service,
|
||||||
subprotocol_name: params.subprotocol_name,
|
subprotocol_name: params.subprotocol_name,
|
||||||
network_id: params.network_id,
|
network_id: params.network_id,
|
||||||
@ -1104,10 +1069,6 @@ impl ManageNetwork for LightSync {
|
|||||||
::light::net::PROTOCOL_VERSIONS,
|
::light::net::PROTOCOL_VERSIONS,
|
||||||
)
|
)
|
||||||
.unwrap_or_else(|e| warn!("Error registering light client protocol: {:?}", e));
|
.unwrap_or_else(|e| warn!("Error registering light client protocol: {:?}", e));
|
||||||
|
|
||||||
for proto in &self.attached_protos {
|
|
||||||
proto.register(&self.network)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop_network(&self) {
|
fn stop_network(&self) {
|
||||||
|
@ -52,4 +52,3 @@ _This project is a part of the Parity Ethereum toolchain._
|
|||||||
- [ethabi](https://github.com/paritytech/ethabi) - Parity Ethereum function calls encoding.
|
- [ethabi](https://github.com/paritytech/ethabi) - Parity Ethereum function calls encoding.
|
||||||
- [ethstore](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethstore) - Parity Ethereum key management.
|
- [ethstore](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethstore) - Parity Ethereum key management.
|
||||||
- [ethkey](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethkey) - Parity Ethereum keys generator.
|
- [ethkey](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethkey) - Parity Ethereum keys generator.
|
||||||
- [whisper](https://github.com/paritytech/parity-ethereum/blob/master/whisper/) - Implementation of Whisper-v2 PoC.
|
|
||||||
|
@ -483,9 +483,9 @@ usage! {
|
|||||||
"--jsonrpc-interface=[IP]",
|
"--jsonrpc-interface=[IP]",
|
||||||
"Specify the hostname portion of the HTTP JSON-RPC API server, IP should be an interface's IP address, or all (all interfaces) or local.",
|
"Specify the hostname portion of the HTTP JSON-RPC API server, IP should be an interface's IP address, or all (all interfaces) or local.",
|
||||||
|
|
||||||
ARG arg_jsonrpc_apis: (String) = "web3,eth,pubsub,net,parity,private,parity_pubsub,traces,shh,shh_pubsub", or |c: &Config| c.rpc.as_ref()?.apis.as_ref().map(|vec| vec.join(",")),
|
ARG arg_jsonrpc_apis: (String) = "web3,eth,pubsub,net,parity,private,parity_pubsub,traces", or |c: &Config| c.rpc.as_ref()?.apis.as_ref().map(|vec| vec.join(",")),
|
||||||
"--jsonrpc-apis=[APIS]",
|
"--jsonrpc-apis=[APIS]",
|
||||||
"Specify the APIs available through the HTTP JSON-RPC interface using a comma-delimited list of API names. Possible names are: all, safe, debug, web3, net, eth, pubsub, personal, signer, parity, parity_pubsub, parity_accounts, parity_set, traces, secretstore, shh, shh_pubsub. You can also disable a specific API by putting '-' in the front, example: all,-personal. 'safe' enables the following APIs: web3, net, eth, pubsub, parity, parity_pubsub, traces, shh, shh_pubsub",
|
"Specify the APIs available through the HTTP JSON-RPC interface using a comma-delimited list of API names. Possible names are: all, safe, debug, web3, net, eth, pubsub, personal, signer, parity, parity_pubsub, parity_accounts, parity_set, traces, secretstore. You can also disable a specific API by putting '-' in the front, example: all,-personal. 'safe' enables the following APIs: web3, net, eth, pubsub, parity, parity_pubsub, traces",
|
||||||
|
|
||||||
ARG arg_jsonrpc_hosts: (String) = "none", or |c: &Config| c.rpc.as_ref()?.hosts.as_ref().map(|vec| vec.join(",")),
|
ARG arg_jsonrpc_hosts: (String) = "none", or |c: &Config| c.rpc.as_ref()?.hosts.as_ref().map(|vec| vec.join(",")),
|
||||||
"--jsonrpc-hosts=[HOSTS]",
|
"--jsonrpc-hosts=[HOSTS]",
|
||||||
@ -524,9 +524,9 @@ usage! {
|
|||||||
"--ws-interface=[IP]",
|
"--ws-interface=[IP]",
|
||||||
"Specify the hostname portion of the WebSockets JSON-RPC server, IP should be an interface's IP address, or all (all interfaces) or local.",
|
"Specify the hostname portion of the WebSockets JSON-RPC server, IP should be an interface's IP address, or all (all interfaces) or local.",
|
||||||
|
|
||||||
ARG arg_ws_apis: (String) = "web3,eth,pubsub,net,parity,parity_pubsub,private,traces,shh,shh_pubsub", or |c: &Config| c.websockets.as_ref()?.apis.as_ref().map(|vec| vec.join(",")),
|
ARG arg_ws_apis: (String) = "web3,eth,pubsub,net,parity,parity_pubsub,private,traces", or |c: &Config| c.websockets.as_ref()?.apis.as_ref().map(|vec| vec.join(",")),
|
||||||
"--ws-apis=[APIS]",
|
"--ws-apis=[APIS]",
|
||||||
"Specify the JSON-RPC APIs available through the WebSockets interface using a comma-delimited list of API names. Possible names are: all, safe, web3, net, eth, pubsub, personal, signer, parity, parity_pubsub, parity_accounts, parity_set, traces, secretstore, shh, shh_pubsub. You can also disable a specific API by putting '-' in the front, example: all,-personal. 'safe' enables the following APIs: web3, net, eth, pubsub, parity, parity_pubsub, traces, shh, shh_pubsub",
|
"Specify the JSON-RPC APIs available through the WebSockets interface using a comma-delimited list of API names. Possible names are: all, safe, web3, net, eth, pubsub, personal, signer, parity, parity_pubsub, parity_accounts, parity_set, traces, secretstore. You can also disable a specific API by putting '-' in the front, example: all,-personal. 'safe' enables the following APIs: web3, net, eth, pubsub, parity, parity_pubsub, traces",
|
||||||
|
|
||||||
ARG arg_ws_origins: (String) = "parity://*,chrome-extension://*,moz-extension://*", or |c: &Config| c.websockets.as_ref()?.origins.as_ref().map(|vec| vec.join(",")),
|
ARG arg_ws_origins: (String) = "parity://*,chrome-extension://*,moz-extension://*", or |c: &Config| c.websockets.as_ref()?.origins.as_ref().map(|vec| vec.join(",")),
|
||||||
"--ws-origins=[URL]",
|
"--ws-origins=[URL]",
|
||||||
@ -549,9 +549,9 @@ usage! {
|
|||||||
"--ipc-path=[PATH]",
|
"--ipc-path=[PATH]",
|
||||||
"Specify custom path for JSON-RPC over IPC service.",
|
"Specify custom path for JSON-RPC over IPC service.",
|
||||||
|
|
||||||
ARG arg_ipc_apis: (String) = "web3,eth,pubsub,net,parity,parity_pubsub,parity_accounts,private,traces,shh,shh_pubsub", or |c: &Config| c.ipc.as_ref()?.apis.as_ref().map(|vec| vec.join(",")),
|
ARG arg_ipc_apis: (String) = "web3,eth,pubsub,net,parity,parity_pubsub,parity_accounts,private,traces", or |c: &Config| c.ipc.as_ref()?.apis.as_ref().map(|vec| vec.join(",")),
|
||||||
"--ipc-apis=[APIS]",
|
"--ipc-apis=[APIS]",
|
||||||
"Specify custom API set available via JSON-RPC over IPC using a comma-delimited list of API names. Possible names are: all, safe, web3, net, eth, pubsub, personal, signer, parity, parity_pubsub, parity_accounts, parity_set, traces, secretstore, shh, shh_pubsub. You can also disable a specific API by putting '-' in the front, example: all,-personal. 'safe' enables the following APIs: web3, net, eth, pubsub, parity, parity_pubsub, traces, shh, shh_pubsub",
|
"Specify custom API set available via JSON-RPC over IPC using a comma-delimited list of API names. Possible names are: all, safe, web3, net, eth, pubsub, personal, signer, parity, parity_pubsub, parity_accounts, parity_set, traces, secretstore. You can also disable a specific API by putting '-' in the front, example: all,-personal. 'safe' enables the following APIs: web3, net, eth, pubsub, parity, parity_pubsub, traces",
|
||||||
|
|
||||||
["API and Console Options – IPFS"]
|
["API and Console Options – IPFS"]
|
||||||
FLAG flag_ipfs_api: (bool) = false, or |c: &Config| c.ipfs.as_ref()?.enable.clone(),
|
FLAG flag_ipfs_api: (bool) = false, or |c: &Config| c.ipfs.as_ref()?.enable.clone(),
|
||||||
@ -905,15 +905,6 @@ usage! {
|
|||||||
ARG arg_snapshot_threads: (Option<usize>) = None, or |c: &Config| c.snapshots.as_ref()?.processing_threads,
|
ARG arg_snapshot_threads: (Option<usize>) = None, or |c: &Config| c.snapshots.as_ref()?.processing_threads,
|
||||||
"--snapshot-threads=[NUM]",
|
"--snapshot-threads=[NUM]",
|
||||||
"Enables multiple threads for snapshots creation.",
|
"Enables multiple threads for snapshots creation.",
|
||||||
|
|
||||||
["Whisper Options"]
|
|
||||||
FLAG flag_whisper: (bool) = false, or |c: &Config| c.whisper.as_ref()?.enabled,
|
|
||||||
"--whisper",
|
|
||||||
"Enable the Whisper network.",
|
|
||||||
|
|
||||||
ARG arg_whisper_pool_size: (usize) = 10usize, or |c: &Config| c.whisper.as_ref()?.pool_size.clone(),
|
|
||||||
"--whisper-pool-size=[MB]",
|
|
||||||
"Target size of the whisper message pool in megabytes.",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -935,7 +926,6 @@ struct Config {
|
|||||||
snapshots: Option<Snapshots>,
|
snapshots: Option<Snapshots>,
|
||||||
misc: Option<Misc>,
|
misc: Option<Misc>,
|
||||||
stratum: Option<Stratum>,
|
stratum: Option<Stratum>,
|
||||||
whisper: Option<Whisper>,
|
|
||||||
light: Option<Light>,
|
light: Option<Light>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1164,13 +1154,6 @@ struct Misc {
|
|||||||
unsafe_expose: Option<bool>,
|
unsafe_expose: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, PartialEq, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
struct Whisper {
|
|
||||||
enabled: Option<bool>,
|
|
||||||
pool_size: Option<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Debug, PartialEq, Deserialize)]
|
#[derive(Default, Debug, PartialEq, Deserialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
struct Light {
|
struct Light {
|
||||||
@ -1185,7 +1168,7 @@ struct Light {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
Account, Args, ArgsError, Config, Footprint, Ipc, Ipfs, Light, Mining, Misc, Network,
|
Account, Args, ArgsError, Config, Footprint, Ipc, Ipfs, Light, Mining, Misc, Network,
|
||||||
Operating, Rpc, SecretStore, Snapshots, Whisper, Ws,
|
Operating, Rpc, SecretStore, Snapshots, Ws,
|
||||||
};
|
};
|
||||||
use clap::ErrorKind as ClapErrorKind;
|
use clap::ErrorKind as ClapErrorKind;
|
||||||
use toml;
|
use toml;
|
||||||
@ -1606,10 +1589,6 @@ mod tests {
|
|||||||
arg_on_demand_request_backoff_rounds_max: Some(100),
|
arg_on_demand_request_backoff_rounds_max: Some(100),
|
||||||
arg_on_demand_request_consecutive_failures: Some(1),
|
arg_on_demand_request_consecutive_failures: Some(1),
|
||||||
|
|
||||||
// -- Whisper options.
|
|
||||||
flag_whisper: false,
|
|
||||||
arg_whisper_pool_size: 20,
|
|
||||||
|
|
||||||
// -- Internal Options
|
// -- Internal Options
|
||||||
flag_can_restart: false,
|
flag_can_restart: false,
|
||||||
|
|
||||||
@ -1826,10 +1805,6 @@ mod tests {
|
|||||||
ports_shift: Some(0),
|
ports_shift: Some(0),
|
||||||
unsafe_expose: Some(false),
|
unsafe_expose: Some(false),
|
||||||
}),
|
}),
|
||||||
whisper: Some(Whisper {
|
|
||||||
enabled: Some(true),
|
|
||||||
pool_size: Some(50),
|
|
||||||
}),
|
|
||||||
stratum: None,
|
stratum: None,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -156,7 +156,3 @@ disable_periodic = false
|
|||||||
logging = "own_tx=trace"
|
logging = "own_tx=trace"
|
||||||
log_file = "/var/log/parity.log"
|
log_file = "/var/log/parity.log"
|
||||||
color = true
|
color = true
|
||||||
|
|
||||||
[whisper]
|
|
||||||
enabled = false
|
|
||||||
pool_size = 20
|
|
||||||
|
@ -78,7 +78,3 @@ log_file = "/var/log/parity.log"
|
|||||||
color = true
|
color = true
|
||||||
ports_shift = 0
|
ports_shift = 0
|
||||||
unsafe_expose = false
|
unsafe_expose = false
|
||||||
|
|
||||||
[whisper]
|
|
||||||
enabled = true
|
|
||||||
pool_size = 50
|
|
||||||
|
@ -398,7 +398,6 @@ impl Configuration {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let verifier_settings = self.verifier_settings();
|
let verifier_settings = self.verifier_settings();
|
||||||
let whisper_config = self.whisper_config();
|
|
||||||
let (private_provider_conf, private_enc_conf, private_tx_enabled) =
|
let (private_provider_conf, private_enc_conf, private_tx_enabled) =
|
||||||
self.private_provider_config()?;
|
self.private_provider_config()?;
|
||||||
|
|
||||||
@ -448,7 +447,6 @@ impl Configuration {
|
|||||||
serve_light: !self.args.flag_no_serve_light,
|
serve_light: !self.args.flag_no_serve_light,
|
||||||
light: self.args.flag_light,
|
light: self.args.flag_light,
|
||||||
no_persistent_txqueue: self.args.flag_no_persistent_txqueue,
|
no_persistent_txqueue: self.args.flag_no_persistent_txqueue,
|
||||||
whisper: whisper_config,
|
|
||||||
no_hardcoded_sync: self.args.flag_no_hardcoded_sync,
|
no_hardcoded_sync: self.args.flag_no_hardcoded_sync,
|
||||||
max_round_blocks_to_import: self.args.arg_max_round_blocks_to_import,
|
max_round_blocks_to_import: self.args.arg_max_round_blocks_to_import,
|
||||||
on_demand_response_time_window: self.args.arg_on_demand_response_time_window,
|
on_demand_response_time_window: self.args.arg_on_demand_response_time_window,
|
||||||
@ -1332,13 +1330,6 @@ impl Configuration {
|
|||||||
|
|
||||||
settings
|
settings
|
||||||
}
|
}
|
||||||
|
|
||||||
fn whisper_config(&self) -> ::whisper::Config {
|
|
||||||
::whisper::Config {
|
|
||||||
enabled: self.args.flag_whisper,
|
|
||||||
target_message_pool_size: self.args.arg_whisper_pool_size * 1024 * 1024,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_secretstore_service_contract_address(
|
fn into_secretstore_service_contract_address(
|
||||||
@ -1687,7 +1678,6 @@ mod tests {
|
|||||||
light: false,
|
light: false,
|
||||||
no_hardcoded_sync: false,
|
no_hardcoded_sync: false,
|
||||||
no_persistent_txqueue: false,
|
no_persistent_txqueue: false,
|
||||||
whisper: Default::default(),
|
|
||||||
max_round_blocks_to_import: 12,
|
max_round_blocks_to_import: 12,
|
||||||
on_demand_response_time_window: None,
|
on_demand_response_time_window: None,
|
||||||
on_demand_request_backoff_start: None,
|
on_demand_request_backoff_start: None,
|
||||||
|
@ -69,7 +69,6 @@ extern crate parity_rpc;
|
|||||||
extern crate parity_runtime;
|
extern crate parity_runtime;
|
||||||
extern crate parity_updater as updater;
|
extern crate parity_updater as updater;
|
||||||
extern crate parity_version;
|
extern crate parity_version;
|
||||||
extern crate parity_whisper;
|
|
||||||
extern crate registrar;
|
extern crate registrar;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@ -115,7 +114,6 @@ mod signer;
|
|||||||
mod snapshot;
|
mod snapshot;
|
||||||
mod upgrade;
|
mod upgrade;
|
||||||
mod user_defaults;
|
mod user_defaults;
|
||||||
mod whisper;
|
|
||||||
|
|
||||||
use std::{fs::File, io::BufReader, sync::Arc};
|
use std::{fs::File, io::BufReader, sync::Arc};
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ use std::sync::{mpsc, Arc};
|
|||||||
|
|
||||||
use ethcore::{client::BlockChainClient, snapshot::SnapshotService};
|
use ethcore::{client::BlockChainClient, snapshot::SnapshotService};
|
||||||
use light::Provider;
|
use light::Provider;
|
||||||
use sync::{self, AttachedProtocol, ConnectionFilter, NetworkConfiguration, Params, SyncConfig};
|
use sync::{self, ConnectionFilter, NetworkConfiguration, Params, SyncConfig};
|
||||||
|
|
||||||
pub use ethcore::client::ChainNotify;
|
pub use ethcore::client::ChainNotify;
|
||||||
use ethcore_logger::Config as LogConfig;
|
use ethcore_logger::Config as LogConfig;
|
||||||
@ -39,7 +39,6 @@ pub fn sync(
|
|||||||
private_tx_handler: Option<Arc<dyn PrivateTxHandler>>,
|
private_tx_handler: Option<Arc<dyn PrivateTxHandler>>,
|
||||||
provider: Arc<dyn Provider>,
|
provider: Arc<dyn Provider>,
|
||||||
_log_settings: &LogConfig,
|
_log_settings: &LogConfig,
|
||||||
attached_protos: Vec<AttachedProtocol>,
|
|
||||||
connection_filter: Option<Arc<dyn ConnectionFilter>>,
|
connection_filter: Option<Arc<dyn ConnectionFilter>>,
|
||||||
) -> Result<SyncModules, sync::Error> {
|
) -> Result<SyncModules, sync::Error> {
|
||||||
let eth_sync = EthSync::new(
|
let eth_sync = EthSync::new(
|
||||||
@ -50,7 +49,6 @@ pub fn sync(
|
|||||||
snapshot_service,
|
snapshot_service,
|
||||||
private_tx_handler,
|
private_tx_handler,
|
||||||
network_config,
|
network_config,
|
||||||
attached_protos,
|
|
||||||
},
|
},
|
||||||
connection_filter,
|
connection_filter,
|
||||||
)?;
|
)?;
|
||||||
|
@ -64,12 +64,6 @@ pub enum Api {
|
|||||||
Traces,
|
Traces,
|
||||||
/// Private transaction manager (Safe)
|
/// Private transaction manager (Safe)
|
||||||
Private,
|
Private,
|
||||||
/// Whisper (Safe)
|
|
||||||
// TODO: _if_ someone guesses someone else's key or filter IDs they can remove
|
|
||||||
// BUT these are all ephemeral so it seems fine.
|
|
||||||
Whisper,
|
|
||||||
/// Whisper Pub-Sub (Safe but same concerns as above).
|
|
||||||
WhisperPubSub,
|
|
||||||
/// Parity PubSub - Generic Publish-Subscriber (Safety depends on other APIs exposed).
|
/// Parity PubSub - Generic Publish-Subscriber (Safety depends on other APIs exposed).
|
||||||
ParityPubSub,
|
ParityPubSub,
|
||||||
/// Parity Accounts extensions (UNSAFE: Passwords, Side Effects (new account))
|
/// Parity Accounts extensions (UNSAFE: Passwords, Side Effects (new account))
|
||||||
@ -101,8 +95,6 @@ impl FromStr for Api {
|
|||||||
"private" => Ok(Private),
|
"private" => Ok(Private),
|
||||||
"pubsub" => Ok(EthPubSub),
|
"pubsub" => Ok(EthPubSub),
|
||||||
"secretstore" => Ok(SecretStore),
|
"secretstore" => Ok(SecretStore),
|
||||||
"shh" => Ok(Whisper),
|
|
||||||
"shh_pubsub" => Ok(WhisperPubSub),
|
|
||||||
"signer" => Ok(Signer),
|
"signer" => Ok(Signer),
|
||||||
"traces" => Ok(Traces),
|
"traces" => Ok(Traces),
|
||||||
"web3" => Ok(Web3),
|
"web3" => Ok(Web3),
|
||||||
@ -220,7 +212,6 @@ pub struct FullDependencies {
|
|||||||
pub ws_address: Option<Host>,
|
pub ws_address: Option<Host>,
|
||||||
pub fetch: FetchClient,
|
pub fetch: FetchClient,
|
||||||
pub executor: Executor,
|
pub executor: Executor,
|
||||||
pub whisper_rpc: Option<::whisper::RpcFactory>,
|
|
||||||
pub gas_price_percentile: usize,
|
pub gas_price_percentile: usize,
|
||||||
pub poll_lifetime: u32,
|
pub poll_lifetime: u32,
|
||||||
pub allow_missing_blocks: bool,
|
pub allow_missing_blocks: bool,
|
||||||
@ -407,22 +398,6 @@ impl FullDependencies {
|
|||||||
#[cfg(feature = "accounts")]
|
#[cfg(feature = "accounts")]
|
||||||
handler.extend_with(SecretStoreClient::new(&self.accounts).to_delegate());
|
handler.extend_with(SecretStoreClient::new(&self.accounts).to_delegate());
|
||||||
}
|
}
|
||||||
Api::Whisper => {
|
|
||||||
if let Some(ref whisper_rpc) = self.whisper_rpc {
|
|
||||||
let whisper = whisper_rpc.make_handler(self.net.clone());
|
|
||||||
handler.extend_with(::parity_whisper::rpc::Whisper::to_delegate(whisper));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Api::WhisperPubSub => {
|
|
||||||
if !for_generic_pubsub {
|
|
||||||
if let Some(ref whisper_rpc) = self.whisper_rpc {
|
|
||||||
let whisper = whisper_rpc.make_handler(self.net.clone());
|
|
||||||
handler.extend_with(::parity_whisper::rpc::WhisperPubSub::to_delegate(
|
|
||||||
whisper,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Api::Private => {
|
Api::Private => {
|
||||||
handler.extend_with(
|
handler.extend_with(
|
||||||
PrivateClient::new(self.private_tx_service.as_ref().map(|p| p.provider()))
|
PrivateClient::new(self.private_tx_service.as_ref().map(|p| p.provider()))
|
||||||
@ -474,7 +449,6 @@ pub struct LightDependencies<T> {
|
|||||||
pub fetch: FetchClient,
|
pub fetch: FetchClient,
|
||||||
pub experimental_rpcs: bool,
|
pub experimental_rpcs: bool,
|
||||||
pub executor: Executor,
|
pub executor: Executor,
|
||||||
pub whisper_rpc: Option<::whisper::RpcFactory>,
|
|
||||||
pub private_tx_service: Option<Arc<PrivateTransactionManager>>,
|
pub private_tx_service: Option<Arc<PrivateTransactionManager>>,
|
||||||
pub gas_price_percentile: usize,
|
pub gas_price_percentile: usize,
|
||||||
pub poll_lifetime: u32,
|
pub poll_lifetime: u32,
|
||||||
@ -640,20 +614,6 @@ impl<C: LightChainClient + 'static> LightDependencies<C> {
|
|||||||
#[cfg(feature = "accounts")]
|
#[cfg(feature = "accounts")]
|
||||||
handler.extend_with(SecretStoreClient::new(&self.accounts).to_delegate());
|
handler.extend_with(SecretStoreClient::new(&self.accounts).to_delegate());
|
||||||
}
|
}
|
||||||
Api::Whisper => {
|
|
||||||
if let Some(ref whisper_rpc) = self.whisper_rpc {
|
|
||||||
let whisper = whisper_rpc.make_handler(self.net.clone());
|
|
||||||
handler.extend_with(::parity_whisper::rpc::Whisper::to_delegate(whisper));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Api::WhisperPubSub => {
|
|
||||||
if let Some(ref whisper_rpc) = self.whisper_rpc {
|
|
||||||
let whisper = whisper_rpc.make_handler(self.net.clone());
|
|
||||||
handler.extend_with(::parity_whisper::rpc::WhisperPubSub::to_delegate(
|
|
||||||
whisper,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Api::Private => {
|
Api::Private => {
|
||||||
if let Some(ref tx_manager) = self.private_tx_service {
|
if let Some(ref tx_manager) = self.private_tx_service {
|
||||||
let private_tx_service = Some(tx_manager.clone());
|
let private_tx_service = Some(tx_manager.clone());
|
||||||
@ -693,8 +653,6 @@ impl ApiSet {
|
|||||||
Api::Eth,
|
Api::Eth,
|
||||||
Api::EthPubSub,
|
Api::EthPubSub,
|
||||||
Api::Parity,
|
Api::Parity,
|
||||||
Api::Whisper,
|
|
||||||
Api::WhisperPubSub,
|
|
||||||
Api::Private,
|
Api::Private,
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
@ -758,8 +716,6 @@ mod test {
|
|||||||
assert_eq!(Api::Traces, "traces".parse().unwrap());
|
assert_eq!(Api::Traces, "traces".parse().unwrap());
|
||||||
assert_eq!(Api::SecretStore, "secretstore".parse().unwrap());
|
assert_eq!(Api::SecretStore, "secretstore".parse().unwrap());
|
||||||
assert_eq!(Api::Private, "private".parse().unwrap());
|
assert_eq!(Api::Private, "private".parse().unwrap());
|
||||||
assert_eq!(Api::Whisper, "shh".parse().unwrap());
|
|
||||||
assert_eq!(Api::WhisperPubSub, "shh_pubsub".parse().unwrap());
|
|
||||||
assert!("rp".parse::<Api>().is_err());
|
assert!("rp".parse::<Api>().is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -787,8 +743,6 @@ mod test {
|
|||||||
Api::Parity,
|
Api::Parity,
|
||||||
Api::ParityPubSub,
|
Api::ParityPubSub,
|
||||||
Api::Traces,
|
Api::Traces,
|
||||||
Api::Whisper,
|
|
||||||
Api::WhisperPubSub,
|
|
||||||
Api::Private,
|
Api::Private,
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -807,8 +761,6 @@ mod test {
|
|||||||
Api::Parity,
|
Api::Parity,
|
||||||
Api::ParityPubSub,
|
Api::ParityPubSub,
|
||||||
Api::Traces,
|
Api::Traces,
|
||||||
Api::Whisper,
|
|
||||||
Api::WhisperPubSub,
|
|
||||||
Api::Private,
|
Api::Private,
|
||||||
// semi-safe
|
// semi-safe
|
||||||
Api::ParityAccounts,
|
Api::ParityAccounts,
|
||||||
@ -832,8 +784,6 @@ mod test {
|
|||||||
Api::ParityPubSub,
|
Api::ParityPubSub,
|
||||||
Api::Traces,
|
Api::Traces,
|
||||||
Api::SecretStore,
|
Api::SecretStore,
|
||||||
Api::Whisper,
|
|
||||||
Api::WhisperPubSub,
|
|
||||||
Api::ParityAccounts,
|
Api::ParityAccounts,
|
||||||
Api::ParitySet,
|
Api::ParitySet,
|
||||||
Api::Signer,
|
Api::Signer,
|
||||||
@ -861,8 +811,6 @@ mod test {
|
|||||||
Api::ParityPubSub,
|
Api::ParityPubSub,
|
||||||
Api::Traces,
|
Api::Traces,
|
||||||
Api::SecretStore,
|
Api::SecretStore,
|
||||||
Api::Whisper,
|
|
||||||
Api::WhisperPubSub,
|
|
||||||
Api::ParityAccounts,
|
Api::ParityAccounts,
|
||||||
Api::ParitySet,
|
Api::ParitySet,
|
||||||
Api::Signer,
|
Api::Signer,
|
||||||
@ -888,8 +836,6 @@ mod test {
|
|||||||
Api::Parity,
|
Api::Parity,
|
||||||
Api::ParityPubSub,
|
Api::ParityPubSub,
|
||||||
Api::Traces,
|
Api::Traces,
|
||||||
Api::Whisper,
|
|
||||||
Api::WhisperPubSub,
|
|
||||||
Api::Private,
|
Api::Private,
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -135,7 +135,6 @@ pub struct RunCmd {
|
|||||||
pub serve_light: bool,
|
pub serve_light: bool,
|
||||||
pub light: bool,
|
pub light: bool,
|
||||||
pub no_persistent_txqueue: bool,
|
pub no_persistent_txqueue: bool,
|
||||||
pub whisper: ::whisper::Config,
|
|
||||||
pub no_hardcoded_sync: bool,
|
pub no_hardcoded_sync: bool,
|
||||||
pub max_round_blocks_to_import: usize,
|
pub max_round_blocks_to_import: usize,
|
||||||
pub on_demand_response_time_window: Option<u64>,
|
pub on_demand_response_time_window: Option<u64>,
|
||||||
@ -309,16 +308,6 @@ where
|
|||||||
net_conf.boot_nodes = spec.nodes.clone();
|
net_conf.boot_nodes = spec.nodes.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut attached_protos = Vec::new();
|
|
||||||
let whisper_factory = if cmd.whisper.enabled {
|
|
||||||
let whisper_factory =
|
|
||||||
::whisper::setup(cmd.whisper.target_message_pool_size, &mut attached_protos)
|
|
||||||
.map_err(|e| format!("Failed to initialize whisper: {}", e))?;
|
|
||||||
whisper_factory
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
// set network path.
|
// set network path.
|
||||||
net_conf.net_config_path = Some(db_dirs.network_path().to_string_lossy().into_owned());
|
net_conf.net_config_path = Some(db_dirs.network_path().to_string_lossy().into_owned());
|
||||||
let sync_params = LightSyncParams {
|
let sync_params = LightSyncParams {
|
||||||
@ -329,7 +318,6 @@ where
|
|||||||
network_id: cmd.network_id.unwrap_or(spec.network_id()),
|
network_id: cmd.network_id.unwrap_or(spec.network_id()),
|
||||||
subprotocol_name: sync::LIGHT_PROTOCOL,
|
subprotocol_name: sync::LIGHT_PROTOCOL,
|
||||||
handlers: vec![on_demand.clone()],
|
handlers: vec![on_demand.clone()],
|
||||||
attached_protos: attached_protos,
|
|
||||||
};
|
};
|
||||||
let light_sync =
|
let light_sync =
|
||||||
LightSync::new(sync_params).map_err(|e| format!("Error starting network: {}", e))?;
|
LightSync::new(sync_params).map_err(|e| format!("Error starting network: {}", e))?;
|
||||||
@ -376,7 +364,6 @@ where
|
|||||||
fetch: fetch,
|
fetch: fetch,
|
||||||
experimental_rpcs: cmd.experimental_rpcs,
|
experimental_rpcs: cmd.experimental_rpcs,
|
||||||
executor: runtime.executor(),
|
executor: runtime.executor(),
|
||||||
whisper_rpc: whisper_factory,
|
|
||||||
private_tx_service: None, //TODO: add this to client.
|
private_tx_service: None, //TODO: add this to client.
|
||||||
gas_price_percentile: cmd.gas_price_percentile,
|
gas_price_percentile: cmd.gas_price_percentile,
|
||||||
poll_lifetime: cmd.poll_lifetime,
|
poll_lifetime: cmd.poll_lifetime,
|
||||||
@ -748,18 +735,6 @@ where
|
|||||||
.map_err(|e| format!("Stratum start error: {:?}", e))?;
|
.map_err(|e| format!("Stratum start error: {:?}", e))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut attached_protos = Vec::new();
|
|
||||||
|
|
||||||
let whisper_factory = if cmd.whisper.enabled {
|
|
||||||
let whisper_factory =
|
|
||||||
::whisper::setup(cmd.whisper.target_message_pool_size, &mut attached_protos)
|
|
||||||
.map_err(|e| format!("Failed to initialize whisper: {}", e))?;
|
|
||||||
|
|
||||||
whisper_factory
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let private_tx_sync: Option<Arc<dyn PrivateTxHandler>> = match cmd.private_tx_enabled {
|
let private_tx_sync: Option<Arc<dyn PrivateTxHandler>> = match cmd.private_tx_enabled {
|
||||||
true => Some(private_tx_service.clone() as Arc<dyn PrivateTxHandler>),
|
true => Some(private_tx_service.clone() as Arc<dyn PrivateTxHandler>),
|
||||||
false => None,
|
false => None,
|
||||||
@ -774,7 +749,6 @@ where
|
|||||||
private_tx_sync,
|
private_tx_sync,
|
||||||
client.clone(),
|
client.clone(),
|
||||||
&cmd.logger_config,
|
&cmd.logger_config,
|
||||||
attached_protos,
|
|
||||||
connection_filter
|
connection_filter
|
||||||
.clone()
|
.clone()
|
||||||
.map(|f| f as Arc<dyn crate::sync::ConnectionFilter + 'static>),
|
.map(|f| f as Arc<dyn crate::sync::ConnectionFilter + 'static>),
|
||||||
@ -867,7 +841,6 @@ where
|
|||||||
ws_address: cmd.ws_conf.address(),
|
ws_address: cmd.ws_conf.address(),
|
||||||
fetch: fetch.clone(),
|
fetch: fetch.clone(),
|
||||||
executor: runtime.executor(),
|
executor: runtime.executor(),
|
||||||
whisper_rpc: whisper_factory,
|
|
||||||
private_tx_service: Some(private_tx_service.clone()),
|
private_tx_service: Some(private_tx_service.clone()),
|
||||||
gas_price_percentile: cmd.gas_price_percentile,
|
gas_price_percentile: cmd.gas_price_percentile,
|
||||||
poll_lifetime: cmd.poll_lifetime,
|
poll_lifetime: cmd.poll_lifetime,
|
||||||
|
@ -1,127 +0,0 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
|
||||||
// This file is part of Parity Ethereum.
|
|
||||||
|
|
||||||
// Parity Ethereum 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.
|
|
||||||
|
|
||||||
// Parity Ethereum 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 Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
use std::{io, sync::Arc};
|
|
||||||
|
|
||||||
use parity_rpc::Metadata;
|
|
||||||
use parity_whisper::{
|
|
||||||
message::Message,
|
|
||||||
net::{self as whisper_net, Network as WhisperNetwork},
|
|
||||||
rpc::{FilterManager, PoolHandle, WhisperClient},
|
|
||||||
};
|
|
||||||
use sync::{AttachedProtocol, ManageNetwork};
|
|
||||||
|
|
||||||
/// Whisper config.
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub struct Config {
|
|
||||||
pub enabled: bool,
|
|
||||||
pub target_message_pool_size: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Config {
|
|
||||||
fn default() -> Self {
|
|
||||||
Config {
|
|
||||||
enabled: false,
|
|
||||||
target_message_pool_size: 10 * 1024 * 1024,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Standard pool handle.
|
|
||||||
pub struct NetPoolHandle {
|
|
||||||
/// Pool handle.
|
|
||||||
handle: Arc<WhisperNetwork<Arc<FilterManager>>>,
|
|
||||||
/// Network manager.
|
|
||||||
net: Arc<dyn ManageNetwork>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PoolHandle for NetPoolHandle {
|
|
||||||
fn relay(&self, message: Message) -> bool {
|
|
||||||
let mut res = false;
|
|
||||||
let mut message = Some(message);
|
|
||||||
self.net
|
|
||||||
.with_proto_context(whisper_net::PROTOCOL_ID, &mut |ctx| {
|
|
||||||
if let Some(message) = message.take() {
|
|
||||||
res = self.handle.post_message(message, ctx);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pool_status(&self) -> whisper_net::PoolStatus {
|
|
||||||
self.handle.pool_status()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Factory for standard whisper RPC.
|
|
||||||
pub struct RpcFactory {
|
|
||||||
net: Arc<WhisperNetwork<Arc<FilterManager>>>,
|
|
||||||
manager: Arc<FilterManager>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RpcFactory {
|
|
||||||
pub fn make_handler(
|
|
||||||
&self,
|
|
||||||
net: Arc<dyn ManageNetwork>,
|
|
||||||
) -> WhisperClient<NetPoolHandle, Metadata> {
|
|
||||||
let handle = NetPoolHandle {
|
|
||||||
handle: self.net.clone(),
|
|
||||||
net: net,
|
|
||||||
};
|
|
||||||
WhisperClient::new(handle, self.manager.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets up whisper protocol and RPC handler.
|
|
||||||
///
|
|
||||||
/// Will target the given pool size.
|
|
||||||
#[cfg(not(feature = "ipc"))]
|
|
||||||
pub fn setup(
|
|
||||||
target_pool_size: usize,
|
|
||||||
protos: &mut Vec<AttachedProtocol>,
|
|
||||||
) -> io::Result<Option<RpcFactory>> {
|
|
||||||
let manager = Arc::new(FilterManager::new()?);
|
|
||||||
let net = Arc::new(WhisperNetwork::new(target_pool_size, manager.clone()));
|
|
||||||
|
|
||||||
protos.push(AttachedProtocol {
|
|
||||||
handler: net.clone() as Arc<_>,
|
|
||||||
versions: whisper_net::SUPPORTED_VERSIONS,
|
|
||||||
protocol_id: whisper_net::PROTOCOL_ID,
|
|
||||||
});
|
|
||||||
|
|
||||||
// parity-only extensions to whisper.
|
|
||||||
protos.push(AttachedProtocol {
|
|
||||||
handler: Arc::new(whisper_net::ParityExtensions),
|
|
||||||
versions: whisper_net::SUPPORTED_VERSIONS,
|
|
||||||
protocol_id: whisper_net::PARITY_PROTOCOL_ID,
|
|
||||||
});
|
|
||||||
|
|
||||||
let factory = RpcFactory {
|
|
||||||
net: net,
|
|
||||||
manager: manager,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Some(factory))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: make it possible to attach generic protocols in IPC.
|
|
||||||
#[cfg(feature = "ipc")]
|
|
||||||
pub fn setup(
|
|
||||||
_target_pool_size: usize,
|
|
||||||
_protos: &mut Vec<AttachedProtocol>,
|
|
||||||
) -> io::Result<Option<RpcFactory>> {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
@ -25,9 +25,6 @@ apps:
|
|||||||
ethstore:
|
ethstore:
|
||||||
command: ethstore
|
command: ethstore
|
||||||
plugs: [home, removable-media]
|
plugs: [home, removable-media]
|
||||||
whisper:
|
|
||||||
command: whisper
|
|
||||||
plugs: [home, network-bind, removable-media]
|
|
||||||
|
|
||||||
icon: ./scripts/snap/icon.png
|
icon: ./scripts/snap/icon.png
|
||||||
|
|
||||||
@ -49,5 +46,4 @@ parts:
|
|||||||
cp -v parity-evm $SNAPCRAFT_PART_INSTALL/usr/bin/parity-evm
|
cp -v parity-evm $SNAPCRAFT_PART_INSTALL/usr/bin/parity-evm
|
||||||
cp -v ethkey $SNAPCRAFT_PART_INSTALL/usr/bin/ethkey
|
cp -v ethkey $SNAPCRAFT_PART_INSTALL/usr/bin/ethkey
|
||||||
cp -v ethstore $SNAPCRAFT_PART_INSTALL/usr/bin/ethstore
|
cp -v ethstore $SNAPCRAFT_PART_INSTALL/usr/bin/ethstore
|
||||||
cp -v whisper $SNAPCRAFT_PART_INSTALL/usr/bin/whisper
|
|
||||||
stage-packages: [libstdc++6, cmake, libdb5.3]
|
stage-packages: [libstdc++6, cmake, libdb5.3]
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
[package]
|
|
||||||
description = "Parity Ethereum Whisper Protocol Implementation"
|
|
||||||
name = "parity-whisper"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Parity Technologies <admin@parity.io>"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bitflags = "0.9"
|
|
||||||
byteorder = "1.0.0"
|
|
||||||
ethereum-types = "0.4"
|
|
||||||
ethcore-network = { path = "../util/network" }
|
|
||||||
parity-crypto = "0.3.0"
|
|
||||||
ethkey = { path = "../accounts/ethkey" }
|
|
||||||
hex = "0.2"
|
|
||||||
log = "0.4"
|
|
||||||
memzero = { path = "../util/memzero" }
|
|
||||||
ordered-float = "0.5"
|
|
||||||
parking_lot = "0.7"
|
|
||||||
rand = "0.4"
|
|
||||||
rlp = { version = "0.3.0", features = ["ethereum"] }
|
|
||||||
serde = "1.0"
|
|
||||||
serde_derive = "1.0"
|
|
||||||
serde_json = "1.0"
|
|
||||||
slab = "0.3"
|
|
||||||
smallvec = "0.6"
|
|
||||||
tiny-keccak = "1.4"
|
|
||||||
time-utils = { path = "../util/time-utils" }
|
|
||||||
|
|
||||||
jsonrpc-core = "14.0.3"
|
|
||||||
jsonrpc-derive = "14.0.3"
|
|
||||||
jsonrpc-pubsub = "14.0.3"
|
|
@ -1,33 +0,0 @@
|
|||||||
## Whisper
|
|
||||||
|
|
||||||
Implementation of Whisper based on the Whisper-v2 PoC.
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
```
|
|
||||||
Parity Whisper-v2 CLI.
|
|
||||||
Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
whisper [options]
|
|
||||||
whisper [-h | --help]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--whisper-pool-size SIZE Specify Whisper pool size [default: 10].
|
|
||||||
-p, --port PORT Specify which P2P port to use [default: random].
|
|
||||||
-a, --address ADDRESS Specify which P2P address to use [default: 127.0.0.1].
|
|
||||||
-s, --secret KEYFILE Specify which file contains the key to generate the enode.
|
|
||||||
-P, --rpc-port PORT Specify which RPC port to use [default: 8545].
|
|
||||||
-A, --rpc-address ADDRESS Specify which RPC address to use [default: 127.0.0.1].
|
|
||||||
-l, --log LEVEL Specify the logging level. Must conform to the same format as RUST_LOG [default: Error].
|
|
||||||
-h, --help Display this message and exit.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Parity Ethereum toolchain
|
|
||||||
_This project is a part of the Parity Ethereum toolchain._
|
|
||||||
|
|
||||||
- [evmbin](https://github.com/paritytech/parity-ethereum/blob/master/evmbin/) - EVM implementation for Parity Ethereum.
|
|
||||||
- [ethabi](https://github.com/paritytech/ethabi) - Parity Ethereum function calls encoding.
|
|
||||||
- [ethstore](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethstore) - Parity Ethereum key management.
|
|
||||||
- [ethkey](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethkey) - Parity Ethereum keys generator.
|
|
||||||
- [whisper](https://github.com/paritytech/parity-ethereum/blob/master/whisper/) - Implementation of Whisper-v2 PoC.
|
|
@ -1,26 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "whisper-cli"
|
|
||||||
description = "Whisper command line interface"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Parity Technologies <admin@parity.io>"]
|
|
||||||
license = "GPL-3.0"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
docopt = "1.0"
|
|
||||||
env_logger = "0.5"
|
|
||||||
ethcore-network = { path = "../../util/network" }
|
|
||||||
ethcore-network-devp2p = { path = "../../util/network-devp2p" }
|
|
||||||
jsonrpc-core = "14.0.0"
|
|
||||||
jsonrpc-http-server = "14.0.0"
|
|
||||||
jsonrpc-pubsub = "14.0.0"
|
|
||||||
log = "0.4"
|
|
||||||
panic_hook = { path = "../../util/panic-hook" }
|
|
||||||
parity-whisper = { path = "../" }
|
|
||||||
serde = "1.0"
|
|
||||||
serde_derive = "1.0"
|
|
||||||
ethkey = { path = "../../accounts/ethkey" }
|
|
||||||
rustc-hex = "2.0"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "whisper"
|
|
||||||
path = "src/main.rs"
|
|
@ -1,377 +0,0 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
|
||||||
// This file is part of Parity Ethereum.
|
|
||||||
|
|
||||||
// Parity Ethereum 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.
|
|
||||||
|
|
||||||
// Parity Ethereum 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 Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
//! Whisper command line interface
|
|
||||||
//!
|
|
||||||
//! Spawns an Ethereum network instance and attaches the Whisper protocol RPCs to it.
|
|
||||||
//!
|
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
|
||||||
#![cfg_attr(feature = "cargo-clippy", deny(clippy, clippy_pedantic))]
|
|
||||||
|
|
||||||
extern crate docopt;
|
|
||||||
extern crate env_logger;
|
|
||||||
extern crate ethcore_network as net;
|
|
||||||
extern crate ethcore_network_devp2p as devp2p;
|
|
||||||
extern crate panic_hook;
|
|
||||||
extern crate parity_whisper as whisper;
|
|
||||||
extern crate serde;
|
|
||||||
|
|
||||||
extern crate ethkey;
|
|
||||||
extern crate jsonrpc_core;
|
|
||||||
extern crate jsonrpc_http_server;
|
|
||||||
extern crate jsonrpc_pubsub;
|
|
||||||
extern crate rustc_hex;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log as rlog;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
|
|
||||||
use docopt::Docopt;
|
|
||||||
use ethkey::Secret;
|
|
||||||
use jsonrpc_core::{MetaIoHandler, Metadata};
|
|
||||||
use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation};
|
|
||||||
use jsonrpc_pubsub::{PubSubMetadata, Session};
|
|
||||||
use rustc_hex::FromHex;
|
|
||||||
use std::{
|
|
||||||
env, fmt, io,
|
|
||||||
net::{Ipv4Addr, SocketAddr, SocketAddrV4},
|
|
||||||
process,
|
|
||||||
str::FromStr,
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
const POOL_UNIT: usize = 1024 * 1024;
|
|
||||||
const USAGE: &'static str = r#"
|
|
||||||
Parity Whisper-v2 CLI.
|
|
||||||
Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
whisper [options]
|
|
||||||
whisper [-h | --help]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--whisper-pool-size SIZE Specify Whisper pool size [default: 10].
|
|
||||||
-p, --port PORT Specify which P2P port to use [default: random].
|
|
||||||
-a, --address ADDRESS Specify which P2P address to use [default: 127.0.0.1].
|
|
||||||
-s, --secret KEYFILE Specify which file contains the key to generate the enode.
|
|
||||||
-P, --rpc-port PORT Specify which RPC port to use [default: 8545].
|
|
||||||
-A, --rpc-address ADDRESS Specify which RPC address to use [default: 127.0.0.1].
|
|
||||||
-l, --log LEVEL Specify the logging level. Must conform to the same format as RUST_LOG [default: Error].
|
|
||||||
-h, --help Display this message and exit.
|
|
||||||
"#;
|
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
|
||||||
struct Meta;
|
|
||||||
|
|
||||||
impl Metadata for Meta {}
|
|
||||||
|
|
||||||
impl PubSubMetadata for Meta {
|
|
||||||
fn session(&self) -> Option<Arc<Session>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct Args {
|
|
||||||
flag_whisper_pool_size: usize,
|
|
||||||
flag_port: String,
|
|
||||||
flag_address: String,
|
|
||||||
flag_rpc_port: String,
|
|
||||||
flag_rpc_address: String,
|
|
||||||
flag_log: String,
|
|
||||||
flag_secret: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WhisperPoolHandle {
|
|
||||||
/// Pool handle.
|
|
||||||
handle: Arc<whisper::net::Network<Arc<whisper::rpc::FilterManager>>>,
|
|
||||||
/// Network manager.
|
|
||||||
net: Arc<devp2p::NetworkService>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl whisper::rpc::PoolHandle for WhisperPoolHandle {
|
|
||||||
fn relay(&self, message: whisper::message::Message) -> bool {
|
|
||||||
let mut res = false;
|
|
||||||
let mut message = Some(message);
|
|
||||||
self.with_proto_context(whisper::net::PROTOCOL_ID, &mut |ctx| {
|
|
||||||
if let Some(message) = message.take() {
|
|
||||||
res = self.handle.post_message(message, ctx);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pool_status(&self) -> whisper::net::PoolStatus {
|
|
||||||
self.handle.pool_status()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WhisperPoolHandle {
|
|
||||||
fn with_proto_context(
|
|
||||||
&self,
|
|
||||||
proto: net::ProtocolId,
|
|
||||||
f: &mut dyn FnMut(&dyn net::NetworkContext),
|
|
||||||
) {
|
|
||||||
self.net.with_context_eval(proto, f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RpcFactory {
|
|
||||||
handle: Arc<whisper::Network<Arc<whisper::rpc::FilterManager>>>,
|
|
||||||
manager: Arc<whisper::rpc::FilterManager>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RpcFactory {
|
|
||||||
fn make_handler(
|
|
||||||
&self,
|
|
||||||
net: Arc<devp2p::NetworkService>,
|
|
||||||
) -> whisper::rpc::WhisperClient<WhisperPoolHandle, Meta> {
|
|
||||||
let whisper_pool_handle = WhisperPoolHandle {
|
|
||||||
handle: self.handle.clone(),
|
|
||||||
net: net,
|
|
||||||
};
|
|
||||||
whisper::rpc::WhisperClient::new(whisper_pool_handle, self.manager.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Error {
|
|
||||||
Docopt(docopt::Error),
|
|
||||||
Io(io::Error),
|
|
||||||
JsonRpc(jsonrpc_core::Error),
|
|
||||||
Network(net::Error),
|
|
||||||
SockAddr(std::net::AddrParseError),
|
|
||||||
FromHex(rustc_hex::FromHexError),
|
|
||||||
ParseInt(std::num::ParseIntError),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::net::AddrParseError> for Error {
|
|
||||||
fn from(err: std::net::AddrParseError) -> Self {
|
|
||||||
Error::SockAddr(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<net::Error> for Error {
|
|
||||||
fn from(err: net::Error) -> Self {
|
|
||||||
Error::Network(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<docopt::Error> for Error {
|
|
||||||
fn from(err: docopt::Error) -> Self {
|
|
||||||
Error::Docopt(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<io::Error> for Error {
|
|
||||||
fn from(err: io::Error) -> Self {
|
|
||||||
Error::Io(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<jsonrpc_core::Error> for Error {
|
|
||||||
fn from(err: jsonrpc_core::Error) -> Self {
|
|
||||||
Error::JsonRpc(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<rustc_hex::FromHexError> for Error {
|
|
||||||
fn from(err: rustc_hex::FromHexError) -> Self {
|
|
||||||
Error::FromHex(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::num::ParseIntError> for Error {
|
|
||||||
fn from(err: std::num::ParseIntError) -> Self {
|
|
||||||
Error::ParseInt(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
|
||||||
match *self {
|
|
||||||
Error::SockAddr(ref e) => write!(f, "{}", e),
|
|
||||||
Error::Docopt(ref e) => write!(f, "{}", e),
|
|
||||||
Error::Io(ref e) => write!(f, "{}", e),
|
|
||||||
Error::JsonRpc(ref e) => write!(f, "{:?}", e),
|
|
||||||
Error::Network(ref e) => write!(f, "{}", e),
|
|
||||||
Error::ParseInt(ref e) => write!(f, "Invalid port: {}", e),
|
|
||||||
Error::FromHex(ref e) => write!(f, "Error deciphering key: {}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
panic_hook::set_abort();
|
|
||||||
|
|
||||||
match execute(env::args()) {
|
|
||||||
Ok(_) => {
|
|
||||||
println!("whisper-cli terminated");
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
Err(Error::Docopt(ref e)) => e.exit(),
|
|
||||||
Err(err) => {
|
|
||||||
println!("{}", err);
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute<S, I>(command: I) -> Result<(), Error>
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = S>,
|
|
||||||
S: AsRef<str>,
|
|
||||||
{
|
|
||||||
// Parse arguments
|
|
||||||
let args: Args = Docopt::new(USAGE).and_then(|d| d.argv(command).deserialize())?;
|
|
||||||
let pool_size = args.flag_whisper_pool_size * POOL_UNIT;
|
|
||||||
let rpc_url = format!("{}:{}", args.flag_rpc_address, args.flag_rpc_port);
|
|
||||||
|
|
||||||
initialize_logger(args.flag_log);
|
|
||||||
info!(target: "whisper-cli", "start");
|
|
||||||
|
|
||||||
// Filter manager that will dispatch `decryption tasks`
|
|
||||||
let manager = Arc::new(whisper::rpc::FilterManager::new()?);
|
|
||||||
|
|
||||||
// Whisper protocol network handler
|
|
||||||
let whisper_network_handler = Arc::new(whisper::net::Network::new(pool_size, manager.clone()));
|
|
||||||
|
|
||||||
let network_config = {
|
|
||||||
let mut cfg = net::NetworkConfiguration::new();
|
|
||||||
let port = match args.flag_port.as_str() {
|
|
||||||
"random" => 0 as u16,
|
|
||||||
port => port.parse::<u16>()?,
|
|
||||||
};
|
|
||||||
let addr = Ipv4Addr::from_str(&args.flag_address[..])?;
|
|
||||||
cfg.listen_address = Some(SocketAddr::V4(SocketAddrV4::new(addr, port)));
|
|
||||||
cfg.use_secret = match args.flag_secret.as_str() {
|
|
||||||
"" => None,
|
|
||||||
fname => {
|
|
||||||
let key_text = std::fs::read_to_string(fname)?;
|
|
||||||
let key: Vec<u8> = FromHex::from_hex(key_text.as_str())?;
|
|
||||||
Secret::from_slice(key.as_slice())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
cfg.nat_enabled = false;
|
|
||||||
cfg
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create network service
|
|
||||||
let network = devp2p::NetworkService::new(network_config, None)?;
|
|
||||||
|
|
||||||
// Start network service
|
|
||||||
network.start().map_err(|(err, _)| err)?;
|
|
||||||
|
|
||||||
// Attach whisper protocol to the network service
|
|
||||||
network.register_protocol(
|
|
||||||
whisper_network_handler.clone(),
|
|
||||||
whisper::net::PROTOCOL_ID,
|
|
||||||
whisper::net::SUPPORTED_VERSIONS,
|
|
||||||
)?;
|
|
||||||
network.register_protocol(
|
|
||||||
Arc::new(whisper::net::ParityExtensions),
|
|
||||||
whisper::net::PARITY_PROTOCOL_ID,
|
|
||||||
whisper::net::SUPPORTED_VERSIONS,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Request handler
|
|
||||||
let mut io = MetaIoHandler::default();
|
|
||||||
|
|
||||||
// Shared network service
|
|
||||||
let shared_network = Arc::new(network);
|
|
||||||
|
|
||||||
// Pool handler
|
|
||||||
let whisper_factory = RpcFactory {
|
|
||||||
handle: whisper_network_handler,
|
|
||||||
manager: manager,
|
|
||||||
};
|
|
||||||
|
|
||||||
io.extend_with(whisper::rpc::Whisper::to_delegate(
|
|
||||||
whisper_factory.make_handler(shared_network.clone()),
|
|
||||||
));
|
|
||||||
io.extend_with(whisper::rpc::WhisperPubSub::to_delegate(
|
|
||||||
whisper_factory.make_handler(shared_network.clone()),
|
|
||||||
));
|
|
||||||
|
|
||||||
let server = jsonrpc_http_server::ServerBuilder::new(io)
|
|
||||||
.cors(DomainsValidation::AllowOnly(vec![
|
|
||||||
AccessControlAllowOrigin::Null,
|
|
||||||
]))
|
|
||||||
.start_http(&rpc_url.parse()?)?;
|
|
||||||
|
|
||||||
server.wait();
|
|
||||||
|
|
||||||
// This will never return if the http server runs without errors
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn initialize_logger(log_level: String) {
|
|
||||||
env_logger::Builder::from_env(env_logger::Env::default())
|
|
||||||
.parse(&log_level)
|
|
||||||
.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::execute;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn invalid_argument() {
|
|
||||||
let command = vec!["whisper", "--foo=12"]
|
|
||||||
.into_iter()
|
|
||||||
.map(Into::into)
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
|
|
||||||
assert!(execute(command).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[ignore]
|
|
||||||
fn privileged_port() {
|
|
||||||
let command = vec!["whisper", "--port=3"]
|
|
||||||
.into_iter()
|
|
||||||
.map(Into::into)
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
|
|
||||||
assert!(execute(command).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn invalid_ip_address() {
|
|
||||||
let command = vec!["whisper", "--address=x.x.x.x"]
|
|
||||||
.into_iter()
|
|
||||||
.map(Into::into)
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
|
|
||||||
assert!(execute(command).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn invalid_whisper_pool_size() {
|
|
||||||
let command = vec![
|
|
||||||
"whisper",
|
|
||||||
"--whisper-pool-size=-100000000000000000000000000000000000000",
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.map(Into::into)
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
|
|
||||||
assert!(execute(command).is_err());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
|
||||||
// This file is part of Parity Ethereum.
|
|
||||||
|
|
||||||
// Parity Ethereum 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.
|
|
||||||
|
|
||||||
// Parity Ethereum 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 Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
//! Whisper P2P messaging system as a DevP2P subprotocol, with RPC and Rust
|
|
||||||
//! interface.
|
|
||||||
|
|
||||||
#![cfg_attr(feature = "time_checked_add", feature(time_checked_add))]
|
|
||||||
#![allow(deprecated)]
|
|
||||||
|
|
||||||
extern crate byteorder;
|
|
||||||
extern crate ethcore_network as network;
|
|
||||||
extern crate ethereum_types;
|
|
||||||
extern crate ethkey;
|
|
||||||
extern crate hex;
|
|
||||||
extern crate memzero;
|
|
||||||
extern crate ordered_float;
|
|
||||||
extern crate parity_crypto as crypto;
|
|
||||||
extern crate parking_lot;
|
|
||||||
extern crate rand;
|
|
||||||
extern crate rlp;
|
|
||||||
extern crate serde;
|
|
||||||
extern crate slab;
|
|
||||||
extern crate smallvec;
|
|
||||||
extern crate tiny_keccak;
|
|
||||||
|
|
||||||
extern crate jsonrpc_core;
|
|
||||||
extern crate jsonrpc_derive;
|
|
||||||
extern crate jsonrpc_pubsub;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate bitflags;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
extern crate serde_derive;
|
|
||||||
|
|
||||||
#[cfg(not(time_checked_add))]
|
|
||||||
extern crate time_utils;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
extern crate serde_json;
|
|
||||||
|
|
||||||
pub use self::{
|
|
||||||
message::Message,
|
|
||||||
net::{MessageHandler, Network},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod message;
|
|
||||||
pub mod net;
|
|
||||||
pub mod rpc;
|
|
@ -1,576 +0,0 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
|
||||||
// This file is part of Parity Ethereum.
|
|
||||||
|
|
||||||
// Parity Ethereum 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.
|
|
||||||
|
|
||||||
// Parity Ethereum 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 Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
//! Whisper message parsing, handlers, and construction.
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
fmt,
|
|
||||||
time::{self, Duration, Instant, SystemTime},
|
|
||||||
};
|
|
||||||
|
|
||||||
use ethereum_types::{H256, H512};
|
|
||||||
use rlp::{self, DecoderError, Rlp, RlpStream};
|
|
||||||
use smallvec::SmallVec;
|
|
||||||
use tiny_keccak::{keccak256, Keccak};
|
|
||||||
|
|
||||||
#[cfg(not(time_checked_add))]
|
|
||||||
use time_utils::CheckedSystemTime;
|
|
||||||
|
|
||||||
/// Work-factor proved. Takes 3 parameters: size of message, time to live,
|
|
||||||
/// and hash.
|
|
||||||
///
|
|
||||||
/// Panics if size or TTL is zero.
|
|
||||||
pub fn work_factor_proved(size: u64, ttl: u64, hash: H256) -> f64 {
|
|
||||||
assert!(size != 0 && ttl != 0);
|
|
||||||
|
|
||||||
let leading_zeros = {
|
|
||||||
let leading_bytes = hash.iter().take_while(|&&x| x == 0).count();
|
|
||||||
let remaining_leading_bits = hash
|
|
||||||
.get(leading_bytes)
|
|
||||||
.map_or(0, |byte| byte.leading_zeros() as usize);
|
|
||||||
(leading_bytes * 8) + remaining_leading_bits
|
|
||||||
};
|
|
||||||
let spacetime = size as f64 * ttl as f64;
|
|
||||||
|
|
||||||
2.0_f64.powi(leading_zeros as i32) / spacetime
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A topic of a message.
|
|
||||||
#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub struct Topic(pub [u8; 4]);
|
|
||||||
|
|
||||||
impl From<[u8; 4]> for Topic {
|
|
||||||
fn from(x: [u8; 4]) -> Self {
|
|
||||||
Topic(x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Topic {
|
|
||||||
/// set up to three bits in the 64-byte bloom passed.
|
|
||||||
///
|
|
||||||
/// this takes 3 sets of 9 bits, treating each as an index in the range
|
|
||||||
/// 0..512 into the bloom and setting the corresponding bit in the bloom to 1.
|
|
||||||
pub fn bloom_into(&self, bloom: &mut H512) {
|
|
||||||
let data = &self.0;
|
|
||||||
for i in 0..3 {
|
|
||||||
let mut idx = data[i] as usize;
|
|
||||||
|
|
||||||
if data[3] & (1 << i) != 0 {
|
|
||||||
idx += 256;
|
|
||||||
}
|
|
||||||
|
|
||||||
debug_assert!(idx <= 511);
|
|
||||||
bloom[idx / 8] |= 1 << (7 - idx % 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get bloom for single topic.
|
|
||||||
pub fn bloom(&self) -> H512 {
|
|
||||||
let mut bloom = Default::default();
|
|
||||||
self.bloom_into(&mut bloom);
|
|
||||||
bloom
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl rlp::Encodable for Topic {
|
|
||||||
fn rlp_append(&self, s: &mut RlpStream) {
|
|
||||||
s.encoder().encode_value(&self.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl rlp::Decodable for Topic {
|
|
||||||
fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
|
|
||||||
use std::cmp;
|
|
||||||
|
|
||||||
rlp.decoder()
|
|
||||||
.decode_value(|bytes| match bytes.len().cmp(&4) {
|
|
||||||
cmp::Ordering::Less => Err(DecoderError::RlpIsTooShort),
|
|
||||||
cmp::Ordering::Greater => Err(DecoderError::RlpIsTooBig),
|
|
||||||
cmp::Ordering::Equal => {
|
|
||||||
let mut t = [0u8; 4];
|
|
||||||
t.copy_from_slice(bytes);
|
|
||||||
Ok(Topic(t))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate union of blooms for given topics.
|
|
||||||
pub fn bloom_topics(topics: &[Topic]) -> H512 {
|
|
||||||
let mut bloom = H512::default();
|
|
||||||
for topic in topics {
|
|
||||||
topic.bloom_into(&mut bloom);
|
|
||||||
}
|
|
||||||
bloom
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Message errors.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
Decoder(DecoderError),
|
|
||||||
EmptyTopics,
|
|
||||||
LivesTooLong,
|
|
||||||
IssuedInFuture,
|
|
||||||
TimestampOverflow,
|
|
||||||
ZeroTTL,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<DecoderError> for Error {
|
|
||||||
fn from(err: DecoderError) -> Self {
|
|
||||||
Error::Decoder(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match *self {
|
|
||||||
Error::Decoder(ref err) => write!(f, "Failed to decode message: {}", err),
|
|
||||||
Error::LivesTooLong => write!(f, "Message claims to be issued before the unix epoch."),
|
|
||||||
Error::IssuedInFuture => write!(f, "Message issued in future."),
|
|
||||||
Error::ZeroTTL => write!(f, "Message live for zero time."),
|
|
||||||
Error::TimestampOverflow => write!(f, "Timestamp overflow"),
|
|
||||||
Error::EmptyTopics => write!(f, "Message has no topics."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn append_topics<'a>(s: &'a mut RlpStream, topics: &[Topic]) -> &'a mut RlpStream {
|
|
||||||
if topics.len() == 1 {
|
|
||||||
s.append(&topics[0])
|
|
||||||
} else {
|
|
||||||
s.append_list(&topics)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode_topics(rlp: Rlp) -> Result<SmallVec<[Topic; 4]>, DecoderError> {
|
|
||||||
if rlp.is_list() {
|
|
||||||
rlp.iter().map(|r| r.as_val::<Topic>()).collect()
|
|
||||||
} else {
|
|
||||||
rlp.as_val().map(|t| SmallVec::from_slice(&[t]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Raw envelope struct.
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Envelope {
|
|
||||||
/// Expiry timestamp
|
|
||||||
pub expiry: u64,
|
|
||||||
/// Time-to-live in seconds
|
|
||||||
pub ttl: u64,
|
|
||||||
/// series of 4-byte topics.
|
|
||||||
pub topics: SmallVec<[Topic; 4]>,
|
|
||||||
/// The message contained within.
|
|
||||||
pub data: Vec<u8>,
|
|
||||||
/// Arbitrary value used to target lower PoW hash.
|
|
||||||
pub nonce: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Envelope {
|
|
||||||
/// Whether the message is multi-topic. Only relay these to Parity peers.
|
|
||||||
pub fn is_multitopic(&self) -> bool {
|
|
||||||
self.topics.len() != 1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn proving_hash(&self) -> H256 {
|
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
|
||||||
|
|
||||||
let mut buf = [0; 32];
|
|
||||||
|
|
||||||
let mut stream = RlpStream::new_list(4);
|
|
||||||
stream.append(&self.expiry).append(&self.ttl);
|
|
||||||
|
|
||||||
append_topics(&mut stream, &self.topics).append(&self.data);
|
|
||||||
|
|
||||||
let mut digest = Keccak::new_keccak256();
|
|
||||||
digest.update(&*stream.drain());
|
|
||||||
digest.update(&{
|
|
||||||
let mut nonce_bytes = [0u8; 8];
|
|
||||||
BigEndian::write_u64(&mut nonce_bytes, self.nonce);
|
|
||||||
|
|
||||||
nonce_bytes
|
|
||||||
});
|
|
||||||
|
|
||||||
digest.finalize(&mut buf);
|
|
||||||
H256(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl rlp::Encodable for Envelope {
|
|
||||||
fn rlp_append(&self, s: &mut RlpStream) {
|
|
||||||
s.begin_list(5).append(&self.expiry).append(&self.ttl);
|
|
||||||
|
|
||||||
append_topics(s, &self.topics)
|
|
||||||
.append(&self.data)
|
|
||||||
.append(&self.nonce);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl rlp::Decodable for Envelope {
|
|
||||||
fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
|
|
||||||
if rlp.item_count()? != 5 {
|
|
||||||
return Err(DecoderError::RlpIncorrectListLen);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Envelope {
|
|
||||||
expiry: rlp.val_at(0)?,
|
|
||||||
ttl: rlp.val_at(1)?,
|
|
||||||
topics: decode_topics(rlp.at(2)?)?,
|
|
||||||
data: rlp.val_at(3)?,
|
|
||||||
nonce: rlp.val_at(4)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Message creation parameters.
|
|
||||||
/// Pass this to `Message::create` to make a message.
|
|
||||||
pub struct CreateParams {
|
|
||||||
/// time-to-live in seconds.
|
|
||||||
pub ttl: u64,
|
|
||||||
/// payload data.
|
|
||||||
pub payload: Vec<u8>,
|
|
||||||
/// Topics. May not be empty.
|
|
||||||
pub topics: Vec<Topic>,
|
|
||||||
/// How many milliseconds to spend proving work.
|
|
||||||
pub work: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A whisper message. This is a checked message carrying around metadata.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct Message {
|
|
||||||
envelope: Envelope,
|
|
||||||
bloom: H512,
|
|
||||||
hash: H256,
|
|
||||||
encoded_size: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Message {
|
|
||||||
/// Create a message from creation parameters.
|
|
||||||
/// Panics if TTL is 0.
|
|
||||||
pub fn create(params: CreateParams) -> Result<Self, Error> {
|
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
|
||||||
use rand::{Rng, SeedableRng, XorShiftRng};
|
|
||||||
|
|
||||||
if params.topics.is_empty() {
|
|
||||||
return Err(Error::EmptyTopics);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut rng = {
|
|
||||||
let mut thread_rng = ::rand::thread_rng();
|
|
||||||
XorShiftRng::from_seed(thread_rng.gen::<[u32; 4]>())
|
|
||||||
};
|
|
||||||
|
|
||||||
assert!(params.ttl > 0);
|
|
||||||
|
|
||||||
let expiry = {
|
|
||||||
let since_epoch = SystemTime::now()
|
|
||||||
.checked_add(Duration::from_secs(params.ttl))
|
|
||||||
.and_then(|t| t.checked_add(Duration::from_millis(params.work)))
|
|
||||||
.ok_or(Error::TimestampOverflow)?
|
|
||||||
.duration_since(time::UNIX_EPOCH)
|
|
||||||
.expect("time after now is after unix epoch; qed");
|
|
||||||
|
|
||||||
// round up the sub-second to next whole second.
|
|
||||||
since_epoch.as_secs()
|
|
||||||
+ if since_epoch.subsec_nanos() == 0 {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let start_digest = {
|
|
||||||
let mut stream = RlpStream::new_list(4);
|
|
||||||
stream.append(&expiry).append(¶ms.ttl);
|
|
||||||
append_topics(&mut stream, ¶ms.topics).append(¶ms.payload);
|
|
||||||
|
|
||||||
let mut digest = Keccak::new_keccak256();
|
|
||||||
digest.update(&*stream.drain());
|
|
||||||
digest
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut buf = [0; 32];
|
|
||||||
let mut try_nonce = move |nonce: &[u8; 8]| {
|
|
||||||
let mut digest = start_digest.clone();
|
|
||||||
digest.update(&nonce[..]);
|
|
||||||
digest.finalize(&mut buf[..]);
|
|
||||||
|
|
||||||
buf.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut nonce: [u8; 8] = rng.gen();
|
|
||||||
let mut best_found = try_nonce(&nonce);
|
|
||||||
|
|
||||||
let start = Instant::now();
|
|
||||||
|
|
||||||
while start.elapsed() <= Duration::from_millis(params.work) {
|
|
||||||
let temp_nonce = rng.gen();
|
|
||||||
let hash = try_nonce(&temp_nonce);
|
|
||||||
|
|
||||||
if hash < best_found {
|
|
||||||
nonce = temp_nonce;
|
|
||||||
best_found = hash;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let envelope = Envelope {
|
|
||||||
expiry: expiry,
|
|
||||||
ttl: params.ttl,
|
|
||||||
topics: params.topics.into_iter().collect(),
|
|
||||||
data: params.payload,
|
|
||||||
nonce: BigEndian::read_u64(&nonce[..]),
|
|
||||||
};
|
|
||||||
|
|
||||||
debug_assert_eq!(H256(best_found.clone()), envelope.proving_hash());
|
|
||||||
|
|
||||||
let encoded = ::rlp::encode(&envelope);
|
|
||||||
|
|
||||||
Ok(Message::from_components(
|
|
||||||
envelope,
|
|
||||||
encoded.len(),
|
|
||||||
H256(keccak256(&encoded)),
|
|
||||||
SystemTime::now(),
|
|
||||||
)
|
|
||||||
.expect("Message generated here known to be valid; qed"))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decode message from RLP and check for validity against system time.
|
|
||||||
pub fn decode(rlp: Rlp, now: SystemTime) -> Result<Self, Error> {
|
|
||||||
let envelope: Envelope = rlp.as_val()?;
|
|
||||||
let encoded_size = rlp.as_raw().len();
|
|
||||||
let hash = H256(keccak256(rlp.as_raw()));
|
|
||||||
|
|
||||||
Message::from_components(envelope, encoded_size, hash, now)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create message from envelope, hash, and encoded size.
|
|
||||||
// does checks for validity.
|
|
||||||
fn from_components(
|
|
||||||
envelope: Envelope,
|
|
||||||
size: usize,
|
|
||||||
hash: H256,
|
|
||||||
now: SystemTime,
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
const LEEWAY_SECONDS: u64 = 2;
|
|
||||||
|
|
||||||
if envelope.expiry <= envelope.ttl {
|
|
||||||
return Err(Error::LivesTooLong);
|
|
||||||
}
|
|
||||||
if envelope.ttl == 0 {
|
|
||||||
return Err(Error::ZeroTTL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if envelope.topics.is_empty() {
|
|
||||||
return Err(Error::EmptyTopics);
|
|
||||||
}
|
|
||||||
|
|
||||||
let issue_time_adjusted =
|
|
||||||
Duration::from_secs((envelope.expiry - envelope.ttl).saturating_sub(LEEWAY_SECONDS));
|
|
||||||
|
|
||||||
let issue_time_adjusted = time::UNIX_EPOCH
|
|
||||||
.checked_add(issue_time_adjusted)
|
|
||||||
.ok_or(Error::TimestampOverflow)?;
|
|
||||||
|
|
||||||
if issue_time_adjusted > now {
|
|
||||||
return Err(Error::IssuedInFuture);
|
|
||||||
}
|
|
||||||
|
|
||||||
// other validity checks?
|
|
||||||
let bloom = bloom_topics(&envelope.topics);
|
|
||||||
|
|
||||||
Ok(Message {
|
|
||||||
envelope: envelope,
|
|
||||||
bloom: bloom,
|
|
||||||
hash: hash,
|
|
||||||
encoded_size: size,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a reference to the envelope.
|
|
||||||
pub fn envelope(&self) -> &Envelope {
|
|
||||||
&self.envelope
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the encoded size of the envelope.
|
|
||||||
pub fn encoded_size(&self) -> usize {
|
|
||||||
self.encoded_size
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a uniquely identifying hash for the message.
|
|
||||||
pub fn hash(&self) -> &H256 {
|
|
||||||
&self.hash
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the bloom filter of the topics
|
|
||||||
pub fn bloom(&self) -> &H512 {
|
|
||||||
&self.bloom
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the work proved by the hash.
|
|
||||||
pub fn work_proved(&self) -> f64 {
|
|
||||||
let proving_hash = self.envelope.proving_hash();
|
|
||||||
|
|
||||||
work_factor_proved(self.encoded_size as _, self.envelope.ttl, proving_hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the expiry time.
|
|
||||||
pub fn expiry(&self) -> Option<SystemTime> {
|
|
||||||
time::UNIX_EPOCH.checked_add(Duration::from_secs(self.envelope.expiry))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the topics.
|
|
||||||
pub fn topics(&self) -> &[Topic] {
|
|
||||||
&self.envelope.topics
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the message data.
|
|
||||||
pub fn data(&self) -> &[u8] {
|
|
||||||
&self.envelope.data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use ethereum_types::H256;
|
|
||||||
use rlp::Rlp;
|
|
||||||
use smallvec::SmallVec;
|
|
||||||
use std::time::{self, Duration, SystemTime};
|
|
||||||
|
|
||||||
fn unix_time(x: u64) -> SystemTime {
|
|
||||||
time::UNIX_EPOCH + Duration::from_secs(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn create_message() {
|
|
||||||
assert!(Message::create(CreateParams {
|
|
||||||
ttl: 100,
|
|
||||||
payload: vec![1, 2, 3, 4],
|
|
||||||
topics: vec![Topic([1, 2, 1, 2])],
|
|
||||||
work: 50,
|
|
||||||
})
|
|
||||||
.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn round_trip() {
|
|
||||||
let envelope = Envelope {
|
|
||||||
expiry: 100_000,
|
|
||||||
ttl: 30,
|
|
||||||
data: vec![9; 256],
|
|
||||||
topics: SmallVec::from_slice(&[Default::default()]),
|
|
||||||
nonce: 1010101,
|
|
||||||
};
|
|
||||||
|
|
||||||
let encoded = ::rlp::encode(&envelope);
|
|
||||||
let decoded = ::rlp::decode(&encoded).expect("failure decoding Envelope");
|
|
||||||
|
|
||||||
assert_eq!(envelope, decoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn round_trip_multitopic() {
|
|
||||||
let envelope = Envelope {
|
|
||||||
expiry: 100_000,
|
|
||||||
ttl: 30,
|
|
||||||
data: vec![9; 256],
|
|
||||||
topics: SmallVec::from_slice(&[Default::default(), Topic([1, 2, 3, 4])]),
|
|
||||||
nonce: 1010101,
|
|
||||||
};
|
|
||||||
|
|
||||||
let encoded = ::rlp::encode(&envelope);
|
|
||||||
let decoded = ::rlp::decode(&encoded).expect("failure decoding Envelope");
|
|
||||||
|
|
||||||
assert_eq!(envelope, decoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn passes_checks() {
|
|
||||||
let envelope = Envelope {
|
|
||||||
expiry: 100_000,
|
|
||||||
ttl: 30,
|
|
||||||
data: vec![9; 256],
|
|
||||||
topics: SmallVec::from_slice(&[Default::default()]),
|
|
||||||
nonce: 1010101,
|
|
||||||
};
|
|
||||||
|
|
||||||
let encoded = ::rlp::encode(&envelope);
|
|
||||||
|
|
||||||
for i in 0..30 {
|
|
||||||
let now = unix_time(100_000 - i);
|
|
||||||
Message::decode(Rlp::new(&*encoded), now).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic]
|
|
||||||
fn future_message() {
|
|
||||||
let envelope = Envelope {
|
|
||||||
expiry: 100_000,
|
|
||||||
ttl: 30,
|
|
||||||
data: vec![9; 256],
|
|
||||||
topics: SmallVec::from_slice(&[Default::default()]),
|
|
||||||
nonce: 1010101,
|
|
||||||
};
|
|
||||||
|
|
||||||
let encoded = ::rlp::encode(&envelope);
|
|
||||||
|
|
||||||
let now = unix_time(100_000 - 1_000);
|
|
||||||
Message::decode(Rlp::new(&*encoded), now).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic]
|
|
||||||
fn pre_epoch() {
|
|
||||||
let envelope = Envelope {
|
|
||||||
expiry: 100_000,
|
|
||||||
ttl: 200_000,
|
|
||||||
data: vec![9; 256],
|
|
||||||
topics: SmallVec::from_slice(&[Default::default()]),
|
|
||||||
nonce: 1010101,
|
|
||||||
};
|
|
||||||
|
|
||||||
let encoded = ::rlp::encode(&envelope);
|
|
||||||
|
|
||||||
let now = unix_time(95_000);
|
|
||||||
Message::decode(Rlp::new(&*encoded), now).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn work_factor() {
|
|
||||||
// 256 leading zeros -> 2^256 / 1
|
|
||||||
assert_eq!(
|
|
||||||
work_factor_proved(1, 1, H256::from(0)),
|
|
||||||
115792089237316200000000000000000000000000000000000000000000000000000000000000.0
|
|
||||||
);
|
|
||||||
// 255 leading zeros -> 2^255 / 1
|
|
||||||
assert_eq!(
|
|
||||||
work_factor_proved(1, 1, H256::from(1)),
|
|
||||||
57896044618658100000000000000000000000000000000000000000000000000000000000000.0
|
|
||||||
);
|
|
||||||
// 0 leading zeros -> 2^0 / 1
|
|
||||||
assert_eq!(
|
|
||||||
work_factor_proved(
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
serde_json::from_str::<H256>(
|
|
||||||
"\"0xff00000000000000000000000000000000000000000000000000000000000000\""
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
),
|
|
||||||
1.0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,766 +0,0 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
|
||||||
// This file is part of Parity Ethereum.
|
|
||||||
|
|
||||||
// Parity Ethereum 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.
|
|
||||||
|
|
||||||
// Parity Ethereum 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 Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
//! Whisper messaging system as a DevP2P subprotocol.
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
cmp::Ordering,
|
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
fmt,
|
|
||||||
sync::Arc,
|
|
||||||
time::{Duration, SystemTime},
|
|
||||||
};
|
|
||||||
|
|
||||||
use ethereum_types::{H256, H512};
|
|
||||||
use network::{self, NetworkContext, NodeId, PeerId, ProtocolId, TimerToken};
|
|
||||||
use ordered_float::OrderedFloat;
|
|
||||||
use parking_lot::{Mutex, RwLock};
|
|
||||||
use rlp::{DecoderError, Rlp, RlpStream};
|
|
||||||
|
|
||||||
use message::{Error as MessageError, Message};
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
|
|
||||||
// how often periodic relays are. when messages are imported
|
|
||||||
// we directly broadcast.
|
|
||||||
const RALLY_TOKEN: TimerToken = 1;
|
|
||||||
const RALLY_TIMEOUT: Duration = Duration::from_millis(2500);
|
|
||||||
|
|
||||||
/// Current protocol version.
|
|
||||||
pub const PROTOCOL_VERSION: usize = 6;
|
|
||||||
|
|
||||||
/// Number of packets. A bunch are reserved.
|
|
||||||
const PACKET_COUNT: u8 = 128;
|
|
||||||
|
|
||||||
/// Supported protocol versions.
|
|
||||||
pub const SUPPORTED_VERSIONS: &'static [(u8, u8)] = &[(PROTOCOL_VERSION as u8, PACKET_COUNT)];
|
|
||||||
|
|
||||||
// maximum tolerated delay between messages packets.
|
|
||||||
const MAX_TOLERATED_DELAY: Duration = Duration::from_millis(5000);
|
|
||||||
|
|
||||||
/// Whisper protocol ID
|
|
||||||
pub const PROTOCOL_ID: ::network::ProtocolId = *b"shh";
|
|
||||||
|
|
||||||
/// Parity-whisper protocol ID
|
|
||||||
/// Current parity-specific extensions:
|
|
||||||
/// - Multiple topics in packet.
|
|
||||||
pub const PARITY_PROTOCOL_ID: ::network::ProtocolId = *b"pwh";
|
|
||||||
|
|
||||||
mod packet {
|
|
||||||
pub const STATUS: u8 = 0;
|
|
||||||
pub const MESSAGES: u8 = 1;
|
|
||||||
pub const POW_REQUIREMENT: u8 = 2;
|
|
||||||
pub const TOPIC_FILTER: u8 = 3;
|
|
||||||
|
|
||||||
// 126, 127 for mail server stuff we will never implement here.
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handles messages within a single packet.
|
|
||||||
pub trait MessageHandler: Send + Sync {
|
|
||||||
/// Evaluate the message and handle it.
|
|
||||||
///
|
|
||||||
/// The same message will not be passed twice.
|
|
||||||
/// Heavy handling should be done asynchronously.
|
|
||||||
/// If there is a significant overhead in this thread, then an attacker
|
|
||||||
/// can determine which kinds of messages we are listening for.
|
|
||||||
fn handle_messages(&self, message: &[Message]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// errors in importing a whisper message.
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Error {
|
|
||||||
Decoder(DecoderError),
|
|
||||||
Network(network::Error),
|
|
||||||
Message(MessageError),
|
|
||||||
UnknownPeer(PeerId),
|
|
||||||
UnexpectedMessage,
|
|
||||||
InvalidPowReq,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<DecoderError> for Error {
|
|
||||||
fn from(err: DecoderError) -> Self {
|
|
||||||
Error::Decoder(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<network::Error> for Error {
|
|
||||||
fn from(err: network::Error) -> Self {
|
|
||||||
Error::Network(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<MessageError> for Error {
|
|
||||||
fn from(err: MessageError) -> Self {
|
|
||||||
Error::Message(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match *self {
|
|
||||||
Error::Decoder(ref err) => write!(f, "Failed to decode packet: {}", err),
|
|
||||||
Error::Network(ref err) => write!(f, "Network error: {}", err),
|
|
||||||
Error::Message(ref err) => write!(f, "Error decoding message: {}", err),
|
|
||||||
Error::UnknownPeer(ref id) => write!(f, "Message received from unknown peer: {}", id),
|
|
||||||
Error::UnexpectedMessage => write!(f, "Unexpected message."),
|
|
||||||
Error::InvalidPowReq => write!(f, "Peer sent invalid PoW requirement."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sorts by work proved, descending.
|
|
||||||
#[derive(PartialEq, Eq)]
|
|
||||||
struct SortedEntry {
|
|
||||||
slab_id: usize,
|
|
||||||
work_proved: OrderedFloat<f64>,
|
|
||||||
expiry: SystemTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for SortedEntry {
|
|
||||||
fn cmp(&self, other: &SortedEntry) -> Ordering {
|
|
||||||
self.work_proved.cmp(&other.work_proved)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for SortedEntry {
|
|
||||||
fn partial_cmp(&self, other: &SortedEntry) -> Option<Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// stores messages by two metrics: expiry and PoW rating
|
|
||||||
// when full, will accept messages above the minimum stored.
|
|
||||||
struct Messages {
|
|
||||||
slab: ::slab::Slab<Message>,
|
|
||||||
sorted: Vec<SortedEntry>,
|
|
||||||
known: HashSet<H256>,
|
|
||||||
removed_hashes: Vec<H256>,
|
|
||||||
cumulative_size: usize,
|
|
||||||
ideal_size: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Messages {
|
|
||||||
fn new(ideal_size: usize) -> Self {
|
|
||||||
Messages {
|
|
||||||
slab: ::slab::Slab::with_capacity(0),
|
|
||||||
sorted: Vec::new(),
|
|
||||||
known: HashSet::new(),
|
|
||||||
removed_hashes: Vec::new(),
|
|
||||||
cumulative_size: 0,
|
|
||||||
ideal_size: ideal_size,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// reserve space for additional elements.
|
|
||||||
fn reserve(&mut self, additional: usize) {
|
|
||||||
self.slab.reserve_exact(additional);
|
|
||||||
self.sorted.reserve(additional);
|
|
||||||
self.known.reserve(additional);
|
|
||||||
}
|
|
||||||
|
|
||||||
// whether a message is not known and within the bounds of PoW.
|
|
||||||
fn may_accept(&self, message: &Message) -> bool {
|
|
||||||
!self.known.contains(message.hash()) && {
|
|
||||||
self.sorted.last().map_or(true, |entry| {
|
|
||||||
let work_proved = OrderedFloat(message.work_proved());
|
|
||||||
OrderedFloat(self.slab[entry.slab_id].work_proved()) < work_proved
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert a message into the store. for best performance,
|
|
||||||
// call `reserve` before inserting a bunch.
|
|
||||||
//
|
|
||||||
fn insert(&mut self, message: Message) -> bool {
|
|
||||||
if !self.known.insert(message.hash().clone()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let work_proved = OrderedFloat(message.work_proved());
|
|
||||||
|
|
||||||
// pop off entries by low PoW until we have enough space for the higher
|
|
||||||
// PoW message being inserted.
|
|
||||||
let size_upon_insertion = self.cumulative_size + message.encoded_size();
|
|
||||||
if size_upon_insertion >= self.ideal_size {
|
|
||||||
let diff = size_upon_insertion - self.ideal_size;
|
|
||||||
let mut found_diff = 0;
|
|
||||||
for entry in self.sorted.iter().rev() {
|
|
||||||
if found_diff >= diff {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we encounter a message with at least the PoW we're looking
|
|
||||||
// at, don't push that message out.
|
|
||||||
if entry.work_proved >= work_proved {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
found_diff += self.slab[entry.slab_id].encoded_size();
|
|
||||||
}
|
|
||||||
|
|
||||||
// message larger than ideal size.
|
|
||||||
if found_diff < diff {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
while found_diff > 0 {
|
|
||||||
let entry = self.sorted.pop()
|
|
||||||
.expect("found_diff built by traversing entries; therefore that many entries exist; qed");
|
|
||||||
|
|
||||||
let message = self
|
|
||||||
.slab
|
|
||||||
.remove(entry.slab_id)
|
|
||||||
.expect("sorted entry slab IDs always filled; qed");
|
|
||||||
|
|
||||||
found_diff -= message.encoded_size();
|
|
||||||
|
|
||||||
self.cumulative_size -= message.encoded_size();
|
|
||||||
self.known.remove(message.hash());
|
|
||||||
self.removed_hashes.push(message.hash().clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let expiry = match message.expiry() {
|
|
||||||
Some(time) => time,
|
|
||||||
_ => return false,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.cumulative_size += message.encoded_size();
|
|
||||||
|
|
||||||
if !self.slab.has_available() {
|
|
||||||
self.slab.reserve_exact(1)
|
|
||||||
}
|
|
||||||
let id = self
|
|
||||||
.slab
|
|
||||||
.insert(message)
|
|
||||||
.expect("just ensured enough space in slab; qed");
|
|
||||||
|
|
||||||
let sorted_entry = SortedEntry {
|
|
||||||
slab_id: id,
|
|
||||||
work_proved,
|
|
||||||
expiry,
|
|
||||||
};
|
|
||||||
|
|
||||||
match self.sorted.binary_search(&sorted_entry) {
|
|
||||||
Ok(idx) | Err(idx) => self.sorted.insert(idx, sorted_entry),
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
// prune expired messages, and then prune low proof-of-work messages
|
|
||||||
// until below ideal size.
|
|
||||||
fn prune(&mut self, now: SystemTime) -> Vec<H256> {
|
|
||||||
{
|
|
||||||
let slab = &mut self.slab;
|
|
||||||
let known = &mut self.known;
|
|
||||||
let cumulative_size = &mut self.cumulative_size;
|
|
||||||
let ideal_size = &self.ideal_size;
|
|
||||||
let removed = &mut self.removed_hashes;
|
|
||||||
|
|
||||||
// first pass, we look just at expired entries.
|
|
||||||
let all_expired = self
|
|
||||||
.sorted
|
|
||||||
.iter()
|
|
||||||
.filter(|entry| entry.expiry <= now)
|
|
||||||
.map(|x| (true, x));
|
|
||||||
|
|
||||||
// second pass, we look at entries which aren't expired but in order
|
|
||||||
// by PoW
|
|
||||||
let low_proof = self
|
|
||||||
.sorted
|
|
||||||
.iter()
|
|
||||||
.rev()
|
|
||||||
.filter(|entry| entry.expiry > now)
|
|
||||||
.map(|x| (false, x));
|
|
||||||
|
|
||||||
for (is_expired, entry) in all_expired.chain(low_proof) {
|
|
||||||
// break once we've removed all expired entries
|
|
||||||
// or have taken enough low-work entries.
|
|
||||||
if !is_expired && *cumulative_size <= *ideal_size {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let message = slab.remove(entry.slab_id).expect(
|
|
||||||
"references to ID kept upon creation; only destroyed upon removal; qed",
|
|
||||||
);
|
|
||||||
|
|
||||||
known.remove(message.hash());
|
|
||||||
removed.push(message.hash().clone());
|
|
||||||
|
|
||||||
*cumulative_size -= message.encoded_size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear all the sorted entries we removed from slab.
|
|
||||||
let slab = &self.slab;
|
|
||||||
self.sorted.retain(|entry| slab.contains(entry.slab_id));
|
|
||||||
|
|
||||||
::std::mem::replace(&mut self.removed_hashes, Vec::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn iter(&self) -> ::slab::Iter<Message, usize> {
|
|
||||||
self.slab.iter()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_full(&self) -> bool {
|
|
||||||
self.cumulative_size >= self.ideal_size
|
|
||||||
}
|
|
||||||
|
|
||||||
fn status(&self) -> PoolStatus {
|
|
||||||
PoolStatus {
|
|
||||||
required_pow: if self.is_full() {
|
|
||||||
self.sorted.last().map(|entry| entry.work_proved.0)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
message_count: self.sorted.len(),
|
|
||||||
cumulative_size: self.cumulative_size,
|
|
||||||
target_size: self.ideal_size,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum State {
|
|
||||||
Unconfirmed(SystemTime), // awaiting status packet.
|
|
||||||
Confirmed,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)] // for node key. this will be useful for topic routing.
|
|
||||||
struct Peer {
|
|
||||||
node_key: NodeId,
|
|
||||||
state: State,
|
|
||||||
known_messages: HashSet<H256>,
|
|
||||||
topic_filter: Option<H512>,
|
|
||||||
pow_requirement: f64,
|
|
||||||
is_parity: bool,
|
|
||||||
_protocol_version: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Peer {
|
|
||||||
// note that a message has been evicted from the queue.
|
|
||||||
fn note_evicted(&mut self, messages: &[H256]) {
|
|
||||||
for message_hash in messages {
|
|
||||||
self.known_messages.remove(message_hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// whether this peer will accept the message.
|
|
||||||
fn will_accept(&self, message: &Message) -> bool {
|
|
||||||
if self.known_messages.contains(message.hash()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// only parity peers will accept multitopic messages.
|
|
||||||
if message.envelope().is_multitopic() && !self.is_parity {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if message.work_proved() < self.pow_requirement {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.topic_filter.as_ref().map_or(true, |filter| {
|
|
||||||
&(filter & message.bloom()) == message.bloom()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// note a message as known. returns false if it was already
|
|
||||||
// known, true otherwise.
|
|
||||||
fn note_known(&mut self, message: &Message) -> bool {
|
|
||||||
self.known_messages.insert(message.hash().clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_topic_filter(&mut self, topic: H512) {
|
|
||||||
self.topic_filter = Some(topic);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_pow_requirement(&mut self, pow_requirement: f64) {
|
|
||||||
self.pow_requirement = pow_requirement;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn can_send_messages(&self) -> bool {
|
|
||||||
match self.state {
|
|
||||||
State::Unconfirmed(_) => false,
|
|
||||||
State::Confirmed => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pool status.
|
|
||||||
pub struct PoolStatus {
|
|
||||||
/// Required PoW to be accepted into the pool
|
|
||||||
pub required_pow: Option<f64>,
|
|
||||||
/// Number of messages in the pool.
|
|
||||||
pub message_count: usize,
|
|
||||||
/// Cumulative size of the messages in the pool
|
|
||||||
pub cumulative_size: usize,
|
|
||||||
/// Target size of the pool.
|
|
||||||
pub target_size: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generic network context.
|
|
||||||
pub trait Context {
|
|
||||||
/// Disconnect a peer.
|
|
||||||
fn disconnect_peer(&self, PeerId);
|
|
||||||
/// Disable a peer.
|
|
||||||
fn disable_peer(&self, PeerId);
|
|
||||||
/// Get a peer's node key.
|
|
||||||
fn node_key(&self, PeerId) -> Option<NodeId>;
|
|
||||||
/// Get a peer's protocol version for given protocol.
|
|
||||||
fn protocol_version(&self, ProtocolId, PeerId) -> Option<u8>;
|
|
||||||
/// Send message to peer.
|
|
||||||
fn send(&self, PeerId, u8, Vec<u8>);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Context for T
|
|
||||||
where
|
|
||||||
T: ?Sized + NetworkContext,
|
|
||||||
{
|
|
||||||
fn disconnect_peer(&self, peer: PeerId) {
|
|
||||||
NetworkContext::disconnect_peer(self, peer);
|
|
||||||
}
|
|
||||||
fn disable_peer(&self, peer: PeerId) {
|
|
||||||
NetworkContext::disable_peer(self, peer)
|
|
||||||
}
|
|
||||||
fn node_key(&self, peer: PeerId) -> Option<NodeId> {
|
|
||||||
self.session_info(peer).and_then(|info| info.id)
|
|
||||||
}
|
|
||||||
fn protocol_version(&self, proto_id: ProtocolId, peer: PeerId) -> Option<u8> {
|
|
||||||
NetworkContext::protocol_version(self, proto_id, peer)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send(&self, peer: PeerId, packet_id: u8, message: Vec<u8>) {
|
|
||||||
if let Err(e) = NetworkContext::send(self, peer, packet_id, message) {
|
|
||||||
debug!(target: "whisper", "Failed to send packet {} to peer {}: {}",
|
|
||||||
packet_id, peer, e);
|
|
||||||
|
|
||||||
self.disconnect_peer(peer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The whisper network protocol handler.
|
|
||||||
pub struct Network<T> {
|
|
||||||
messages: Arc<RwLock<Messages>>,
|
|
||||||
handler: T,
|
|
||||||
peers: RwLock<HashMap<PeerId, Mutex<Peer>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// public API.
|
|
||||||
impl<T> Network<T> {
|
|
||||||
/// Create a new network handler.
|
|
||||||
pub fn new(messages_size_bytes: usize, handler: T) -> Self {
|
|
||||||
Network {
|
|
||||||
messages: Arc::new(RwLock::new(Messages::new(messages_size_bytes))),
|
|
||||||
handler: handler,
|
|
||||||
peers: RwLock::new(HashMap::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Post a message to the whisper network to be relayed.
|
|
||||||
pub fn post_message<C: ?Sized + Context>(&self, message: Message, context: &C) -> bool
|
|
||||||
where
|
|
||||||
T: MessageHandler,
|
|
||||||
{
|
|
||||||
let ok = self.messages.write().insert(message);
|
|
||||||
if ok {
|
|
||||||
self.rally(context)
|
|
||||||
}
|
|
||||||
ok
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get number of messages and amount of memory used by them.
|
|
||||||
pub fn pool_status(&self) -> PoolStatus {
|
|
||||||
self.messages.read().status()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: MessageHandler> Network<T> {
|
|
||||||
fn rally<C: ?Sized + Context>(&self, io: &C) {
|
|
||||||
// cannot be greater than 16MB (protocol limitation)
|
|
||||||
const MAX_MESSAGES_PACKET_SIZE: usize = 8 * 1024 * 1024;
|
|
||||||
|
|
||||||
// prune messages.
|
|
||||||
let now = SystemTime::now();
|
|
||||||
let pruned_hashes = self.messages.write().prune(now);
|
|
||||||
|
|
||||||
let messages = self.messages.read();
|
|
||||||
let peers = self.peers.read();
|
|
||||||
|
|
||||||
// send each peer a packet with new messages it may find relevant.
|
|
||||||
for (peer_id, peer) in peers.iter() {
|
|
||||||
let mut peer_data = peer.lock();
|
|
||||||
peer_data.note_evicted(&pruned_hashes);
|
|
||||||
|
|
||||||
let punish_timeout = |last_activity: &SystemTime| {
|
|
||||||
if *last_activity + MAX_TOLERATED_DELAY <= now {
|
|
||||||
debug!(target: "whisper", "Disconnecting peer {} due to excessive timeout.", peer_id);
|
|
||||||
io.disconnect_peer(*peer_id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// check timeouts and skip peers who we can't send a rally to.
|
|
||||||
match peer_data.state {
|
|
||||||
State::Unconfirmed(ref time) => {
|
|
||||||
punish_timeout(time);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
State::Confirmed => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// construct packet, skipping messages the peer won't accept.
|
|
||||||
let mut stream = RlpStream::new();
|
|
||||||
stream.begin_unbounded_list();
|
|
||||||
|
|
||||||
for message in messages.iter() {
|
|
||||||
if !peer_data.will_accept(message) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if stream.estimate_size(message.encoded_size()) > MAX_MESSAGES_PACKET_SIZE {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
peer_data.note_known(message);
|
|
||||||
stream.append(message.envelope());
|
|
||||||
}
|
|
||||||
|
|
||||||
stream.complete_unbounded_list();
|
|
||||||
|
|
||||||
io.send(*peer_id, packet::MESSAGES, stream.out());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle status packet from peer.
|
|
||||||
fn on_status(&self, peer: &PeerId, _status: Rlp) -> Result<(), Error> {
|
|
||||||
let peers = self.peers.read();
|
|
||||||
|
|
||||||
match peers.get(peer) {
|
|
||||||
Some(peer) => {
|
|
||||||
peer.lock().state = State::Confirmed;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
debug!(target: "whisper", "Received message from unknown peer.");
|
|
||||||
Err(Error::UnknownPeer(*peer))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_messages(&self, peer: &PeerId, message_packet: Rlp) -> Result<(), Error> {
|
|
||||||
let mut messages_vec = {
|
|
||||||
let peers = self.peers.read();
|
|
||||||
let peer = match peers.get(peer) {
|
|
||||||
Some(peer) => peer,
|
|
||||||
None => {
|
|
||||||
debug!(target: "whisper", "Received message from unknown peer.");
|
|
||||||
return Err(Error::UnknownPeer(*peer));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut peer = peer.lock();
|
|
||||||
|
|
||||||
if !peer.can_send_messages() {
|
|
||||||
return Err(Error::UnexpectedMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
let now = SystemTime::now();
|
|
||||||
let mut messages_vec = message_packet
|
|
||||||
.iter()
|
|
||||||
.map(|rlp| Message::decode(rlp, now))
|
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
|
||||||
|
|
||||||
if messages_vec.is_empty() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// disallow duplicates in packet.
|
|
||||||
messages_vec.retain(|message| peer.note_known(&message));
|
|
||||||
messages_vec
|
|
||||||
};
|
|
||||||
|
|
||||||
// import for relaying.
|
|
||||||
let mut messages = self.messages.write();
|
|
||||||
|
|
||||||
messages_vec.retain(|message| messages.may_accept(&message));
|
|
||||||
messages.reserve(messages_vec.len());
|
|
||||||
|
|
||||||
self.handler.handle_messages(&messages_vec);
|
|
||||||
|
|
||||||
for message in messages_vec {
|
|
||||||
messages.insert(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_pow_requirement(&self, peer: &PeerId, requirement: Rlp) -> Result<(), Error> {
|
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
|
||||||
|
|
||||||
let peers = self.peers.read();
|
|
||||||
match peers.get(peer) {
|
|
||||||
Some(peer) => {
|
|
||||||
let mut peer = peer.lock();
|
|
||||||
|
|
||||||
if let State::Unconfirmed(_) = peer.state {
|
|
||||||
return Err(Error::UnexpectedMessage);
|
|
||||||
}
|
|
||||||
let bytes: Vec<u8> = requirement.as_val()?;
|
|
||||||
if bytes.len() != ::std::mem::size_of::<f64>() {
|
|
||||||
return Err(Error::InvalidPowReq);
|
|
||||||
}
|
|
||||||
|
|
||||||
// as of byteorder 1.1.0, this is always defined.
|
|
||||||
let req = BigEndian::read_f64(&bytes[..]);
|
|
||||||
|
|
||||||
if !req.is_normal() {
|
|
||||||
return Err(Error::InvalidPowReq);
|
|
||||||
}
|
|
||||||
|
|
||||||
peer.set_pow_requirement(req);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
debug!(target: "whisper", "Received message from unknown peer.");
|
|
||||||
return Err(Error::UnknownPeer(*peer));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_topic_filter(&self, peer: &PeerId, filter: Rlp) -> Result<(), Error> {
|
|
||||||
let peers = self.peers.read();
|
|
||||||
match peers.get(peer) {
|
|
||||||
Some(peer) => {
|
|
||||||
let mut peer = peer.lock();
|
|
||||||
|
|
||||||
if let State::Unconfirmed(_) = peer.state {
|
|
||||||
return Err(Error::UnexpectedMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
peer.set_topic_filter(filter.as_val()?)
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
debug!(target: "whisper", "Received message from unknown peer.");
|
|
||||||
return Err(Error::UnknownPeer(*peer));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_connect<C: ?Sized + Context>(&self, io: &C, peer: &PeerId) {
|
|
||||||
trace!(target: "whisper", "Connecting peer {}", peer);
|
|
||||||
|
|
||||||
let node_key = match io.node_key(*peer) {
|
|
||||||
Some(node_key) => node_key,
|
|
||||||
None => {
|
|
||||||
debug!(target: "whisper", "Disconnecting peer {}, who has no node key.", peer);
|
|
||||||
io.disable_peer(*peer);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let version = match io.protocol_version(PROTOCOL_ID, *peer) {
|
|
||||||
Some(version) => version as usize,
|
|
||||||
None => {
|
|
||||||
io.disable_peer(*peer);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.peers.write().insert(
|
|
||||||
*peer,
|
|
||||||
Mutex::new(Peer {
|
|
||||||
node_key: node_key,
|
|
||||||
state: State::Unconfirmed(SystemTime::now()),
|
|
||||||
known_messages: HashSet::new(),
|
|
||||||
topic_filter: None,
|
|
||||||
pow_requirement: 0f64,
|
|
||||||
is_parity: io.protocol_version(PARITY_PROTOCOL_ID, *peer).is_some(),
|
|
||||||
_protocol_version: version,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
io.send(*peer, packet::STATUS, ::rlp::EMPTY_LIST_RLP.to_vec());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_packet<C: ?Sized + Context>(&self, io: &C, peer: &PeerId, packet_id: u8, data: &[u8]) {
|
|
||||||
let rlp = Rlp::new(data);
|
|
||||||
let res = match packet_id {
|
|
||||||
packet::STATUS => self.on_status(peer, rlp),
|
|
||||||
packet::MESSAGES => self.on_messages(peer, rlp),
|
|
||||||
packet::POW_REQUIREMENT => self.on_pow_requirement(peer, rlp),
|
|
||||||
packet::TOPIC_FILTER => self.on_topic_filter(peer, rlp),
|
|
||||||
_ => Ok(()), // ignore unknown packets.
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(e) = res {
|
|
||||||
trace!(target: "whisper", "Disabling peer due to misbehavior: {}", e);
|
|
||||||
io.disable_peer(*peer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_disconnect(&self, peer: &PeerId) {
|
|
||||||
trace!(target: "whisper", "Disconnecting peer {}", peer);
|
|
||||||
let _ = self.peers.write().remove(peer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: MessageHandler> ::network::NetworkProtocolHandler for Network<T> {
|
|
||||||
fn initialize(&self, io: &dyn NetworkContext) {
|
|
||||||
// set up broadcast timer (< 1s)
|
|
||||||
io.register_timer(RALLY_TOKEN, RALLY_TIMEOUT)
|
|
||||||
.expect("Failed to initialize message rally timer");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read(&self, io: &dyn NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) {
|
|
||||||
self.on_packet(io, peer, packet_id, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn connected(&self, io: &dyn NetworkContext, peer: &PeerId) {
|
|
||||||
// peer with higher ID should begin rallying.
|
|
||||||
self.on_connect(io, peer)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn disconnected(&self, _io: &dyn NetworkContext, peer: &PeerId) {
|
|
||||||
self.on_disconnect(peer)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn timeout(&self, io: &dyn NetworkContext, timer: TimerToken) {
|
|
||||||
// rally with each peer and handle timeouts.
|
|
||||||
match timer {
|
|
||||||
RALLY_TOKEN => self.rally(io),
|
|
||||||
other => debug!(target: "whisper", "Timeout triggered on unknown token {}", other),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dummy subprotocol used for parity extensions.
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub struct ParityExtensions;
|
|
||||||
|
|
||||||
impl ::network::NetworkProtocolHandler for ParityExtensions {
|
|
||||||
fn initialize(&self, _io: &dyn NetworkContext) {}
|
|
||||||
|
|
||||||
fn read(&self, _io: &dyn NetworkContext, _peer: &PeerId, _id: u8, _msg: &[u8]) {}
|
|
||||||
|
|
||||||
fn connected(&self, _io: &dyn NetworkContext, _peer: &PeerId) {}
|
|
||||||
|
|
||||||
fn disconnected(&self, _io: &dyn NetworkContext, _peer: &PeerId) {}
|
|
||||||
|
|
||||||
fn timeout(&self, _io: &dyn NetworkContext, _timer: TimerToken) {}
|
|
||||||
}
|
|
@ -1,198 +0,0 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
|
||||||
// This file is part of Parity Ethereum.
|
|
||||||
|
|
||||||
// Parity Ethereum 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.
|
|
||||||
|
|
||||||
// Parity Ethereum 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 Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
//! Tests for the whisper network module.
|
|
||||||
|
|
||||||
use std::{collections::HashSet, sync::mpsc};
|
|
||||||
|
|
||||||
use network::{NodeId, PeerId};
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use message::{CreateParams, Message};
|
|
||||||
|
|
||||||
struct TestHandler(Mutex<mpsc::Sender<Message>>);
|
|
||||||
|
|
||||||
impl MessageHandler for TestHandler {
|
|
||||||
fn handle_messages(&self, messages: &[Message]) {
|
|
||||||
let tx = self.0.lock();
|
|
||||||
for message in messages {
|
|
||||||
let _ = tx.send(message.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TestPeer {
|
|
||||||
network: Network<TestHandler>,
|
|
||||||
recv: mpsc::Receiver<Message>,
|
|
||||||
disconnected: Mutex<HashSet<PeerId>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TestPeer {
|
|
||||||
fn create() -> Self {
|
|
||||||
let (tx, rx) = mpsc::channel();
|
|
||||||
|
|
||||||
TestPeer {
|
|
||||||
network: Network::new(10 * 1024 * 1024, TestHandler(Mutex::new(tx))),
|
|
||||||
recv: rx,
|
|
||||||
disconnected: Mutex::new(HashSet::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TestNetwork {
|
|
||||||
peers: Vec<TestPeer>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TestNetwork {
|
|
||||||
fn new(n_peers: usize) -> Self {
|
|
||||||
let unconnected_peers: Vec<_> = (0..n_peers).map(|_| TestPeer::create()).collect();
|
|
||||||
for i in 0..n_peers {
|
|
||||||
for j in (i + 1)..n_peers {
|
|
||||||
let (peer1, peer2) = (&unconnected_peers[i], &unconnected_peers[j]);
|
|
||||||
let ctx1 = TestContext::new(&unconnected_peers, i);
|
|
||||||
let ctx2 = TestContext::new(&unconnected_peers, j);
|
|
||||||
|
|
||||||
peer1.network.on_connect(&ctx1, &j);
|
|
||||||
peer2.network.on_connect(&ctx2, &i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TestNetwork {
|
|
||||||
peers: unconnected_peers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn post_message_from(&self, id: PeerId, msg: Message) {
|
|
||||||
self.peers[id]
|
|
||||||
.network
|
|
||||||
.post_message(msg, &TestContext::new(&self.peers, id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Event {
|
|
||||||
Disconnect(PeerId, PeerId),
|
|
||||||
Send(PeerId, PeerId, u8, Vec<u8>),
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TestContext<'a> {
|
|
||||||
peers: &'a [TestPeer],
|
|
||||||
local_id: PeerId,
|
|
||||||
events: Mutex<Vec<Event>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TestContext<'a> {
|
|
||||||
fn new(peers: &'a [TestPeer], local_id: PeerId) -> Self {
|
|
||||||
TestContext {
|
|
||||||
peers,
|
|
||||||
local_id,
|
|
||||||
events: Mutex::new(Vec::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Context for TestContext<'a> {
|
|
||||||
fn disconnect_peer(&self, id: PeerId) {
|
|
||||||
self.events
|
|
||||||
.lock()
|
|
||||||
.push(Event::Disconnect(self.local_id, id));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn disable_peer(&self, id: PeerId) {
|
|
||||||
self.events
|
|
||||||
.lock()
|
|
||||||
.push(Event::Disconnect(self.local_id, id));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn node_key(&self, peer: PeerId) -> Option<NodeId> {
|
|
||||||
let mut id = NodeId::default();
|
|
||||||
id[0] = peer as _;
|
|
||||||
Some(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn protocol_version(&self, id: ::network::ProtocolId, _peer: PeerId) -> Option<u8> {
|
|
||||||
if &id == b"shh" || &id == b"pwh" {
|
|
||||||
Some(PROTOCOL_VERSION as _)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send(&self, peer: PeerId, packet: u8, data: Vec<u8>) {
|
|
||||||
self.events
|
|
||||||
.lock()
|
|
||||||
.push(Event::Send(self.local_id, peer, packet, data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Drop for TestContext<'a> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let events = self.events.get_mut();
|
|
||||||
while !events.is_empty() {
|
|
||||||
let mut deferred = Vec::new();
|
|
||||||
for event in events.drain(..) {
|
|
||||||
match event {
|
|
||||||
Event::Disconnect(from, target) => {
|
|
||||||
self.peers[from].network.on_disconnect(&target);
|
|
||||||
self.peers[target].network.on_disconnect(&from);
|
|
||||||
|
|
||||||
self.peers[from].disconnected.lock().insert(target);
|
|
||||||
self.peers[target].disconnected.lock().insert(from);
|
|
||||||
}
|
|
||||||
Event::Send(from, target, packet, data) => {
|
|
||||||
if self.peers[from].disconnected.lock().contains(&target) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut inner_ctx = TestContext::new(self.peers, target);
|
|
||||||
|
|
||||||
self.peers[target]
|
|
||||||
.network
|
|
||||||
.on_packet(&inner_ctx, &from, packet, &data[..]);
|
|
||||||
|
|
||||||
// don't recursively apply disconnects or new messages
|
|
||||||
// from the receiver's actions yet.
|
|
||||||
let inner_events =
|
|
||||||
::std::mem::replace(inner_ctx.events.get_mut(), Vec::new());
|
|
||||||
deferred.extend(inner_events);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
events.extend(deferred);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn message_gets_relayed() {
|
|
||||||
let network = TestNetwork::new(5);
|
|
||||||
let message = Message::create(CreateParams {
|
|
||||||
ttl: 500,
|
|
||||||
payload: b"this is my payload, pal".to_vec(),
|
|
||||||
topics: vec![[0, 1, 2, 3].into()],
|
|
||||||
work: 25,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
network.post_message_from(0, message.clone());
|
|
||||||
|
|
||||||
assert!(network.peers[0].recv.try_recv().is_err());
|
|
||||||
|
|
||||||
for i in 1..5 {
|
|
||||||
assert_eq!(network.peers[i].recv.try_recv().unwrap(), message);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,279 +0,0 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
|
||||||
// This file is part of Parity Ethereum.
|
|
||||||
|
|
||||||
// Parity Ethereum 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.
|
|
||||||
|
|
||||||
// Parity Ethereum 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 Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
//! Encryption schemes supported by RPC layer.
|
|
||||||
|
|
||||||
use crypto::aes_gcm::{Decryptor, Encryptor};
|
|
||||||
use ethereum_types::H256;
|
|
||||||
use ethkey::{self, crypto::ecies, Public, Secret};
|
|
||||||
use memzero::Memzero;
|
|
||||||
|
|
||||||
/// Length of AES key
|
|
||||||
pub const AES_KEY_LEN: usize = 32;
|
|
||||||
/// Length of AES nonce (IV)
|
|
||||||
pub const AES_NONCE_LEN: usize = 12;
|
|
||||||
|
|
||||||
// nonce used for encryption when broadcasting
|
|
||||||
const BROADCAST_IV: [u8; AES_NONCE_LEN] = [0xff; AES_NONCE_LEN];
|
|
||||||
|
|
||||||
// how to encode aes key/nonce.
|
|
||||||
enum AesEncode {
|
|
||||||
AppendedNonce, // receiver known, random nonce appended.
|
|
||||||
OnTopics(Vec<H256>), // receiver knows topics but not key. nonce global.
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EncryptionInner {
|
|
||||||
AES(Memzero<[u8; AES_KEY_LEN]>, [u8; AES_NONCE_LEN], AesEncode),
|
|
||||||
ECIES(Public),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encryption good for single usage.
|
|
||||||
pub struct EncryptionInstance(EncryptionInner);
|
|
||||||
|
|
||||||
impl EncryptionInstance {
|
|
||||||
/// ECIES encryption using public key. Fails if invalid public key.
|
|
||||||
pub fn ecies(public: Public) -> Result<Self, &'static str> {
|
|
||||||
if !ethkey::public_is_valid(&public) {
|
|
||||||
return Err("Invalid public key");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(EncryptionInstance(EncryptionInner::ECIES(public)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 256-bit AES GCM encryption with given nonce.
|
|
||||||
/// It is extremely insecure to reuse nonces.
|
|
||||||
///
|
|
||||||
/// If generating nonces with a secure RNG, limit uses such that
|
|
||||||
/// the chance of collision is negligible.
|
|
||||||
pub fn aes(key: Memzero<[u8; AES_KEY_LEN]>, nonce: [u8; AES_NONCE_LEN]) -> Self {
|
|
||||||
EncryptionInstance(EncryptionInner::AES(key, nonce, AesEncode::AppendedNonce))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Broadcast encryption for the message based on the given topics.
|
|
||||||
///
|
|
||||||
/// Key reuse here is extremely dangerous. It should be randomly generated
|
|
||||||
/// with a secure RNG.
|
|
||||||
pub fn broadcast(key: Memzero<[u8; AES_KEY_LEN]>, topics: Vec<H256>) -> Self {
|
|
||||||
EncryptionInstance(EncryptionInner::AES(
|
|
||||||
key,
|
|
||||||
BROADCAST_IV,
|
|
||||||
AesEncode::OnTopics(topics),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encrypt the supplied plaintext
|
|
||||||
pub fn encrypt(self, plain: &[u8]) -> Option<Vec<u8>> {
|
|
||||||
match self.0 {
|
|
||||||
EncryptionInner::AES(key, nonce, encode) => match encode {
|
|
||||||
AesEncode::AppendedNonce => {
|
|
||||||
let enc = Encryptor::aes_256_gcm(&*key).ok()?;
|
|
||||||
let mut buf = enc.encrypt(&nonce, plain.to_vec()).ok()?;
|
|
||||||
buf.extend(&nonce[..]);
|
|
||||||
Some(buf)
|
|
||||||
}
|
|
||||||
AesEncode::OnTopics(topics) => {
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
for mut t in topics {
|
|
||||||
xor(&mut t.0, &key);
|
|
||||||
buf.extend(&t.0);
|
|
||||||
}
|
|
||||||
let mut enc = Encryptor::aes_256_gcm(&*key).ok()?;
|
|
||||||
enc.offset(buf.len());
|
|
||||||
buf.extend(plain);
|
|
||||||
let ciphertext = enc.encrypt(&nonce, buf).ok()?;
|
|
||||||
Some(ciphertext)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
EncryptionInner::ECIES(valid_public) => ecies::encrypt(&valid_public, &[], plain).ok(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn xor(a: &mut [u8; 32], b: &[u8; 32]) {
|
|
||||||
for i in 0..32 {
|
|
||||||
a[i] ^= b[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum AesExtract {
|
|
||||||
AppendedNonce(Memzero<[u8; AES_KEY_LEN]>), // extract appended nonce.
|
|
||||||
OnTopics(usize, usize, H256), // number of topics, index we know, topic we know.
|
|
||||||
}
|
|
||||||
|
|
||||||
enum DecryptionInner {
|
|
||||||
AES(AesExtract),
|
|
||||||
ECIES(Secret),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decryption instance good for single usage.
|
|
||||||
pub struct DecryptionInstance(DecryptionInner);
|
|
||||||
|
|
||||||
impl DecryptionInstance {
|
|
||||||
/// ECIES decryption using secret key. Fails if invalid secret.
|
|
||||||
pub fn ecies(secret: Secret) -> Result<Self, &'static str> {
|
|
||||||
secret.check_validity().map_err(|_| "Invalid secret key")?;
|
|
||||||
|
|
||||||
Ok(DecryptionInstance(DecryptionInner::ECIES(secret)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 256-bit AES GCM decryption with appended nonce.
|
|
||||||
pub fn aes(key: Memzero<[u8; AES_KEY_LEN]>) -> Self {
|
|
||||||
DecryptionInstance(DecryptionInner::AES(AesExtract::AppendedNonce(key)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decode broadcast based on number of topics and known topic.
|
|
||||||
/// Known topic index may not be larger than num topics - 1.
|
|
||||||
pub fn broadcast(
|
|
||||||
num_topics: usize,
|
|
||||||
topic_idx: usize,
|
|
||||||
known_topic: H256,
|
|
||||||
) -> Result<Self, &'static str> {
|
|
||||||
if topic_idx >= num_topics {
|
|
||||||
return Err("topic index out of bounds");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(DecryptionInstance(DecryptionInner::AES(
|
|
||||||
AesExtract::OnTopics(num_topics, topic_idx, known_topic),
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrypt ciphertext. Fails if it's an invalid message.
|
|
||||||
pub fn decrypt(self, ciphertext: &[u8]) -> Option<Vec<u8>> {
|
|
||||||
match self.0 {
|
|
||||||
DecryptionInner::AES(extract) => {
|
|
||||||
match extract {
|
|
||||||
AesExtract::AppendedNonce(key) => {
|
|
||||||
if ciphertext.len() < AES_NONCE_LEN {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
// nonce is the suffix of ciphertext.
|
|
||||||
let mut nonce = [0; AES_NONCE_LEN];
|
|
||||||
let nonce_offset = ciphertext.len() - AES_NONCE_LEN;
|
|
||||||
nonce.copy_from_slice(&ciphertext[nonce_offset..]);
|
|
||||||
Decryptor::aes_256_gcm(&*key)
|
|
||||||
.ok()?
|
|
||||||
.decrypt(&nonce, Vec::from(&ciphertext[..nonce_offset]))
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
AesExtract::OnTopics(num_topics, known_index, known_topic) => {
|
|
||||||
if ciphertext.len() < num_topics * 32 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let mut salted_topic = H256::new();
|
|
||||||
salted_topic.copy_from_slice(&ciphertext[(known_index * 32)..][..32]);
|
|
||||||
let key = Memzero::from((salted_topic ^ known_topic).0);
|
|
||||||
let offset = num_topics * 32;
|
|
||||||
Decryptor::aes_256_gcm(&*key)
|
|
||||||
.ok()?
|
|
||||||
.decrypt(&BROADCAST_IV, Vec::from(&ciphertext[offset..]))
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DecryptionInner::ECIES(secret) => {
|
|
||||||
// secret is checked for validity, so only fails on invalid message.
|
|
||||||
ecies::decrypt(&secret, &[], ciphertext).ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn encrypt_asymmetric() {
|
|
||||||
use ethkey::{Generator, Random};
|
|
||||||
|
|
||||||
let key_pair = Random.generate().unwrap();
|
|
||||||
let test_message = move |message: &[u8]| {
|
|
||||||
let instance = EncryptionInstance::ecies(key_pair.public().clone()).unwrap();
|
|
||||||
let ciphertext = instance.encrypt(&message).unwrap();
|
|
||||||
|
|
||||||
if !message.is_empty() {
|
|
||||||
assert!(&ciphertext[..message.len()] != message)
|
|
||||||
}
|
|
||||||
|
|
||||||
let instance = DecryptionInstance::ecies(key_pair.secret().clone()).unwrap();
|
|
||||||
let decrypted = instance.decrypt(&ciphertext).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(message, &decrypted[..])
|
|
||||||
};
|
|
||||||
|
|
||||||
test_message(&[1, 2, 3, 4, 5]);
|
|
||||||
test_message(&[]);
|
|
||||||
test_message(&[255; 512]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn encrypt_symmetric() {
|
|
||||||
use rand::{OsRng, Rng};
|
|
||||||
|
|
||||||
let mut rng = OsRng::new().unwrap();
|
|
||||||
let mut test_message = move |message: &[u8]| {
|
|
||||||
let key = Memzero::from(rng.gen::<[u8; 32]>());
|
|
||||||
|
|
||||||
let instance = EncryptionInstance::aes(key.clone(), rng.gen());
|
|
||||||
let ciphertext = instance.encrypt(message).unwrap();
|
|
||||||
|
|
||||||
if !message.is_empty() {
|
|
||||||
assert!(&ciphertext[..message.len()] != message)
|
|
||||||
}
|
|
||||||
|
|
||||||
let instance = DecryptionInstance::aes(key);
|
|
||||||
let decrypted = instance.decrypt(&ciphertext).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(message, &decrypted[..])
|
|
||||||
};
|
|
||||||
|
|
||||||
test_message(&[1, 2, 3, 4, 5]);
|
|
||||||
test_message(&[]);
|
|
||||||
test_message(&[255; 512]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn encrypt_broadcast() {
|
|
||||||
use rand::{OsRng, Rng};
|
|
||||||
|
|
||||||
let mut rng = OsRng::new().unwrap();
|
|
||||||
|
|
||||||
let mut test_message = move |message: &[u8]| {
|
|
||||||
let all_topics = (0..5).map(|_| rng.gen()).collect::<Vec<_>>();
|
|
||||||
let known_idx = 2;
|
|
||||||
let known_topic = all_topics[2];
|
|
||||||
let key = Memzero::from(rng.gen::<[u8; 32]>());
|
|
||||||
|
|
||||||
let instance = EncryptionInstance::broadcast(key, all_topics);
|
|
||||||
let ciphertext = instance.encrypt(message).unwrap();
|
|
||||||
|
|
||||||
if !message.is_empty() {
|
|
||||||
assert!(&ciphertext[..message.len()] != message)
|
|
||||||
}
|
|
||||||
|
|
||||||
let instance = DecryptionInstance::broadcast(5, known_idx, known_topic).unwrap();
|
|
||||||
|
|
||||||
let decrypted = instance.decrypt(&ciphertext).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(message, &decrypted[..])
|
|
||||||
};
|
|
||||||
|
|
||||||
test_message(&[1, 2, 3, 4, 5]);
|
|
||||||
test_message(&[]);
|
|
||||||
test_message(&[255; 512]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,486 +0,0 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
|
||||||
// This file is part of Parity Ethereum.
|
|
||||||
|
|
||||||
// Parity Ethereum 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.
|
|
||||||
|
|
||||||
// Parity Ethereum 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 Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
//! Abstraction over filters which works with polling and subscription.
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
collections::HashMap,
|
|
||||||
sync::{atomic, atomic::AtomicBool, mpsc, Arc},
|
|
||||||
thread,
|
|
||||||
};
|
|
||||||
|
|
||||||
use ethereum_types::{H256, H512};
|
|
||||||
use ethkey::Public;
|
|
||||||
use jsonrpc_pubsub::typed::{Sink, Subscriber};
|
|
||||||
use parking_lot::{Mutex, RwLock};
|
|
||||||
use rand::{OsRng, Rng};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
key_store::KeyStore,
|
|
||||||
types::{self, FilterItem, HexEncode},
|
|
||||||
};
|
|
||||||
use message::{Message, Topic};
|
|
||||||
|
|
||||||
/// Kinds of filters,
|
|
||||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
|
||||||
pub enum Kind {
|
|
||||||
/// Polled filter only returns data upon request
|
|
||||||
Poll,
|
|
||||||
/// Subscription filter pushes data to subscriber immediately.
|
|
||||||
Subscription,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type ItemBuffer = Arc<Mutex<Vec<FilterItem>>>;
|
|
||||||
|
|
||||||
enum FilterEntry {
|
|
||||||
Poll(Arc<Filter>, ItemBuffer),
|
|
||||||
Subscription(Arc<Filter>, Sink<FilterItem>),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Filter manager. Handles filters as well as a thread for doing decryption
|
|
||||||
/// and payload decoding.
|
|
||||||
pub struct Manager {
|
|
||||||
key_store: Arc<RwLock<KeyStore>>,
|
|
||||||
filters: RwLock<HashMap<H256, FilterEntry>>,
|
|
||||||
tx: Mutex<mpsc::Sender<Box<dyn Fn() + Send>>>,
|
|
||||||
join: Option<thread::JoinHandle<()>>,
|
|
||||||
exit: Arc<AtomicBool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Manager {
|
|
||||||
/// Create a new filter manager that will dispatch decryption tasks onto
|
|
||||||
/// the given thread pool.
|
|
||||||
pub fn new() -> ::std::io::Result<Self> {
|
|
||||||
let (tx, rx) = mpsc::channel::<Box<dyn Fn() + Send>>();
|
|
||||||
let exit = Arc::new(AtomicBool::new(false));
|
|
||||||
let e = exit.clone();
|
|
||||||
|
|
||||||
let join_handle = thread::Builder::new()
|
|
||||||
.name("Whisper Decryption Worker".to_string())
|
|
||||||
.spawn(move || {
|
|
||||||
trace!(target: "parity_whisper", "Start decryption worker");
|
|
||||||
loop {
|
|
||||||
if exit.load(atomic::Ordering::Acquire) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if let Ok(item) = rx.try_recv() {
|
|
||||||
item();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(Manager {
|
|
||||||
key_store: Arc::new(RwLock::new(KeyStore::new()?)),
|
|
||||||
filters: RwLock::new(HashMap::new()),
|
|
||||||
tx: Mutex::new(tx),
|
|
||||||
join: Some(join_handle),
|
|
||||||
exit: e,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a handle to the key store.
|
|
||||||
pub fn key_store(&self) -> Arc<RwLock<KeyStore>> {
|
|
||||||
self.key_store.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get filter kind if it's known.
|
|
||||||
pub fn kind(&self, id: &H256) -> Option<Kind> {
|
|
||||||
self.filters.read().get(id).map(|filter| match *filter {
|
|
||||||
FilterEntry::Poll(_, _) => Kind::Poll,
|
|
||||||
FilterEntry::Subscription(_, _) => Kind::Subscription,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove filter by ID.
|
|
||||||
pub fn remove(&self, id: &H256) {
|
|
||||||
self.filters.write().remove(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a new polled filter.
|
|
||||||
pub fn insert_polled(&self, filter: Filter) -> Result<H256, &'static str> {
|
|
||||||
let buffer = Arc::new(Mutex::new(Vec::new()));
|
|
||||||
let entry = FilterEntry::Poll(Arc::new(filter), buffer);
|
|
||||||
let id = OsRng::new()
|
|
||||||
.map_err(|_| "unable to acquire secure randomness")?
|
|
||||||
.gen();
|
|
||||||
|
|
||||||
self.filters.write().insert(id, entry);
|
|
||||||
Ok(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Insert new subscription filter. Generates a secure ID and sends it to
|
|
||||||
/// the subscriber
|
|
||||||
pub fn insert_subscription(
|
|
||||||
&self,
|
|
||||||
filter: Filter,
|
|
||||||
sub: Subscriber<FilterItem>,
|
|
||||||
) -> Result<(), &'static str> {
|
|
||||||
let id: H256 = OsRng::new()
|
|
||||||
.map_err(|_| "unable to acquire secure randomness")?
|
|
||||||
.gen();
|
|
||||||
|
|
||||||
sub.assign_id(::jsonrpc_pubsub::SubscriptionId::String(format!(
|
|
||||||
"{:x}",
|
|
||||||
id
|
|
||||||
)))
|
|
||||||
.map(move |sink| {
|
|
||||||
let entry = FilterEntry::Subscription(Arc::new(filter), sink);
|
|
||||||
self.filters.write().insert(id, entry);
|
|
||||||
})
|
|
||||||
.map_err(|_| "subscriber disconnected")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Poll changes on filter identified by ID.
|
|
||||||
pub fn poll_changes(&self, id: &H256) -> Option<Vec<FilterItem>> {
|
|
||||||
self.filters
|
|
||||||
.read()
|
|
||||||
.get(id)
|
|
||||||
.and_then(|filter| match *filter {
|
|
||||||
FilterEntry::Subscription(_, _) => None,
|
|
||||||
FilterEntry::Poll(_, ref changes) => {
|
|
||||||
Some(::std::mem::replace(&mut *changes.lock(), Vec::new()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// machinery for attaching the manager to the network instance.
|
|
||||||
impl ::net::MessageHandler for Arc<Manager> {
|
|
||||||
fn handle_messages(&self, messages: &[Message]) {
|
|
||||||
let filters = self.filters.read();
|
|
||||||
let filters_iter = filters
|
|
||||||
.values()
|
|
||||||
.flat_map(|filter| messages.iter().map(move |msg| (filter, msg)));
|
|
||||||
|
|
||||||
for (filter, message) in filters_iter {
|
|
||||||
// if the message matches any of the possible bloom filters,
|
|
||||||
// send to thread pool to attempt decryption and avoid
|
|
||||||
// blocking the network thread for long.
|
|
||||||
let failed_send = match *filter {
|
|
||||||
FilterEntry::Poll(ref filter, _) | FilterEntry::Subscription(ref filter, _)
|
|
||||||
if !filter.basic_matches(message) =>
|
|
||||||
{
|
|
||||||
None
|
|
||||||
}
|
|
||||||
FilterEntry::Poll(ref filter, ref buffer) => {
|
|
||||||
let (message, key_store) = (message.clone(), self.key_store.clone());
|
|
||||||
let (filter, buffer) = (filter.clone(), buffer.clone());
|
|
||||||
|
|
||||||
self.tx
|
|
||||||
.lock()
|
|
||||||
.send(Box::new(move || {
|
|
||||||
filter.handle_message(&message, &*key_store, |matched| {
|
|
||||||
buffer.lock().push(matched)
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
.err()
|
|
||||||
.map(|x| x.0)
|
|
||||||
}
|
|
||||||
FilterEntry::Subscription(ref filter, ref sink) => {
|
|
||||||
let (message, key_store) = (message.clone(), self.key_store.clone());
|
|
||||||
let (filter, sink) = (filter.clone(), sink.clone());
|
|
||||||
|
|
||||||
self.tx
|
|
||||||
.lock()
|
|
||||||
.send(Box::new(move || {
|
|
||||||
filter.handle_message(&message, &*key_store, |matched| {
|
|
||||||
let _ = sink.notify(Ok(matched));
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
.err()
|
|
||||||
.map(|x| x.0)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// if we failed to send work, no option but to do it locally.
|
|
||||||
if let Some(local_work) = failed_send {
|
|
||||||
(local_work)()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Manager {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
trace!(target: "parity_whisper", "waiting to drop FilterManager");
|
|
||||||
self.exit.store(true, atomic::Ordering::Release);
|
|
||||||
if let Some(guard) = self.join.take() {
|
|
||||||
let _ = guard.join();
|
|
||||||
}
|
|
||||||
trace!(target: "parity_whisper", "FilterManager dropped");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Filter incoming messages by critera.
|
|
||||||
pub struct Filter {
|
|
||||||
topics: Vec<(Vec<u8>, H512, Topic)>,
|
|
||||||
from: Option<Public>,
|
|
||||||
decrypt_with: Option<H256>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Filter {
|
|
||||||
/// Create a new filter from filter request.
|
|
||||||
///
|
|
||||||
/// Fails if the topics vector is empty.
|
|
||||||
pub fn new(params: types::FilterRequest) -> Result<Self, &'static str> {
|
|
||||||
if params.topics.is_empty() {
|
|
||||||
return Err("no topics for filter");
|
|
||||||
}
|
|
||||||
|
|
||||||
let topics: Vec<_> = params
|
|
||||||
.topics
|
|
||||||
.into_iter()
|
|
||||||
.map(|x| x.into_inner())
|
|
||||||
.map(|topic| {
|
|
||||||
let abridged = super::abridge_topic(&topic);
|
|
||||||
(topic, abridged.bloom(), abridged)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(Filter {
|
|
||||||
topics: topics,
|
|
||||||
from: params.from.map(|x| x.into_inner()),
|
|
||||||
decrypt_with: params.decrypt_with.map(|x| x.into_inner()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// does basic matching:
|
|
||||||
// whether the given message matches at least one of the topics of the
|
|
||||||
// filter.
|
|
||||||
// TODO: minimum PoW heuristic.
|
|
||||||
fn basic_matches(&self, message: &Message) -> bool {
|
|
||||||
self.topics
|
|
||||||
.iter()
|
|
||||||
.any(|&(_, ref bloom, _)| &(bloom & message.bloom()) == bloom)
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle a message that matches the bloom.
|
|
||||||
fn handle_message<F: Fn(FilterItem)>(
|
|
||||||
&self,
|
|
||||||
message: &Message,
|
|
||||||
store: &RwLock<KeyStore>,
|
|
||||||
on_match: F,
|
|
||||||
) {
|
|
||||||
use rpc::crypto::DecryptionInstance;
|
|
||||||
use tiny_keccak::keccak256;
|
|
||||||
|
|
||||||
let matched_indices: Vec<_> = self
|
|
||||||
.topics
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter_map(|(i, &(_, ref bloom, ref abridged))| {
|
|
||||||
let contains_topic =
|
|
||||||
&(bloom & message.bloom()) == bloom && message.topics().contains(abridged);
|
|
||||||
|
|
||||||
if contains_topic {
|
|
||||||
Some(i)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if matched_indices.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let decrypt = match self.decrypt_with {
|
|
||||||
Some(ref id) => match store.read().decryption_instance(id) {
|
|
||||||
Some(d) => d,
|
|
||||||
None => {
|
|
||||||
warn!(target: "whisper", "Filter attempted to decrypt with destroyed identity {}",
|
|
||||||
id);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
let known_idx = matched_indices[0];
|
|
||||||
let known_topic = H256(keccak256(&self.topics[0].0));
|
|
||||||
|
|
||||||
DecryptionInstance::broadcast(message.topics().len(), known_idx, known_topic)
|
|
||||||
.expect("known idx is within the range 0..message.topics.len(); qed")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let decrypted = match decrypt.decrypt(message.data()) {
|
|
||||||
Some(d) => d,
|
|
||||||
None => {
|
|
||||||
trace!(target: "whisper", "Failed to decrypt message with {} matching topics",
|
|
||||||
matched_indices.len());
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match ::rpc::payload::decode(&decrypted) {
|
|
||||||
Ok(decoded) => {
|
|
||||||
if decoded.from != self.from {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let matched_topics = matched_indices
|
|
||||||
.into_iter()
|
|
||||||
.map(|i| self.topics[i].0.clone())
|
|
||||||
.map(HexEncode)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
on_match(FilterItem {
|
|
||||||
from: decoded.from.map(HexEncode),
|
|
||||||
recipient: self.decrypt_with.map(HexEncode),
|
|
||||||
ttl: message.envelope().ttl,
|
|
||||||
topics: matched_topics,
|
|
||||||
timestamp: message.envelope().expiry - message.envelope().ttl,
|
|
||||||
payload: HexEncode(decoded.message.to_vec()),
|
|
||||||
padding: decoded.padding.map(|pad| HexEncode(pad.to_vec())),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Err(reason) => {
|
|
||||||
trace!(target: "whisper", "Bad payload in decrypted message with {} topics: {}",
|
|
||||||
matched_indices.len(), reason)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use message::{CreateParams, Message, Topic};
|
|
||||||
use rpc::{
|
|
||||||
abridge_topic,
|
|
||||||
types::{FilterRequest, HexEncode},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn rejects_empty_topics() {
|
|
||||||
let req = FilterRequest {
|
|
||||||
decrypt_with: Default::default(),
|
|
||||||
from: None,
|
|
||||||
topics: Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
assert!(Filter::new(req).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_match() {
|
|
||||||
let topics = vec![vec![1, 2, 3, 4], vec![5, 6, 7, 8]];
|
|
||||||
let abridged_topics: Vec<_> = topics.iter().map(|x| abridge_topic(&x)).collect();
|
|
||||||
|
|
||||||
let req = FilterRequest {
|
|
||||||
decrypt_with: Default::default(),
|
|
||||||
from: None,
|
|
||||||
topics: topics.into_iter().map(HexEncode).collect(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let filter = Filter::new(req).unwrap();
|
|
||||||
let message = Message::create(CreateParams {
|
|
||||||
ttl: 100,
|
|
||||||
payload: vec![1, 3, 5, 7, 9],
|
|
||||||
topics: abridged_topics.clone(),
|
|
||||||
work: 0,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(filter.basic_matches(&message));
|
|
||||||
|
|
||||||
let message = Message::create(CreateParams {
|
|
||||||
ttl: 100,
|
|
||||||
payload: vec![1, 3, 5, 7, 9],
|
|
||||||
topics: abridged_topics.clone(),
|
|
||||||
work: 0,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(filter.basic_matches(&message));
|
|
||||||
|
|
||||||
let message = Message::create(CreateParams {
|
|
||||||
ttl: 100,
|
|
||||||
payload: vec![1, 3, 5, 7, 9],
|
|
||||||
topics: vec![Topic([1, 8, 3, 99])],
|
|
||||||
work: 0,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(!filter.basic_matches(&message));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn decrypt_and_decode() {
|
|
||||||
use rpc::{
|
|
||||||
key_store::{Key, KeyStore},
|
|
||||||
payload::{self, EncodeParams},
|
|
||||||
};
|
|
||||||
|
|
||||||
let topics = vec![vec![1, 2, 3, 4], vec![5, 6, 7, 8]];
|
|
||||||
let abridged_topics: Vec<_> = topics.iter().map(|x| abridge_topic(&x)).collect();
|
|
||||||
|
|
||||||
let mut store = KeyStore::new().unwrap();
|
|
||||||
let signing_pair = Key::new_asymmetric(store.rng());
|
|
||||||
let encrypting_key = Key::new_symmetric(store.rng());
|
|
||||||
|
|
||||||
let decrypt_id = store.insert(encrypting_key);
|
|
||||||
let encryption_instance = store.encryption_instance(&decrypt_id).unwrap();
|
|
||||||
|
|
||||||
let store = ::parking_lot::RwLock::new(store);
|
|
||||||
|
|
||||||
let payload = payload::encode(EncodeParams {
|
|
||||||
message: &[1, 2, 3],
|
|
||||||
padding: Some(&[4, 5, 4, 5]),
|
|
||||||
sign_with: Some(signing_pair.secret().unwrap()),
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let encrypted = encryption_instance.encrypt(&payload).unwrap();
|
|
||||||
|
|
||||||
let message = Message::create(CreateParams {
|
|
||||||
ttl: 100,
|
|
||||||
payload: encrypted,
|
|
||||||
topics: abridged_topics.clone(),
|
|
||||||
work: 0,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let message2 = Message::create(CreateParams {
|
|
||||||
ttl: 100,
|
|
||||||
payload: vec![3, 5, 7, 9],
|
|
||||||
topics: abridged_topics,
|
|
||||||
work: 0,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let filter = Filter::new(FilterRequest {
|
|
||||||
decrypt_with: Some(HexEncode(decrypt_id)),
|
|
||||||
from: Some(HexEncode(signing_pair.public().unwrap().clone())),
|
|
||||||
topics: topics.into_iter().map(HexEncode).collect(),
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(filter.basic_matches(&message));
|
|
||||||
assert!(filter.basic_matches(&message2));
|
|
||||||
|
|
||||||
let items = ::std::cell::Cell::new(0);
|
|
||||||
let on_match = |_| {
|
|
||||||
items.set(items.get() + 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
filter.handle_message(&message, &store, &on_match);
|
|
||||||
filter.handle_message(&message2, &store, &on_match);
|
|
||||||
|
|
||||||
assert_eq!(items.get(), 1);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,196 +0,0 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
|
||||||
// This file is part of Parity Ethereum.
|
|
||||||
|
|
||||||
// Parity Ethereum 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.
|
|
||||||
|
|
||||||
// Parity Ethereum 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 Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
//! Identity and keystore for Whisper sessions.
|
|
||||||
//!
|
|
||||||
//! Can handle symmetric and asymmetric keys.
|
|
||||||
//! Symmetric encryption is done via AES-256 in GCM mode.
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use ethereum_types::H256;
|
|
||||||
use ethkey::{KeyPair, Public, Secret};
|
|
||||||
use memzero::Memzero;
|
|
||||||
use rand::{OsRng, Rng};
|
|
||||||
|
|
||||||
use rpc::crypto::{DecryptionInstance, EncryptionInstance, AES_KEY_LEN};
|
|
||||||
|
|
||||||
/// A symmetric or asymmetric key used for encryption, decryption, and signing
|
|
||||||
/// of payloads.
|
|
||||||
pub enum Key {
|
|
||||||
/// ECIES key pair for Secp2561k curve. Suitable for encryption, decryption,
|
|
||||||
/// and signing.
|
|
||||||
Asymmetric(KeyPair),
|
|
||||||
/// AES-256 GCM mode. Suitable for encryption, decryption, but not signing.
|
|
||||||
Symmetric(Memzero<[u8; AES_KEY_LEN]>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Key {
|
|
||||||
/// Generate a random asymmetric key with the given cryptographic RNG.
|
|
||||||
pub fn new_asymmetric(rng: &mut OsRng) -> Self {
|
|
||||||
match ::ethkey::Generator::generate(rng) {
|
|
||||||
Ok(pair) => Key::Asymmetric(pair),
|
|
||||||
Err(void) => match void {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate a random symmetric key with the given cryptographic RNG.
|
|
||||||
pub fn new_symmetric(rng: &mut OsRng) -> Self {
|
|
||||||
Key::Symmetric(Memzero::from(rng.gen::<[u8; 32]>()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// From secret asymmetric key. Fails if secret is invalid.
|
|
||||||
pub fn from_secret(secret: Secret) -> Option<Self> {
|
|
||||||
KeyPair::from_secret(secret).map(Key::Asymmetric).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// From raw symmetric key.
|
|
||||||
pub fn from_raw_symmetric(key: [u8; AES_KEY_LEN]) -> Self {
|
|
||||||
Key::Symmetric(Memzero::from(key))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a handle to the public key if this is an asymmetric key.
|
|
||||||
pub fn public(&self) -> Option<&Public> {
|
|
||||||
match *self {
|
|
||||||
Key::Asymmetric(ref pair) => Some(pair.public()),
|
|
||||||
Key::Symmetric(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a handle to the secret key if this is an asymmetric key.
|
|
||||||
pub fn secret(&self) -> Option<&Secret> {
|
|
||||||
match *self {
|
|
||||||
Key::Asymmetric(ref pair) => Some(pair.secret()),
|
|
||||||
Key::Symmetric(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a handle to the symmetric key.
|
|
||||||
pub fn symmetric(&self) -> Option<&[u8; AES_KEY_LEN]> {
|
|
||||||
match *self {
|
|
||||||
Key::Asymmetric(_) => None,
|
|
||||||
Key::Symmetric(ref key) => Some(key),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Key store.
|
|
||||||
pub struct KeyStore {
|
|
||||||
rng: OsRng,
|
|
||||||
identities: HashMap<H256, Key>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeyStore {
|
|
||||||
/// Create the key store. Returns any error in accessing the system's secure
|
|
||||||
/// RNG.
|
|
||||||
pub fn new() -> Result<Self, ::std::io::Error> {
|
|
||||||
Ok(KeyStore {
|
|
||||||
rng: OsRng::new()?,
|
|
||||||
identities: HashMap::new(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Import a key, generating a random identity for it.
|
|
||||||
pub fn insert(&mut self, key: Key) -> H256 {
|
|
||||||
let id = self.rng().gen();
|
|
||||||
self.identities.insert(id, key);
|
|
||||||
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a key by ID.
|
|
||||||
pub fn get<'a>(&'a self, id: &H256) -> Option<&'a Key> {
|
|
||||||
self.identities.get(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get asymmetric ID's public key.
|
|
||||||
pub fn public<'a>(&'a self, id: &H256) -> Option<&'a Public> {
|
|
||||||
self.get(id).and_then(Key::public)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get asymmetric ID's secret key.
|
|
||||||
pub fn secret<'a>(&'a self, id: &H256) -> Option<&'a Secret> {
|
|
||||||
self.get(id).and_then(Key::secret)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get symmetric ID's key.
|
|
||||||
pub fn symmetric<'a>(&'a self, id: &H256) -> Option<&'a [u8; AES_KEY_LEN]> {
|
|
||||||
self.get(id).and_then(Key::symmetric)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get encryption instance for identity.
|
|
||||||
pub fn encryption_instance(&self, id: &H256) -> Result<EncryptionInstance, &'static str> {
|
|
||||||
self.get(id)
|
|
||||||
.ok_or("no such identity")
|
|
||||||
.and_then(|key| match *key {
|
|
||||||
Key::Asymmetric(ref pair) => EncryptionInstance::ecies(pair.public().clone())
|
|
||||||
.map_err(|_| "could not create encryption instance for id"),
|
|
||||||
Key::Symmetric(ref key) => OsRng::new()
|
|
||||||
.map(|mut rng| EncryptionInstance::aes(key.clone(), rng.gen()))
|
|
||||||
.map_err(|_| "unable to get secure randomness"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get decryption instance for identity.
|
|
||||||
/// If the identity is known, always succeeds.
|
|
||||||
pub fn decryption_instance(&self, id: &H256) -> Option<DecryptionInstance> {
|
|
||||||
self.get(id).map(|key| match *key {
|
|
||||||
Key::Asymmetric(ref pair) => DecryptionInstance::ecies(pair.secret().clone())
|
|
||||||
.expect("all keys stored are valid; qed"),
|
|
||||||
Key::Symmetric(ref key) => DecryptionInstance::aes(key.clone()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether the store contains a key by this ID.
|
|
||||||
pub fn contains(&self, id: &H256) -> bool {
|
|
||||||
self.identities.contains_key(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove a key by ID.
|
|
||||||
pub fn remove(&mut self, id: &H256) -> bool {
|
|
||||||
self.identities.remove(id).is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get RNG.
|
|
||||||
pub fn rng(&mut self) -> &mut OsRng {
|
|
||||||
&mut self.rng
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn rejects_invalid_secret() {
|
|
||||||
let bad_secret = ::ethkey::Secret::from([0xff; 32]);
|
|
||||||
assert!(Key::from_secret(bad_secret).is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn generated_key_should_exist() {
|
|
||||||
let mut store = KeyStore::new().unwrap();
|
|
||||||
let key = Key::new_asymmetric(store.rng());
|
|
||||||
|
|
||||||
assert!(key.public().is_some());
|
|
||||||
assert!(key.secret().is_some());
|
|
||||||
|
|
||||||
let id = store.insert(key);
|
|
||||||
|
|
||||||
assert!(store.contains(&id));
|
|
||||||
assert!(store.get(&id).is_some());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,424 +0,0 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
|
||||||
// This file is part of Parity Ethereum.
|
|
||||||
|
|
||||||
// Parity Ethereum 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.
|
|
||||||
|
|
||||||
// Parity Ethereum 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 Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
//! JSONRPC interface for Whisper.
|
|
||||||
//!
|
|
||||||
//! Manages standard message format decoding, ephemeral identities, signing,
|
|
||||||
//! encryption, and decryption.
|
|
||||||
//!
|
|
||||||
//! Provides an interface for using whisper to transmit data securely.
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use jsonrpc_core::{Error, ErrorCode, Metadata};
|
|
||||||
use jsonrpc_derive::rpc;
|
|
||||||
use jsonrpc_pubsub::{typed::Subscriber, PubSubMetadata, Session, SubscriptionId};
|
|
||||||
|
|
||||||
use ethereum_types::H256;
|
|
||||||
use memzero::Memzero;
|
|
||||||
use parking_lot::RwLock;
|
|
||||||
|
|
||||||
use self::{
|
|
||||||
filter::Filter,
|
|
||||||
key_store::{Key, KeyStore},
|
|
||||||
types::HexEncode,
|
|
||||||
};
|
|
||||||
|
|
||||||
use message::{CreateParams, Message, Topic};
|
|
||||||
|
|
||||||
mod crypto;
|
|
||||||
mod filter;
|
|
||||||
mod key_store;
|
|
||||||
mod payload;
|
|
||||||
mod types;
|
|
||||||
|
|
||||||
pub use self::filter::Manager as FilterManager;
|
|
||||||
|
|
||||||
// create whisper RPC error.
|
|
||||||
fn whisper_error<T: Into<String>>(message: T) -> Error {
|
|
||||||
const ERROR_CODE: i64 = -32085;
|
|
||||||
|
|
||||||
Error {
|
|
||||||
code: ErrorCode::ServerError(ERROR_CODE),
|
|
||||||
message: message.into(),
|
|
||||||
data: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn topic_hash(topic: &[u8]) -> H256 {
|
|
||||||
H256(::tiny_keccak::keccak256(topic))
|
|
||||||
}
|
|
||||||
|
|
||||||
// abridge topic using first four bytes of hash.
|
|
||||||
fn abridge_topic(topic: &[u8]) -> Topic {
|
|
||||||
let mut abridged = [0; 4];
|
|
||||||
let hash = topic_hash(topic).0;
|
|
||||||
abridged.copy_from_slice(&hash[..4]);
|
|
||||||
abridged.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whisper RPC interface.
|
|
||||||
#[rpc(server)]
|
|
||||||
pub trait Whisper {
|
|
||||||
/// Info about the node.
|
|
||||||
#[rpc(name = "shh_info")]
|
|
||||||
fn info(&self) -> Result<types::NodeInfo, Error>;
|
|
||||||
|
|
||||||
/// Generate a new asymmetric key pair and return an identity.
|
|
||||||
#[rpc(name = "shh_newKeyPair")]
|
|
||||||
fn new_key_pair(&self) -> Result<types::Identity, Error>;
|
|
||||||
|
|
||||||
/// Import the given SECP2561k private key and return an identity.
|
|
||||||
#[rpc(name = "shh_addPrivateKey")]
|
|
||||||
fn add_private_key(&self, _: types::Private) -> Result<types::Identity, Error>;
|
|
||||||
|
|
||||||
/// Generate a new symmetric key and return an identity.
|
|
||||||
#[rpc(name = "shh_newSymKey")]
|
|
||||||
fn new_sym_key(&self) -> Result<types::Identity, Error>;
|
|
||||||
|
|
||||||
/// Import the given symmetric key and return an identity.
|
|
||||||
#[rpc(name = "shh_addSymKey")]
|
|
||||||
fn add_sym_key(&self, _: types::Symmetric) -> Result<types::Identity, Error>;
|
|
||||||
|
|
||||||
/// Get public key. Succeeds if identity is stored and asymmetric.
|
|
||||||
#[rpc(name = "shh_getPublicKey")]
|
|
||||||
fn get_public(&self, _: types::Identity) -> Result<types::Public, Error>;
|
|
||||||
|
|
||||||
/// Get private key. Succeeds if identity is stored and asymmetric.
|
|
||||||
#[rpc(name = "shh_getPrivateKey")]
|
|
||||||
fn get_private(&self, _: types::Identity) -> Result<types::Private, Error>;
|
|
||||||
|
|
||||||
#[rpc(name = "shh_getSymKey")]
|
|
||||||
fn get_symmetric(&self, _: types::Identity) -> Result<types::Symmetric, Error>;
|
|
||||||
|
|
||||||
/// Delete key pair denoted by given identity.
|
|
||||||
///
|
|
||||||
/// Return true if successfully removed, false if unknown,
|
|
||||||
/// and error otherwise.
|
|
||||||
#[rpc(name = "shh_deleteKey")]
|
|
||||||
fn remove_key(&self, _: types::Identity) -> Result<bool, Error>;
|
|
||||||
|
|
||||||
/// Post a message to the network with given parameters.
|
|
||||||
#[rpc(name = "shh_post")]
|
|
||||||
fn post(&self, _: types::PostRequest) -> Result<bool, Error>;
|
|
||||||
|
|
||||||
/// Create a new polled filter.
|
|
||||||
#[rpc(name = "shh_newMessageFilter")]
|
|
||||||
fn new_filter(&self, _: types::FilterRequest) -> Result<types::Identity, Error>;
|
|
||||||
|
|
||||||
/// Poll changes on a polled filter.
|
|
||||||
#[rpc(name = "shh_getFilterMessages")]
|
|
||||||
fn poll_changes(&self, _: types::Identity) -> Result<Vec<types::FilterItem>, Error>;
|
|
||||||
|
|
||||||
/// Delete polled filter. Return bool indicating success.
|
|
||||||
#[rpc(name = "shh_deleteMessageFilter")]
|
|
||||||
fn delete_filter(&self, _: types::Identity) -> Result<bool, Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whisper RPC pubsub.
|
|
||||||
#[rpc(server)]
|
|
||||||
pub trait WhisperPubSub {
|
|
||||||
// RPC Metadata
|
|
||||||
type Metadata;
|
|
||||||
/// Subscribe to messages matching the filter.
|
|
||||||
#[pubsub(subscription = "shh_subscription", subscribe, name = "shh_subscribe")]
|
|
||||||
fn subscribe(
|
|
||||||
&self,
|
|
||||||
_: Self::Metadata,
|
|
||||||
_: Subscriber<types::FilterItem>,
|
|
||||||
_: types::FilterRequest,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Unsubscribe from filter matching given ID. Return
|
|
||||||
/// true on success, error otherwise.
|
|
||||||
#[pubsub(
|
|
||||||
subscription = "shh_subscription",
|
|
||||||
unsubscribe,
|
|
||||||
name = "shh_unsubscribe"
|
|
||||||
)]
|
|
||||||
fn unsubscribe(&self, _: Option<Self::Metadata>, _: SubscriptionId) -> Result<bool, Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Something which can send messages to the network.
|
|
||||||
pub trait PoolHandle: Send + Sync {
|
|
||||||
/// Give message to the whisper network for relay.
|
|
||||||
/// Returns false if PoW too low.
|
|
||||||
fn relay(&self, message: Message) -> bool;
|
|
||||||
|
|
||||||
/// Number of messages and memory used by resident messages.
|
|
||||||
fn pool_status(&self) -> ::net::PoolStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Default, simple metadata implementation.
|
|
||||||
#[derive(Clone, Default)]
|
|
||||||
pub struct Meta {
|
|
||||||
session: Option<Arc<Session>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Metadata for Meta {}
|
|
||||||
impl PubSubMetadata for Meta {
|
|
||||||
fn session(&self) -> Option<Arc<Session>> {
|
|
||||||
self.session.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implementation of whisper RPC.
|
|
||||||
pub struct WhisperClient<P, M = Meta> {
|
|
||||||
store: Arc<RwLock<KeyStore>>,
|
|
||||||
pool: P,
|
|
||||||
filter_manager: Arc<filter::Manager>,
|
|
||||||
_meta: ::std::marker::PhantomData<M>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P> WhisperClient<P> {
|
|
||||||
/// Create a new whisper client with basic metadata.
|
|
||||||
pub fn with_simple_meta(pool: P, filter_manager: Arc<filter::Manager>) -> Self {
|
|
||||||
WhisperClient::new(pool, filter_manager)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P, M> WhisperClient<P, M> {
|
|
||||||
/// Create a new whisper client.
|
|
||||||
pub fn new(pool: P, filter_manager: Arc<filter::Manager>) -> Self {
|
|
||||||
WhisperClient {
|
|
||||||
store: filter_manager.key_store(),
|
|
||||||
pool: pool,
|
|
||||||
filter_manager: filter_manager,
|
|
||||||
_meta: ::std::marker::PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delete_filter_kind(&self, id: H256, kind: filter::Kind) -> bool {
|
|
||||||
match self.filter_manager.kind(&id) {
|
|
||||||
Some(k) if k == kind => {
|
|
||||||
self.filter_manager.remove(&id);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
None | Some(_) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: PoolHandle + 'static, M: Send + Sync + 'static> Whisper for WhisperClient<P, M> {
|
|
||||||
fn info(&self) -> Result<types::NodeInfo, Error> {
|
|
||||||
let status = self.pool.pool_status();
|
|
||||||
|
|
||||||
Ok(types::NodeInfo {
|
|
||||||
required_pow: status.required_pow,
|
|
||||||
messages: status.message_count,
|
|
||||||
memory: status.cumulative_size,
|
|
||||||
target_memory: status.target_size,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_key_pair(&self) -> Result<types::Identity, Error> {
|
|
||||||
let mut store = self.store.write();
|
|
||||||
let key_pair = Key::new_asymmetric(store.rng());
|
|
||||||
|
|
||||||
Ok(HexEncode(store.insert(key_pair)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_private_key(&self, private: types::Private) -> Result<types::Identity, Error> {
|
|
||||||
let key_pair = Key::from_secret(private.into_inner().into())
|
|
||||||
.ok_or_else(|| whisper_error("Invalid private key"))?;
|
|
||||||
|
|
||||||
Ok(HexEncode(self.store.write().insert(key_pair)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_sym_key(&self) -> Result<types::Identity, Error> {
|
|
||||||
let mut store = self.store.write();
|
|
||||||
let key = Key::new_symmetric(store.rng());
|
|
||||||
|
|
||||||
Ok(HexEncode(store.insert(key)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_sym_key(&self, raw_key: types::Symmetric) -> Result<types::Identity, Error> {
|
|
||||||
let raw_key = raw_key.into_inner().0;
|
|
||||||
let key = Key::from_raw_symmetric(raw_key);
|
|
||||||
|
|
||||||
Ok(HexEncode(self.store.write().insert(key)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_public(&self, id: types::Identity) -> Result<types::Public, Error> {
|
|
||||||
self.store
|
|
||||||
.read()
|
|
||||||
.public(&id.into_inner())
|
|
||||||
.cloned()
|
|
||||||
.map(HexEncode)
|
|
||||||
.ok_or_else(|| whisper_error("Unknown identity"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_private(&self, id: types::Identity) -> Result<types::Private, Error> {
|
|
||||||
self.store
|
|
||||||
.read()
|
|
||||||
.secret(&id.into_inner())
|
|
||||||
.map(|x| (&**x).clone())
|
|
||||||
.map(HexEncode)
|
|
||||||
.ok_or_else(|| whisper_error("Unknown identity"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_symmetric(&self, id: types::Identity) -> Result<types::Symmetric, Error> {
|
|
||||||
self.store
|
|
||||||
.read()
|
|
||||||
.symmetric(&id.into_inner())
|
|
||||||
.cloned()
|
|
||||||
.map(H256)
|
|
||||||
.map(HexEncode)
|
|
||||||
.ok_or_else(|| whisper_error("Unknown identity"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_key(&self, id: types::Identity) -> Result<bool, Error> {
|
|
||||||
Ok(self.store.write().remove(&id.into_inner()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn post(&self, req: types::PostRequest) -> Result<bool, Error> {
|
|
||||||
use self::crypto::EncryptionInstance;
|
|
||||||
|
|
||||||
let encryption = match req.to {
|
|
||||||
Some(types::Receiver::Public(public)) => {
|
|
||||||
EncryptionInstance::ecies(public.into_inner()).map_err(whisper_error)?
|
|
||||||
}
|
|
||||||
Some(types::Receiver::Identity(id)) => self
|
|
||||||
.store
|
|
||||||
.read()
|
|
||||||
.encryption_instance(&id.into_inner())
|
|
||||||
.map_err(whisper_error)?,
|
|
||||||
None => {
|
|
||||||
use rand::{OsRng, Rng};
|
|
||||||
|
|
||||||
// broadcast mode: use fixed nonce and fresh key each time.
|
|
||||||
|
|
||||||
let mut rng = OsRng::new()
|
|
||||||
.map_err(|_| whisper_error("unable to acquire secure randomness"))?;
|
|
||||||
|
|
||||||
let key = Memzero::from(rng.gen::<[u8; 32]>());
|
|
||||||
if req.topics.is_empty() {
|
|
||||||
return Err(whisper_error(
|
|
||||||
"must supply at least one topic for broadcast message",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
EncryptionInstance::broadcast(
|
|
||||||
key,
|
|
||||||
req.topics.iter().map(|x| topic_hash(&x)).collect(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let sign_with = match req.from {
|
|
||||||
Some(from) => Some(
|
|
||||||
self.store
|
|
||||||
.read()
|
|
||||||
.secret(&from.into_inner())
|
|
||||||
.cloned()
|
|
||||||
.ok_or_else(|| whisper_error("Unknown identity `from`"))?,
|
|
||||||
),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let encrypted = {
|
|
||||||
let payload = payload::encode(payload::EncodeParams {
|
|
||||||
message: &req.payload.into_inner(),
|
|
||||||
padding: req.padding.map(|p| p.into_inner()).as_ref().map(|x| &x[..]),
|
|
||||||
sign_with: sign_with.as_ref(),
|
|
||||||
})
|
|
||||||
.map_err(whisper_error)?;
|
|
||||||
|
|
||||||
encryption
|
|
||||||
.encrypt(&payload)
|
|
||||||
.ok_or(whisper_error("encryption error"))?
|
|
||||||
};
|
|
||||||
|
|
||||||
// mining the packet is the heaviest item of work by far.
|
|
||||||
// there may be a benefit to dispatching this onto the CPU pool
|
|
||||||
// and returning a future. but then things get _less_ efficient
|
|
||||||
// if the server infrastructure has more threads than the CPU pool.
|
|
||||||
let message = Message::create(CreateParams {
|
|
||||||
ttl: req.ttl,
|
|
||||||
payload: encrypted,
|
|
||||||
topics: req
|
|
||||||
.topics
|
|
||||||
.into_iter()
|
|
||||||
.map(|x| abridge_topic(&x.into_inner()))
|
|
||||||
.collect(),
|
|
||||||
work: req.priority,
|
|
||||||
})
|
|
||||||
.map_err(|_| whisper_error("Empty topics"))?;
|
|
||||||
|
|
||||||
if !self.pool.relay(message) {
|
|
||||||
Err(whisper_error("PoW too low to compete with other messages"))
|
|
||||||
} else {
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_filter(&self, req: types::FilterRequest) -> Result<types::Identity, Error> {
|
|
||||||
let filter = Filter::new(req).map_err(whisper_error)?;
|
|
||||||
|
|
||||||
self.filter_manager
|
|
||||||
.insert_polled(filter)
|
|
||||||
.map(HexEncode)
|
|
||||||
.map_err(whisper_error)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_changes(&self, id: types::Identity) -> Result<Vec<types::FilterItem>, Error> {
|
|
||||||
match self.filter_manager.poll_changes(&id.into_inner()) {
|
|
||||||
None => Err(whisper_error("no such message filter")),
|
|
||||||
Some(items) => Ok(items),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delete_filter(&self, id: types::Identity) -> Result<bool, Error> {
|
|
||||||
Ok(self.delete_filter_kind(id.into_inner(), filter::Kind::Poll))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: PoolHandle + 'static, M: Send + Sync + PubSubMetadata> WhisperPubSub
|
|
||||||
for WhisperClient<P, M>
|
|
||||||
{
|
|
||||||
type Metadata = M;
|
|
||||||
|
|
||||||
fn subscribe(
|
|
||||||
&self,
|
|
||||||
_meta: Self::Metadata,
|
|
||||||
subscriber: Subscriber<types::FilterItem>,
|
|
||||||
req: types::FilterRequest,
|
|
||||||
) {
|
|
||||||
match Filter::new(req) {
|
|
||||||
Ok(filter) => {
|
|
||||||
if let Err(e) = self.filter_manager.insert_subscription(filter, subscriber) {
|
|
||||||
debug!(target: "whisper", "Failed to add subscription: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(reason) => {
|
|
||||||
let _ = subscriber.reject(whisper_error(reason));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unsubscribe(&self, _: Option<Self::Metadata>, id: SubscriptionId) -> Result<bool, Error> {
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
let res = match id {
|
|
||||||
SubscriptionId::String(s) => H256::from_str(&s)
|
|
||||||
.map_err(|_| "unrecognized ID")
|
|
||||||
.map(|id| self.delete_filter_kind(id, filter::Kind::Subscription)),
|
|
||||||
SubscriptionId::Number(_) => Err("unrecognized ID"),
|
|
||||||
};
|
|
||||||
|
|
||||||
res.map_err(whisper_error)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,366 +0,0 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
|
||||||
// This file is part of Parity Ethereum.
|
|
||||||
|
|
||||||
// Parity Ethereum 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.
|
|
||||||
|
|
||||||
// Parity Ethereum 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 Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
//! Common payload format definition, construction, and decoding.
|
|
||||||
//!
|
|
||||||
//! Format:
|
|
||||||
//! flags: 1 byte
|
|
||||||
//!
|
|
||||||
//! payload size: 0..4 bytes, BE, determined by flags.
|
|
||||||
//! optional padding: byte array up to 2^24 bytes in length. encoded in payload size.
|
|
||||||
//! optional signature: 65 bytes (r, s, v)
|
|
||||||
//!
|
|
||||||
//! payload: byte array of length of arbitrary size.
|
|
||||||
//!
|
|
||||||
//! flag bits used:
|
|
||||||
//! 0, 1 => how many bytes indicate padding length (up to 3)
|
|
||||||
//! 2 => whether signature is present
|
|
||||||
//!
|
|
||||||
//! padding is used to mask information about size of message.
|
|
||||||
//!
|
|
||||||
//! AES-256-GCM will append 12 bytes of metadata to the front of the message.
|
|
||||||
|
|
||||||
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
|
|
||||||
use ethereum_types::H256;
|
|
||||||
use ethkey::{Public, Secret};
|
|
||||||
use tiny_keccak::keccak256;
|
|
||||||
|
|
||||||
const SIGNATURE_LEN: usize = 65;
|
|
||||||
|
|
||||||
const STANDARD_PAYLOAD_VERSION: u8 = 1;
|
|
||||||
|
|
||||||
bitflags! {
|
|
||||||
struct Flags: u8 {
|
|
||||||
const FLAG_PAD_LEN_HIGH = 0b10000000;
|
|
||||||
const FLAG_PAD_LEN_LOW = 0b01000000;
|
|
||||||
const FLAG_SIGNED = 0b00100000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// number of bytes of padding length (in the range 0..4)
|
|
||||||
fn padding_length_bytes(flags: Flags) -> usize {
|
|
||||||
match (flags & FLAG_PAD_LEN_HIGH, flags & FLAG_PAD_LEN_LOW) {
|
|
||||||
(FLAG_PAD_LEN_HIGH, FLAG_PAD_LEN_LOW) => 3,
|
|
||||||
(FLAG_PAD_LEN_HIGH, _) => 2,
|
|
||||||
(_, FLAG_PAD_LEN_LOW) => 1,
|
|
||||||
(_, _) => 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// how many bytes are necessary to encode the given length. Range 0..4.
|
|
||||||
// `None` if too large.
|
|
||||||
fn num_padding_length_bytes(padding_len: usize) -> Option<usize> {
|
|
||||||
let bits = 64 - (padding_len as u64).leading_zeros();
|
|
||||||
match bits {
|
|
||||||
0 => Some(0),
|
|
||||||
1..=8 => Some(1),
|
|
||||||
9..=16 => Some(2),
|
|
||||||
17..=24 => Some(3),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parameters for encoding a standard payload.
|
|
||||||
pub struct EncodeParams<'a> {
|
|
||||||
/// Message to encode.
|
|
||||||
pub message: &'a [u8],
|
|
||||||
/// Padding bytes. Maximum padding allowed is 65536 bytes.
|
|
||||||
pub padding: Option<&'a [u8]>,
|
|
||||||
/// Private key to sign with.
|
|
||||||
pub sign_with: Option<&'a Secret>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Default for EncodeParams<'a> {
|
|
||||||
fn default() -> Self {
|
|
||||||
EncodeParams {
|
|
||||||
message: &[],
|
|
||||||
padding: None,
|
|
||||||
sign_with: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parameters for decoding a standard payload.
|
|
||||||
pub struct Decoded<'a> {
|
|
||||||
/// Decoded message.
|
|
||||||
pub message: &'a [u8],
|
|
||||||
/// optional padding.
|
|
||||||
pub padding: Option<&'a [u8]>,
|
|
||||||
/// Recovered signature.
|
|
||||||
pub from: Option<Public>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encode using provided parameters.
|
|
||||||
pub fn encode(params: EncodeParams) -> Result<Vec<u8>, &'static str> {
|
|
||||||
const VEC_WRITE_INFALLIBLE: &'static str = "writing to a Vec<u8> can never fail; qed";
|
|
||||||
|
|
||||||
let padding_len = params.padding.map_or(0, |x| x.len());
|
|
||||||
let padding_len_bytes =
|
|
||||||
num_padding_length_bytes(padding_len).ok_or_else(|| "padding size too long")?;
|
|
||||||
|
|
||||||
let signature = params.sign_with.map(|secret| {
|
|
||||||
let hash = H256(keccak256(params.message));
|
|
||||||
::ethkey::sign(secret, &hash)
|
|
||||||
});
|
|
||||||
|
|
||||||
let signature = match signature {
|
|
||||||
Some(Ok(sig)) => Some(sig),
|
|
||||||
Some(Err(_)) => return Err("invalid signing key provided"),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let (flags, plaintext_size) = {
|
|
||||||
let mut flags = Flags::empty();
|
|
||||||
|
|
||||||
// 1 byte each for flags and version.
|
|
||||||
let mut plaintext_size = 2 + padding_len_bytes + padding_len + params.message.len();
|
|
||||||
|
|
||||||
flags.bits = (padding_len_bytes << 6) as u8;
|
|
||||||
debug_assert_eq!(padding_length_bytes(flags), padding_len_bytes);
|
|
||||||
|
|
||||||
if let Some(ref sig) = signature {
|
|
||||||
plaintext_size += sig.len();
|
|
||||||
flags |= FLAG_SIGNED;
|
|
||||||
}
|
|
||||||
|
|
||||||
(flags, plaintext_size)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut plaintext = Vec::with_capacity(plaintext_size);
|
|
||||||
|
|
||||||
plaintext.push(STANDARD_PAYLOAD_VERSION);
|
|
||||||
plaintext.push(flags.bits);
|
|
||||||
|
|
||||||
if let Some(padding) = params.padding {
|
|
||||||
plaintext
|
|
||||||
.write_uint::<BigEndian>(padding_len as u64, padding_len_bytes)
|
|
||||||
.expect(VEC_WRITE_INFALLIBLE);
|
|
||||||
|
|
||||||
plaintext.extend(padding)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(signature) = signature {
|
|
||||||
plaintext.extend(signature.r());
|
|
||||||
plaintext.extend(signature.s());
|
|
||||||
plaintext.push(signature.v());
|
|
||||||
}
|
|
||||||
|
|
||||||
plaintext.extend(params.message);
|
|
||||||
|
|
||||||
Ok(plaintext)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decode using provided parameters
|
|
||||||
pub fn decode(payload: &[u8]) -> Result<Decoded, &'static str> {
|
|
||||||
let mut offset = 0;
|
|
||||||
|
|
||||||
let (padding, signature) = {
|
|
||||||
// use a closure for reading slices since std::io::Read would require
|
|
||||||
// us to copy.
|
|
||||||
let mut next_slice = |len| {
|
|
||||||
let end = offset + len;
|
|
||||||
if payload.len() >= end {
|
|
||||||
let slice = &payload[offset..end];
|
|
||||||
offset = end;
|
|
||||||
|
|
||||||
Ok(slice)
|
|
||||||
} else {
|
|
||||||
return Err("unexpected end of payload");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if next_slice(1)?[0] != STANDARD_PAYLOAD_VERSION {
|
|
||||||
return Err("unknown payload version.");
|
|
||||||
}
|
|
||||||
|
|
||||||
let flags = Flags::from_bits_truncate(next_slice(1)?[0]);
|
|
||||||
|
|
||||||
let padding_len_bytes = padding_length_bytes(flags);
|
|
||||||
let padding = if padding_len_bytes != 0 {
|
|
||||||
let padding_len =
|
|
||||||
BigEndian::read_uint(next_slice(padding_len_bytes)?, padding_len_bytes);
|
|
||||||
|
|
||||||
Some(next_slice(padding_len as usize)?)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let signature = if flags & FLAG_SIGNED == FLAG_SIGNED {
|
|
||||||
let slice = next_slice(SIGNATURE_LEN)?;
|
|
||||||
let mut arr = [0; SIGNATURE_LEN];
|
|
||||||
|
|
||||||
arr.copy_from_slice(slice);
|
|
||||||
let signature = ::ethkey::Signature::from(arr);
|
|
||||||
|
|
||||||
let not_rsv = signature.r() != &slice[..32]
|
|
||||||
|| signature.s() != &slice[32..64]
|
|
||||||
|| signature.v() != slice[64];
|
|
||||||
|
|
||||||
if not_rsv {
|
|
||||||
return Err("signature not in RSV format");
|
|
||||||
} else {
|
|
||||||
Some(signature)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
(padding, signature)
|
|
||||||
};
|
|
||||||
|
|
||||||
// remaining data is the message.
|
|
||||||
let message = &payload[offset..];
|
|
||||||
|
|
||||||
let from = match signature {
|
|
||||||
None => None,
|
|
||||||
Some(sig) => {
|
|
||||||
let hash = H256(keccak256(message));
|
|
||||||
Some(::ethkey::recover(&sig, &hash).map_err(|_| "invalid signature")?)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Decoded {
|
|
||||||
message: message,
|
|
||||||
padding: padding,
|
|
||||||
from: from,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use ethkey::{Generator, Random};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn padding_len_bytes_sanity() {
|
|
||||||
const U24_MAX: usize = (1 << 24) - 1;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
padding_length_bytes(FLAG_PAD_LEN_HIGH | FLAG_PAD_LEN_LOW),
|
|
||||||
3
|
|
||||||
);
|
|
||||||
assert_eq!(padding_length_bytes(FLAG_PAD_LEN_HIGH), 2);
|
|
||||||
assert_eq!(padding_length_bytes(FLAG_PAD_LEN_LOW), 1);
|
|
||||||
assert_eq!(padding_length_bytes(Flags::empty()), 0);
|
|
||||||
|
|
||||||
assert!(num_padding_length_bytes(u32::max_value() as _).is_none());
|
|
||||||
assert!(num_padding_length_bytes(U24_MAX + 1).is_none());
|
|
||||||
|
|
||||||
assert_eq!(num_padding_length_bytes(U24_MAX), Some(3));
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
num_padding_length_bytes(u16::max_value() as usize + 1),
|
|
||||||
Some(3)
|
|
||||||
);
|
|
||||||
assert_eq!(num_padding_length_bytes(u16::max_value() as usize), Some(2));
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
num_padding_length_bytes(u8::max_value() as usize + 1),
|
|
||||||
Some(2)
|
|
||||||
);
|
|
||||||
assert_eq!(num_padding_length_bytes(u8::max_value() as usize), Some(1));
|
|
||||||
|
|
||||||
assert_eq!(num_padding_length_bytes(1), Some(1));
|
|
||||||
assert_eq!(num_padding_length_bytes(0), Some(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn encode_decode_roundtrip() {
|
|
||||||
let message = [1, 2, 3, 4, 5];
|
|
||||||
let encoded = encode(EncodeParams {
|
|
||||||
message: &message,
|
|
||||||
padding: None,
|
|
||||||
sign_with: None,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let decoded = decode(&encoded).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(message, decoded.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn encode_empty() {
|
|
||||||
let encoded = encode(EncodeParams {
|
|
||||||
message: &[],
|
|
||||||
padding: None,
|
|
||||||
sign_with: None,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let decoded = decode(&encoded).unwrap();
|
|
||||||
|
|
||||||
assert!(decoded.message.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn encode_with_signature() {
|
|
||||||
let key_pair = Random.generate().unwrap();
|
|
||||||
let message = [1, 3, 5, 7, 9];
|
|
||||||
|
|
||||||
let encoded = encode(EncodeParams {
|
|
||||||
message: &message,
|
|
||||||
padding: None,
|
|
||||||
sign_with: Some(key_pair.secret()),
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let decoded = decode(&encoded).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(decoded.message, message);
|
|
||||||
assert_eq!(decoded.from, Some(key_pair.public().clone()));
|
|
||||||
assert!(decoded.padding.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn encode_with_padding() {
|
|
||||||
let message = [1, 3, 5, 7, 9];
|
|
||||||
let padding = [0xff; 1024 - 5];
|
|
||||||
|
|
||||||
let encoded = encode(EncodeParams {
|
|
||||||
message: &message,
|
|
||||||
padding: Some(&padding),
|
|
||||||
sign_with: None,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let decoded = decode(&encoded).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(decoded.message, message);
|
|
||||||
assert_eq!(decoded.padding, Some(&padding[..]));
|
|
||||||
assert!(decoded.from.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn encode_with_padding_and_signature() {
|
|
||||||
let key_pair = Random.generate().unwrap();
|
|
||||||
let message = [1, 3, 5, 7, 9];
|
|
||||||
let padding = [0xff; 1024 - 5];
|
|
||||||
|
|
||||||
let encoded = encode(EncodeParams {
|
|
||||||
message: &message,
|
|
||||||
padding: Some(&padding),
|
|
||||||
sign_with: Some(key_pair.secret()),
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let decoded = decode(&encoded).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(decoded.message, message);
|
|
||||||
assert_eq!(decoded.padding, Some(&padding[..]));
|
|
||||||
assert_eq!(decoded.from, Some(key_pair.public().clone()));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,310 +0,0 @@
|
|||||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
|
||||||
// This file is part of Parity Ethereum.
|
|
||||||
|
|
||||||
// Parity Ethereum 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.
|
|
||||||
|
|
||||||
// Parity Ethereum 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 Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
//! Types for Whisper RPC.
|
|
||||||
|
|
||||||
use std::{fmt, ops::Deref};
|
|
||||||
|
|
||||||
use ethereum_types::{H128, H256, H264, H32, H512, H64};
|
|
||||||
use hex::{FromHex, ToHex};
|
|
||||||
|
|
||||||
use serde::{
|
|
||||||
de::{Error, Visitor},
|
|
||||||
Deserialize, Deserializer, Serialize, Serializer,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Helper trait for generic hex bytes encoding.
|
|
||||||
pub trait HexEncodable: Sized + ::std::ops::Deref<Target = [u8]> {
|
|
||||||
fn from_bytes(bytes: Vec<u8>) -> Option<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HexEncodable for Vec<u8> {
|
|
||||||
fn from_bytes(bytes: Vec<u8>) -> Option<Self> {
|
|
||||||
Some(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_hex_for_hash {
|
|
||||||
($($t: ident)*) => {
|
|
||||||
$(
|
|
||||||
impl HexEncodable for $t {
|
|
||||||
fn from_bytes(bytes: Vec<u8>) -> Option<Self> {
|
|
||||||
if bytes.len() != $t::len() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some($t::from_slice(&bytes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_hex_for_hash!(
|
|
||||||
H32 H64 H128 H256 H264 H512
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Wrapper structure around hex-encoded data.
|
|
||||||
#[derive(Debug, PartialEq, Eq, Default, Hash, Clone)]
|
|
||||||
pub struct HexEncode<T>(pub T);
|
|
||||||
|
|
||||||
impl<T> From<T> for HexEncode<T> {
|
|
||||||
fn from(x: T) -> Self {
|
|
||||||
HexEncode(x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> HexEncode<T> {
|
|
||||||
/// Create a new wrapper from the inner value.
|
|
||||||
pub fn new(x: T) -> Self {
|
|
||||||
HexEncode(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Consume the wrapper, yielding the inner value.
|
|
||||||
pub fn into_inner(self) -> T {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Deref for HexEncode<T> {
|
|
||||||
type Target = T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &T {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hex-encoded arbitrary-byte vector.
|
|
||||||
pub type Bytes = HexEncode<Vec<u8>>;
|
|
||||||
|
|
||||||
/// 32-byte local identity
|
|
||||||
pub type Identity = HexEncode<H256>;
|
|
||||||
|
|
||||||
/// Public key for ECIES, SECP256k1
|
|
||||||
pub type Public = HexEncode<::ethkey::Public>;
|
|
||||||
|
|
||||||
/// Unvalidated private key for ECIES, SECP256k1
|
|
||||||
pub type Private = HexEncode<H256>;
|
|
||||||
|
|
||||||
/// Abridged topic is four bytes.
|
|
||||||
// only used in tests for now.
|
|
||||||
#[cfg(test)]
|
|
||||||
pub type AbridgedTopic = HexEncode<H32>;
|
|
||||||
|
|
||||||
/// 32-byte AES key.
|
|
||||||
pub type Symmetric = HexEncode<H256>;
|
|
||||||
|
|
||||||
impl<T: HexEncodable> Serialize for HexEncode<T> {
|
|
||||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
|
||||||
let data = &self.0[..];
|
|
||||||
let serialized = "0x".to_owned() + &data.to_hex();
|
|
||||||
|
|
||||||
serializer.serialize_str(serialized.as_ref())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: 'a + HexEncodable> Deserialize<'a> for HexEncode<T> {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'a>,
|
|
||||||
{
|
|
||||||
deserializer.deserialize_any(HexEncodeVisitor::<T>(::std::marker::PhantomData))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper type for decoding anything from hex.
|
|
||||||
struct HexEncodeVisitor<T>(::std::marker::PhantomData<T>);
|
|
||||||
|
|
||||||
impl<'a, T: HexEncodable> Visitor<'a> for HexEncodeVisitor<T> {
|
|
||||||
type Value = HexEncode<T>;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(formatter, "a 0x-prefixed, hex-encoded vector of bytes")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_str<E: Error>(self, value: &str) -> Result<Self::Value, E> {
|
|
||||||
let decoded = if value.len() >= 2 && &value[0..2] == "0x" && value.len() & 1 == 0 {
|
|
||||||
Ok(Vec::from_hex(&value[2..]).map_err(|_| Error::custom("invalid hex"))?)
|
|
||||||
} else {
|
|
||||||
Err(Error::custom("invalid format"))
|
|
||||||
};
|
|
||||||
|
|
||||||
decoded
|
|
||||||
.and_then(|x| T::from_bytes(x).ok_or(Error::custom("invalid format")))
|
|
||||||
.map(HexEncode)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: Error,
|
|
||||||
{
|
|
||||||
self.visit_str(value.as_ref())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Receiver of a message. Either a public key, identity (presumably symmetric),
|
|
||||||
/// or broadcast over the topics.
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub enum Receiver {
|
|
||||||
Public(Public),
|
|
||||||
Identity(Identity),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A request to post a message to the whisper network.
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct PostRequest {
|
|
||||||
/// Receiver of the message. Either a public key or
|
|
||||||
/// an identity. If the identity is symmetric, it will
|
|
||||||
/// encrypt to that identity.
|
|
||||||
///
|
|
||||||
/// If the receiver is missing, this will be a broadcast message.
|
|
||||||
pub to: Option<Receiver>,
|
|
||||||
|
|
||||||
/// Sender of the message.
|
|
||||||
///
|
|
||||||
/// If present, the payload will be signed by this
|
|
||||||
/// identity. The call will fail if the whisper node doesn't store the
|
|
||||||
/// signing key for this identity.
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub from: Option<Identity>,
|
|
||||||
|
|
||||||
/// Full topics to identify a message by.
|
|
||||||
/// At least one topic must be specified if the receiver is
|
|
||||||
/// not specified.
|
|
||||||
pub topics: Vec<Bytes>,
|
|
||||||
|
|
||||||
/// Payload of the message
|
|
||||||
pub payload: Bytes,
|
|
||||||
|
|
||||||
/// Optional padding of the message. No larger than 2^24 - 1.
|
|
||||||
pub padding: Option<Bytes>,
|
|
||||||
|
|
||||||
/// Priority of the message: how many milliseconds to spend doing PoW
|
|
||||||
pub priority: u64,
|
|
||||||
|
|
||||||
/// Time-To-Live of the message in seconds.
|
|
||||||
pub ttl: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Request for filter or subscription creation.
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct FilterRequest {
|
|
||||||
/// ID of key used for decryption.
|
|
||||||
///
|
|
||||||
/// If this identity is removed, then no further messages will be returned.
|
|
||||||
///
|
|
||||||
/// If optional, this will listen for broadcast messages.
|
|
||||||
pub decrypt_with: Option<Identity>,
|
|
||||||
|
|
||||||
/// Accept only messages signed by given public key.
|
|
||||||
pub from: Option<Public>,
|
|
||||||
|
|
||||||
/// Possible topics. Cannot be empty if the identity is `None`
|
|
||||||
pub topics: Vec<Bytes>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A message captured by a filter or subscription.
|
|
||||||
#[derive(Serialize, Clone)]
|
|
||||||
pub struct FilterItem {
|
|
||||||
/// Public key that signed this message.
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub from: Option<Public>,
|
|
||||||
|
|
||||||
/// Identity of recipient. If the filter wasn't registered with a
|
|
||||||
/// recipient, this will be `None`.
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub recipient: Option<Identity>,
|
|
||||||
|
|
||||||
/// Time to live in seconds.
|
|
||||||
pub ttl: u64,
|
|
||||||
|
|
||||||
/// Topics that matched the filter.
|
|
||||||
pub topics: Vec<Bytes>,
|
|
||||||
|
|
||||||
/// Unix timestamp of the message generation.
|
|
||||||
pub timestamp: u64,
|
|
||||||
|
|
||||||
/// Decrypted/Interpreted payload.
|
|
||||||
pub payload: Bytes,
|
|
||||||
|
|
||||||
/// Optional padding data.
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub padding: Option<Bytes>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whisper node info.
|
|
||||||
#[derive(Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct NodeInfo {
|
|
||||||
/// min PoW to be accepted into the local pool.
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
#[serde(rename = "minPow")]
|
|
||||||
pub required_pow: Option<f64>,
|
|
||||||
|
|
||||||
/// Number of messages in the pool.
|
|
||||||
pub messages: usize,
|
|
||||||
|
|
||||||
/// Memory used by messages in the pool.
|
|
||||||
pub memory: usize,
|
|
||||||
|
|
||||||
/// Target memory of the pool.
|
|
||||||
pub target_memory: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use hex::FromHex;
|
|
||||||
use serde_json;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_bytes_serialize() {
|
|
||||||
let bytes = Bytes::new(Vec::from_hex("0123456789abcdef").unwrap());
|
|
||||||
let serialized = serde_json::to_string(&bytes).unwrap();
|
|
||||||
assert_eq!(serialized, r#""0x0123456789abcdef""#);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_bytes_deserialize() {
|
|
||||||
let bytes2: Result<Bytes, serde_json::Error> = serde_json::from_str(r#""0x123""#);
|
|
||||||
let bytes3: Result<Bytes, serde_json::Error> = serde_json::from_str(r#""0xgg""#);
|
|
||||||
|
|
||||||
let bytes4: Bytes = serde_json::from_str(r#""0x""#).unwrap();
|
|
||||||
let bytes5: Bytes = serde_json::from_str(r#""0x12""#).unwrap();
|
|
||||||
let bytes6: Bytes = serde_json::from_str(r#""0x0123""#).unwrap();
|
|
||||||
|
|
||||||
assert!(bytes2.is_err());
|
|
||||||
assert!(bytes3.is_err());
|
|
||||||
assert_eq!(bytes4, Bytes::new(vec![]));
|
|
||||||
assert_eq!(bytes5, Bytes::new(vec![0x12]));
|
|
||||||
assert_eq!(bytes6, Bytes::new(vec![0x1, 0x23]));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deserialize_topic() {
|
|
||||||
let topic = AbridgedTopic::new([1, 2, 3, 15].into());
|
|
||||||
|
|
||||||
let topic1: Result<AbridgedTopic, _> = serde_json::from_str(r#""0x010203""#);
|
|
||||||
let topic2: Result<AbridgedTopic, _> = serde_json::from_str(r#""0102030F""#);
|
|
||||||
let topic3: AbridgedTopic = serde_json::from_str(r#""0x0102030F""#).unwrap();
|
|
||||||
|
|
||||||
assert!(topic1.is_err());
|
|
||||||
assert!(topic2.is_err());
|
|
||||||
assert_eq!(topic3, topic);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user