diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 585d96ba..abc48281 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -12,7 +12,7 @@ on: jobs: build-docker: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 59115798..783cbf86 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -16,7 +16,7 @@ env: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 @@ -28,7 +28,7 @@ jobs: run: cargo build --locked --workspace --all-features --verbose test: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 @@ -40,7 +40,7 @@ jobs: run: cargo test --locked --workspace --all-features --verbose lint: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 @@ -55,7 +55,7 @@ jobs: run: cargo clippy --workspace --all-features -- -D warnings wasm-build: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 diff --git a/Cargo.lock b/Cargo.lock index aa395e2d..93609019 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,7 +20,7 @@ dependencies = [ "ethers", "js-sys", "once_cell", - "serde 1.0.137", + "serde 1.0.147", "sha3 0.9.1", "thiserror", "wasm-bindgen", @@ -140,7 +140,7 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" dependencies = [ - "serde 1.0.137", + "serde 1.0.147", "serde_json", ] @@ -378,7 +378,7 @@ dependencies = [ "metrics-exporter-prometheus", "nomad-base", "nomad-ethereum", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "tokio", ] @@ -441,7 +441,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" dependencies = [ - "serde 1.0.137", + "serde 1.0.147", ] [[package]] @@ -450,7 +450,7 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "serde 1.0.137", + "serde 1.0.147", ] [[package]] @@ -642,7 +642,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" dependencies = [ - "serde 1.0.137", + "serde 1.0.147", ] [[package]] @@ -668,7 +668,7 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412" dependencies = [ - "serde 1.0.137", + "serde 1.0.147", ] [[package]] @@ -677,7 +677,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" dependencies = [ - "serde 1.0.137", + "serde 1.0.147", ] [[package]] @@ -689,7 +689,7 @@ dependencies = [ "camino", "cargo-platform", "semver", - "serde 1.0.137", + "serde 1.0.147", "serde_json", ] @@ -732,7 +732,8 @@ dependencies = [ "libc", "num-integer", "num-traits 0.2.15", - "serde 1.0.137", + "serde 1.0.147", + "time", "winapi", ] @@ -779,8 +780,8 @@ checksum = "29e724a68d9319343bb3328c9cc2dfde263f4b3142ee1059a9980580171c954b" dependencies = [ "atty", "bitflags", - "clap_derive", - "clap_lex", + "clap_derive 3.2.17", + "clap_lex 0.2.4", "indexmap", "once_cell", "strsim 0.10.0", @@ -788,6 +789,21 @@ dependencies = [ "textwrap 0.15.0", ] +[[package]] +name = "clap" +version = "4.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2148adefda54e14492fb9bddcc600b4344c5d1a3123bd666dcb939c6f0e0e57e" +dependencies = [ + "atty", + "bitflags", + "clap_derive 4.0.21", + "clap_lex 0.3.0", + "once_cell", + "strsim 0.10.0", + "termcolor", +] + [[package]] name = "clap_derive" version = "3.2.17" @@ -801,6 +817,19 @@ dependencies = [ "syn", ] +[[package]] +name = "clap_derive" +version = "4.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +dependencies = [ + "heck 0.4.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "clap_lex" version = "0.2.4" @@ -810,6 +839,15 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "clap_lex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "coins-bip32" version = "0.6.0" @@ -824,7 +862,7 @@ dependencies = [ "hmac 0.11.0", "k256", "lazy_static", - "serde 1.0.137", + "serde 1.0.147", "sha2 0.9.9", "thiserror", ] @@ -860,7 +898,7 @@ dependencies = [ "generic-array 0.14.5", "hex", "ripemd160", - "serde 1.0.137", + "serde 1.0.147", "serde_derive", "sha2 0.9.9", "sha3 0.9.1", @@ -923,7 +961,7 @@ dependencies = [ "lazy_static", "nom 5.1.2", "rust-ini", - "serde 1.0.137", + "serde 1.0.147", "serde-hjson", "serde_json", "toml", @@ -939,7 +977,7 @@ dependencies = [ "lazy_static", "nom 5.1.2", "rust-ini", - "serde 1.0.137", + "serde 1.0.147", "serde-hjson", "serde_json", "toml", @@ -1360,7 +1398,7 @@ dependencies = [ "curve25519-dalek 3.2.0", "ed25519", "rand 0.7.3", - "serde 1.0.137", + "serde 1.0.147", "sha2 0.9.9", "zeroize", ] @@ -1440,7 +1478,7 @@ dependencies = [ "pbkdf2 0.10.1", "rand 0.8.5", "scrypt", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "sha2 0.10.2", "sha3 0.10.1", @@ -1458,7 +1496,7 @@ dependencies = [ "hex", "once_cell", "regex", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "sha3 0.10.1", "thiserror", @@ -1514,7 +1552,7 @@ source = "git+https://github.com/gakonst/ethers-rs?branch=master#00b38c437a7cae6 dependencies = [ "ethers-core", "once_cell", - "serde 1.0.137", + "serde 1.0.147", "serde_json", ] @@ -1531,7 +1569,7 @@ dependencies = [ "hex", "once_cell", "pin-project", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "thiserror", ] @@ -1551,7 +1589,7 @@ dependencies = [ "proc-macro2", "quote", "reqwest", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "syn", "url", @@ -1593,7 +1631,7 @@ dependencies = [ "rlp", "rlp-derive", "rust_decimal", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "strum 0.24.0", "syn", @@ -1611,7 +1649,7 @@ dependencies = [ "ethers-solc", "reqwest", "semver", - "serde 1.0.137", + "serde 1.0.147", "serde-aux", "serde_json", "thiserror", @@ -1633,7 +1671,7 @@ dependencies = [ "futures-util", "instant", "reqwest", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "thiserror", "tokio", @@ -1662,7 +1700,7 @@ dependencies = [ "parking_lot 0.11.2", "pin-project", "reqwest", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "thiserror", "tokio", @@ -1718,7 +1756,7 @@ dependencies = [ "rayon", "regex", "semver", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "solang-parser", "thiserror", @@ -1853,7 +1891,7 @@ dependencies = [ "cfg-if 1.0.0", "parity-scale-codec", "scale-info", - "serde 1.0.137", + "serde 1.0.147", ] [[package]] @@ -2007,7 +2045,7 @@ dependencies = [ "once_cell", "pin-project", "reqwest", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "serde_repr", "thiserror", @@ -2302,7 +2340,7 @@ checksum = "39f357a500abcbd7c5f967c1d45c8838585b36743823b9d43488f24850534e36" dependencies = [ "backtrace", "os_type", - "serde 1.0.137", + "serde 1.0.147", "serde_derive", "termcolor", "toml", @@ -2414,7 +2452,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" dependencies = [ - "serde 1.0.137", + "serde 1.0.147", ] [[package]] @@ -2422,7 +2460,7 @@ name = "impl-serde" version = "0.4.0" source = "git+https://github.com/paritytech/parity-common.git?branch=master#706c989b3a9b2ba93418e5af583580f536d6a7bb" dependencies = [ - "serde 1.0.137", + "serde 1.0.147", ] [[package]] @@ -2444,12 +2482,12 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.8.2" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", - "hashbrown 0.11.2", + "hashbrown 0.12.3", ] [[package]] @@ -2564,7 +2602,7 @@ dependencies = [ "futures-util", "jsonrpsee-types", "rustc-hash", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "thiserror", "tokio", @@ -2580,7 +2618,7 @@ checksum = "e290bba767401b646812f608c099b922d8142603c9e73a50fb192d3ac86f4a0d" dependencies = [ "anyhow", "beef", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "thiserror", "tracing", @@ -2619,7 +2657,7 @@ dependencies = [ "nomad-xyz-configuration", "prometheus", "rand 0.8.5", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "serial_test", "thiserror", @@ -2640,7 +2678,7 @@ name = "killswitch" version = "0.1.0" dependencies = [ "assert_matches", - "clap 3.2.17", + "clap 4.0.26", "ethers", "futures-util", "nomad-base", @@ -2649,8 +2687,12 @@ dependencies = [ "nomad-test", "nomad-types", "nomad-xyz-configuration", - "serde 1.0.137", + "rusoto_core", + "rusoto_mock", + "rusoto_s3", + "serde 1.0.147", "serde_json", + "serde_yaml 0.9.14", "serial_test", "thiserror", "tokio", @@ -2782,7 +2824,7 @@ dependencies = [ "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", "rand 0.8.5", - "serde 1.0.137", + "serde 1.0.147", "sha2 0.9.9", "typenum", ] @@ -3229,7 +3271,7 @@ dependencies = [ "rocksdb", "rusoto_core", "rusoto_kms", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "thiserror", "tokio", @@ -3282,7 +3324,7 @@ dependencies = [ "rusoto_credential", "rusoto_kms", "rusoto_sts", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "sha3 0.9.1", "subxt", @@ -3315,7 +3357,7 @@ dependencies = [ "prometheus", "reqwest", "rocksdb", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "thiserror", "tokio", @@ -3344,7 +3386,7 @@ dependencies = [ "parity-util-mem", "primitive-types 0.11.1 (git+https://github.com/paritytech/parity-common.git?branch=master)", "scale-info", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "sp-keyring", "subxt", @@ -3372,7 +3414,7 @@ dependencies = [ "prometheus", "rand 0.8.5", "rocksdb", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "thiserror", "tokio", @@ -3388,7 +3430,7 @@ dependencies = [ "ethers", "hex", "prometheus", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "thiserror", ] @@ -3406,9 +3448,9 @@ dependencies = [ "nomad-types", "once_cell", "reqwest", - "serde 1.0.137", + "serde 1.0.147", "serde_json", - "serde_yaml", + "serde_yaml 0.8.24", "serial_test", "tokio", "tracing", @@ -3456,7 +3498,7 @@ dependencies = [ "autocfg", "num-integer", "num-traits 0.2.15", - "serde 1.0.137", + "serde 1.0.147", ] [[package]] @@ -3466,7 +3508,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fbc387afefefd5e9e39493299f3069e14a140dd34dc19b4c1c1a8fddb6a790" dependencies = [ "num-traits 0.2.15", - "serde 1.0.137", + "serde 1.0.147", ] [[package]] @@ -3522,7 +3564,7 @@ dependencies = [ "num-bigint 0.4.3", "num-integer", "num-traits 0.2.15", - "serde 1.0.137", + "serde 1.0.147", ] [[package]] @@ -3676,7 +3718,7 @@ dependencies = [ "byte-slice-cast", "impl-trait-for-tuples", "parity-scale-codec-derive", - "serde 1.0.137", + "serde 1.0.147", ] [[package]] @@ -4100,9 +4142,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.39" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ "unicode-ident", ] @@ -4128,7 +4170,7 @@ dependencies = [ "rocksdb", "rusoto_core", "rusoto_s3", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "serial_test", "thiserror", @@ -4399,7 +4441,7 @@ dependencies = [ "nomad-test", "nomad-xyz-configuration", "prometheus", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "serial_test", "thiserror", @@ -4446,7 +4488,7 @@ dependencies = [ "pin-project-lite", "rustls", "rustls-pemfile 0.3.0", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "serde_urlencoded", "tokio", @@ -4546,7 +4588,7 @@ dependencies = [ "rusoto_credential", "rusoto_signature", "rustc_version", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "tokio", "xml-rs", @@ -4563,7 +4605,7 @@ dependencies = [ "dirs-next", "futures", "hyper", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "shlex", "tokio", @@ -4580,7 +4622,22 @@ dependencies = [ "bytes", "futures", "rusoto_core", - "serde 1.0.137", + "serde 1.0.147", + "serde_json", +] + +[[package]] +name = "rusoto_mock" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a384880f3c6d514e9499e6df75490bef5f6f39237bc24844e3933dfc09e9e55" +dependencies = [ + "async-trait", + "chrono", + "futures", + "http", + "rusoto_core", + "serde 1.0.147", "serde_json", ] @@ -4618,7 +4675,7 @@ dependencies = [ "pin-project-lite", "rusoto_credential", "rustc_version", - "serde 1.0.137", + "serde 1.0.147", "sha2 0.9.9", "tokio", ] @@ -4652,7 +4709,7 @@ checksum = "e2ee7337df68898256ad0d4af4aad178210d9e44d2ff900ce44064a97cd86530" dependencies = [ "arrayvec 0.7.2", "num-traits 0.2.15", - "serde 1.0.137", + "serde 1.0.147", ] [[package]] @@ -4783,7 +4840,7 @@ dependencies = [ "derive_more", "parity-scale-codec", "scale-info-derive", - "serde 1.0.137", + "serde 1.0.147", ] [[package]] @@ -4810,7 +4867,7 @@ dependencies = [ "parity-scale-codec", "scale-decode", "scale-info", - "serde 1.0.137", + "serde 1.0.147", "thiserror", "yap", ] @@ -4947,7 +5004,7 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" dependencies = [ - "serde 1.0.137", + "serde 1.0.147", ] [[package]] @@ -4964,9 +5021,9 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" [[package]] name = "serde" -version = "1.0.137" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" dependencies = [ "serde_derive", ] @@ -4977,7 +5034,7 @@ version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93abf9799c576f004252b2a05168d58527fb7c54de12e94b4d12fe3475ffad24" dependencies = [ - "serde 1.0.137", + "serde 1.0.147", "serde_json", ] @@ -4996,9 +5053,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" dependencies = [ "proc-macro2", "quote", @@ -5013,7 +5070,7 @@ checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" dependencies = [ "itoa 1.0.2", "ryu", - "serde 1.0.137", + "serde 1.0.147", ] [[package]] @@ -5045,7 +5102,7 @@ dependencies = [ "form_urlencoded", "itoa 1.0.2", "ryu", - "serde 1.0.137", + "serde 1.0.147", ] [[package]] @@ -5056,10 +5113,23 @@ checksum = "707d15895415db6628332b737c838b88c598522e4dc70647e59b72312924aebc" dependencies = [ "indexmap", "ryu", - "serde 1.0.137", + "serde 1.0.147", "yaml-rust", ] +[[package]] +name = "serde_yaml" +version = "0.9.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d232d893b10de3eb7258ff01974d6ee20663d8e833263c99409d4b13a0209da" +dependencies = [ + "indexmap", + "itoa 1.0.2", + "ryu", + "serde 1.0.147", + "unsafe-libyaml", +] + [[package]] name = "serial_test" version = "0.6.0" @@ -5276,7 +5346,7 @@ checksum = "acb4490364cb3b097a6755343e552495b0013778152300714be4647d107e9a2e" dependencies = [ "parity-scale-codec", "scale-info", - "serde 1.0.137", + "serde 1.0.147", "sp-core", "sp-io", "sp-std", @@ -5292,7 +5362,7 @@ dependencies = [ "num-traits 0.2.15", "parity-scale-codec", "scale-info", - "serde 1.0.137", + "serde 1.0.147", "sp-debug-derive", "sp-std", "static_assertions", @@ -5330,7 +5400,7 @@ dependencies = [ "schnorrkel", "secp256k1", "secrecy", - "serde 1.0.137", + "serde 1.0.147", "sp-core-hashing", "sp-debug-derive", "sp-externalities", @@ -5463,7 +5533,7 @@ dependencies = [ "paste", "rand 0.7.3", "scale-info", - "serde 1.0.137", + "serde 1.0.147", "sp-application-crypto", "sp-arithmetic", "sp-core", @@ -5541,7 +5611,7 @@ dependencies = [ "impl-serde 0.3.2", "parity-scale-codec", "ref-cast", - "serde 1.0.137", + "serde 1.0.147", "sp-debug-derive", "sp-std", ] @@ -5623,7 +5693,7 @@ dependencies = [ "num-format", "proc-macro2", "quote", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "unicode-xid", ] @@ -5763,7 +5833,7 @@ dependencies = [ "scale-decode", "scale-info", "scale-value", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "sp-core", "sp-runtime", @@ -5928,6 +5998,16 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "tiny-bip39" version = "0.8.2" @@ -6111,7 +6191,7 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ - "serde 1.0.137", + "serde 1.0.147", ] [[package]] @@ -6204,7 +6284,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" dependencies = [ - "serde 1.0.137", + "serde 1.0.147", "tracing-core", ] @@ -6219,7 +6299,7 @@ dependencies = [ "lazy_static", "matchers", "regex", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "sharded-slab", "smallvec", @@ -6237,7 +6317,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a713421342a5a666b7577783721d3117f1b69a393df803ee17bb73b1e122a59" dependencies = [ "ansi_term", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "sharded-slab", "smallvec", @@ -6412,6 +6492,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" +[[package]] +name = "unsafe-libyaml" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e5fa573d8ac5f1a856f8d7be41d390ee973daf97c806b2c1a465e4e1406e68" + [[package]] name = "untrusted" version = "0.7.1" @@ -6439,7 +6525,7 @@ dependencies = [ "nomad-xyz-configuration", "prometheus", "rocksdb", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "serial_test", "thiserror", @@ -6475,7 +6561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ "getrandom 0.2.6", - "serde 1.0.137", + "serde 1.0.147", ] [[package]] @@ -6558,7 +6644,7 @@ dependencies = [ "percent-encoding", "pin-project", "scoped-tls", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "serde_urlencoded", "tokio", @@ -6594,7 +6680,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" dependencies = [ "cfg-if 1.0.0", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "wasm-bindgen-macro", ] @@ -6713,7 +6799,7 @@ dependencies = [ "nomad-xyz-configuration", "prometheus", "rocksdb", - "serde 1.0.137", + "serde 1.0.147", "serde_json", "serial_test", "thiserror", diff --git a/Dockerfile b/Dockerfile index f0bd972d..db20ddad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 ENV TARGET_DIR='target' WORKDIR /app diff --git a/configuration/src/chains/mod.rs b/configuration/src/chains/mod.rs index 15cb135e..4f4909e5 100644 --- a/configuration/src/chains/mod.rs +++ b/configuration/src/chains/mod.rs @@ -111,7 +111,7 @@ impl ChainConf { }) .unwrap_or_else(|_| { tracing::debug!("falling back to ethereum"); - "etherum".to_owned() + "ethereum".to_owned() }); let rpc_url: Connection = std::env::var(&format!("{}_CONNECTION_URL", network)) diff --git a/fixtures/killswitch_secrets.testing.yaml b/fixtures/killswitch_secrets.testing.yaml new file mode 100644 index 00000000..a0dec367 --- /dev/null +++ b/fixtures/killswitch_secrets.testing.yaml @@ -0,0 +1,26 @@ +# Equivalent to `CONFIG_URL` +configUrl: "" + +# Equivalent to `CONFIG_PATH`. Included for testing only +configPath: fixtures/killswitch_config.json + +# Equivalent to the set of `_CONNECTION_URL` +connectionUrls: + RINKEBY_CONNECTION_URL: https://rinkeby-light.eth.linkpool.io.bad.url + POLYGONMUMBAI_CONNECTION_URL: https://rpc-mumbai.maticvigil.com.bad.url + GOERLI_CONNECTION_URL: https://goerli-light.eth.linkpool.io.bad.url + EVMOSTESTNET_CONNECTION_URL: https://eth.bd.evmos.dev:8545.bad.url + +# Equivalent to the set of `_TXSIGNER_ID` +txsignerIds: + RINKEBY_TXSIGNER_ID: 00000000-0000-0000-0000-000000000000 + POLYGONMUMBAI_TXSIGNER_ID: 00000000-0000-0000-0000-000000000000 + GOERLI_TXSIGNER_ID: 00000000-0000-0000-0000-000000000000 + EVMOSTESTNET_TXSIGNER_ID: 00000000-0000-0000-0000-000000000000 + +# Equivalent to the set of `_ATTESTATION_SIGNER_ID` +attestationSignerIds: + RINKEBY_ATTESTATION_SIGNER_ID: 00000000-0000-0000-0000-000000000000 + POLYGONMUMBAI_ATTESTATION_SIGNER_ID: 00000000-0000-0000-0000-000000000000 + GOERLI_ATTESTATION_SIGNER_ID: 00000000-0000-0000-0000-000000000000 + EVMOSTESTNET_ATTESTATION_SIGNER_ID: 00000000-0000-0000-0000-000000000000 diff --git a/nomad-core/src/aws.rs b/nomad-core/src/aws.rs index 27cb86e6..ac40dfbf 100644 --- a/nomad-core/src/aws.rs +++ b/nomad-core/src/aws.rs @@ -1,13 +1,13 @@ -use once_cell::sync::OnceCell; use rusoto_core::{ credential::{AutoRefreshingProvider, ProvideAwsCredentials}, Client, HttpClient, }; use rusoto_kms::KmsClient; use rusoto_sts::WebIdentityProvider; +use tokio::sync::OnceCell; -static CLIENT: OnceCell = OnceCell::new(); -static KMS_CLIENT: OnceCell = OnceCell::new(); +static CLIENT: OnceCell = OnceCell::const_new(); +static KMS_CLIENT: OnceCell = OnceCell::const_new(); // Try to get an irsa provider #[tracing::instrument] @@ -34,37 +34,28 @@ async fn try_irsa_provider() -> Option &'static Client { - // init exactly once - if CLIENT.get().is_none() { - // try IRSA first - let client = match try_irsa_provider().await { - Some(credentials_provider) => { - let dispatcher = HttpClient::new().unwrap(); - Client::new_with(credentials_provider, dispatcher) + CLIENT + .get_or_init(|| async { + match try_irsa_provider().await { + Some(credentials_provider) => { + let dispatcher = HttpClient::new().unwrap(); + Client::new_with(credentials_provider, dispatcher) + } + // if the IRSA provider returned no creds, use the default + // credentials chain + None => Client::shared(), } - // if the IRSA provider returned no creds, use the default - // credentials chain - None => Client::shared(), - }; - - if CLIENT.set(client).is_err() { - panic!("unable to set Client") - }; - } - - CLIENT.get().expect("just init") + }) + .await } /// Get a shared KMS client pub async fn get_kms_client() -> &'static KmsClient { - if KMS_CLIENT.get().is_none() { - let client = get_client().await.clone(); + KMS_CLIENT + .get_or_init(|| async { + let client = get_client().await.clone(); - let kms = KmsClient::new_with_client(client, Default::default()); - - if KMS_CLIENT.set(kms).is_err() { - panic!("unable to set KmsClient") - }; - } - KMS_CLIENT.get().expect("just init") + KmsClient::new_with_client(client, Default::default()) + }) + .await } diff --git a/tools/CHANGELOG.md b/tools/CHANGELOG.md index 9e0ecf83..0217e999 100644 --- a/tools/CHANGELOG.md +++ b/tools/CHANGELOG.md @@ -2,6 +2,9 @@ ### Unreleased +- adds the ability for killswitch to auto-configure +- makes killswitch output human readable +- makes killswitch execute transactions in parallel to improve speed - adds a killswitch binary that allows for the manual shutdown of bridge channels - adds tests and test fixtures for killswitch - adds a readme, including documentation for killswitch diff --git a/tools/README.md b/tools/README.md index f8df0a62..2ecc330f 100644 --- a/tools/README.md +++ b/tools/README.md @@ -3,51 +3,4 @@ ### Killswitch - Kills bridge channels manually in effectively the same way as Watcher -- Takes a set of environment variables and an agent config file -- Can either kill all configured networks or all inbound to a single network - -#### Interface - -```$ killswitch --help``` -```bash -killswitch -Command line args - -USAGE: - killswitch --app <--all|--all-inbound > - -OPTIONS: - --all Kill all available networks - --all-inbound Kill all replicas on network - --app Which app to kill [possible values: token-bridge] - -h, --help Print help information -``` - -#### Environment variables - -A config file can be specified with `CONFIG_URL` (remote), `CONFIG_PATH` (local) or `RUN_ENV` (local) and are evaluated in that order, the first found being used. - -Secrets must be in the explicit form `_TXSIGNER_{KEY,ID}` and `_ATTESTATION_SIGNER_{KEY,ID}`. - -#### Example environment variables file -```text -CONFIG_PATH=./config.json -DEFAULT_RPCSTYLE=ethereum -DEFAULT_SUBMITTER_TYPE=local - -GOERLI_CONNECTION_URL=https://rpc.endpoint -POLYGONMUMBAI_CONNECTION_URL=https://rpc.endpoint -RINKEBY_CONNECTION_URL=https://rpc.endpoint - -GOERLI_TXSIGNER_KEY=0x0 -POLYGONMUMBAI_TXSIGNER_KEY=0x0 -RINKEBY_TXSIGNER_KEY=0x0 - -GOERLI_ATTESTATION_SIGNER_KEY=0x0 -POLYGONMUMBAI_ATTESTATION_SIGNER_KEY=0x0 -RINKEBY_ATTESTATION_SIGNER_KEY=0x0 -``` - -#### Return value - -- Streams newline-delimited JSON containing error / success reports +- Can either kill all configured networks or all inbound to a single network \ No newline at end of file diff --git a/tools/killswitch/Cargo.toml b/tools/killswitch/Cargo.toml index 12994dcb..9912c89c 100644 --- a/tools/killswitch/Cargo.toml +++ b/tools/killswitch/Cargo.toml @@ -6,12 +6,15 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap = { version = "3.1.6", features = ["derive"] } +clap = { version = "4.0", features = ["derive"] } tokio = { version = "1.0.1", features = ["rt", "macros"] } futures-util = "0.3.12" thiserror = { version = "1.0.22", default-features = false } serde = "1.0.120" serde_json = { version = "1.0.61", default-features = false } +serde_yaml = "0.9.14" +rusoto_core = "0.48.0" +rusoto_s3 = "0.48.0" ethers = { git = "https://github.com/gakonst/ethers-rs", branch = "master" } nomad-core = { path = "../../nomad-core" } nomad-base = { path = "../../nomad-base" } @@ -19,6 +22,7 @@ nomad-xyz-configuration = { path = "../../configuration" } nomad-ethereum = { path = "../../chains/nomad-ethereum" } [dev-dependencies] +rusoto_mock = "0.48.0" assert_matches = "1.5" serial_test = "0.6.0" nomad-test = { path = "../../nomad-test" } diff --git a/tools/killswitch/src/errors.rs b/tools/killswitch/src/errors.rs index ddc6f816..4ae0d163 100644 --- a/tools/killswitch/src/errors.rs +++ b/tools/killswitch/src/errors.rs @@ -1,9 +1,25 @@ use nomad_base::ChainCommunicationError; use nomad_ethereum::EthereumSignersError; +use rusoto_core::{credential::CredentialsError, RusotoError}; +use rusoto_s3::GetObjectError; +use serde_yaml::Error as YamlError; +use std::io::Error as IOError; /// `Error` for KillSwitch #[derive(Debug, thiserror::Error)] pub enum Error { + /// Cannot find local AWS credentials + #[error("BadCredentials: Cannot find AWS credentials: {0}")] + BadCredentials(#[source] CredentialsError), + /// Yaml ser/de error + #[error("YamlBadDeser: Error converting to/from yaml: {0}")] + YamlBadDeser(#[source] YamlError), + /// Rusto S3 fetch object error + #[error("RusotoGetObject: Error fetching object from AWS S3: {0}")] + RusotoGetObject(#[source] RusotoError), + /// Generic IO error + #[error("BadIO: Error reading or writing from stream: {0}")] + BadIO(#[source] IOError), /// No configuration env var #[error( "NoConfigVar: No configuration found. Set CONFIG_URL or CONFIG_PATH environment variable" diff --git a/tools/killswitch/src/killswitch.rs b/tools/killswitch/src/killswitch.rs index 3fca3729..52981bc8 100644 --- a/tools/killswitch/src/killswitch.rs +++ b/tools/killswitch/src/killswitch.rs @@ -1,22 +1,21 @@ -use crate::{ - errors::Error, output::build_output_message, settings::Settings, Args, Message, Result, -}; -use futures_util::future::join_all; +use crate::{errors::Error, settings::Settings, Args, Result}; +use ethers::prelude::H256; use nomad_base::{AttestationSigner, ChainSetup, ChainSetupType, ConnectionManagers, Homes}; -use nomad_core::{ - Common, ConnectionManager, FailureNotification, FromSignerConf, Home, - SignedFailureNotification, TxOutcome, -}; +use nomad_core::{Common, ConnectionManager, FailureNotification, FromSignerConf, Home}; use nomad_xyz_configuration::AgentSecrets; +use std::sync::mpsc::Sender; +use tokio::task::JoinHandle; /// Main `KillSwitch` struct #[derive(Debug)] pub(crate) struct KillSwitch { - /// A vector of all `ChannelKiller` - channel_killers: Vec, + /// Our `Settings` + settings: Settings, + /// A vector of all `Channel`s + channels: Vec, } -/// The set of origin->destination networks +/// The set of origin -> destination networks #[derive(Debug, Clone)] pub(crate) struct Channel { /// Origin network @@ -25,63 +24,18 @@ pub(crate) struct Channel { pub(crate) replica: String, } -/// The channel and contracts required or errors encountered -#[derive(Debug)] -struct ChannelKiller { - /// The channel we want to kill - channel: Channel, - /// Home contract - home_contract: Option, - /// Connection manager - connection_manager: Option, - /// Attestation signer - attestation_signer: Option, - /// Contract init errors we've encountered - errors: Vec, -} - -impl ChannelKiller { - /// Have we collected *any* errors - fn has_errors(&self) -> bool { - !self.errors.is_empty() - } - - /// Take all available errors - fn take_all_errors(&mut self) -> Vec { - self.errors.drain(..).collect() - } - - /// Create a `SignedFailureNotification` - async fn create_signed_failure(&mut self) -> Result { - // Force unwrap here as we're not calling this on contract with errors - let home_contract = self.home_contract.take().unwrap(); - let signer = self.attestation_signer.take().unwrap(); - let updater = home_contract - .updater() - .await - .map_err(Error::UpdaterAddress)?; - FailureNotification { - home_domain: home_contract.local_domain(), - updater: updater.into(), - } - .sign_with(&signer) - .await - .map_err(Error::AttestationSignerFailed) +impl KillSwitch { + /// Get a count of the `Channel`s we're configured for + pub(crate) fn channel_count(&self) -> usize { + self.channels.len() } - /// Kill channel - async fn kill(&mut self, signed_failure: &SignedFailureNotification) -> Result { - // Force unwrap here as we're not calling this on contract with errors - let connection_manager = self.connection_manager.take().unwrap(); - connection_manager - .unenroll_replica(signed_failure) - .await - .map_err(Error::UnenrollmentFailed) + /// Get a copy of the `Channel`s we're configured for + pub(crate) fn channels(&self) -> Vec { + self.channels.clone() } -} -impl KillSwitch { - /// Get all available home->network channels in config + /// Get all available home->replica channels in config fn make_channels(settings: &Settings) -> Vec { settings .config @@ -170,125 +124,83 @@ impl KillSwitch { .map_err(|report| Error::AttestationSignerInit(format!("{:#}", report))) } - /// Build a new `KillSwitch`, configuring best effort and storing, not returning most errors - pub(crate) async fn new(args: Args, settings: Settings) -> Result { + /// Build a new `KillSwitch` + pub(crate) async fn new(args: &Args, settings: Settings) -> Result { let channels = if args.all { Self::make_channels(&settings) } else { let destination_network = args .all_inbound + .as_ref() .expect("Should not happen. Clap requires this to be present"); let all = Self::make_channels(&settings); - Self::make_inbound_channels(&destination_network, all) + Self::make_inbound_channels(destination_network, all) }; if channels.is_empty() { // The one error we bail on, since there's nothing else left to do return Err(Error::NoNetworks); } - - let futs = channels.into_iter().map(|channel| async { - let home_contract = Self::make_home(&channel, &settings).await; - let connection_manager = Self::make_connection_manager(&channel, &settings).await; - let attestation_signer = Self::make_signer(&channel, &settings).await; - let mut killer = ChannelKiller { - channel, - home_contract: None, - connection_manager: None, - attestation_signer: None, - errors: vec![], - }; - - if let Err(err) = home_contract { - killer.errors.push(err); - } else { - killer.home_contract = home_contract.ok(); - } - - if let Err(err) = connection_manager { - killer.errors.push(err); - } else { - killer.connection_manager = connection_manager.ok(); - } - - if let Err(err) = attestation_signer { - killer.errors.push(err); - } else { - killer.attestation_signer = attestation_signer.ok(); - } - killer - }); - let channel_killers = join_all(futs).await.into_iter().collect::>(); - Ok(Self { channel_killers }) + Ok(Self { settings, channels }) } - /// Collect all blocking errors, returning a `KillSwitch` with a set of channels - /// that can actually fire off transactions, as well as any errors collected - pub(crate) async fn get_blocking_errors(self) -> (Self, Option) { - let (mut failed, maybe_ok): (Vec<_>, Vec<_>) = self - .channel_killers - .into_iter() - .partition(|killer| killer.has_errors()); - - // These are blocking errors for each channel - let bad = failed - .iter_mut() - .map(|killer| (killer.channel.clone(), killer.take_all_errors())) - .collect::>(); - - // Produce errors to stream before running txs - let message = if bad.is_empty() { - None - } else { - Some(build_output_message(bad, vec![])) - }; - ( - KillSwitch { - channel_killers: maybe_ok, - }, - message, - ) - } - - /// Run `KillSwitch` against remaining, non-blocked channels - pub(crate) async fn run(mut self) -> Message { - let futs = self - .channel_killers - .iter_mut() - .map(|killer| async { - let fut = async { - let signed_failure = killer.create_signed_failure().await?; - killer.kill(&signed_failure).await + /// Run `KillSwitch` against channels in parallel, sending results back via `mpsc::channel` + pub(crate) fn run(&self, output: Sender<(Channel, Result)>) -> Vec> { + let mut handles = Vec::new(); + // Run our channels in parallel + for channel in &self.channels { + let output = output.clone(); + let channel = channel.clone(); + let settings = self.settings.clone(); + let handle = tokio::spawn(async move { + // Build our contracts and signers, if we fail here, bail + let setup = tokio::try_join!( + Self::make_home(&channel, &settings), + Self::make_connection_manager(&channel, &settings), + Self::make_signer(&channel, &settings), + ); + // Maybe bail + if let Err(error) = setup { + output + .send((channel.clone(), Err(error))) + .expect("Should not happen. Channel should be ok during operation"); + return; } - .await; - (killer.channel.clone(), fut) - }) - .collect::>(); - - let results = join_all(futs).await; - - let (failed, ok): (Vec<_>, Vec<_>) = - results.into_iter().partition(|(_, result)| result.is_err()); - - // We encountered errors - let bad = failed - .into_iter() - .map(|(channel, result)| (channel, vec![result.unwrap_err()])) - .collect::>(); - - // These were successful - let good = ok - .into_iter() - .map(|(channel, result)| (channel, result.unwrap())) - .collect::>(); - - build_output_message(bad, good) + // Create our signed failure notification and attempt to unenroll replica + let (home_contract, connection_manager, attestation_signer) = setup.unwrap(); + let result = async move { + let updater = home_contract + .updater() + .await + .map_err(Error::UpdaterAddress)?; + let failure = FailureNotification { + home_domain: home_contract.local_domain(), + updater: updater.into(), + }; + let signed_failure = failure + .sign_with(&attestation_signer) + .await + .map_err(Error::AttestationSignerFailed)?; + connection_manager + .unenroll_replica(&signed_failure) + .await + .map_err(Error::UnenrollmentFailed) + } + .await + .map(|tx_outcome| tx_outcome.txid); + output + .send((channel.clone(), result)) + .expect("Should not happen. Channel should be ok during operation"); + }); + handles.push(handle); + } + handles } } #[cfg(test)] mod test { use super::*; - use crate::App; + use crate::{App, Environment}; use nomad_test::test_utils; use nomad_xyz_configuration::{ChainConf, Connection}; use std::collections::HashMap; @@ -446,50 +358,18 @@ mod test { test_utils::run_test_with_env("../../fixtures/env.test-killswitch", || async move { let args = Args { app: App::TokenBridge, + environment: Environment::LocalPath, all: false, all_inbound: Some("avalanche".into()), // Unused network - pretty: false, + force: true, }; let settings = Settings::new().await; assert!(settings.is_ok()); let settings = settings.unwrap(); - let killswitch = KillSwitch::new(args, settings).await; + let killswitch = KillSwitch::new(&args, settings).await; assert_matches!(killswitch.unwrap_err(), Error::NoNetworks); }) .await } - - /// `ChannelKiller` with errors - fn make_bad_channel_killer() -> ChannelKiller { - let channel = Channel { - home: "goerli".into(), - replica: "rinkeby".into(), - }; - ChannelKiller { - channel: channel.clone(), - home_contract: None, - connection_manager: None, - attestation_signer: None, - errors: vec![ - Error::MissingTxSubmitterConf(channel.home.clone()), - Error::MissingTxSubmitterConf(channel.replica.clone()), - ], - } - } - - #[test] - fn it_has_errors() { - let killer = make_bad_channel_killer(); - assert!(killer.has_errors()); - } - - #[test] - fn it_takes_errors() { - let mut killer = make_bad_channel_killer(); - let errors = killer.take_all_errors(); - assert_eq!(errors.len(), 2); - assert_matches!(errors[0], Error::MissingTxSubmitterConf(_)); - assert_matches!(errors[1], Error::MissingTxSubmitterConf(_)); - } } diff --git a/tools/killswitch/src/main.rs b/tools/killswitch/src/main.rs index 8759cc74..cc731dd7 100644 --- a/tools/killswitch/src/main.rs +++ b/tools/killswitch/src/main.rs @@ -4,22 +4,50 @@ extern crate assert_matches; mod errors; mod killswitch; -mod output; +mod secrets; mod settings; -use crate::killswitch::KillSwitch; -use crate::output::Message; -use crate::{errors::Error, output::Output, settings::Settings}; +use crate::{errors::Error, killswitch::KillSwitch, secrets::Secrets, settings::Settings}; use clap::{ArgGroup, Parser, ValueEnum}; use std::{ env, io::{stdout, Write}, process::exit, + sync::mpsc::channel, }; +/// AWS settings +const AWS_REGION: &str = "us-west-2"; +const AWS_CREDENTIALS_PROFILE_DEVELOPMENT: &str = "nomad-xyz-dev"; +const AWS_CREDENTIALS_PROFILE_PRODUCTION: &str = "nomad-xyz-prod"; +const CONFIG_S3_BUCKET_DEVELOPMENT: &str = "nomad-killswitch-config-dev"; +const CONFIG_S3_BUCKET_PRODUCTION: &str = "nomad-killswitch-config-prod"; +const CONFIG_S3_KEY: &str = "config.yaml"; + +/// Local secrets. For testing only +const SECRETS_PATH_LOCAL_TESTING: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../fixtures/killswitch_secrets.testing.yaml" +); + /// Result returning KillSwitch `Error` pub(crate) type Result = std::result::Result; +/// The environment we're targeting +#[derive(ValueEnum, Clone, Debug, Eq, PartialEq)] +enum Environment { + /// The development environment + Development, + /// The production environment + Production, + /// Use local secrets. For testing only + #[clap(hide = true)] + LocalPath, + /// Pull all secrets from environment. For testing only + #[clap(hide = true)] + AlreadySet, +} + /// What we're killing, currently only `TokenBridge` #[derive(ValueEnum, Clone, Debug)] enum App { @@ -33,11 +61,15 @@ enum App { ArgGroup::new("which_networks") .required(true) .multiple(false) - .args(&["all", "all-inbound"]) + .args(&["all", "all_inbound"]) ))] struct Args { + /// Which environment to target + #[clap(long, value_enum)] + environment: Environment, + /// Which app to kill - #[clap(long, arg_enum)] + #[clap(long, value_enum)] app: App, /// Kill all available networks @@ -48,10 +80,11 @@ struct Args { #[clap(long, value_name = "NETWORK")] all_inbound: Option, - // The most common form of streaming JSON is line delimited - // hide this behind a (hidden) flag so it's not abused + /// Actually execute `killswitch`. This is an inverse of + /// a `--dry_run` flag and makes more sense given the destructive + /// nature of this utility #[clap(long, hide = true)] - pretty: bool, + force: bool, } /// Exit codes as found in @@ -60,56 +93,118 @@ enum ExitCode { BadConfig = 78, } -/// Print `Output` to stdout as json -fn report(message: Message, pretty: bool) { - let command = env::args().collect::>().join(" "); - let output = Output { command, message }; - let json = if pretty { - serde_json::to_string_pretty(&output) - } else { - serde_json::to_string(&output) - } - .expect("Serialization error. Should never happen"); - stdout() - .lock() - .write_all(&format!("{}\n", json).into_bytes()) - .expect("Write to stdout error. Should never happen"); +fn write_stdout(message: &str) { + stdout().lock().write_all(message.as_bytes()).unwrap() } /// KillSwitch entry point -#[tokio::main(flavor = "current_thread")] -async fn main() { +#[tokio::main(flavor = "multi_thread")] +async fn main() -> Result<()> { let args = Args::parse(); - let pretty = args.pretty; + let command = env::args().collect::>().join(" "); + write_stdout("\n"); + write_stdout(&format!("Running `{}`\n", command)); + + if Environment::AlreadySet != args.environment { + write_stdout("Fetching secrets from S3 using local AWS credentials... "); + let secrets = match &args.environment { + Environment::Development => { + Secrets::fetch( + AWS_CREDENTIALS_PROFILE_DEVELOPMENT, + AWS_REGION, + CONFIG_S3_BUCKET_DEVELOPMENT, + CONFIG_S3_KEY, + ) + .await + } + Environment::Production => { + Secrets::fetch( + AWS_CREDENTIALS_PROFILE_PRODUCTION, + AWS_REGION, + CONFIG_S3_BUCKET_PRODUCTION, + CONFIG_S3_KEY, + ) + .await + } + Environment::LocalPath => Secrets::load(SECRETS_PATH_LOCAL_TESTING).await, + Environment::AlreadySet => unreachable!(), + }; + if let Err(error) = secrets { + write_stdout(&format!("Failed: {}\n", error)); + exit(ExitCode::BadConfig as i32) + } + write_stdout("Ok\n"); + + // Set `Secrets` as environment variables for `Settings` to pick up + secrets.unwrap().set_environment(); + } - // Try to build `NomadConfig`, exiting immediately if we can't + write_stdout("Building settings from environment... "); let settings = Settings::new().await; if let Err(error) = settings { - // We've hit a blocking error, bail - report(error.into(), pretty); + write_stdout(&format!("Failed: {}\n", error)); exit(ExitCode::BadConfig as i32) } + write_stdout("Ok\n"); - // Try to build `KillSwitch`. If we hit `NoNetworks` error, nothing to do, bail - let killswitch = KillSwitch::new(args, settings.unwrap()).await; + let settings = settings.unwrap(); + + write_stdout("Checking `killswitch` for killable networks... "); + let killswitch = KillSwitch::new(&args, settings).await; if let Err(error) = killswitch { - // We've hit a blocking error, bail - report(error.into(), pretty); + write_stdout(&format!("Failed: {}\n", error)); exit(ExitCode::BadConfig as i32) } + write_stdout("Ok\n"); - // Get errors that block individual channels, report before proceeding. Do not bail - let (killswitch, errors) = killswitch.unwrap().get_blocking_errors().await; - if let Some(errors) = errors { - // Stream these blocking errors before running transactions - // so users can be updated as fast as possible - report(errors, pretty); - } + let killswitch = killswitch.unwrap(); - // Run all channels that *could* succeed - let results = killswitch.run().await; + write_stdout("\n"); + write_stdout("`killswitch` is ready to attempt to kill the selected channels:\n"); + for channel in killswitch.channels() { + write_stdout(&format!( + "[CHANNEL] {} -> {}\n", + channel.home, channel.replica + )); + } - // Give users final results, exit ok - report(results, pretty); - exit(ExitCode::Ok as i32) + if !&args.force { + write_stdout("\n"); + write_stdout("[NOTICE] Nothing killed!\n"); + write_stdout("\n"); + write_stdout("To kill the selected networks, run the same command again with the `--force` flag added.\n"); + write_stdout("\n"); + exit(ExitCode::Ok as i32) + } else { + write_stdout("\n"); + write_stdout("Running `killswitch`...\n"); + let (tx, rx) = channel(); + let handles = killswitch.run(tx); + for _ in 0..killswitch.channel_count() { + let (channel, result) = rx.recv().unwrap(); + write_stdout("\n"); + write_stdout(&format!( + "[CHANNEL] {} -> {}\n", + channel.home, channel.replica + )); + match result { + Ok(txid) => { + write_stdout(&format!( + "[SUCCESS] transaction id for unenrollment: {:?}\n", + txid + )); + } + Err(error) => { + write_stdout(&format!("[FAILURE] {}\n", error)); + } + } + } + write_stdout("\n"); + for handle in handles { + handle + .await + .expect("Should not happen. Errors should have been caught"); + } + exit(ExitCode::Ok as i32) + } } diff --git a/tools/killswitch/src/output.rs b/tools/killswitch/src/output.rs deleted file mode 100644 index 729bdf42..00000000 --- a/tools/killswitch/src/output.rs +++ /dev/null @@ -1,321 +0,0 @@ -use crate::{errors::Error, killswitch::Channel}; -use ethers::prelude::H256; -use nomad_core::TxOutcome; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -/// KillSwitch response showing success / failure of configuration -/// and tx submission. Gets serialized to json -#[derive(Debug, Serialize, Deserialize)] -pub(crate) struct Output { - /// The original command `killswitch` was run with - pub command: String, - /// The success / failure message - pub message: Message, -} - -/// A wrapper for success / failure messages -#[derive(Debug, Serialize, Deserialize)] -#[serde(untagged)] -pub(crate) enum Message { - /// An wrapper for a single error we bailed on - SimpleError(SimpleErrorOutput), - /// A full results message - FullMessage(HomesOutput), -} - -impl From for Message { - /// Convert a blocking `Error` to `Message` - fn from(error: Error) -> Self { - Message::SimpleError(SimpleErrorOutput::Result { - status: Status::Error, - message: format!("{}", error), - }) - } -} - -/// Simple error output -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub(crate) enum SimpleErrorOutput { - /// Simple errors have a result object - Result { - /// Currently, this will always be `Error` - status: Status, - /// The error message - message: String, - }, -} - -/// Map of homes by name -#[derive(Debug, Serialize, Deserialize)] -pub(crate) struct HomesOutput { - /// Homes by name - homes: HashMap, -} - -/// Home -#[derive(Debug, Serialize, Deserialize)] -pub(crate) struct HomeOutput { - /// `Success` if *all* replicas succeeded - status: Status, - /// Map of replicas - message: ReplicasOutput, -} - -/// Map of replicas by name -#[derive(Debug, Serialize, Deserialize)] -pub(crate) struct ReplicasOutput { - /// Replica by name - replicas: HashMap, -} - -/// Replica -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -enum ReplicaOutput { - /// Replicas have a result object - Result { - /// Replica status - status: Status, - /// Will be populated if successful - tx_hash: Option, - /// Will be populated with errors on failure - message: Option>, - }, -} - -/// Status -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub(crate) enum Status { - /// Successful kill - Success, - /// Errors encountered - Error, -} - -/// Build output `Message::FullMessage(Homes)` accepting a set -/// of errored channels as well as successful channels -#[allow(clippy::type_complexity)] -pub(crate) fn build_output_message( - bad: Vec<(Channel, Vec)>, - good: Vec<(Channel, TxOutcome)>, -) -> Message { - // Failed channels - let mut replicas = bad - .into_iter() - .map(|(channel, errors)| { - let replica = ReplicaOutput::Result { - status: Status::Error, - tx_hash: None, - message: Some( - errors - .iter() - // Serializing these requires upstream errors to also be - // Serialize, just use Display - .map(|e| format!("{}", e)) - .collect::>(), - ), - }; - (channel.clone(), (false, (channel.replica, replica))) - }) - .collect::>(); - - // Successful channels - replicas.extend(good.into_iter().map(|(channel, tx)| { - let replica = ReplicaOutput::Result { - status: Status::Success, - tx_hash: Some(tx.txid), - message: None, - }; - (channel.clone(), (true, (channel.replica, replica))) - })); - - // Map replicas to homes - let mut homes: HashMap> = HashMap::new(); - for (channel, (success, replica)) in replicas { - if let Some(replicas) = homes.get_mut(&channel.home) { - replicas.push((success, replica)); - } else { - homes.insert(channel.home, vec![(success, replica)]); - } - } - - // Full output - Message::FullMessage(HomesOutput { - homes: homes - .into_iter() - .map(|(home, replicas)| { - // report error for *any* errors encountered - let success = replicas.iter().all(|(s, _)| *s); - ( - home, - HomeOutput { - status: if success { - Status::Success - } else { - Status::Error - }, - message: ReplicasOutput { - replicas: replicas.into_iter().map(|(_, replica)| replica).collect(), - }, - }, - ) - }) - .collect(), - }) -} - -#[cfg(test)] -mod test { - use super::*; - use nomad_core::TxOutcome; - use std::str::FromStr; - - #[test] - fn it_produces_correct_simple_error_output() { - let error = Error::BadConfigVar("/bad/path".into()); - let message: Message = error.into(); - let simple_error = match message { - Message::SimpleError(error) => error, - _ => panic!("Match error. Should never happen"), - }; - let json = serde_json::to_string_pretty(&simple_error).unwrap(); - let simple_error: SimpleErrorOutput = serde_json::from_str(&json).unwrap(); - - assert_matches!( - simple_error, - SimpleErrorOutput::Result { status: Status::Error, message } - if message == "BadConfigVar: Unable to load config from: /bad/path" - ); - } - - #[test] - fn it_produces_correct_bad_output() { - let channel1 = Channel { - home: "ethereum".into(), - replica: "avalanche".into(), - }; - let channel2 = Channel { - home: "avalanche".into(), - replica: "ethereum".into(), - }; - let error1 = Error::MissingRPC(channel1.home.clone()); - let error2 = Error::MissingAttestationSignerConf(channel1.home.clone()); - let error3 = Error::MissingTxSubmitterConf(channel2.replica.clone()); - let bad = vec![ - (channel1, vec![error1, error2]), - (channel2.clone(), vec![error3]), - ]; - let homes = match build_output_message(bad, vec![]) { - Message::FullMessage(homes) => homes, - _ => panic!("Match error. Should never happen"), - }; - let json = serde_json::to_string(&homes).unwrap(); - - let result: HomesOutput = serde_json::from_str(&json).unwrap(); - let ethereum = result.homes.get("ethereum").unwrap(); - let avalanche = result.homes.get("avalanche").unwrap(); - let error = format!( - "{}", - Error::MissingTxSubmitterConf(channel2.replica.clone()) - ); - assert_matches!(ethereum.status, Status::Error); - assert_matches!(avalanche.status, Status::Error); - assert_matches!( - avalanche.message.replicas.get("ethereum").unwrap(), - ReplicaOutput::Result { - message: Some(errors), - .. - } if errors.first().unwrap() == &error - ); - } - - #[test] - fn it_produces_correct_good_output() { - let channel1 = Channel { - home: "ethereum".into(), - replica: "avalanche".into(), - }; - let channel2 = Channel { - home: "avalanche".into(), - replica: "ethereum".into(), - }; - let tx1 = TxOutcome { - txid: H256::from_str( - "0x1111111111111111111111111111111111111111111111111111111111111111", - ) - .unwrap(), - }; - let tx2 = TxOutcome { - txid: H256::from_str( - "0x2222222222222222222222222222222222222222222222222222222222222222", - ) - .unwrap(), - }; - let good = vec![(channel1, tx1), (channel2, tx2)]; - let homes = match build_output_message(vec![], good) { - Message::FullMessage(homes) => homes, - _ => panic!("Match error. Should never happen"), - }; - let json = serde_json::to_string(&homes).unwrap(); - - let result: HomesOutput = serde_json::from_str(&json).unwrap(); - let ethereum = result.homes.get("ethereum").unwrap(); - let avalanche = result.homes.get("avalanche").unwrap(); - assert_matches!(ethereum.status, Status::Success); - assert_matches!(avalanche.status, Status::Success); - assert_matches!( - avalanche.message.replicas.get("ethereum").unwrap(), - ReplicaOutput::Result { tx_hash: Some(tx), .. } if tx == &tx2.txid - ); - } - - #[test] - fn it_produces_correct_mixed_output() { - let channel1 = Channel { - home: "ethereum".into(), - replica: "avalanche".into(), - }; - let channel2 = Channel { - home: "avalanche".into(), - replica: "ethereum".into(), - }; - let tx = TxOutcome { - txid: H256::from_str( - "0x1111111111111111111111111111111111111111111111111111111111111111", - ) - .unwrap(), - }; - let error = Error::MissingTxSubmitterConf(channel1.replica.clone()); - let bad = vec![(channel1.clone(), vec![error])]; - let good = vec![(channel2, tx)]; - let homes = match build_output_message(bad, good) { - Message::FullMessage(homes) => homes, - _ => panic!("Match error. Should never happen"), - }; - let json = serde_json::to_string(&homes).unwrap(); - - let result: HomesOutput = serde_json::from_str(&json).unwrap(); - let ethereum = result.homes.get("ethereum").unwrap(); - let avalanche = result.homes.get("avalanche").unwrap(); - let error = format!( - "{}", - Error::MissingTxSubmitterConf(channel1.replica.clone()) - ); - assert_matches!(ethereum.status, Status::Error); - assert_matches!(avalanche.status, Status::Success); - assert_matches!( - avalanche.message.replicas.get("ethereum").unwrap(), - ReplicaOutput::Result { tx_hash: Some(t), .. } if t == &tx.txid - ); - assert_matches!( - ethereum.message.replicas.get("avalanche").unwrap(), - ReplicaOutput::Result { - message: Some(errors), - .. - } if errors.first().unwrap() == &error - ); - } -} diff --git a/tools/killswitch/src/secrets.rs b/tools/killswitch/src/secrets.rs new file mode 100644 index 00000000..7cb1c781 --- /dev/null +++ b/tools/killswitch/src/secrets.rs @@ -0,0 +1,191 @@ +use crate::{errors::Error, Result}; +use rusoto_core::{credential::ProfileProvider, Client, HttpClient, Region}; +use rusoto_s3::{GetObjectRequest, S3Client, S3}; +use std::{collections::HashMap, default::Default, env, fs, str::FromStr}; +use tokio::io::AsyncReadExt; + +/// A model for our remote secrets file +#[derive(Debug, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct Secrets { + /// Equivalent to `CONFIG_URL` + pub config_url: String, + /// Equivalent to `CONFIG_PATH`. Included for testing only + pub config_path: Option, + /// Equivalent to the set of `_CONNECTION_URL` + pub connection_urls: HashMap, + /// Equivalent to the set of `_TXSIGNER_ID` + pub txsigner_ids: HashMap, + /// Equivalent to the set of `_ATTESTATION_SIGNER_ID` + pub attestation_signer_ids: HashMap, +} + +impl Secrets { + /// Create a `Secrets` by fetching yaml from an S3 bucket + pub(crate) async fn fetch( + profile: &str, + region: &str, + bucket: &str, + key: &str, + ) -> Result { + let credentials_provider = + ProfileProvider::with_default_credentials(profile).map_err(Error::BadCredentials)?; + let client = Client::new_with(credentials_provider, HttpClient::new().unwrap()); + let s3_client = S3Client::new_with_client(client, Region::from_str(region).unwrap()); + Self::fetch_with_client(s3_client, bucket, key).await + } + + /// Create a `Secrets` by fetching yaml from an S3 bucket given an `S3Client` + pub(crate) async fn fetch_with_client( + client: S3Client, + bucket: &str, + key: &str, + ) -> Result { + let mut yaml = String::new(); + let request = GetObjectRequest { + bucket: bucket.into(), + key: key.into(), + ..Default::default() + }; + let response = client + .get_object(request) + .await + .map_err(Error::RusotoGetObject)?; + response + .body + .unwrap() + .into_async_read() + .read_to_string(&mut yaml) + .await + .map_err(Error::BadIO)?; + serde_yaml::from_slice::(yaml.as_bytes()).map_err(Error::YamlBadDeser) + } + + /// Set `Secrets` as environment variables so they can be picked up by `Settings` + pub(crate) fn set_environment(&self) { + // We've included `CONFIG_PATH` for testing and `CONFIG_URL` takes precedence + // so force precedence here. + if let Some(ref path) = self.config_path { + env::set_var("CONFIG_PATH", path); + } else { + env::set_var("CONFIG_URL", &self.config_url); + } + // Set everything else + for (k, v) in self.connection_urls.iter() { + env::set_var(k, v); + } + for (k, v) in self.txsigner_ids.iter() { + env::set_var(k, v); + } + for (k, v) in self.attestation_signer_ids.iter() { + env::set_var(k, v); + } + // Set constant values that don't need to be in the secrets file + env::set_var("DEFAULT_RPCSTYLE", "ethereum"); + env::set_var("DEFAULT_SUBMITTER_TYPE", "local"); + } + + /// Create a `Secrets` by loading a local file. Included for testing only + pub(crate) async fn load(path: &str) -> Result { + let secrets = fs::read_to_string(path).unwrap(); + serde_yaml::from_slice::(secrets.as_bytes()).map_err(Error::YamlBadDeser) + } +} + +#[cfg(test)] +mod test { + use super::*; + use nomad_test::test_utils; + use rusoto_mock::{ + MockCredentialsProvider, MockRequestDispatcher, MultipleMockRequestDispatcher, + }; + use std::fs; + + fn mock_s3_client() -> S3Client { + let secrets_response = + fs::read_to_string("../../fixtures/killswitch_secrets.testing.yaml").unwrap(); + let request_dispatcher = MultipleMockRequestDispatcher::new([ + MockRequestDispatcher::default().with_body(&secrets_response), + ]); + S3Client::new_with( + request_dispatcher, + MockCredentialsProvider, + Region::default(), + ) + } + + #[tokio::test] + #[serial_test::serial] + async fn it_fetches_secrets_from_s3() { + let s3_client = mock_s3_client(); + let secrets = Secrets::fetch_with_client(s3_client, "any-bucket", "any-key.yaml").await; + assert!(secrets.is_ok()); + } + + #[tokio::test] + #[serial_test::serial] + async fn it_sets_secrets_as_env_vars() { + let s3_client = mock_s3_client(); + let secrets = Secrets::fetch_with_client(s3_client, "any-bucket", "any-key.yaml").await; + assert!(secrets.is_ok()); + + secrets.unwrap().set_environment(); + + assert_eq!( + env::var("CONFIG_PATH").unwrap(), + "fixtures/killswitch_config.json" + ); + assert_eq!( + env::var("RINKEBY_CONNECTION_URL").unwrap(), + "https://rinkeby-light.eth.linkpool.io.bad.url" + ); + assert_eq!( + env::var("POLYGONMUMBAI_CONNECTION_URL").unwrap(), + "https://rpc-mumbai.maticvigil.com.bad.url" + ); + assert_eq!( + env::var("EVMOSTESTNET_CONNECTION_URL").unwrap(), + "https://eth.bd.evmos.dev:8545.bad.url" + ); + assert_eq!( + env::var("GOERLI_CONNECTION_URL").unwrap(), + "https://goerli-light.eth.linkpool.io.bad.url" + ); + assert_eq!( + env::var("POLYGONMUMBAI_TXSIGNER_ID").unwrap(), + "00000000-0000-0000-0000-000000000000" + ); + assert_eq!( + env::var("GOERLI_TXSIGNER_ID").unwrap(), + "00000000-0000-0000-0000-000000000000" + ); + assert_eq!( + env::var("EVMOSTESTNET_TXSIGNER_ID").unwrap(), + "00000000-0000-0000-0000-000000000000" + ); + assert_eq!( + env::var("RINKEBY_TXSIGNER_ID").unwrap(), + "00000000-0000-0000-0000-000000000000" + ); + assert_eq!( + env::var("EVMOSTESTNET_ATTESTATION_SIGNER_ID").unwrap(), + "00000000-0000-0000-0000-000000000000" + ); + assert_eq!( + env::var("POLYGONMUMBAI_ATTESTATION_SIGNER_ID").unwrap(), + "00000000-0000-0000-0000-000000000000" + ); + assert_eq!( + env::var("RINKEBY_ATTESTATION_SIGNER_ID").unwrap(), + "00000000-0000-0000-0000-000000000000" + ); + assert_eq!( + env::var("GOERLI_ATTESTATION_SIGNER_ID").unwrap(), + "00000000-0000-0000-0000-000000000000" + ); + assert_eq!(env::var("DEFAULT_RPCSTYLE").unwrap(), "ethereum"); + assert_eq!(env::var("DEFAULT_SUBMITTER_TYPE").unwrap(), "local"); + + test_utils::clear_env_vars(); + } +} diff --git a/tools/killswitch/src/settings.rs b/tools/killswitch/src/settings.rs index 2765f704..5d9f4dd7 100644 --- a/tools/killswitch/src/settings.rs +++ b/tools/killswitch/src/settings.rs @@ -3,7 +3,7 @@ use nomad_xyz_configuration::{agent::SignerConf, ChainConf, NomadConfig, TxSubmi use std::{collections::HashMap, env, result::Result}; /// KillSwitch `Settings` contains all available configuration for all networks present -#[derive(Debug)] +#[derive(Debug, Clone)] pub(crate) struct Settings { /// NomadConfig pub config: NomadConfig,