diff --git a/Cargo.lock b/Cargo.lock index 442aba829..3746434d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -224,6 +224,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "ascii-canvas" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff8eb72df928aafb99fe5d37b383f2fe25bd2a765e3e5f7c365916b6f2463a29" +dependencies = [ + "term 0.5.2", +] + [[package]] name = "asn1_der" version = "0.6.3" @@ -243,6 +252,117 @@ dependencies = [ "syn", ] +[[package]] +name = "assert-json-diff" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4259cbe96513d2f1073027a259fc2ca917feb3026a5a8d984e3628e490255cc0" +dependencies = [ + "extend", + "serde", + "serde_json", +] + +[[package]] +name = "async-channel" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59740d83946db6a5af71ae25ddf9562c2b176b2ca42cf99a455f09f4a220d6b9" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d373d78ded7d0b3fa8039375718cde0aace493f2e34fb60f51cbf567562ca801" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "once_cell", + "vec-arena", +] + +[[package]] +name = "async-global-executor" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73079b49cd26b8fd5a15f68fc7707fc78698dc2a3d61430f2a7a9430230dfa04" +dependencies = [ + "async-executor", + "async-io", + "futures-lite", + "num_cpus", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54bc4c1c7292475efb2253227dbcfad8fe1ca4c02bc62c510cc2f3da5c4704e" +dependencies = [ + "concurrent-queue", + "fastrand", + "futures-lite", + "libc", + "log 0.4.11", + "nb-connect", + "once_cell", + "parking", + "polling", + "vec-arena", + "waker-fn", + "winapi 0.3.9", +] + +[[package]] +name = "async-mutex" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-std" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e82538bc65a25dbdff70e4c5439d52f068048ab97cdea0acd73f131594caa1" +dependencies = [ + "async-global-executor", + "async-io", + "async-mutex", + "blocking", + "crossbeam-utils 0.8.0", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log 0.4.11", + "memchr", + "num_cpus", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab 0.4.2", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" + [[package]] name = "async-tls" version = "0.10.0" @@ -256,6 +376,17 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "async-trait" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b246867b8b3b6ae56035f1eb1ed557c1d8eae97f0d53696138a50fa0e3a3b8c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic" version = "0.5.0" @@ -271,6 +402,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0db678acb667b525ac40a324fc5f7d3390e29239b31c7327bb8157f5b4fff593" +[[package]] +name = "atomic-waker" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" + [[package]] name = "attohttpc" version = "0.10.1" @@ -347,6 +484,17 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "basic-cookies" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb53b6b315f924c7f113b162e53b3901c05fc9966baf84d201dfcc7432a4bb38" +dependencies = [ + "lalrpop", + "lalrpop-util", + "regex", +] + [[package]] name = "beacon_chain" version = "0.2.0" @@ -447,6 +595,21 @@ dependencies = [ "serde", ] +[[package]] +name = "bit-set" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3" + [[package]] name = "bitflags" version = "0.9.1" @@ -571,6 +734,20 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "blocking" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9" +dependencies = [ + "async-channel", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", + "once_cell", +] + [[package]] name = "bls" version = "0.2.0" @@ -731,6 +908,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "cache-padded" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" + [[package]] name = "cached_tree_hash" version = "0.1.0" @@ -818,7 +1001,7 @@ dependencies = [ "ansi_term 0.11.0", "atty", "bitflags 1.2.1", - "strsim", + "strsim 0.8.0", "textwrap", "unicode-width", "vec_map", @@ -932,6 +1115,15 @@ dependencies = [ "syn", ] +[[package]] +name = "concurrent-queue" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +dependencies = [ + "cache-padded", +] + [[package]] name = "console_error_panic_hook" version = "0.1.6" @@ -1209,6 +1401,37 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "curl" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e268162af1a5fe89917ae25ba3b0a77c8da752bdc58e7dbb4f15b91fbd33756e" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2", + "winapi 0.3.9", +] + +[[package]] +name = "curl-sys" +version = "0.4.38+curl-7.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498ecfb4f59997fd40023d62a9f1e506e768b2baeb59a1d311eb9751cdcd7e3f" +dependencies = [ + "cc", + "libc", + "libnghttp2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "winapi 0.3.9", +] + [[package]] name = "curve25519-dalek" version = "3.0.0" @@ -1301,6 +1524,18 @@ dependencies = [ "syn", ] +[[package]] +name = "diff" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" + +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + [[package]] name = "digest" version = "0.8.1" @@ -1329,6 +1564,17 @@ dependencies = [ "eth2_testnet_config", ] +[[package]] +name = "dirs" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" +dependencies = [ + "libc", + "redox_users", + "winapi 0.3.9", +] + [[package]] name = "dirs" version = "2.0.2" @@ -1430,6 +1676,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "docopt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f525a586d310c87df72ebcd98009e57f1cc030c8c268305287a476beb653969" +dependencies = [ + "lazy_static", + "regex", + "serde", + "strsim 0.9.3", +] + [[package]] name = "dtoa" version = "0.4.6" @@ -1516,6 +1774,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ena" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" +dependencies = [ + "log 0.4.11", +] + [[package]] name = "encoding_rs" version = "0.8.26" @@ -1881,6 +2148,12 @@ dependencies = [ "uint", ] +[[package]] +name = "event-listener" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" + [[package]] name = "exit-future" version = "0.2.0" @@ -1890,6 +2163,18 @@ dependencies = [ "futures 0.3.8", ] +[[package]] +name = "extend" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47da3a72ec598d9c8937a7ebca8962a5c7a1f28444e38c2b33c771ba3f55f05" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "fake-simd" version = "0.1.2" @@ -1908,6 +2193,15 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[package]] +name = "fastrand" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3" +dependencies = [ + "instant", +] + [[package]] name = "ff" version = "0.8.0" @@ -2091,6 +2385,21 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb" +[[package]] +name = "futures-lite" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6c079abfac3ab269e2927ec048dabc89d009ebfdda6b8ee86624f30c689658" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-macro" version = "0.3.8" @@ -2267,6 +2576,19 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "gloo-timers" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "group" version = "0.8.0" @@ -2564,6 +2886,33 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" +[[package]] +name = "httpmock" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1e098238acaa95e0bb3fd357317817fde35efb9cdedf74b18b24698499eea9d" +dependencies = [ + "assert-json-diff", + "async-trait", + "base64 0.13.0", + "basic-cookies", + "crossbeam-utils 0.8.0", + "difference", + "futures-util", + "hyper 0.13.9", + "isahc", + "lazy_static", + "levenshtein", + "log 0.4.11", + "puddle", + "qstring", + "regex", + "serde", + "serde_json", + "serde_regex", + "tokio 0.2.23", +] + [[package]] name = "humantime" version = "1.3.0" @@ -2816,6 +3165,31 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" +[[package]] +name = "isahc" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cf84ee8215bcaa999a24870485ef49d051cd858985a80d123e56be2d921d811" +dependencies = [ + "bytes 0.5.6", + "crossbeam-channel 0.5.0", + "crossbeam-utils 0.8.0", + "curl", + "curl-sys", + "encoding_rs", + "futures-channel", + "futures-io", + "futures-util", + "http 0.2.1", + "log 0.4.11", + "mime 0.3.16", + "once_cell", + "slab 0.4.2", + "sluice", + "tracing", + "tracing-futures", +] + [[package]] name = "itertools" version = "0.8.2" @@ -2890,6 +3264,46 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log 0.4.11", +] + +[[package]] +name = "lalrpop" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60fb56191fb8ed5311597e5750debe6779c9fdb487dbaa5ff302592897d7a2c8" +dependencies = [ + "ascii-canvas", + "atty", + "bit-set", + "diff", + "docopt", + "ena", + "itertools 0.9.0", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax", + "serde", + "serde_derive", + "sha2 0.8.2", + "string_cache", + "term 0.5.2", + "unicode-xid", +] + +[[package]] +name = "lalrpop-util" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6771161eff561647fad8bb7e745e002c304864fb8f436b52b30acda51fca4408" + [[package]] name = "language-tags" version = "0.2.2" @@ -2966,6 +3380,12 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "levenshtein" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66189c12161c65c0023ceb53e2fccc0013311bcb36a7cbd0f9c5e938b408ac96" + [[package]] name = "libc" version = "0.2.80" @@ -2996,6 +3416,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" +[[package]] +name = "libnghttp2-sys" +version = "0.1.4+1.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03624ec6df166e79e139a2310ca213283d6b3c30810c54844f307086d4488df1" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "libp2p" version = "0.30.0" @@ -3700,6 +4130,16 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nb-connect" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8123a81538e457d44b933a02faf885d3fe8408806b23fa700e8f01c6c3a98998" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "net2" version = "0.2.35" @@ -3750,6 +4190,12 @@ dependencies = [ "types", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + [[package]] name = "nix" version = "0.17.0" @@ -4015,6 +4461,12 @@ dependencies = [ "serde", ] +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + [[package]] name = "parking_lot" version = "0.9.0" @@ -4131,6 +4583,15 @@ dependencies = [ "indexmap", ] +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "0.4.27" @@ -4207,6 +4668,19 @@ dependencies = [ "web-sys", ] +[[package]] +name = "polling" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2a7bc6b2a29e632e45451c941832803a18cce6781db04de8a04696cdca8bde4" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "log 0.4.11", + "wepoll-sys", + "winapi 0.3.9", +] + [[package]] name = "poly1305" version = "0.6.1" @@ -4232,6 +4706,12 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "primitive-types" version = "0.7.3" @@ -4245,6 +4725,30 @@ dependencies = [ "uint", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check 0.9.2", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check 0.9.2", +] + [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -4381,6 +4885,24 @@ dependencies = [ "unescape", ] +[[package]] +name = "puddle" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cf84452e80b28e2b05e53964d6f5a44a57978ce19b4920be49e1a61079a24d7" +dependencies = [ + "async-std", +] + +[[package]] +name = "qstring" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" +dependencies = [ + "percent-encoding 2.1.0", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -4780,17 +5302,33 @@ dependencies = [ "types", ] +[[package]] +name = "remote_signer_consumer" +version = "0.2.0" +dependencies = [ + "rand 0.7.3", + "remote_signer_test", + "reqwest", + "serde", + "tokio 0.2.23", + "types", +] + [[package]] name = "remote_signer_test" version = "0.2.0" dependencies = [ "clap", "environment", + "hex", + "httpmock", "remote_signer_client", + "remote_signer_consumer", "reqwest", "serde", "serde_json", "tempdir", + "tokio 0.2.23", "types", ] @@ -5148,6 +5686,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" +dependencies = [ + "regex", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.6" @@ -5320,6 +5868,12 @@ dependencies = [ "validator_client", ] +[[package]] +name = "siphasher" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7" + [[package]] name = "slab" version = "0.3.0" @@ -5421,7 +5975,7 @@ dependencies = [ "atty", "chrono", "slog", - "term", + "term 0.6.1", "thread_local", ] @@ -5454,6 +6008,18 @@ dependencies = [ "types", ] +[[package]] +name = "sluice" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed13b7cb46f13a15db2c4740f087a848acc8b31af89f95844d40137451f89b1" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", +] + [[package]] name = "smallvec" version = "0.6.13" @@ -5671,12 +6237,31 @@ dependencies = [ "bytes 0.4.12", ] +[[package]] +name = "string_cache" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2940c75beb4e3bf3a494cef919a747a2cb81e52571e212bfbd185074add7208a" +dependencies = [ + "lazy_static", + "new_debug_unreachable", + "phf_shared", + "precomputed-hash", + "serde", +] + [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" + [[package]] name = "subtle" version = "1.0.0" @@ -5775,6 +6360,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "term" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" +dependencies = [ + "byteorder", + "dirs 1.0.5", + "winapi 0.3.9", +] + [[package]] name = "term" version = "0.6.1" @@ -6774,6 +7370,12 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" +[[package]] +name = "vec-arena" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d" + [[package]] name = "vec_map" version = "0.8.2" @@ -6798,6 +7400,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" version = "2.3.1" @@ -7089,6 +7697,15 @@ dependencies = [ "ws", ] +[[package]] +name = "wepoll-sys" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcb14dea929042224824779fbc82d9fab8d2e6d3cbc0ac404de8edf489e77ff" +dependencies = [ + "cc", +] + [[package]] name = "which" version = "3.1.1" diff --git a/Cargo.toml b/Cargo.toml index 3913f16d3..c4e057cb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,11 +32,12 @@ members = [ "common/lighthouse_version", "common/logging", "common/lru_cache", + "common/remote_signer_consumer", "common/slot_clock", - "common/test_random_derive", - "common/warp_utils", "common/task_executor", + "common/test_random_derive", "common/validator_dir", + "common/warp_utils", "consensus/cached_tree_hash", "consensus/int_to_bytes", diff --git a/common/remote_signer_consumer/Cargo.toml b/common/remote_signer_consumer/Cargo.toml new file mode 100644 index 000000000..1f952b6e3 --- /dev/null +++ b/common/remote_signer_consumer/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "remote_signer_consumer" +version = "0.2.0" +authors = ["Herman Junge "] +edition = "2018" + +[dev-dependencies] +rand = "0.7.3" +remote_signer_test = { path = "../../testing/remote_signer_test" } + +[dependencies] +reqwest = { version = "0.10.8", features = ["json"] } +serde = { version = "1.0.116", features = ["derive"] } +tokio = { version = "0.2.22", features = ["time"] } +types = { path = "../../consensus/types" } diff --git a/common/remote_signer_consumer/src/http_client.rs b/common/remote_signer_consumer/src/http_client.rs new file mode 100644 index 000000000..951fc5d09 --- /dev/null +++ b/common/remote_signer_consumer/src/http_client.rs @@ -0,0 +1,88 @@ +use crate::{ + Error, RemoteSignerObject, RemoteSignerRequestBody, RemoteSignerResponseBodyError, + RemoteSignerResponseBodyOK, +}; +use reqwest::StatusCode; +pub use reqwest::Url; +use types::{Domain, Fork, Hash256}; + +/// A wrapper around `reqwest::Client` which provides convenience methods +/// to interface with a BLS Remote Signer. +pub struct RemoteSignerHttpConsumer { + client: reqwest::Client, + server: Url, +} + +impl RemoteSignerHttpConsumer { + pub fn from_components(server: Url, client: reqwest::Client) -> Self { + Self { client, server } + } + + /// `POST /sign/:public-key` + /// + /// # Arguments + /// + /// * `public_key` - Goes within the url to identify the key we want to use as signer. + /// * `bls_domain` - BLS Signature domain. Supporting `BeaconProposer`, `BeaconAttester`,`Randao`. + /// * `data` - A `BeaconBlock`, `AttestationData`, or `Epoch`. + /// * `fork` - A `Fork` object containing previous and current versions. + /// * `genesis_validators_root` - A `Hash256` for domain separation and chain versioning. + /// + /// It sends through the wire a serialized `RemoteSignerRequestBody`. + pub async fn sign( + &self, + public_key: &str, + bls_domain: Domain, + data: R, + fork: Fork, + genesis_validators_root: Hash256, + ) -> Result { + if public_key.is_empty() { + return Err(Error::InvalidParameter( + "Empty parameter public_key".to_string(), + )); + } + + let mut path = self.server.clone(); + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("sign") + .push(public_key); + + let bls_domain = match bls_domain { + Domain::BeaconProposer => data.validate_object(bls_domain), + Domain::BeaconAttester => data.validate_object(bls_domain), + Domain::Randao => data.validate_object(bls_domain), + _ => Err(Error::InvalidParameter(format!( + "Unsupported BLS Domain: {:?}", + bls_domain + ))), + }?; + + let body = RemoteSignerRequestBody { + bls_domain, + data, + fork, + genesis_validators_root, + }; + + let response = self + .client + .post(path) + .json(&body) + .send() + .await + .map_err(Error::Reqwest)?; + + match response.status() { + StatusCode::OK => match response.json::().await { + Ok(resp_json) => Ok(resp_json.signature), + Err(e) => Err(Error::Reqwest(e)), + }, + _ => match response.json::().await { + Ok(resp_json) => Err(Error::ServerMessage(resp_json.error)), + Err(e) => Err(Error::Reqwest(e)), + }, + } + } +} diff --git a/common/remote_signer_consumer/src/lib.rs b/common/remote_signer_consumer/src/lib.rs new file mode 100644 index 000000000..cef64d47a --- /dev/null +++ b/common/remote_signer_consumer/src/lib.rs @@ -0,0 +1,211 @@ +//! Enables the [Lighthouse Ethereum 2.0 Client] to consume signatures from the +//! [BLS Remote Signer]. +//! +//! ## About +//! +//! The lighthouse client needs to include this crate, and implement the +//! adequate bypasses and CLI flags needed to find the remote signer and perform +//! the HTTP requests. +//! +//! As defined by the [EIP-3030] specification, this crate will take the +//! received object data and parameters, and send them to the remote signer +//! for the production of a signing root hash and signature (the latter if the +//! signer has in storage the key identified at request). +//! +//! ## Usage +//! +//! ### RemoteSignerHttpConsumer +//! +//! Just provide an `Url` and a timeout +//! +//! ``` +//! use remote_signer_consumer::RemoteSignerHttpConsumer; +//! use reqwest::{ClientBuilder, Url}; +//! use tokio::time::Duration; +//! +//! let url: Url = "http://127.0.0.1:9000".parse().unwrap(); +//! let reqwest_client = ClientBuilder::new() +//! .timeout(Duration::from_secs(2)) +//! .build() +//! .unwrap(); +//! +//! let signer = RemoteSignerHttpConsumer::from_components(url, reqwest_client); +//! +//! ``` +//! +//! ## sign API +//! +//! `POST /sign/:identifier` +//! +//! ### Arguments +//! +//! #### `public_key` +//! +//! Goes within the url to identify the key we want to use as signer. +//! +//! #### `bls_domain` +//! +//! [BLS Signature domain]. Supporting `BeaconProposer`, `BeaconAttester`, +//! `Randao`. +//! +//! #### `data` +//! +//! A `BeaconBlock`, `AttestationData`, or `Epoch`. +//! +//! #### `fork` +//! +//! A [`Fork`] object, containing previous and current versions. +//! +//! #### `genesis_validators_root` +//! +//! A [`Hash256`] for domain separation and chain versioning. +//! +//! ### Behavior +//! +//! Upon receiving and validating the parameters, the signer sends through the +//! wire a serialized `RemoteSignerRequestBody`. Receiving a `200` message with +//! the `signature` field inside a JSON payload, or an error. +//! +//! ## How it works +//! +//! The production of a _local_ signature (i.e. inside the Lighthouse client) +//! has slight variations among the kind of objects (block, attestation, +//! randao). +//! +//! To sign a message, the following procedures are needed: +//! +//! * Get the `fork_version` - From the objects `Fork` and `Epoch`. +//! * Compute the [`fork_data_root`] - From the `fork_version` and the +//! `genesis_validators_root`. +//! * Compute the [`domain`] - From the `fork_data_root` and the `bls_domain`. +//! * With the `domain`, the object (or `epoch` in the case of [`randao`]) +//! can be merkelized into its [`signing_root`] to be signed. +//! +//! In short, to obtain a signature from the remote signer, we need to produce +//! (and serialize) the following objects: +//! +//! * `bls_domain`. +//! * `data` of the object, if this is a block proposal, an attestation, or an epoch. +//! * `epoch`, obtained from the object. +//! * `fork`. +//! * `genesis_validators_root`. +//! +//! And, of course, the identifier of the secret key, the `public_key`. +//! +//! ## Future Work +//! +//! ### EIP-3030 +//! +//! Work is being done to [standardize the API of the remote signers]. +//! +//! [`domain`]: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#compute_domain +//! [`Epoch`]: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#custom-types +//! [`fork_data_root`]: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#compute_fork_data_root +//! [`Fork`]: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#fork +//! [`Hash256`]: https://docs.rs/ethereum-types/0.9.2/ethereum_types/struct.H256.html +//! [`randao`]: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#randao +//! [`signing_root`]: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#compute_signing_root +//! [BLS Remote Signer]: https://github.com/sigp/rust-bls-remote-signer +//! [BLS Signature domain]: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#domain-types +//! [EIP-3030]: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3030.md +//! [Lighthouse Ethereum 2.0 Client]: https://github.com/sigp/lighthouse +//! [standardize the API of the remote signers]: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3030.md + +mod http_client; + +pub use http_client::RemoteSignerHttpConsumer; +pub use reqwest::Url; +use serde::{Deserialize, Serialize}; +use types::{AttestationData, BeaconBlock, Domain, Epoch, EthSpec, Fork, Hash256, SignedRoot}; + +#[derive(Debug)] +pub enum Error { + /// The `reqwest` client raised an error. + Reqwest(reqwest::Error), + /// The server returned an error message where the body was able to be parsed. + ServerMessage(String), + /// The supplied URL is badly formatted. It should look something like `http://127.0.0.1:5052`. + InvalidUrl(Url), + /// The supplied parameter is invalid. + InvalidParameter(String), +} + +#[derive(Serialize)] +struct RemoteSignerRequestBody { + /// BLS Signature domain. Supporting `BeaconProposer`, `BeaconAttester`,`Randao`. + bls_domain: String, + + /// A `BeaconBlock`, `AttestationData`, or `Epoch`. + data: T, + + /// A `Fork` object containing previous and current versions. + fork: Fork, + + /// A `Hash256` for domain separation and chain versioning. + genesis_validators_root: Hash256, +} + +#[derive(Deserialize)] +struct RemoteSignerResponseBodyOK { + signature: String, +} + +#[derive(Deserialize)] +struct RemoteSignerResponseBodyError { + error: String, +} + +/// Allows the verification of the BeaconBlock and AttestationData objects +/// to be sent through the wire, against their BLS Domains. +pub trait RemoteSignerObject: SignedRoot + Serialize { + fn validate_object(&self, domain: Domain) -> Result; + fn get_epoch(&self) -> Epoch; +} + +impl RemoteSignerObject for BeaconBlock { + fn validate_object(&self, domain: Domain) -> Result { + match domain { + Domain::BeaconProposer => Ok("beacon_proposer".to_string()), + _ => Err(Error::InvalidParameter(format!( + "Domain mismatch for the BeaconBlock object. Expected BeaconProposer, got {:?}", + domain + ))), + } + } + + fn get_epoch(&self) -> Epoch { + self.epoch() + } +} + +impl RemoteSignerObject for AttestationData { + fn validate_object(&self, domain: Domain) -> Result { + match domain { + Domain::BeaconAttester => Ok("beacon_attester".to_string()), + _ => Err(Error::InvalidParameter(format!( + "Domain mismatch for the AttestationData object. Expected BeaconAttester, got {:?}", + domain + ))), + } + } + + fn get_epoch(&self) -> Epoch { + self.target.epoch + } +} + +impl RemoteSignerObject for Epoch { + fn validate_object(&self, domain: Domain) -> Result { + match domain { + Domain::Randao => Ok("randao".to_string()), + _ => Err(Error::InvalidParameter(format!( + "Domain mismatch for the Epoch object. Expected Randao, got {:?}", + domain + ))), + } + } + + fn get_epoch(&self) -> Epoch { + *self + } +} diff --git a/common/remote_signer_consumer/tests/message_preparation.rs b/common/remote_signer_consumer/tests/message_preparation.rs new file mode 100644 index 000000000..967c67997 --- /dev/null +++ b/common/remote_signer_consumer/tests/message_preparation.rs @@ -0,0 +1,181 @@ +mod message_preparation { + use remote_signer_consumer::Error; + use remote_signer_test::*; + use types::Domain; + + #[test] + fn beacon_block_and_bls_domain_mismatch() { + let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message(); + let test_client = set_up_test_consumer(&test_signer.address); + + macro_rules! test_case { + ($f: expr, $bls_domain: expr, $msg: expr) => { + match do_sign_request(&test_client, get_input_data_and_set_domain($f, $bls_domain)) + .unwrap_err() + { + Error::InvalidParameter(message) => assert_eq!(message, $msg), + e => panic!("{:?}", e), + } + }; + } + + test_case!( + get_input_data_block, + Domain::BeaconAttester, + "Domain mismatch for the BeaconBlock object. Expected BeaconProposer, got BeaconAttester" + ); + test_case!( + get_input_data_block, + Domain::Randao, + "Domain mismatch for the BeaconBlock object. Expected BeaconProposer, got Randao" + ); + test_case!( + get_input_data_attestation, + Domain::BeaconProposer, + "Domain mismatch for the AttestationData object. Expected BeaconAttester, got BeaconProposer" + ); + test_case!( + get_input_data_attestation, + Domain::Randao, + "Domain mismatch for the AttestationData object. Expected BeaconAttester, got Randao" + ); + test_case!( + get_input_data_randao, + Domain::BeaconProposer, + "Domain mismatch for the Epoch object. Expected Randao, got BeaconProposer" + ); + test_case!( + get_input_data_randao, + Domain::BeaconAttester, + "Domain mismatch for the Epoch object. Expected Randao, got BeaconAttester" + ); + + test_signer.shutdown(); + } + + #[test] + fn empty_public_key_parameter() { + let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message(); + let test_client = set_up_test_consumer(&test_signer.address); + + macro_rules! test_case { + ($f: expr, $p: expr, $msg: expr) => { + match do_sign_request(&test_client, get_input_data_and_set_public_key($f, $p)) + .unwrap_err() + { + Error::InvalidParameter(message) => assert_eq!(message, $msg), + e => panic!("{:?}", e), + } + }; + } + + test_case!(get_input_data_block, "", "Empty parameter public_key"); + test_case!(get_input_data_attestation, "", "Empty parameter public_key"); + test_case!(get_input_data_randao, "", "Empty parameter public_key"); + + test_signer.shutdown(); + } + + #[test] + fn invalid_public_key_param() { + let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message(); + let test_client = set_up_test_consumer(&test_signer.address); + + macro_rules! test_case { + ($f: expr, $p: expr, $msg: expr) => { + match do_sign_request(&test_client, get_input_data_and_set_public_key($f, $p)) + .unwrap_err() + { + Error::ServerMessage(message) => assert_eq!(message, $msg), + e => panic!("{:?}", e), + } + }; + } + + test_case!(get_input_data_block, "/", "Invalid public key: %2F"); + test_case!(get_input_data_attestation, "/", "Invalid public key: %2F"); + test_case!(get_input_data_randao, "/", "Invalid public key: %2F"); + test_case!(get_input_data_block, "//", "Invalid public key: %2F%2F"); + test_case!(get_input_data_block, "///", "Invalid public key: %2F%2F%2F"); + test_case!( + get_input_data_block, + "/?'or 1 = 1 --", + "Invalid public key: %2F%3F\'or%201%20=%201%20--" + ); + + test_signer.shutdown(); + } + + #[test] + fn unsupported_bls_domain() { + let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message(); + let test_client = set_up_test_consumer(&test_signer.address); + + let test_case = |bls_domain, msg| { + let mut test_input = get_input_data_block(0xc137); + test_input.bls_domain = bls_domain; + let signature = do_sign_request(&test_client, test_input); + + match signature.unwrap_err() { + Error::InvalidParameter(message) => assert_eq!(message, msg), + e => panic!("{:?}", e), + } + }; + + test_case(Domain::Deposit, "Unsupported BLS Domain: Deposit"); + test_case( + Domain::VoluntaryExit, + "Unsupported BLS Domain: VoluntaryExit", + ); + test_case( + Domain::SelectionProof, + "Unsupported BLS Domain: SelectionProof", + ); + test_case( + Domain::AggregateAndProof, + "Unsupported BLS Domain: AggregateAndProof", + ); + + test_signer.shutdown(); + } + + #[test] + fn invalid_public_key_param_additional_path_segments() { + let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message(); + let test_client = set_up_test_consumer(&test_signer.address); + + macro_rules! test_case { + ($f: expr, $p: expr, $msg: expr) => { + match do_sign_request(&test_client, get_input_data_and_set_public_key($f, $p)) + .unwrap_err() + { + Error::ServerMessage(message) => assert_eq!(message, $msg), + e => panic!("{:?}", e), + } + }; + } + + test_case!( + get_input_data_block, + "this/receipt", + "Invalid public key: this%2Freceipt" + ); + test_case!( + get_input_data_attestation, + "/this/receipt/please", + "Invalid public key: %2Fthis%2Freceipt%2Fplease" + ); + test_case!( + get_input_data_randao, + "this/receipt/please?", + "Invalid public key: this%2Freceipt%2Fplease%3F" + ); + test_case!( + get_input_data_block, + &format!("{}/valid/pk", PUBLIC_KEY_1), + format!("Invalid public key: {}%2Fvalid%2Fpk", PUBLIC_KEY_1) + ); + + test_signer.shutdown(); + } +} diff --git a/common/remote_signer_consumer/tests/mock.rs b/common/remote_signer_consumer/tests/mock.rs new file mode 100644 index 000000000..aa1d20ea6 --- /dev/null +++ b/common/remote_signer_consumer/tests/mock.rs @@ -0,0 +1,168 @@ +mod mock { + use remote_signer_consumer::Error; + use remote_signer_test::*; + + #[test] + fn timeout() { + let mock_server = + set_up_mock_server_with_timeout(200, "{\"signature\":\"irrelevant_value\"}", 2); + let test_client = set_up_test_consumer_with_timeout(&mock_server.url(""), 1); + let test_input = get_input_data_block(0xc137); + + let r = do_sign_request(&test_client, test_input).unwrap_err(); + + match r { + Error::Reqwest(e) => { + let error_msg = e.to_string(); + assert!(error_msg.contains("error sending request for url (http://127.0.0.1:")); + assert!(error_msg.contains("/sign/")); + assert!(error_msg.contains(PUBLIC_KEY_1)); + assert!(error_msg.contains("): operation timed out")); + } + e => panic!("{:?}", e), + } + } + + #[test] + fn no_json_in_ok_response() { + let mock_server = set_up_mock_server(200, "NO JSON"); + let test_client = set_up_test_consumer(&mock_server.url("")); + let test_input = get_input_data_block(0xc137); + + let r = do_sign_request(&test_client, test_input).unwrap_err(); + + match r { + Error::Reqwest(e) => { + let error_msg = e.to_string(); + assert_eq!( + error_msg, + "error decoding response body: expected value at line 1 column 1" + ); + } + e => panic!("{:?}", e), + } + } + + #[test] + fn missing_signature_in_ok_json() { + let mock_server = set_up_mock_server(200, "{\"foo\":\"bar\"}"); + let test_client = set_up_test_consumer(&mock_server.url("")); + let test_input = get_input_data_block(0xc137); + + let r = do_sign_request(&test_client, test_input).unwrap_err(); + + match r { + Error::Reqwest(e) => { + let error_msg = e.to_string(); + assert_eq!( + error_msg, + "error decoding response body: missing field `signature` at line 1 column 13" + ); + } + e => panic!("{:?}", e), + } + } + + #[test] + fn empty_signature_in_ok_json() { + let mock_server = set_up_mock_server(200, "{\"signature\":\"\"}"); + let test_client = set_up_test_consumer(&mock_server.url("")); + let test_input = get_input_data_block(0xc137); + + let r = do_sign_request(&test_client, test_input).unwrap(); + + assert_eq!(r, ""); + } + + #[test] + fn extra_fields_in_ok_json() { + let mock_server = set_up_mock_server( + 200, + &format!( + "{{\"signature\":\"{}\", \"foo\":\"bar\", \"red\":\"green\"}}", + EXPECTED_SIGNATURE_1 + ), + ); + let test_client = set_up_test_consumer(&mock_server.url("")); + let test_input = get_input_data_block(0xc137); + + let r = do_sign_request(&test_client, test_input).unwrap(); + + assert_eq!(r, EXPECTED_SIGNATURE_1); + } + + #[test] + fn no_json_in_error_response() { + let mock_server = set_up_mock_server(500, "NO JSON"); + let test_client = set_up_test_consumer(&mock_server.url("")); + let test_input = get_input_data_block(0xc137); + + let r = do_sign_request(&test_client, test_input).unwrap_err(); + + match r { + Error::Reqwest(e) => { + let error_msg = e.to_string(); + assert_eq!( + error_msg, + "error decoding response body: expected value at line 1 column 1" + ); + } + e => panic!("{:?}", e), + } + } + + #[test] + fn missing_error_field_in_error_json() { + let mock_server = set_up_mock_server(500, "{\"foo\":\"bar\"}"); + let test_client = set_up_test_consumer(&mock_server.url("")); + let test_input = get_input_data_block(0xc137); + + let r = do_sign_request(&test_client, test_input).unwrap_err(); + + match r { + Error::Reqwest(e) => { + let error_msg = e.to_string(); + assert_eq!( + error_msg, + "error decoding response body: missing field `error` at line 1 column 13" + ); + } + e => panic!("{:?}", e), + } + } + + #[test] + fn empty_error_field_in_error_json() { + let mock_server = set_up_mock_server(500, "{\"error\":\"\"}"); + let test_client = set_up_test_consumer(&mock_server.url("")); + let test_input = get_input_data_block(0xc137); + + let r = do_sign_request(&test_client, test_input).unwrap_err(); + + match r { + Error::ServerMessage(msg) => { + assert_eq!(msg, ""); + } + e => panic!("{:?}", e), + } + } + + #[test] + fn extra_fields_in_error_json() { + let mock_server = set_up_mock_server( + 500, + "{\"error\":\"some_error_msg\", \"foo\":\"bar\", \"red\":\"green\"}", + ); + let test_client = set_up_test_consumer(&mock_server.url("")); + let test_input = get_input_data_block(0xc137); + + let r = do_sign_request(&test_client, test_input).unwrap_err(); + + match r { + Error::ServerMessage(msg) => { + assert_eq!(msg, "some_error_msg"); + } + e => panic!("{:?}", e), + } + } +} diff --git a/common/remote_signer_consumer/tests/post.rs b/common/remote_signer_consumer/tests/post.rs new file mode 100644 index 000000000..32a4e1f22 --- /dev/null +++ b/common/remote_signer_consumer/tests/post.rs @@ -0,0 +1,225 @@ +mod post { + use remote_signer_consumer::{Error, RemoteSignerHttpConsumer}; + use remote_signer_test::*; + use reqwest::{ClientBuilder, Url}; + use tokio::time::Duration; + + #[test] + fn server_unavailable() { + let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message(); + let test_client = set_up_test_consumer(&test_signer.address); + + test_signer.shutdown(); + + let test_input = get_input_data_block(0xc137); + let signature = do_sign_request(&test_client, test_input); + + match signature.unwrap_err() { + Error::Reqwest(e) => { + let error_msg = e.to_string(); + assert!(error_msg.contains("error sending request for url")); + assert!(error_msg.contains(PUBLIC_KEY_1)); + assert!(error_msg.contains("error trying to connect")); + assert!(error_msg.contains("tcp connect error")); + assert!(error_msg.contains("Connection refused")); + } + e => panic!("{:?}", e), + } + } + + #[test] + fn server_error() { + let (test_signer, tmp_dir) = set_up_api_test_signer_to_sign_message(); + set_permissions(tmp_dir.path(), 0o40311); + set_permissions(&tmp_dir.path().join(PUBLIC_KEY_1), 0o40311); + + let test_client = set_up_test_consumer(&test_signer.address); + let test_input = get_input_data_block(0xc137); + let signature = do_sign_request(&test_client, test_input); + + set_permissions(tmp_dir.path(), 0o40755); + set_permissions(&tmp_dir.path().join(PUBLIC_KEY_1), 0o40755); + + match signature.unwrap_err() { + Error::ServerMessage(message) => assert_eq!(message, "Storage error: PermissionDenied"), + e => panic!("{:?}", e), + } + + test_signer.shutdown(); + } + + #[test] + fn invalid_url() { + let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message(); + + let run_testcase = |u: &str| -> Result { + let url: Url = u.parse().map_err(|e| format!("[ParseError] {:?}", e))?; + + let reqwest_client = ClientBuilder::new() + .timeout(Duration::from_secs(12)) + .build() + .unwrap(); + + let test_client = RemoteSignerHttpConsumer::from_components(url, reqwest_client); + + let test_input = get_input_data_block(0xc137); + let signature = do_sign_request(&test_client, test_input); + + signature.map_err(|e| match e { + Error::InvalidUrl(message) => format!("[InvalidUrl] {:?}", message), + Error::Reqwest(re) => { + if re.is_builder() { + format!("[Reqwest - Builder] {:?}", re.url().unwrap()) + } else if re.is_request() { + format!("[Reqwest - Request] {:?}", re.url().unwrap()) + } else { + format!("[Reqwest] {:?}", re) + } + } + _ => format!("{:?}", e), + }) + }; + + let testcase = |u: &str, msg: &str| assert_eq!(run_testcase(u).unwrap_err(), msg); + + // url::parser::ParseError. + // These cases don't even make it to the step of building a RemoteSignerHttpConsumer. + testcase("", "[ParseError] RelativeUrlWithoutBase"); + testcase("/4/8/15/16/23/42", "[ParseError] RelativeUrlWithoutBase"); + testcase("localhost", "[ParseError] RelativeUrlWithoutBase"); + testcase(":", "[ParseError] RelativeUrlWithoutBase"); + testcase("0.0:0", "[ParseError] RelativeUrlWithoutBase"); + testcase(":aa", "[ParseError] RelativeUrlWithoutBase"); + testcase("0:", "[ParseError] RelativeUrlWithoutBase"); + testcase("ftp://", "[ParseError] EmptyHost"); + testcase("http://", "[ParseError] EmptyHost"); + testcase("http://127.0.0.1:abcd", "[ParseError] InvalidPort"); + testcase("http://280.0.0.1", "[ParseError] InvalidIpv4Address"); + + // `Error::InvalidUrl`. + // The RemoteSignerHttpConsumer is created, but fails at `path_segments_mut()`. + testcase("localhost:abcd", "[InvalidUrl] Url { scheme: \"localhost\", host: None, port: None, path: \"abcd\", query: None, fragment: None }"); + testcase("localhost:", "[InvalidUrl] Url { scheme: \"localhost\", host: None, port: None, path: \"\", query: None, fragment: None }"); + + // `Reqwest::Error` of the `Builder` kind. + // POST is not made. + testcase( + "unix:/run/foo.socket", + &format!( + "[Reqwest - Builder] Url {{ scheme: \"unix\", host: None, port: None, path: \"/run/foo.socket/sign/{}\", query: None, fragment: None }}", + PUBLIC_KEY_1 + ), + ); + // `Reqwest::Error` of the `Request` kind. + testcase( + "http://127.0.0.1:0", + &format!( + "[Reqwest - Request] Url {{ scheme: \"http\", host: Some(Ipv4(127.0.0.1)), port: Some(0), path: \"/sign/{}\", query: None, fragment: None }}", + PUBLIC_KEY_1 + ), + ); + + test_signer.shutdown(); + } + + #[test] + fn wrong_url() { + let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message(); + + let run_testcase = |u: &str| -> Result { + let url: Url = u.parse().unwrap(); + + let reqwest_client = ClientBuilder::new() + .timeout(Duration::from_secs(12)) + .build() + .unwrap(); + + let test_client = RemoteSignerHttpConsumer::from_components(url, reqwest_client); + + let test_input = get_input_data_block(0xc137); + let signature = do_sign_request(&test_client, test_input); + + signature.map_err(|e| format!("{:?}", e)) + }; + + let testcase = |u: &str, msgs: Vec<&str>| { + let r = run_testcase(u).unwrap_err(); + + for msg in msgs.iter() { + assert!(r.contains(msg), format!("{:?} should contain {:?}", r, msg)); + } + }; + + testcase( + "http://error-dns", + vec![ + "reqwest::Error", + "kind: Request", + &format!("/sign/{}", PUBLIC_KEY_1), + "hyper::Error(Connect, ConnectError", + "dns error", + "failed to lookup address information", + ], + ); + + test_signer.shutdown(); + } + + #[test] + fn wrong_public_key() { + let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message(); + let test_client = set_up_test_consumer(&test_signer.address); + + let mut test_input = get_input_data_block(0xc137); + test_input.public_key = ABSENT_PUBLIC_KEY.to_string(); + + let signature = do_sign_request(&test_client, test_input); + + match signature.unwrap_err() { + Error::ServerMessage(msg) => { + assert_eq!(msg, format!("Key not found: {}", ABSENT_PUBLIC_KEY)) + } + e => panic!("{:?}", e), + } + } + + #[test] + fn invalid_secret_key() { + let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message(); + let test_client = set_up_test_consumer(&test_signer.address); + + let mut test_input = get_input_data_block(0xc137); + test_input.public_key = PUBLIC_KEY_FOR_INVALID_SECRET_KEY.to_string(); + + let signature = do_sign_request(&test_client, test_input); + + match signature.unwrap_err() { + Error::ServerMessage(msg) => assert_eq!( + msg, + format!( + "Invalid secret key: public_key: {}; Invalid hex character: W at index 0", + PUBLIC_KEY_FOR_INVALID_SECRET_KEY + ) + ), + e => panic!("{:?}", e), + } + } + + #[test] + fn key_mismatch() { + let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message(); + let test_client = set_up_test_consumer(&test_signer.address); + + let mut test_input = get_input_data_block(0xc137); + test_input.public_key = MISMATCHED_PUBLIC_KEY.to_string(); + + let signature = do_sign_request(&test_client, test_input); + + match signature.unwrap_err() { + Error::ServerMessage(msg) => { + assert_eq!(msg, format!("Key mismatch: {}", MISMATCHED_PUBLIC_KEY)) + } + e => panic!("{:?}", e), + } + } +} diff --git a/common/remote_signer_consumer/tests/sign_attestation.rs b/common/remote_signer_consumer/tests/sign_attestation.rs new file mode 100644 index 000000000..49e1bd9c3 --- /dev/null +++ b/common/remote_signer_consumer/tests/sign_attestation.rs @@ -0,0 +1,49 @@ +mod sign_attestation { + use rand::Rng; + use remote_signer_test::*; + + #[test] + fn sanity_check_deterministic() { + let test_input_local = get_input_local_signer_attestation(0xc137); + let local_signature = test_input_local.sign(); + + let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message(); + let test_client = set_up_test_consumer(&test_signer.address); + let test_input = get_input_data_attestation(0xc137); + + let remote_signature = do_sign_request(&test_client, test_input); + + assert_eq!(local_signature, remote_signature.unwrap()); + assert_eq!(local_signature, HAPPY_PATH_ATT_SIGNATURE_C137); + } + + #[test] + fn sanity_check_random() { + let mut rng = rand::thread_rng(); + let seed = rng.gen::() / 1024; + + let test_input_local = get_input_local_signer_attestation(seed); + let local_signature = test_input_local.sign(); + + let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message(); + let test_client = set_up_test_consumer(&test_signer.address); + let test_input = get_input_data_attestation(seed); + + let remote_signature = do_sign_request(&test_client, test_input); + + assert_eq!(local_signature, remote_signature.unwrap()); + } + + #[test] + fn happy_path() { + let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message(); + let test_client = set_up_test_consumer(&test_signer.address); + let test_input = get_input_data_attestation(0xc137); + + let signature = do_sign_request(&test_client, test_input); + + assert_eq!(signature.unwrap(), HAPPY_PATH_ATT_SIGNATURE_C137); + + test_signer.shutdown(); + } +} diff --git a/common/remote_signer_consumer/tests/sign_block.rs b/common/remote_signer_consumer/tests/sign_block.rs new file mode 100644 index 000000000..bec0cfb42 --- /dev/null +++ b/common/remote_signer_consumer/tests/sign_block.rs @@ -0,0 +1,49 @@ +mod sign_block { + use rand::Rng; + use remote_signer_test::*; + + #[test] + fn sanity_check_deterministic() { + let test_input_local = get_input_local_signer_block(0xc137); + let local_signature = test_input_local.sign(); + + let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message(); + let test_client = set_up_test_consumer(&test_signer.address); + let test_input = get_input_data_block(0xc137); + + let remote_signature = do_sign_request(&test_client, test_input); + + assert_eq!(local_signature, remote_signature.unwrap()); + assert_eq!(local_signature, HAPPY_PATH_BLOCK_SIGNATURE_C137); + } + + #[test] + fn sanity_check_random() { + let mut rng = rand::thread_rng(); + let seed = rng.gen::() / 1024; + + let test_input_local = get_input_local_signer_block(seed); + let local_signature = test_input_local.sign(); + + let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message(); + let test_client = set_up_test_consumer(&test_signer.address); + let test_input = get_input_data_block(seed); + + let remote_signature = do_sign_request(&test_client, test_input); + + assert_eq!(local_signature, remote_signature.unwrap()); + } + + #[test] + fn happy_path() { + let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message(); + let test_client = set_up_test_consumer(&test_signer.address); + let test_input = get_input_data_block(0xc137); + + let signature = do_sign_request(&test_client, test_input); + + assert_eq!(signature.unwrap(), HAPPY_PATH_BLOCK_SIGNATURE_C137); + + test_signer.shutdown(); + } +} diff --git a/common/remote_signer_consumer/tests/sign_randao.rs b/common/remote_signer_consumer/tests/sign_randao.rs new file mode 100644 index 000000000..62baf1edc --- /dev/null +++ b/common/remote_signer_consumer/tests/sign_randao.rs @@ -0,0 +1,50 @@ +mod sign_randao { + use rand::Rng; + use remote_signer_test::*; + + #[test] + fn sanity_check_deterministic() { + let test_input_local = get_input_local_signer_randao(0xc137); + let local_signature = test_input_local.sign(); + + let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message(); + let test_client = set_up_test_consumer(&test_signer.address); + + let test_input = get_input_data_randao(0xc137); + + let remote_signature = do_sign_request(&test_client, test_input); + + assert_eq!(local_signature, remote_signature.unwrap()); + assert_eq!(local_signature, HAPPY_PATH_RANDAO_SIGNATURE_C137); + } + + #[test] + fn sanity_check_random() { + let mut rng = rand::thread_rng(); + let seed = rng.gen::(); + + let test_input_local = get_input_local_signer_randao(seed); + let local_signature = test_input_local.sign(); + + let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message(); + let test_client = set_up_test_consumer(&test_signer.address); + + let test_input = get_input_data_randao(seed); + + let remote_signature = do_sign_request(&test_client, test_input); + + assert_eq!(local_signature, remote_signature.unwrap()); + } + + #[test] + fn happy_path() { + let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message(); + let test_client = set_up_test_consumer(&test_signer.address); + + let test_input = get_input_data_randao(0xc137); + + let remote_signature = do_sign_request(&test_client, test_input); + + assert_eq!(remote_signature.unwrap(), HAPPY_PATH_RANDAO_SIGNATURE_C137); + } +} diff --git a/testing/remote_signer_test/Cargo.toml b/testing/remote_signer_test/Cargo.toml index 70afa28b4..789186012 100644 --- a/testing/remote_signer_test/Cargo.toml +++ b/testing/remote_signer_test/Cargo.toml @@ -6,10 +6,14 @@ edition = "2018" [dependencies] clap = "2.33.3" -client = { path = "../../remote_signer/client", package = "remote_signer_client" } environment = { path = "../../lighthouse/environment" } +hex = "0.4.2" +httpmock = "0.5.1" +remote_signer_client = { path = "../../remote_signer/client" } +remote_signer_consumer = { path = "../../common/remote_signer_consumer" } reqwest = { version = "0.10.8", features = ["blocking", "json"] } serde = { version = "1.0.116", features = ["derive"] } serde_json = "1.0.58" tempdir = "0.3.7" +tokio = { version = "0.2.22", features = ["time"] } types = { path = "../../consensus/types" } diff --git a/testing/remote_signer_test/src/api_test_signer.rs b/testing/remote_signer_test/src/api_test_signer.rs new file mode 100644 index 000000000..a1a924daf --- /dev/null +++ b/testing/remote_signer_test/src/api_test_signer.rs @@ -0,0 +1,138 @@ +use crate::*; +use clap::{App, Arg, ArgMatches}; +use environment::{Environment, EnvironmentBuilder}; +pub use local_signer_test_data::*; +use remote_signer_client::Client; +use serde_json::Value; +use std::collections::HashMap; +use tempdir::TempDir; +use types::EthSpec; + +pub struct ApiTestSigner { + pub address: String, + environment: Environment, +} + +pub struct ApiTestResponse { + pub status: u16, + pub json: Value, +} + +impl ApiTestSigner { + pub fn new(arg_vec: Vec<&str>) -> Self { + let matches = set_matches(arg_vec); + let mut environment = get_environment(false); + let runtime_context = environment.core_context(); + + let client = environment + .runtime() + .block_on(Client::new(runtime_context, &matches)) + .map_err(|e| format!("Failed to init Rest API: {}", e)) + .unwrap(); + + let address = get_address(&client); + + Self { + address, + environment, + } + } + + pub fn shutdown(mut self) { + self.environment.fire_signal() + } +} + +pub fn set_matches(arg_vec: Vec<&str>) -> ArgMatches<'static> { + let matches = App::new("BLS_Remote_Signer") + .arg( + Arg::with_name("storage-raw-dir") + .long("storage-raw-dir") + .value_name("DIR"), + ) + .arg( + Arg::with_name("port") + .long("port") + .value_name("PORT") + .default_value("9000") + .takes_value(true), + ); + + matches.get_matches_from(arg_vec) +} + +pub fn get_environment(is_log_active: bool) -> Environment { + let environment_builder = EnvironmentBuilder::mainnet(); + + let builder = if is_log_active { + environment_builder.async_logger("info", None).unwrap() + } else { + environment_builder.null_logger().unwrap() + }; + + builder + .multi_threaded_tokio_runtime() + .unwrap() + .build() + .unwrap() +} + +pub fn set_up_api_test_signer_raw_dir() -> (ApiTestSigner, TempDir) { + let tmp_dir = TempDir::new("bls-remote-signer-test").unwrap(); + let arg_vec = vec![ + "this_test", + "--port", + "0", + "--storage-raw-dir", + tmp_dir.path().to_str().unwrap(), + ]; + let test_signer = ApiTestSigner::new(arg_vec); + + (test_signer, tmp_dir) +} + +pub fn set_up_api_test_signer_to_sign_message() -> (ApiTestSigner, TempDir) { + let (test_signer, tmp_dir) = set_up_api_test_signer_raw_dir(); + add_sub_dirs(&tmp_dir); + add_key_files(&tmp_dir); + add_non_key_files(&tmp_dir); + add_mismatched_key_file(&tmp_dir); + add_invalid_secret_key_file(&tmp_dir); + + (test_signer, tmp_dir) +} + +pub fn http_get(url: &str) -> ApiTestResponse { + let response = reqwest::blocking::get(url).unwrap(); + + ApiTestResponse { + status: response.status().as_u16(), + json: serde_json::from_str(&response.text().unwrap()).unwrap(), + } +} + +pub fn http_post(url: &str, hashmap: HashMap<&str, &str>) -> ApiTestResponse { + let response = reqwest::blocking::Client::new() + .post(url) + .json(&hashmap) + .send() + .unwrap(); + + ApiTestResponse { + status: response.status().as_u16(), + json: serde_json::from_str(&response.text().unwrap()).unwrap(), + } +} + +pub fn http_post_custom_body(url: &str, body: &str) -> ApiTestResponse { + let response = reqwest::blocking::Client::new() + .post(url) + .body(body.to_string()) + .send() + .unwrap(); + + ApiTestResponse { + status: response.status().as_u16(), + json: serde_json::from_str(&response.text().unwrap()).unwrap(), + } +} diff --git a/testing/remote_signer_test/src/consumer.rs b/testing/remote_signer_test/src/consumer.rs new file mode 100644 index 000000000..c072f6d5a --- /dev/null +++ b/testing/remote_signer_test/src/consumer.rs @@ -0,0 +1,148 @@ +use crate::*; +use remote_signer_client::api_response::SignatureApiResponse; +use remote_signer_consumer::{Error, RemoteSignerHttpConsumer, RemoteSignerObject, Url}; +use reqwest::ClientBuilder; +use serde::Serialize; +use tokio::runtime::Builder; +use tokio::time::Duration; +use types::{AttestationData, BeaconBlock, Epoch, EthSpec, Fork, Hash256}; + +pub fn set_up_test_consumer(test_signer_address: &str) -> RemoteSignerHttpConsumer { + set_up_test_consumer_with_timeout(test_signer_address, 12) +} + +pub fn set_up_test_consumer_with_timeout( + test_signer_address: &str, + timeout: u64, +) -> RemoteSignerHttpConsumer { + let url: Url = test_signer_address.parse().unwrap(); + let reqwest_client = ClientBuilder::new() + .timeout(Duration::from_secs(timeout)) + .build() + .unwrap(); + + RemoteSignerHttpConsumer::from_components(url, reqwest_client) +} + +pub fn do_sign_request( + test_client: &RemoteSignerHttpConsumer, + test_input: RemoteSignerTestData, +) -> Result { + let mut runtime = Builder::new() + .basic_scheduler() + .enable_all() + .build() + .unwrap(); + + runtime.block_on(test_client.sign( + &test_input.public_key, + test_input.bls_domain, + test_input.data, + test_input.fork, + test_input.genesis_validators_root, + )) +} + +#[derive(Serialize)] +pub struct BlockRequestBody { + bls_domain: String, + data: BeaconBlock, + fork: Fork, + genesis_validators_root: Hash256, +} + +pub fn get_test_block_body(seed: u64) -> String { + let block: BeaconBlock = get_block(seed); + let epoch = block.epoch(); + + let fork = Fork { + previous_version: [1; 4], + current_version: [2; 4], + epoch, + }; + + let genesis_validators_root = Hash256::from_low_u64_be(seed); + + let block_request_body = BlockRequestBody { + bls_domain: "beacon_proposer".to_string(), + data: block, + fork, + genesis_validators_root, + }; + + serde_json::to_string(&block_request_body).unwrap() +} + +#[derive(Serialize)] +pub struct AttestationRequestBody { + bls_domain: String, + data: AttestationData, + fork: Fork, + genesis_validators_root: Hash256, +} + +pub fn get_test_attestation_body(seed: u64) -> String { + let attestation = get_attestation::(seed); + let epoch = attestation.target.epoch; + + let fork = Fork { + previous_version: [1; 4], + current_version: [2; 4], + epoch, + }; + + let genesis_validators_root = Hash256::from_low_u64_be(seed); + + let attestation_request_body = AttestationRequestBody { + bls_domain: "beacon_attester".to_string(), + data: attestation, + fork, + genesis_validators_root, + }; + + serde_json::to_string(&attestation_request_body).unwrap() +} + +#[derive(Serialize)] +pub struct RandaoRequestBody { + bls_domain: String, + data: Epoch, + fork: Fork, + genesis_validators_root: Hash256, +} + +pub fn get_test_randao_body(seed: u64) -> String { + let epoch = Epoch::new(seed); + + let fork = Fork { + previous_version: [1; 4], + current_version: [2; 4], + epoch, + }; + + let genesis_validators_root = Hash256::from_low_u64_be(seed); + + let randao_request_body = RandaoRequestBody { + bls_domain: "randao".to_string(), + data: epoch, + fork, + genesis_validators_root, + }; + + serde_json::to_string(&randao_request_body).unwrap() +} + +pub fn assert_sign_ok(resp: ApiTestResponse, expected_signature: &str) { + assert_eq!(resp.status, 200); + assert_eq!( + serde_json::from_value::(resp.json) + .unwrap() + .signature, + expected_signature + ); +} + +pub fn assert_sign_error(resp: ApiTestResponse, http_status: u16, error_msg: &str) { + assert_eq!(resp.status, http_status); + assert_eq!(resp.json["error"].as_str().unwrap(), error_msg); +} diff --git a/testing/remote_signer_test/src/lib.rs b/testing/remote_signer_test/src/lib.rs index e141d1589..c0daeaa92 100644 --- a/testing/remote_signer_test/src/lib.rs +++ b/testing/remote_signer_test/src/lib.rs @@ -1,320 +1,18 @@ +mod api_test_signer; mod constants; -mod objects; +mod consumer; +mod local_signer_test_data; +mod mock; +mod remote_signer_test_data; +mod utils; -use clap::{App, Arg, ArgMatches}; -use client::api_response::SignatureApiResponse; -use client::Client; +pub use api_test_signer::*; pub use constants::*; -use environment::{Environment, EnvironmentBuilder}; -pub use objects::*; -use serde::Serialize; -use serde_json::Value; -use std::collections::HashMap; -use std::fs; -use std::fs::{create_dir, File}; -use std::io::Write; -use std::net::IpAddr::{V4, V6}; -use std::os::unix::fs::PermissionsExt; -use std::path::Path; -use tempdir::TempDir; -use types::{AttestationData, BeaconBlock, Epoch, EthSpec, Fork, Hash256, MainnetEthSpec}; +pub use consumer::*; +pub use local_signer_test_data::*; +pub use mock::*; +pub use remote_signer_test_data::*; +use types::MainnetEthSpec; +pub use utils::*; pub type E = MainnetEthSpec; - -pub struct ApiTestSigner { - pub address: String, - environment: Environment, -} - -pub struct ApiTestResponse { - pub status: u16, - pub json: Value, -} - -impl ApiTestSigner { - pub fn new(arg_vec: Vec<&str>) -> Self { - let matches = set_matches(arg_vec); - let mut environment = get_environment(false); - let runtime_context = environment.core_context(); - - let client = environment - .runtime() - .block_on(Client::new(runtime_context, &matches)) - .map_err(|e| format!("Failed to init Rest API: {}", e)) - .unwrap(); - - let address = get_address(&client); - - Self { - address, - environment, - } - } - - pub fn shutdown(mut self) { - self.environment.fire_signal() - } -} - -pub fn set_matches(arg_vec: Vec<&str>) -> ArgMatches<'static> { - let matches = App::new("BLS_Remote_Signer") - .arg( - Arg::with_name("storage-raw-dir") - .long("storage-raw-dir") - .value_name("DIR"), - ) - .arg( - Arg::with_name("port") - .long("port") - .value_name("PORT") - .default_value("9000") - .takes_value(true), - ); - - matches.get_matches_from(arg_vec) -} - -pub fn get_environment(is_log_active: bool) -> Environment { - let environment_builder = EnvironmentBuilder::mainnet(); - - let builder = if is_log_active { - environment_builder.async_logger("info", None).unwrap() - } else { - environment_builder.null_logger().unwrap() - }; - - builder - .multi_threaded_tokio_runtime() - .unwrap() - .build() - .unwrap() -} - -pub fn set_up_api_test_signer_raw_dir() -> (ApiTestSigner, TempDir) { - let tmp_dir = TempDir::new("bls-remote-signer-test").unwrap(); - let arg_vec = vec![ - "this_test", - "--port", - "0", - "--storage-raw-dir", - tmp_dir.path().to_str().unwrap(), - ]; - let test_signer = ApiTestSigner::new(arg_vec); - - (test_signer, tmp_dir) -} - -pub fn set_up_api_test_signer_to_sign_message() -> (ApiTestSigner, TempDir) { - let (test_signer, tmp_dir) = set_up_api_test_signer_raw_dir(); - add_sub_dirs(&tmp_dir); - add_key_files(&tmp_dir); - add_non_key_files(&tmp_dir); - add_mismatched_key_file(&tmp_dir); - add_invalid_secret_key_file(&tmp_dir); - - (test_signer, tmp_dir) -} - -pub fn get_address(client: &Client) -> String { - let listening_address = client.get_listening_address(); - let ip = match listening_address.ip() { - V4(ip) => ip.to_string(), - V6(ip) => ip.to_string(), - }; - - format!("http://{}:{}", ip, listening_address.port()) -} - -pub fn set_permissions(path: &Path, perm_octal: u32) { - let metadata = fs::metadata(path).unwrap(); - let mut permissions = metadata.permissions(); - permissions.set_mode(perm_octal); - fs::set_permissions(path, permissions).unwrap(); -} - -pub fn add_key_files(tmp_dir: &TempDir) { - let pairs = vec![ - (PUBLIC_KEY_1, SECRET_KEY_1), - (PUBLIC_KEY_2, SECRET_KEY_2), - (PUBLIC_KEY_3, SECRET_KEY_3), - ]; - - add_files(tmp_dir, pairs); -} - -pub fn add_mismatched_key_file(tmp_dir: &TempDir) { - let pairs = vec![(MISMATCHED_PUBLIC_KEY, SECRET_KEY_1)]; - - add_files(tmp_dir, pairs); -} - -pub fn add_invalid_secret_key_file(tmp_dir: &TempDir) { - let pairs = vec![(PUBLIC_KEY_FOR_INVALID_SECRET_KEY, INVALID_SECRET_KEY)]; - - add_files(tmp_dir, pairs); -} - -pub fn add_non_key_files(tmp_dir: &TempDir) { - let pairs = vec![ - (SILLY_FILE_NAME_1, SILLY_CONTENT_1), - (SILLY_FILE_NAME_2, SILLY_CONTENT_2), - (SILLY_FILE_NAME_3, SILLY_CONTENT_3), - ]; - - add_files(tmp_dir, pairs); -} - -fn add_files(tmp_dir: &TempDir, pairs: Vec<(&str, &str)>) { - for pair in pairs { - let file_path = tmp_dir.path().join(pair.0); - let mut tmp_file = File::create(file_path).unwrap(); - writeln!(tmp_file, "{}", pair.1).unwrap(); - } -} - -pub fn add_sub_dirs(tmp_dir: &TempDir) { - let random_sub_dir_path = tmp_dir.path().join("random_sub_dir_name"); - create_dir(random_sub_dir_path).unwrap(); - - let another_sub_dir_path = tmp_dir.path().join(SUB_DIR_NAME); - create_dir(another_sub_dir_path).unwrap(); -} - -pub fn http_get(url: &str) -> ApiTestResponse { - let response = reqwest::blocking::get(url).unwrap(); - - ApiTestResponse { - status: response.status().as_u16(), - json: serde_json::from_str(&response.text().unwrap()).unwrap(), - } -} - -pub fn http_post(url: &str, hashmap: HashMap<&str, &str>) -> ApiTestResponse { - let response = reqwest::blocking::Client::new() - .post(url) - .json(&hashmap) - .send() - .unwrap(); - - ApiTestResponse { - status: response.status().as_u16(), - json: serde_json::from_str(&response.text().unwrap()).unwrap(), - } -} - -pub fn http_post_custom_body(url: &str, body: &str) -> ApiTestResponse { - let response = reqwest::blocking::Client::new() - .post(url) - .body(body.to_string()) - .send() - .unwrap(); - - ApiTestResponse { - status: response.status().as_u16(), - json: serde_json::from_str(&response.text().unwrap()).unwrap(), - } -} - -#[derive(Serialize)] -pub struct BlockRequestBody { - bls_domain: String, - data: BeaconBlock, - fork: Fork, - genesis_validators_root: Hash256, -} - -pub fn get_test_block_body(seed: u64) -> String { - let block: BeaconBlock = get_block(seed); - let epoch = block.epoch(); - - let fork = Fork { - previous_version: [1; 4], - current_version: [2; 4], - epoch, - }; - - let genesis_validators_root = Hash256::from_low_u64_be(seed); - - let block_request_body = BlockRequestBody { - bls_domain: "beacon_proposer".to_string(), - data: block, - fork, - genesis_validators_root, - }; - - serde_json::to_string(&block_request_body).unwrap() -} - -#[derive(Serialize)] -pub struct AttestationRequestBody { - bls_domain: String, - data: AttestationData, - fork: Fork, - genesis_validators_root: Hash256, -} - -pub fn get_test_attestation_body(seed: u64) -> String { - let attestation = get_attestation::(seed); - let epoch = attestation.target.epoch; - - let fork = Fork { - previous_version: [1; 4], - current_version: [2; 4], - epoch, - }; - - let genesis_validators_root = Hash256::from_low_u64_be(seed); - - let attestation_request_body = AttestationRequestBody { - bls_domain: "beacon_attester".to_string(), - data: attestation, - fork, - genesis_validators_root, - }; - - serde_json::to_string(&attestation_request_body).unwrap() -} - -#[derive(Serialize)] -pub struct RandaoRequestBody { - bls_domain: String, - data: Epoch, - fork: Fork, - genesis_validators_root: Hash256, -} - -pub fn get_test_randao_body(seed: u64) -> String { - let epoch = Epoch::new(seed); - - let fork = Fork { - previous_version: [1; 4], - current_version: [2; 4], - epoch, - }; - - let genesis_validators_root = Hash256::from_low_u64_be(seed); - - let randao_request_body = RandaoRequestBody { - bls_domain: "randao".to_string(), - data: epoch, - fork, - genesis_validators_root, - }; - - serde_json::to_string(&randao_request_body).unwrap() -} - -pub fn assert_sign_ok(resp: ApiTestResponse, expected_signature: &str) { - assert_eq!(resp.status, 200); - assert_eq!( - serde_json::from_value::(resp.json) - .unwrap() - .signature, - expected_signature - ); -} - -pub fn assert_sign_error(resp: ApiTestResponse, http_status: u16, error_msg: &str) { - assert_eq!(resp.status, http_status); - assert_eq!(resp.json["error"].as_str().unwrap(), error_msg); -} diff --git a/testing/remote_signer_test/src/local_signer_test_data.rs b/testing/remote_signer_test/src/local_signer_test_data.rs new file mode 100644 index 000000000..60d650a54 --- /dev/null +++ b/testing/remote_signer_test/src/local_signer_test_data.rs @@ -0,0 +1,103 @@ +use crate::*; +use hex::decode; +use remote_signer_consumer::RemoteSignerObject; +use std::mem; +use types::{ + AttestationData, BeaconBlock, ChainSpec, Domain, Epoch, EthSpec, Fork, Hash256, SecretKey, + SignedRoot, +}; + +pub struct LocalSignerTestData { + secret_key: SecretKey, + spec: ChainSpec, + fork: Fork, + genesis_validators_root: Hash256, + obj: T, +} + +impl LocalSignerTestData { + pub fn new(obj: T) -> Self { + let epoch = obj.get_epoch(); + + Self { + secret_key: SecretKey::deserialize(&decode(SECRET_KEY_1).unwrap()).unwrap(), + spec: E::default_spec(), + fork: Fork { + previous_version: [1; 4], + current_version: [2; 4], + epoch, + }, + genesis_validators_root: Hash256::from_low_u64_be(0xc137), + obj, + } + } +} + +impl LocalSignerTestData> { + pub fn sign(&self) -> String { + let signed_block = self.obj.clone().sign( + &self.secret_key, + &self.fork, + self.genesis_validators_root, + &self.spec, + ); + + signed_block.signature.to_string() + } +} + +impl LocalSignerTestData { + pub fn sign(&self) -> String { + let domain = self.spec.get_domain( + self.obj.target.epoch, + Domain::BeaconAttester, + &self.fork, + self.genesis_validators_root, + ); + + let message = self.obj.signing_root(domain); + let signature = &self.secret_key.sign(message); + + signature.to_string() + } +} + +impl LocalSignerTestData { + pub fn sign(&self) -> String { + let domain = self.spec.get_domain( + self.obj, + Domain::Randao, + &self.fork, + self.genesis_validators_root, + ); + + let message = self.obj.signing_root(domain); + let signature = &self.secret_key.sign(message); + + signature.to_string() + } +} + +pub fn get_input_local_signer_block(seed: u64) -> LocalSignerTestData> { + let block: BeaconBlock; + + unsafe { + block = mem::transmute(get_block::(seed)); + } + + LocalSignerTestData::new(block) +} + +pub fn get_input_local_signer_attestation(seed: u64) -> LocalSignerTestData { + let attestation: AttestationData; + + unsafe { + attestation = mem::transmute(get_attestation::(seed)); + } + + LocalSignerTestData::new(attestation) +} + +pub fn get_input_local_signer_randao(seed: u64) -> LocalSignerTestData { + LocalSignerTestData::new(Epoch::new(seed)) +} diff --git a/testing/remote_signer_test/src/mock.rs b/testing/remote_signer_test/src/mock.rs new file mode 100644 index 000000000..a22d2db97 --- /dev/null +++ b/testing/remote_signer_test/src/mock.rs @@ -0,0 +1,20 @@ +use crate::*; +use httpmock::{Method::POST, MockServer}; +use tokio::time::Duration; + +pub fn set_up_mock_server(status: u16, body: &str) -> MockServer { + set_up_mock_server_with_timeout(status, body, 0) +} + +pub fn set_up_mock_server_with_timeout(status: u16, body: &str, delay: u64) -> MockServer { + let server = MockServer::start(); + + server.mock(|when, then| { + when.method(POST).path(format!("/sign/{}", PUBLIC_KEY_1)); + then.status(status) + .delay(Duration::from_secs(delay)) + .body(body); + }); + + server +} diff --git a/testing/remote_signer_test/src/remote_signer_test_data.rs b/testing/remote_signer_test/src/remote_signer_test_data.rs new file mode 100644 index 000000000..d3aeeca0e --- /dev/null +++ b/testing/remote_signer_test/src/remote_signer_test_data.rs @@ -0,0 +1,69 @@ +use crate::*; +use remote_signer_consumer::RemoteSignerObject; +use std::marker::PhantomData; +use types::{AttestationData, BeaconBlock, Domain, Epoch, EthSpec, Fork, Hash256}; + +pub struct RemoteSignerTestData { + pub public_key: String, + pub bls_domain: Domain, + pub data: T, + pub fork: Fork, + pub genesis_validators_root: Hash256, + + _phantom: PhantomData, +} + +impl<'a, E: EthSpec, T: RemoteSignerObject> RemoteSignerTestData { + pub fn new(public_key: &str, data: T, bls_domain: Domain) -> Self { + let epoch = data.get_epoch(); + + Self { + public_key: public_key.to_string(), + bls_domain, + data, + fork: Fork { + previous_version: [1; 4], + current_version: [2; 4], + epoch, + }, + genesis_validators_root: Hash256::from_low_u64_be(0xc137), + + _phantom: PhantomData, + } + } +} + +pub fn get_input_data_block(seed: u64) -> RemoteSignerTestData> { + let block = get_block::(seed); + RemoteSignerTestData::new(PUBLIC_KEY_1, block, Domain::BeaconProposer) +} + +pub fn get_input_data_attestation(seed: u64) -> RemoteSignerTestData { + let attestation = get_attestation::(seed); + RemoteSignerTestData::new(PUBLIC_KEY_1, attestation, Domain::BeaconAttester) +} + +pub fn get_input_data_randao(seed: u64) -> RemoteSignerTestData { + let epoch = Epoch::new(seed); + RemoteSignerTestData::new(PUBLIC_KEY_1, epoch, Domain::Randao) +} + +pub fn get_input_data_and_set_domain( + f: fn(u64) -> RemoteSignerTestData, + bls_domain: Domain, +) -> RemoteSignerTestData { + let mut test_input = f(0xc137); + test_input.bls_domain = bls_domain; + + test_input +} + +pub fn get_input_data_and_set_public_key( + f: fn(u64) -> RemoteSignerTestData, + p: &str, +) -> RemoteSignerTestData { + let mut test_input = f(0xc137); + test_input.public_key = p.to_string(); + + test_input +} diff --git a/testing/remote_signer_test/src/objects.rs b/testing/remote_signer_test/src/utils.rs similarity index 64% rename from testing/remote_signer_test/src/objects.rs rename to testing/remote_signer_test/src/utils.rs index 62dfdb214..0600e9020 100644 --- a/testing/remote_signer_test/src/objects.rs +++ b/testing/remote_signer_test/src/utils.rs @@ -1,3 +1,17 @@ +use crate::*; +pub use constants::*; +pub use consumer::*; +pub use local_signer_test_data::*; +pub use mock::*; +use remote_signer_client::Client; +pub use remote_signer_test_data::*; +use std::fs; +use std::fs::{create_dir, File}; +use std::io::Write; +use std::net::IpAddr::{V4, V6}; +use std::os::unix::fs::PermissionsExt; +use std::path::Path; +use tempdir::TempDir; use types::{ AggregateSignature, Attestation, AttestationData, AttesterSlashing, BeaconBlock, BeaconBlockHeader, BitList, Checkpoint, Deposit, DepositData, Epoch, EthSpec, FixedVector, @@ -5,6 +19,71 @@ use types::{ SignedBeaconBlockHeader, SignedVoluntaryExit, Slot, Unsigned, VariableList, VoluntaryExit, }; +pub fn get_address(client: &Client) -> String { + let listening_address = client.get_listening_address(); + let ip = match listening_address.ip() { + V4(ip) => ip.to_string(), + V6(ip) => ip.to_string(), + }; + + format!("http://{}:{}", ip, listening_address.port()) +} + +pub fn set_permissions(path: &Path, perm_octal: u32) { + let metadata = fs::metadata(path).unwrap(); + let mut permissions = metadata.permissions(); + permissions.set_mode(perm_octal); + fs::set_permissions(path, permissions).unwrap(); +} + +pub fn add_key_files(tmp_dir: &TempDir) { + let pairs = vec![ + (PUBLIC_KEY_1, SECRET_KEY_1), + (PUBLIC_KEY_2, SECRET_KEY_2), + (PUBLIC_KEY_3, SECRET_KEY_3), + ]; + + add_files(tmp_dir, pairs); +} + +pub fn add_mismatched_key_file(tmp_dir: &TempDir) { + let pairs = vec![(MISMATCHED_PUBLIC_KEY, SECRET_KEY_1)]; + + add_files(tmp_dir, pairs); +} + +pub fn add_invalid_secret_key_file(tmp_dir: &TempDir) { + let pairs = vec![(PUBLIC_KEY_FOR_INVALID_SECRET_KEY, INVALID_SECRET_KEY)]; + + add_files(tmp_dir, pairs); +} + +pub fn add_non_key_files(tmp_dir: &TempDir) { + let pairs = vec![ + (SILLY_FILE_NAME_1, SILLY_CONTENT_1), + (SILLY_FILE_NAME_2, SILLY_CONTENT_2), + (SILLY_FILE_NAME_3, SILLY_CONTENT_3), + ]; + + add_files(tmp_dir, pairs); +} + +fn add_files(tmp_dir: &TempDir, pairs: Vec<(&str, &str)>) { + for pair in pairs { + let file_path = tmp_dir.path().join(pair.0); + let mut tmp_file = File::create(file_path).unwrap(); + writeln!(tmp_file, "{}", pair.1).unwrap(); + } +} + +pub fn add_sub_dirs(tmp_dir: &TempDir) { + let random_sub_dir_path = tmp_dir.path().join("random_sub_dir_name"); + create_dir(random_sub_dir_path).unwrap(); + + let another_sub_dir_path = tmp_dir.path().join(SUB_DIR_NAME); + create_dir(another_sub_dir_path).unwrap(); +} + /// We spice up some of the values, based on a given `seed` parameter. pub fn get_block(seed: u64) -> BeaconBlock { let spec = &mut E::default_spec();