From e64e1ddb994fb41e982404625e6c027f2f408e11 Mon Sep 17 00:00:00 2001 From: Dzejkop Date: Tue, 21 Nov 2023 23:48:07 +0100 Subject: [PATCH 1/4] Initial --- Cargo.lock | 5240 +++++++++++++++++ Cargo.toml | 74 + README.md | 13 +- TODO.md | 11 + config.toml | 20 + crates/postgres-docker-utils/Cargo.toml | 10 + crates/postgres-docker-utils/src/lib.rs | 117 + db/migrations/001_init.sql | 88 + manual_test.nu | 26 + rust-toolchain.toml | 2 + rustfmt.toml | 3 + src/app.rs | 125 + src/aws.rs | 1 + src/aws/ethers_signer.rs | 365 ++ src/aws/ethers_signer/utils.rs | 98 + src/broadcast_utils.rs | 77 + src/broadcast_utils/gas_estimation.rs | 97 + .../fee_history_ethereum_17977856.json | 32 + .../fee_history_optimism_108598243.json | 20 + src/client.rs | 1 + src/config.rs | 91 + src/db.rs | 494 ++ src/db/data.rs | 169 + src/keys.rs | 131 + src/keys/universal_signer.rs | 87 + src/lib.rs | 12 + src/main.rs | 66 + src/serde_utils.rs | 1 + src/serde_utils/decimal_u256.rs | 42 + src/server.rs | 172 + src/server/data.rs | 59 + src/server/middleware.rs | 5 + src/server/middleware/auth_middleware.rs | 144 + .../middleware/log_response_middleware.rs | 24 + src/task_backoff.rs | 80 + src/tasks.rs | 7 + src/tasks/broadcast.rs | 84 + src/tasks/escalate.rs | 85 + src/tasks/index.rs | 127 + 39 files changed, 8298 insertions(+), 2 deletions(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 TODO.md create mode 100644 config.toml create mode 100644 crates/postgres-docker-utils/Cargo.toml create mode 100644 crates/postgres-docker-utils/src/lib.rs create mode 100644 db/migrations/001_init.sql create mode 100644 manual_test.nu create mode 100644 rust-toolchain.toml create mode 100644 rustfmt.toml create mode 100644 src/app.rs create mode 100644 src/aws.rs create mode 100644 src/aws/ethers_signer.rs create mode 100644 src/aws/ethers_signer/utils.rs create mode 100644 src/broadcast_utils.rs create mode 100644 src/broadcast_utils/gas_estimation.rs create mode 100644 src/broadcast_utils/gas_estimation/fee_history_ethereum_17977856.json create mode 100644 src/broadcast_utils/gas_estimation/fee_history_optimism_108598243.json create mode 100644 src/client.rs create mode 100644 src/config.rs create mode 100644 src/db.rs create mode 100644 src/db/data.rs create mode 100644 src/keys.rs create mode 100644 src/keys/universal_signer.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/serde_utils.rs create mode 100644 src/serde_utils/decimal_u256.rs create mode 100644 src/server.rs create mode 100644 src/server/data.rs create mode 100644 src/server/middleware.rs create mode 100644 src/server/middleware/auth_middleware.rs create mode 100644 src/server/middleware/log_response_middleware.rs create mode 100644 src/task_backoff.rs create mode 100644 src/tasks.rs create mode 100644 src/tasks/broadcast.rs create mode 100644 src/tasks/escalate.rs create mode 100644 src/tasks/index.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..5e1ee40 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,5240 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "ahash" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "async-trait" +version = "0.1.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "auto_impl" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "aws-config" +version = "0.57.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2bf00cb9416daab4ce4927c54ebe63c08b9caf4d7b9314b6d7a4a2c5a1afb09" +dependencies = [ + "aws-credential-types", + "aws-http", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http", + "hyper", + "ring 0.17.5", + "time", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "0.57.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb9073c88dbf12f68ce7d0e149f989627a1d1ae3d2b680459f04ccc29d1cbd0f" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-http" +version = "0.57.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24067106d09620cf02d088166cdaedeaca7146d4d499c41b37accecbea11b246" +dependencies = [ + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http", + "http-body", + "pin-project-lite", + "tracing", +] + +[[package]] +name = "aws-runtime" +version = "0.57.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6ee0152c06d073602236a4e94a8c52a327d310c1ecd596570ce795af8777ff" +dependencies = [ + "aws-credential-types", + "aws-http", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "fastrand", + "http", + "percent-encoding", + "tracing", + "uuid 1.5.0", +] + +[[package]] +name = "aws-sdk-kms" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "674c06944cbef8df0c5ab43226f85ac28a15e6f4498d74aa200e00588b9b75e4" +dependencies = [ + "aws-credential-types", + "aws-http", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http", + "regex", + "tracing", +] + +[[package]] +name = "aws-sdk-sso" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb8158015232b4596ccef74a205600398e152d704b40b7ec9f486092474d7fa" +dependencies = [ + "aws-credential-types", + "aws-http", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http", + "regex", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36a1493e1c57f173e53621935bfb5b6217376168dbdb4cd459aebcf645924a48" +dependencies = [ + "aws-credential-types", + "aws-http", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http", + "regex", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e032b77f5cd1dd3669d777a38ac08cbf8ec68e29460d4ef5d3e50cffa74ec75a" +dependencies = [ + "aws-credential-types", + "aws-http", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "http", + "regex", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "0.57.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64f81a6abc4daab06b53cabf27c54189928893283093e37164ca53aa47488a5b" +dependencies = [ + "aws-credential-types", + "aws-smithy-http", + "aws-smithy-runtime-api", + "bytes", + "form_urlencoded", + "hex", + "hmac", + "http", + "once_cell", + "percent-encoding", + "regex", + "sha2", + "time", + "tracing", +] + +[[package]] +name = "aws-smithy-async" +version = "0.57.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe53fccd3b10414b9cae63767a15a2789b34e6c6727b6e32b33e8c7998a3e80" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-http" +version = "0.57.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7972373213d1d6e619c0edc9dda2d6634154e4ed75c5e0b2bf065cd5ec9f0d1" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http", + "http-body", + "once_cell", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.57.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d64d5af16dd585de9ff6c606423c1aaad47c6baa38de41c2beb32ef21c6645" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-query" +version = "0.57.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7527bf5335154ba1b285479c50b630e44e93d1b4a759eaceb8d0bf9fbc82caa5" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "0.57.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "839b363adf3b2bdab2742a1f540fec23039ea8bc9ec0f9f61df48470cfe5527b" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "http", + "http-body", + "hyper", + "hyper-rustls", + "once_cell", + "pin-project-lite", + "pin-utils", + "rustls", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "0.57.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f24ecc446e62c3924539e7c18dec8038dba4fdf8718d5c2de62f9d2fecca8ba9" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "0.57.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051de910296522a21178a2ea402ea59027eef4b63f1cef04a0be2bb5e25dea03" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http", + "http-body", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.57.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb1e3ac22c652662096c8e37a6f9af80c6f3520cab5610b2fe76c725bce18eac" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "0.57.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048bbf1c24cdf4eb1efcdc243388a93a90ebf63979e25fc1c7b8cbd9cb6beb38" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "http", + "rustc_version", + "tracing", +] + +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "headers", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +dependencies = [ + "serde", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bs58" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +dependencies = [ + "sha2", + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +dependencies = [ + "serde", +] + +[[package]] +name = "bytes-utils" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e47d3a8076e283f3acd27400535992edb3ba4b5bb72f8891ad8fbe7932a7d4b9" +dependencies = [ + "bytes", + "either", +] + +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12024c4645c97566567129c204f65d5815a8c9aecf30fcbe682b2fe034996d36" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "4.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "coins-bip32" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b6be4a5df2098cd811f3194f64ddb96c267606bffd9689ac7b0160097b01ad3" +dependencies = [ + "bs58", + "coins-core", + "digest", + "hmac", + "k256", + "serde", + "sha2", + "thiserror", +] + +[[package]] +name = "coins-bip39" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8fba409ce3dc04f7d804074039eb68b960b0829161f8e06c95fea3f122528" +dependencies = [ + "bitvec", + "coins-bip32", + "hmac", + "once_cell", + "pbkdf2 0.12.2", + "rand", + "sha2", + "thiserror", +] + +[[package]] +name = "coins-core" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5286a0843c21f8367f7be734f89df9b822e0321d8bcce8d6e735aadff7d74979" +dependencies = [ + "base64 0.21.5", + "bech32", + "bs58", + "digest", + "generic-array", + "hex", + "ripemd", + "serde", + "serde_derive", + "sha2", + "sha3", + "thiserror", +] + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "config" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" +dependencies = [ + "async-trait", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml 0.5.11", + "yaml-rust", +] + +[[package]] +name = "const-hex" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5104de16b218eddf8e34ffe2f86f74bfa4e61e95a1b89732fccf6325efd0557" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "proptest", + "serde", +] + +[[package]] +name = "const-oid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "cpufeatures" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28f85c3514d2a6e64160359b45a3918c3b4178bcbf4ae5d03ab2d02e521c479a" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "data-encoding" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +dependencies = [ + "serde", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9775b22bc152ad86a0cf23f0f348b884b26add12bf741e7ffc4d4ab2ab4d205" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "ena" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" +dependencies = [ + "log", +] + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enr" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe81b5c06ecfdbc71dd845216f225f53b62a10cb8a16c946836a3467f701d05b" +dependencies = [ + "base64 0.21.5", + "bytes", + "hex", + "k256", + "log", + "rand", + "rlp", + "serde", + "sha3", + "zeroize", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys", +] + +[[package]] +name = "eth-keystore" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" +dependencies = [ + "aes", + "ctr", + "digest", + "hex", + "hmac", + "pbkdf2 0.11.0", + "rand", + "scrypt", + "serde", + "serde_json", + "sha2", + "sha3", + "thiserror", + "uuid 0.8.2", +] + +[[package]] +name = "ethabi" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "scale-info", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "primitive-types", + "scale-info", + "uint", +] + +[[package]] +name = "ethers" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5344eea9b20effb5efeaad29418215c4d27017639fd1f908260f59cbbd226e" +dependencies = [ + "ethers-addressbook", + "ethers-contract", + "ethers-core", + "ethers-etherscan", + "ethers-middleware", + "ethers-providers", + "ethers-signers", + "ethers-solc", +] + +[[package]] +name = "ethers-addressbook" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c405f24ea3a517899ba7985385c43dc4a7eb1209af3b1e0a1a32d7dcc7f8d09" +dependencies = [ + "ethers-core", + "once_cell", + "serde", + "serde_json", +] + +[[package]] +name = "ethers-contract" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0111ead599d17a7bff6985fd5756f39ca7033edc79a31b23026a8d5d64fa95cd" +dependencies = [ + "const-hex", + "ethers-contract-abigen", + "ethers-contract-derive", + "ethers-core", + "ethers-providers", + "futures-util", + "once_cell", + "pin-project", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "ethers-contract-abigen" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51258120c6b47ea9d9bec0d90f9e8af71c977fbefbef8213c91bfed385fe45eb" +dependencies = [ + "Inflector", + "const-hex", + "dunce", + "ethers-core", + "ethers-etherscan", + "eyre", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "reqwest", + "serde", + "serde_json", + "syn 2.0.39", + "toml 0.8.8", + "walkdir", +] + +[[package]] +name = "ethers-contract-derive" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936e7a0f1197cee2b62dc89f63eff3201dbf87c283ff7e18d86d38f83b845483" +dependencies = [ + "Inflector", + "const-hex", + "ethers-contract-abigen", + "ethers-core", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.39", +] + +[[package]] +name = "ethers-core" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f03e0bdc216eeb9e355b90cf610ef6c5bb8aca631f97b5ae9980ce34ea7878d" +dependencies = [ + "arrayvec", + "bytes", + "cargo_metadata", + "chrono", + "const-hex", + "elliptic-curve", + "ethabi", + "generic-array", + "k256", + "num_enum", + "once_cell", + "open-fastrlp", + "rand", + "rlp", + "serde", + "serde_json", + "strum", + "syn 2.0.39", + "tempfile", + "thiserror", + "tiny-keccak", + "unicode-xid", +] + +[[package]] +name = "ethers-etherscan" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abbac2c890bdbe0f1b8e549a53b00e2c4c1de86bb077c1094d1f38cdf9381a56" +dependencies = [ + "chrono", + "ethers-core", + "reqwest", + "semver", + "serde", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "ethers-middleware" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681ece6eb1d10f7cf4f873059a77c04ff1de4f35c63dd7bccde8f438374fcb93" +dependencies = [ + "async-trait", + "auto_impl", + "ethers-contract", + "ethers-core", + "ethers-etherscan", + "ethers-providers", + "ethers-signers", + "futures-channel", + "futures-locks", + "futures-util", + "instant", + "reqwest", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "tracing-futures", + "url", +] + +[[package]] +name = "ethers-providers" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25d6c0c9455d93d4990c06e049abf9b30daf148cf461ee939c11d88907c60816" +dependencies = [ + "async-trait", + "auto_impl", + "base64 0.21.5", + "bytes", + "const-hex", + "enr", + "ethers-core", + "futures-core", + "futures-timer", + "futures-util", + "hashers", + "http", + "instant", + "jsonwebtoken", + "once_cell", + "pin-project", + "reqwest", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-tungstenite", + "tracing", + "tracing-futures", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "ws_stream_wasm", +] + +[[package]] +name = "ethers-signers" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cb1b714e227bbd2d8c53528adb580b203009728b17d0d0e4119353aa9bc5532" +dependencies = [ + "async-trait", + "coins-bip32", + "coins-bip39", + "const-hex", + "elliptic-curve", + "eth-keystore", + "ethers-core", + "rand", + "sha2", + "thiserror", + "tracing", +] + +[[package]] +name = "ethers-solc" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64f710586d147864cff66540a6d64518b9ff37d73ef827fee430538265b595f" +dependencies = [ + "cfg-if", + "const-hex", + "dirs", + "dunce", + "ethers-core", + "glob", + "home", + "md-5", + "num_cpus", + "once_cell", + "path-slash", + "rayon", + "regex", + "semver", + "serde", + "serde_json", + "solang-parser", + "svm-rs", + "thiserror", + "tiny-keccak", + "tokio", + "tracing", + "walkdir", + "yansi", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "spin 0.9.8", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-executor" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-locks" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ec6fe3675af967e67c5536c0b9d44e34e6c52f86bedc4ea49c5317b8e94d06" +dependencies = [ + "futures-channel", + "futures-task", +] + +[[package]] +name = "futures-macro" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +dependencies = [ + "gloo-timers", + "send_wrapper 0.4.0", +] + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.7", +] + +[[package]] +name = "hashbrown" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +dependencies = [ + "ahash 0.8.6", + "allocator-api2", +] + +[[package]] +name = "hashers" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2bca93b15ea5a746f220e56587f71e73c6165eab783df9e26590069953e3c30" +dependencies = [ + "fxhash", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.2", +] + +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64 0.21.5", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime", + "serde", +] + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http", + "hyper", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown 0.14.2", +] + +[[package]] +name = "indoc" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + +[[package]] +name = "jsonwebtoken" +version = "8.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" +dependencies = [ + "base64 0.21.5", + "pem", + "ring 0.16.20", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "k256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f01b677d82ef7a676aa37e099defd83a28e15687112cafdd112d60236b6115b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8" +dependencies = [ + "ascii-canvas", + "bit-set", + "diff", + "ena", + "is-terminal", + "itertools 0.10.5", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.7.5", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", +] + +[[package]] +name = "lalrpop-util" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "libc" +version = "0.2.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.1", + "libc", + "redox_syscall", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683751d591e6d81200c39fb0d1032608b77724f34114db54f571ff1317b337c0" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c11e44798ad209ccdd91fc192f0526a369a01234f7373e1b141c96d7cee4f0e" +dependencies = [ + "proc-macro-crate 2.0.0", + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "open-fastrlp" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", + "ethereum-types", + "open-fastrlp-derive", +] + +[[package]] +name = "open-fastrlp-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" +dependencies = [ + "bytes", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown 0.12.3", +] + +[[package]] +name = "outref" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parity-scale-codec" +version = "3.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dec8a8073036902368c2cdc0387e85ff9a37054d7e7c98e592145e0c92cd4fb" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312270ee71e1cd70289dacf597cab7b207aa107d2f28191c2ae45b2ece18a260" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "path-slash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pem" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +dependencies = [ + "base64 0.13.1", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pest" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "pest_meta" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared 0.11.2", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared 0.11.2", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared 0.11.2", + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "postgres-docker-utils" +version = "0.1.0" +dependencies = [ + "eyre", + "test-case", + "tokio", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn 2.0.39", +] + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "scale-info", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +dependencies = [ + "toml_edit 0.20.7", +] + +[[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 1.0.109", + "version_check", +] + +[[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", +] + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +dependencies = [ + "bitflags 2.4.1", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax 0.8.2", + "unarray", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.3", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "reqwest" +version = "0.11.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +dependencies = [ + "base64 0.21.5", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "system-configuration", + "tokio", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.25.2", + "winreg", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rlp-derive", + "rustc-hex", +] + +[[package]] +name = "rlp-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ron" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +dependencies = [ + "base64 0.13.1", + "bitflags 1.3.2", + "serde", +] + +[[package]] +name = "rsa" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ef35bf3e7fe15a53c4ab08a998e42271eab13eb0db224126bc7bc4c4bad96d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ad981d6c340a49cdc40a1028d9c6084ec7e9fa33fcb839cab656a267071e234" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustls" +version = "0.21.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" +dependencies = [ + "log", + "ring 0.17.5", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.5", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.5", + "untrusted 0.9.0", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scale-info" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7d66a1128282b7ef025a8ead62a4a9fcf017382ec53b8ffbf4d7bf77bd3c60" +dependencies = [ + "cfg-if", + "derive_more", + "parity-scale-codec", + "scale-info-derive", +] + +[[package]] +name = "scale-info-derive" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf2c68b89cafb3b8d918dd07b42be0da66ff202cf1155c5739a4e0c1ea0dc19" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scrypt" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" +dependencies = [ + "hmac", + "pbkdf2 0.11.0", + "salsa20", + "sha2", +] + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring 0.17.5", + "untrusted 0.9.0", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +dependencies = [ + "serde", +] + +[[package]] +name = "send_wrapper" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + +[[package]] +name = "serde" +version = "1.0.192" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.192" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "service" +version = "0.1.0" +dependencies = [ + "async-trait", + "aws-config", + "aws-credential-types", + "aws-sdk-kms", + "aws-smithy-types", + "aws-types", + "axum", + "chrono", + "clap", + "config", + "dotenv", + "ethers", + "ethers-signers", + "eyre", + "futures", + "headers", + "hex", + "humantime", + "humantime-serde", + "hyper", + "indoc", + "postgres-docker-utils", + "rand", + "reqwest", + "serde", + "serde_json", + "sha3", + "spki", + "sqlx", + "strum", + "test-case", + "thiserror", + "tokio", + "toml 0.8.8", + "tower-http", + "tracing", + "tracing-subscriber", + "uuid 0.8.2", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "solang-parser" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c425ce1c59f4b154717592f0bdf4715c3a1d55058883622d3157e1f0908a5b26" +dependencies = [ + "itertools 0.11.0", + "lalrpop", + "lalrpop-util", + "phf", + "thiserror", + "unicode-xid", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85" +dependencies = [ + "itertools 0.11.0", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e50c216e3624ec8e7ecd14c6a6a6370aad6ee5d8cfc3ab30b5162eeeef2ed33" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d" +dependencies = [ + "ahash 0.8.6", + "atoi", + "byteorder", + "bytes", + "crc", + "crossbeam-queue", + "dotenvy", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", + "webpki-roots 0.24.0", +] + +[[package]] +name = "sqlx-macros" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a793bb3ba331ec8359c1853bd39eed32cdd7baaf22c35ccf5c92a7e8d1189ec" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4ee1e104e00dedb6aa5ffdd1343107b0a4702e862a84320ee7cc74782d96fc" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db" +dependencies = [ + "atoi", + "base64 0.21.5", + "bitflags 2.4.1", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624" +dependencies = [ + "atoi", + "base64 0.21.5", + "bitflags 2.4.1", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59dc83cf45d89c555a577694534fcd1b55c545a816c816ce51f20bbe56a4f3f" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "tracing", + "url", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared 0.10.0", + "precomputed-hash", +] + +[[package]] +name = "stringprep" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" +dependencies = [ + "finl_unicode", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.39", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "svm-rs" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20689c7d03b6461b502d0b95d6c24874c7d24dea2688af80486a130a06af3b07" +dependencies = [ + "dirs", + "fs2", + "hex", + "once_cell", + "reqwest", + "semver", + "serde", + "serde_json", + "sha2", + "thiserror", + "url", + "zip", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "test-case" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8f1e820b7f1d95a0cdbf97a5df9de10e1be731983ab943e56703ac1b8e9d425" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54c25e2cb8f5fcd7318157634e8838aa6f7e4715c96637f969fabaccd1ef5462" +dependencies = [ + "cfg-if", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "test-case-macros" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37cfd7bbc88a0104e304229fba519bdc45501a30b760fb72240342f1289ad257" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.39", + "test-case-core", +] + +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +dependencies = [ + "deranged", + "itoa", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.5.5", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +dependencies = [ + "futures-util", + "log", + "rustls", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots 0.25.2", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.21.0", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +dependencies = [ + "bitflags 2.4.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "serde", + "serde_json", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", + "tracing-serde", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "rustls", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "uuid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.39", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" + +[[package]] +name = "web-sys" +version = "0.3.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" +dependencies = [ + "rustls-webpki", +] + +[[package]] +name = "webpki-roots" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" + +[[package]] +name = "whoami" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys", +] + +[[package]] +name = "ws_stream_wasm" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "log", + "pharos", + "rustc_version", + "send_wrapper 0.6.0", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zerocopy" +version = "0.7.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" + +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2 0.11.0", + "sha1", + "time", + "zstd", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..fcd9ad3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,74 @@ +[package] +name = "service" +version = "0.1.0" +edition = "2021" + +[workspace] +members = ["crates/*"] + +[dependencies] +# Third Party +## AWS +aws-config = { version = "0.57.2" } +aws-sdk-kms = "0.36.0" +aws-smithy-types = "0.57.2" +aws-types = "0.57.2" +aws-credential-types = { version = "0.57.2", features = [ + "hardcoded-credentials", +] } + +## Other +serde = "1.0.136" +axum = { version = "0.6.20", features = ["headers"] } +thiserror = "1.0.50" +headers = "0.3.9" +humantime = "2.1.0" +humantime-serde = "1.1.1" +hyper = "0.14.27" +dotenv = "0.15.0" +clap = { version = "4.3.0", features = ["env", "derive"] } +ethers = { version = "2.0.11" } +ethers-signers = { version = "2.0.11" } +eyre = "0.6.5" +hex = "0.4.3" +reqwest = { version = "0.11.13", default-features = false, features = [ + "rustls-tls", +] } +serde_json = "1.0.91" +strum = { version = "0.25.0", features = ["derive"] } +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = [ + "env-filter", + "std", + "fmt", + "json", + "ansi", +] } +tower-http = { version = "0.4.4", features = ["trace"] } +uuid = { version = "0.8", features = ["v4"] } +futures = "0.3" +chrono = "0.4" +rand = "0.8.5" +sha3 = "0.10.8" +config = "0.13.3" +toml = "0.8.8" +sqlx = { version = "0.7.2", features = [ + "runtime-tokio", + "tls-rustls", + "postgres", + "migrate", +] } +spki = "0.7.2" +async-trait = "0.1.74" + +# Internal +postgres-docker-utils = { path = "crates/postgres-docker-utils" } + +[dev-dependencies] +test-case = "3.1.0" +indoc = "2.0.3" + +[[bin]] +name = "bootstrap" +path = "src/main.rs" diff --git a/README.md b/README.md index 518e8e3..8728317 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,11 @@ -# tx-sitter-monolith -Transaction sitter. But a glorious monolith. +# tx-sitter-service (proper name pending) + +## Testing locally + +Copy `.env.example` to `.env` or set `RUST_LOG=info,service=debug` to have logging. + +1. Spin up the database `docker run --rm -e POSTGRES_HOST_AUTH_METHOD=trust -p 5432:5432 postgres` +2. Spin up the chain `anvil --chain-id 31337 --block-time 2` +3. Start the service `cargo run` + +This will use the `config.toml` configuration. diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..65f1b8e --- /dev/null +++ b/TODO.md @@ -0,0 +1,11 @@ +# TODO + +1. [ ] Handling reorgs +2. [ ] Per network settings (i.e. max inflight txs, max gas price) +3. [ ] Multiple RPCs support +4. [ ] Cross-network dependencies (i.e. Optimism depends on L1 gas cost) +5. [ ] Transaction priority +6. [ ] Metrics +7. [ ] Tracing (add telemetry-batteries) +8. [ ] Automated testing +9. [ ] Parallelization in a few places diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..e4ca7dc --- /dev/null +++ b/config.toml @@ -0,0 +1,20 @@ +[service] +escalation_interval = "1m" + +[server] +host = "127.0.0.1:3000" +disable_auth = true + +[rpc] +rpcs = ["http://127.0.0.1:8545"] + +[database] +connection_string = "postgres://postgres:postgres@127.0.0.1:5432/database" + +[keys] +kind = "local" + +# Example KMS configuration +# [keys] +# kind = "kms" +# region = "us-east-1" diff --git a/crates/postgres-docker-utils/Cargo.toml b/crates/postgres-docker-utils/Cargo.toml new file mode 100644 index 0000000..5cb8ddd --- /dev/null +++ b/crates/postgres-docker-utils/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "postgres-docker-utils" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +eyre = "0.6.8" +test-case = "3.1.0" +tokio = { version = "1.0", features = ["full"] } diff --git a/crates/postgres-docker-utils/src/lib.rs b/crates/postgres-docker-utils/src/lib.rs new file mode 100644 index 0000000..478aca0 --- /dev/null +++ b/crates/postgres-docker-utils/src/lib.rs @@ -0,0 +1,117 @@ +use std::net::SocketAddr; +use std::process::{Command, Stdio}; +use std::time::Duration; + +use eyre::{Context, ContextCompat}; + +pub struct DockerContainerGuard { + container_id: String, + socket_addr: SocketAddr, +} + +impl DockerContainerGuard { + pub fn socket_addr(&self) -> SocketAddr { + self.socket_addr + } + + pub fn address(&self) -> String { + self.socket_addr.to_string() + } +} + +impl Drop for DockerContainerGuard { + fn drop(&mut self) { + if let Err(err) = + run_cmd(&format!("docker stop {}", &self.container_id)) + { + eprintln!("Failed to stop docker container: {}", err); + } + + // Redundant, but better safe than sorry + if let Err(err) = run_cmd(&format!("docker rm {}", &self.container_id)) + { + eprintln!("Failed to remove docker container: {}", err); + } + } +} + +/// Starts a postgres docker container that will accept all connections with a +/// random port assigned by the os or docker. The container will be stopped and +/// removed when the guard is dropped. +/// +/// Note that we're using sync code here so we'll block the executor - but this +/// is fine, because the spawned container will still run in the background. +pub async fn setup() -> eyre::Result { + let container_id = + run_cmd_to_output("docker run --rm -d -e POSTGRES_HOST_AUTH_METHOD=trust -p 5432 postgres") + .context("Starting the Postgres container")?; + + let exposed_port = run_cmd_to_output(&format!( + "docker container port {container_id} 5432" + )) + .context("Fetching container exposed port")?; + let socket_addr = parse_exposed_port(&exposed_port)?; + + std::thread::sleep(Duration::from_secs_f32(2.0)); + + Ok(DockerContainerGuard { + container_id, + socket_addr, + }) +} + +fn run_cmd_to_output(cmd_str: &str) -> eyre::Result { + let args: Vec<_> = cmd_str.split(' ').collect(); + let mut command = Command::new(args[0]); + + for arg in &args[1..] { + command.arg(arg); + } + + command.stdout(Stdio::piped()); + command.stderr(Stdio::null()); + + let Ok(output) = command.output() else { + return Ok(String::new()); + }; + + let utf = String::from_utf8(output.stdout)?; + + Ok(utf.trim().to_string()) +} + +fn run_cmd(cmd_str: &str) -> eyre::Result<()> { + run_cmd_to_output(cmd_str)?; + + Ok(()) +} + +fn parse_exposed_port(s: &str) -> eyre::Result { + let parts: Vec<_> = s + .split_whitespace() + .map(|s| s.trim()) + .filter(|s| !s.is_empty()) + .collect(); + + Ok(parts + .iter() + .map(|p| p.parse::()) + .next() + .context("Missing exposed port")??) +} + +#[cfg(test)] +mod tests { + use test_case::test_case; + + use super::*; + + #[test_case("0.0.0.0:55837" => 55837 ; "base case")] + #[test_case(" 0.0.0.0:55837 " => 55837 ; "ignore whitespace")] + #[test_case("[::]:12345" => 12345 ; "works with ipv6")] + #[test_case("0.0.0.0:12345 \n [::]:12345" => 12345 ; "works with multiple ips")] + #[test_case("0.0.0.0:12345 \n [::]:54321" => 12345 ; "yields first of multiple ips")] + fn test_parse_exposed_port(s: &str) -> u16 { + parse_exposed_port(s).unwrap().port() + } +} diff --git a/db/migrations/001_init.sql b/db/migrations/001_init.sql new file mode 100644 index 0000000..c043647 --- /dev/null +++ b/db/migrations/001_init.sql @@ -0,0 +1,88 @@ +CREATE TYPE block_tx_status AS ENUM ( + 'pending', + 'mined', + 'finalized' +); + +-- create table networks ( +-- id BIGSERIAL PRIMARY KEY, +-- name VARCHAR(255) NOT NULL, +-- chain_id BIGINT NOT NULL +-- ); + +-- create table rpcs ( +-- id BIGSERIAL PRIMARY KEY, +-- network_id BIGINT NOT NULL REFERENCES networks(id), +-- url VARCHAR(255) NOT NULL +-- ); + +CREATE TABLE relayers ( + id VARCHAR(255) PRIMARY KEY, + name VARCHAR(255) NOT NULL, + chain_id BIGINT NOT NULL, + key_id VARCHAR(255) NOT NULL, + address BYTEA NOT NULL, + -- The local nonce value + nonce BIGINT NOT NULL, + -- The confirmed nonce value + current_nonce BIGINT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- CREATE TABLE relayer_deps ( +-- relayer_id VARCHAR(255) NOT NULL REFERENCES relayers(id), +-- ); + +-- Constant tx data - once a tx is created, this data should never change +CREATE TABLE transactions ( + id VARCHAR(255) PRIMARY KEY, + tx_to BYTEA NOT NULL, + data BYTEA NOT NULL, + value BYTEA NOT NULL, + gas_limit BYTEA NOT NULL, + nonce BIGINT NOT NULL, + relayer_id VARCHAR(255) NOT NULL REFERENCES relayers(id) +); + +-- Dynamic tx data & data used for escalations +CREATE TABLE sent_transactions ( + tx_id VARCHAR(255) PRIMARY KEY REFERENCES transactions(id), + initial_max_fee_per_gas BYTEA NOT NULL, + initial_max_priority_fee_per_gas BYTEA NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + first_submitted_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + mined_at TIMESTAMP, + escalation_count BIGINT NOT NULL DEFAULT 0, + last_escalation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + reorg BOOL NOT NULL DEFAULT FALSE +); + +-- Sent transaction attempts +CREATE TABLE tx_hashes ( + tx_hash BYTEA PRIMARY KEY, + tx_id VARCHAR(255) NOT NULL REFERENCES transactions(id), + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + max_fee_per_gas BYTEA NOT NULL, + max_priority_fee_per_gas BYTEA NOT NULL, + escalated BOOL NOT NULL DEFAULT FALSE, + -- pending | mined | finalized + status block_tx_status NOT NULL DEFAULT 'pending' +); + +CREATE TABLE blocks ( + id BIGSERIAL PRIMARY KEY, + block_number BIGINT NOT NULL, + chain_id BIGINT NOT NULL, + -- mined | finalized + status block_tx_status NOT NULL, + fee_estimate JSON +); + +CREATE TABLE block_txs ( + block_id BIGINT REFERENCES blocks(id), + tx_hash BYTEA NOT NULL, + PRIMARY KEY (block_id, tx_hash) +); + diff --git a/manual_test.nu b/manual_test.nu new file mode 100644 index 0000000..6031671 --- /dev/null +++ b/manual_test.nu @@ -0,0 +1,26 @@ +# Create relayer +http post -t application/json http://127.0.0.1:3000/1/relayer/create { "name": "My Relayer", "chainId": 31337 } + +# Relayer id: 4df4464e-d0af-4354-b08a-5a78978ab6e6 +# Address: 0x968420740442caccb5023b2220e79e811f5ca798 + +# Send a transaction +http post -t application/json http://127.0.0.1:3000/1/tx/send { + "relayerId": "a2d426a9-5b55-4048-812a-ef9e9f2b3a53", + "to": "0x14bf69c64d27e5a5b07188b2e19f7501baa79209", + "value": "10", + "data": "" + "gasLimit": "150000" +} + +http get http://127.0.0.1:3000/1/tx/682b734b-87d0-41ee-9210-15fcbe13cc3d + +for i in 0..100 { + http post -t application/json http://127.0.0.1:3000/1/tx/send { + "relayerId": "34e611c6-9b17-463a-8ca9-cdb640381f38", + "to": "0x5ce4a426963abbf4f941c1af48594445fad99322", + "value": "10", + "data": "" + "gasLimit": "150000" + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..381275b --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly-2023-11-15" diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..79a78a1 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,3 @@ +group_imports = "StdExternalCrate" +imports_granularity = "Module" +max_width = 80 diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..f3fffac --- /dev/null +++ b/src/app.rs @@ -0,0 +1,125 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use ethers::middleware::SignerMiddleware; +use ethers::providers::{Http, Middleware, Provider}; +use ethers::types::{BlockNumber, U256}; +use ethers_signers::Signer; +use eyre::{Context, ContextCompat}; + +use crate::config::{Config, KeysConfig}; +use crate::db::{BlockTxStatus, Database}; +use crate::keys::{KeysSource, KmsKeys, LocalKeys, UniversalSigner}; +use crate::tasks::index::fetch_block_with_fee_estimates; + +pub type AppMiddleware = SignerMiddleware>, UniversalSigner>; + +pub struct App { + pub config: Config, + + pub rpcs: HashMap>>, + + pub keys_source: Box, + + pub db: Database, +} + +impl App { + pub async fn new(config: Config) -> eyre::Result { + let rpcs = init_rpcs(&config).await?; + let keys_source = init_keys_source(&config).await?; + let db = Database::new(&config.database).await?; + + seed_initial_blocks(&rpcs, &db).await?; + + Ok(Self { + config, + rpcs, + keys_source, + db, + }) + } + + pub async fn fetch_signer_middleware( + &self, + chain_id: impl Into, + key_id: String, + ) -> eyre::Result { + let chain_id: U256 = chain_id.into(); + + let rpc = self + .rpcs + .get(&chain_id) + .context("Missing RPC for chain id")? + .clone(); + + let wallet = self + .keys_source + .load_signer(key_id.clone()) + .await + .context("Missing signer")?; + + let wallet = wallet.with_chain_id(chain_id.as_u64()); + + let middlware = SignerMiddleware::new(rpc, wallet); + + Ok(middlware) + } +} + +async fn init_keys_source( + config: &Config, +) -> eyre::Result> { + let keys_source: Box = match &config.keys { + KeysConfig::Kms(kms_config) => { + Box::new(KmsKeys::new(kms_config).await?) + } + KeysConfig::Local(local_config) => { + Box::new(LocalKeys::new(local_config)) + } + }; + + Ok(keys_source) +} + +async fn init_rpcs( + config: &Config, +) -> eyre::Result>>> { + let mut providers = HashMap::new(); + + for rpc_url in &config.rpc.rpcs { + let provider = Provider::::try_from(rpc_url.as_str())?; + let chain_id = provider.get_chainid().await?; + + providers.insert(chain_id, Arc::new(provider)); + } + + Ok(providers) +} + +async fn seed_initial_blocks( + rpcs: &HashMap>>, + db: &Database, +) -> eyre::Result<()> { + for (chain_id, rpc) in rpcs { + tracing::info!("Seeding block for chain id {chain_id}"); + + if !db.has_blocks_for_chain(chain_id.as_u64()).await? { + let (block, fee_estimates) = + fetch_block_with_fee_estimates(rpc, BlockNumber::Latest) + .await? + .context("Missing latest block")?; + + db.save_block( + block.number.context("Missing block number")?.as_u64(), + chain_id.as_u64(), + &block.transactions, + &fee_estimates, + BlockTxStatus::Mined, + ) + .await?; + } + } + + Ok(()) +} diff --git a/src/aws.rs b/src/aws.rs new file mode 100644 index 0000000..765275f --- /dev/null +++ b/src/aws.rs @@ -0,0 +1 @@ +pub mod ethers_signer; diff --git a/src/aws/ethers_signer.rs b/src/aws/ethers_signer.rs new file mode 100644 index 0000000..70100d8 --- /dev/null +++ b/src/aws/ethers_signer.rs @@ -0,0 +1,365 @@ +//! AWS KMS-based Signer + +use aws_sdk_kms::error::SdkError; +use aws_sdk_kms::operation::get_public_key::{ + GetPublicKeyError, GetPublicKeyOutput, +}; +use aws_sdk_kms::operation::sign::{SignError, SignOutput}; +use aws_sdk_kms::types::{MessageType, SigningAlgorithmSpec}; +use aws_smithy_types::body::SdkBody; +use aws_smithy_types::Blob; +use ethers::core::k256::ecdsa::{ + Error as K256Error, Signature as KSig, VerifyingKey, +}; +use ethers::core::types::transaction::eip2718::TypedTransaction; +use ethers::core::types::transaction::eip712::Eip712; +use ethers::core::types::{Address, Signature as EthSig, H256}; +use ethers::core::utils::hash_message; +use hyper::Response; +use tracing::{debug, instrument, trace}; + +mod utils; +use utils::{apply_eip155, verifying_key_to_address}; + +/// An ethers Signer that uses keys held in Amazon AWS KMS. +/// +/// The AWS Signer passes signing requests to the cloud service. AWS KMS keys +/// are identified by a UUID, the `key_id`. +/// +/// Because the public key is unknown, we retrieve it on instantiation of the +/// signer. This means that the new function is `async` and must be called +/// within some runtime. +/// +/// ```compile_fail +/// use rusoto_core::Client; +/// use rusoto_kms::{Kms, KmsClient}; +/// +/// user ethers_signers::Signer; +/// +/// let client = Client::new_with( +/// EnvironmentProvider::default(), +/// HttpClient::new().unwrap() +/// ); +/// let kms_client = KmsClient::new_with_client(client, Region::UsWest1); +/// let key_id = "..."; +/// let chain_id = 1; +/// +/// let signer = AwsSigner::new(kms_client, key_id, chain_id).await?; +/// let sig = signer.sign_message(H256::zero()).await?; +/// ``` +#[derive(Clone)] +pub struct AwsSigner { + kms: aws_sdk_kms::Client, + chain_id: u64, + key_id: String, + pubkey: VerifyingKey, + address: Address, +} + +impl std::fmt::Debug for AwsSigner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AwsSigner") + .field("key_id", &self.key_id) + .field("chain_id", &self.chain_id) + .field("pubkey", &hex::encode(self.pubkey.to_sec1_bytes())) + .field("address", &self.address) + .finish() + } +} + +impl std::fmt::Display for AwsSigner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "AwsSigner {{ address: {}, chain_id: {}, key_id: {} }}", + self.address, self.chain_id, self.key_id + ) + } +} + +/// Errors produced by the AwsSigner +#[derive(thiserror::Error, Debug)] +pub enum AwsSignerError { + #[error("{0}")] + SignError(#[from] SdkError>), + #[error("{0}")] + GetPublicKeyError(#[from] SdkError>), + #[error("{0}")] + K256(#[from] K256Error), + #[error("{0}")] + Spki(spki::Error), + #[error("{0}")] + Other(String), + #[error(transparent)] + /// Error when converting from a hex string + HexError(#[from] hex::FromHexError), + /// Error type from Eip712Error message + #[error("error encoding eip712 struct: {0:?}")] + Eip712Error(String), +} + +impl From for AwsSignerError { + fn from(s: String) -> Self { + Self::Other(s) + } +} + +impl From for AwsSignerError { + fn from(e: spki::Error) -> Self { + Self::Spki(e) + } +} + +#[instrument(err, skip(kms, key_id), fields(key_id = %key_id.as_ref()))] +async fn request_get_pubkey( + kms: &aws_sdk_kms::Client, + key_id: T, +) -> Result>> +where + T: AsRef, +{ + debug!("Dispatching get_public_key"); + + let resp = kms.get_public_key().key_id(key_id.as_ref()).send().await; + trace!("{:?}", &resp); + resp +} + +#[instrument(err, skip(kms, digest, key_id), fields(digest = %hex::encode(digest), key_id = %key_id.as_ref()))] +async fn request_sign_digest( + kms: &aws_sdk_kms::Client, + key_id: T, + digest: [u8; 32], +) -> Result>> +where + T: AsRef, +{ + debug!("Dispatching sign"); + let resp = kms + .sign() + .key_id(key_id.as_ref()) + .message(Blob::new(digest)) + .message_type(MessageType::Digest) + .signing_algorithm(SigningAlgorithmSpec::EcdsaSha256) + .send() + .await; + trace!("{:?}", &resp); + resp +} + +impl AwsSigner { + /// Instantiate a new signer from an existing `KmsClient` and Key ID. + /// + /// This function retrieves the public key from AWS and calculates the + /// Etheruem address. It is therefore `async`. + #[instrument(err, skip(kms, key_id, chain_id), fields(key_id = %key_id.as_ref()))] + pub async fn new( + kms: aws_sdk_kms::Client, + key_id: T, + chain_id: u64, + ) -> Result + where + T: AsRef, + { + let pubkey = request_get_pubkey(&kms, &key_id) + .await + .map(utils::decode_pubkey)??; + let address = verifying_key_to_address(&pubkey); + + debug!( + "Instantiated AWS signer with pubkey 0x{} and address 0x{}", + hex::encode(pubkey.to_sec1_bytes()), + hex::encode(address) + ); + + Ok(Self { + kms, + chain_id, + key_id: key_id.as_ref().to_owned(), + pubkey, + address, + }) + } + + /// Fetch the pubkey associated with a key id + pub async fn get_pubkey_for_key( + &self, + key_id: T, + ) -> Result + where + T: AsRef, + { + request_get_pubkey(&self.kms, key_id) + .await + .map(utils::decode_pubkey)? + } + + /// Fetch the pubkey associated with this signer's key ID + pub async fn get_pubkey(&self) -> Result { + self.get_pubkey_for_key(&self.key_id).await + } + + /// Sign a digest with the key associated with a key id + pub async fn sign_digest_with_key( + &self, + key_id: T, + digest: [u8; 32], + ) -> Result + where + T: AsRef, + { + request_sign_digest(&self.kms, key_id, digest) + .await + .map(utils::decode_signature)? + } + + /// Sign a digest with this signer's key + pub async fn sign_digest( + &self, + digest: [u8; 32], + ) -> Result { + self.sign_digest_with_key(self.key_id.clone(), digest).await + } + + /// Sign a digest with this signer's key and add the eip155 `v` value + /// corresponding to the input chain_id + #[instrument(err, skip(digest), fields(digest = %hex::encode(digest)))] + async fn sign_digest_with_eip155( + &self, + digest: H256, + chain_id: u64, + ) -> Result { + let sig = self.sign_digest(digest.into()).await?; + let mut sig = utils::sig_from_digest_bytes_trial_recovery( + &sig, + digest.into(), + &self.pubkey, + ); + apply_eip155(&mut sig, chain_id); + Ok(sig) + } +} + +#[async_trait::async_trait] +impl ethers::signers::Signer for AwsSigner { + type Error = AwsSignerError; + + #[instrument(err, skip(message))] + async fn sign_message>( + &self, + message: S, + ) -> Result { + let message = message.as_ref(); + let message_hash = hash_message(message); + trace!("{:?}", message_hash); + trace!("{:?}", message); + + self.sign_digest_with_eip155(message_hash, self.chain_id) + .await + } + + #[instrument(err)] + async fn sign_transaction( + &self, + tx: &TypedTransaction, + ) -> Result { + let mut tx_with_chain = tx.clone(); + let chain_id = tx_with_chain + .chain_id() + .map(|id| id.as_u64()) + .unwrap_or(self.chain_id); + tx_with_chain.set_chain_id(chain_id); + + let sighash = tx_with_chain.sighash(); + self.sign_digest_with_eip155(sighash, chain_id).await + } + + async fn sign_typed_data( + &self, + payload: &T, + ) -> Result { + let digest = payload + .encode_eip712() + .map_err(|e| Self::Error::Eip712Error(e.to_string()))?; + + let sig = self.sign_digest(digest).await?; + let sig = utils::sig_from_digest_bytes_trial_recovery( + &sig, + digest, + &self.pubkey, + ); + + Ok(sig) + } + + fn address(&self) -> Address { + self.address + } + + /// Returns the signer's chain id + fn chain_id(&self) -> u64 { + self.chain_id + } + + /// Sets the signer's chain id + fn with_chain_id>(mut self, chain_id: T) -> Self { + self.chain_id = chain_id.into(); + self + } +} + +#[cfg(test)] +mod tests { + use aws_credential_types::Credentials; + use aws_sdk_kms::Client as KmsClient; + use aws_types::region::Region; + use ethers::signers::Signer; + + use super::*; + + #[allow(dead_code)] + async fn static_client() -> KmsClient { + let access_key = "".to_owned(); + let secret_access_key = "".to_owned(); + + let credentials = + Credentials::from_keys(access_key, secret_access_key, None); + let config = aws_config::from_env() + .credentials_provider(credentials) + .region(Region::new("us-west-1")) + .load() + .await; + + let kms_client = aws_sdk_kms::Client::new(&config); + + kms_client + } + + #[allow(dead_code)] + async fn env_client() -> KmsClient { + let config = aws_config::from_env() + .region(Region::new("us-west-1")) + .load() + .await; + + let kms_client = aws_sdk_kms::Client::new(&config); + + kms_client + } + + #[tokio::test] + async fn it_signs_messages() { + let chain_id = 1; + let key_id = match std::env::var("AWS_KEY_ID") { + Ok(id) => id, + _ => return, + }; + let client = env_client().await; + let signer = AwsSigner::new(client, key_id, chain_id).await.unwrap(); + + let message = vec![0, 1, 2, 3]; + + let sig = signer.sign_message(&message).await.unwrap(); + sig.verify(message, signer.address).expect("valid sig"); + } +} diff --git a/src/aws/ethers_signer/utils.rs b/src/aws/ethers_signer/utils.rs new file mode 100644 index 0000000..76d5def --- /dev/null +++ b/src/aws/ethers_signer/utils.rs @@ -0,0 +1,98 @@ +//! These utils are NOT meant for general usage. They are ONLY meant for use +//! within this module. They DO NOT perform basic safety checks and may panic +//! if used incorrectly. + +use std::convert::TryFrom; + +use aws_sdk_kms::operation::get_public_key::GetPublicKeyOutput; +use aws_sdk_kms::operation::sign::SignOutput; +use ethers::core::k256::ecdsa::{ + RecoveryId, Signature as RSig, Signature as KSig, VerifyingKey, +}; +use ethers::core::k256::FieldBytes; +use ethers::types::{Address, Signature as EthSig, U256}; +use ethers::utils::keccak256; + +// use rusoto_kms::{GetPublicKeyResponse, SignResponse}; +use super::AwsSignerError; + +/// Makes a trial recovery to check whether an RSig corresponds to a known +/// `VerifyingKey` +fn check_candidate( + sig: &RSig, + recovery_id: RecoveryId, + digest: [u8; 32], + vk: &VerifyingKey, +) -> bool { + VerifyingKey::recover_from_prehash(digest.as_slice(), sig, recovery_id) + .map(|key| key == *vk) + .unwrap_or(false) +} + +/// Recover an rsig from a signature under a known key by trial/error +pub(super) fn sig_from_digest_bytes_trial_recovery( + sig: &KSig, + digest: [u8; 32], + vk: &VerifyingKey, +) -> EthSig { + let r_bytes: FieldBytes = sig.r().into(); + let s_bytes: FieldBytes = sig.s().into(); + let r = U256::from_big_endian(r_bytes.as_slice()); + let s = U256::from_big_endian(s_bytes.as_slice()); + + if check_candidate(sig, RecoveryId::from_byte(0).unwrap(), digest, vk) { + EthSig { r, s, v: 0 } + } else if check_candidate( + sig, + RecoveryId::from_byte(1).unwrap(), + digest, + vk, + ) { + EthSig { r, s, v: 1 } + } else { + panic!("bad sig"); + } +} + +/// Modify the v value of a signature to conform to eip155 +pub(super) fn apply_eip155(sig: &mut EthSig, chain_id: u64) { + let v = (chain_id * 2 + 35) + sig.v; + sig.v = v; +} + +/// Convert a verifying key to an ethereum address +pub(super) fn verifying_key_to_address(key: &VerifyingKey) -> Address { + // false for uncompressed + let uncompressed_pub_key = key.to_encoded_point(false); + let public_key = uncompressed_pub_key.to_bytes(); + debug_assert_eq!(public_key[0], 0x04); + let hash = keccak256(&public_key[1..]); + Address::from_slice(&hash[12..]) +} + +/// Decode an AWS KMS Pubkey response +pub(super) fn decode_pubkey( + resp: GetPublicKeyOutput, +) -> Result { + let raw = resp.public_key.ok_or_else(|| { + AwsSignerError::from("Pubkey not found in response".to_owned()) + })?; + + let spki = spki::SubjectPublicKeyInfoRef::try_from(raw.as_ref())?; + let key = + VerifyingKey::from_sec1_bytes(spki.subject_public_key.raw_bytes())?; + + Ok(key) +} + +/// Decode an AWS KMS Signature response +pub(super) fn decode_signature( + resp: SignOutput, +) -> Result { + let raw = resp.signature.ok_or_else(|| { + AwsSignerError::from("Signature not found in response".to_owned()) + })?; + + let sig = KSig::from_der(raw.as_ref())?; + Ok(sig.normalize_s().unwrap_or(sig)) +} diff --git a/src/broadcast_utils.rs b/src/broadcast_utils.rs new file mode 100644 index 0000000..e4172d9 --- /dev/null +++ b/src/broadcast_utils.rs @@ -0,0 +1,77 @@ +use ethers::types::{Eip1559TransactionRequest, U256}; + +use self::gas_estimation::FeesEstimate; + +pub mod gas_estimation; + +const BASE_FEE_PER_GAS_SURGE_FACTOR: u64 = 2; + +// TODO: Adjust +const MIN_PRIORITY_FEE: U256 = U256([10, 0, 0, 0]); +const MAX_GAS_PRICE: U256 = U256([100_000_000_000, 0, 0, 0]); + +/// Returns a tuple of max and max priority fee per gas +pub fn calculate_gas_fees_from_estimates( + estimates: &FeesEstimate, + tx_priority_index: usize, + max_base_fee_per_gas: U256, +) -> eyre::Result<(U256, U256)> { + let max_priority_fee_per_gas = estimates.percentile_fees[tx_priority_index]; + + let max_priority_fee_per_gas = + std::cmp::max(max_priority_fee_per_gas, MIN_PRIORITY_FEE); + + let max_fee_per_gas = max_base_fee_per_gas + max_priority_fee_per_gas; + let max_fee_per_gas = std::cmp::min(max_fee_per_gas, MAX_GAS_PRICE); + + Ok((max_fee_per_gas, max_priority_fee_per_gas)) +} + +/// Calculates the max base fee per gas +/// Returns an error if the base fee per gas is too high +/// +/// i.e. the base fee from estimates surged by a factor +pub fn calculate_max_base_fee_per_gas( + estimates: &FeesEstimate, +) -> eyre::Result { + let base_fee_per_gas = estimates.base_fee_per_gas; + + if base_fee_per_gas > MAX_GAS_PRICE { + tracing::warn!("Base fee per gas is too high, retrying later"); + eyre::bail!("Base fee per gas is too high"); + } + + // Surge the base fee per gas + let max_base_fee_per_gas = base_fee_per_gas * BASE_FEE_PER_GAS_SURGE_FACTOR; + + Ok(max_base_fee_per_gas) +} + +pub fn escalate_priority_fee( + max_base_fee_per_gas: U256, + max_network_fee_per_gas: U256, + current_max_priority_fee_per_gas: U256, + escalation_count: usize, + tx: &mut Eip1559TransactionRequest, +) { + // Min increase of 20% on the priority fee required for a replacement tx + let increased_gas_price_percentage = + U256::from(100 + (10 * (1 + escalation_count))); + + let factor = U256::from(100); + + let new_max_priority_fee_per_gas = current_max_priority_fee_per_gas + * increased_gas_price_percentage + / factor; + + let new_max_priority_fee_per_gas = + std::cmp::min(new_max_priority_fee_per_gas, max_network_fee_per_gas); + + let new_max_fee_per_gas = + max_base_fee_per_gas + new_max_priority_fee_per_gas; + let new_max_fee_per_gas = + std::cmp::min(new_max_fee_per_gas, max_network_fee_per_gas); + + tx.max_fee_per_gas = Some(new_max_fee_per_gas); + tx.max_priority_fee_per_gas = Some(new_max_priority_fee_per_gas); +} diff --git a/src/broadcast_utils/gas_estimation.rs b/src/broadcast_utils/gas_estimation.rs new file mode 100644 index 0000000..d4fe01a --- /dev/null +++ b/src/broadcast_utils/gas_estimation.rs @@ -0,0 +1,97 @@ +use ethers::types::{FeeHistory, U256}; +use eyre::ContextCompat; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct FeesEstimate { + pub base_fee_per_gas: U256, + pub percentile_fees: Vec, +} + +pub fn estimate_percentile_fees( + fee_history: &FeeHistory, +) -> eyre::Result { + // Take the last base fee per gas + let base_fee_per_gas = fee_history + .base_fee_per_gas + .last() + .context("Missing base fees")?; + + let num_percentiles = + fee_history.reward.first().context("Missing rewards")?.len(); + let percentile_fees = (0..num_percentiles) + .map(|percentile_idx| estimate_percentile(fee_history, percentile_idx)) + .collect::>>()?; + + Ok(FeesEstimate { + base_fee_per_gas: *base_fee_per_gas, + percentile_fees, + }) +} + +// Just takes the average of the percentile fees +fn estimate_percentile( + fee_history: &FeeHistory, + percentile_idx: usize, +) -> eyre::Result { + let mut sum = U256::zero(); + + for rewards in &fee_history.reward { + let percentile_fee = rewards + .get(percentile_idx) + .context("Missing percentile fee")?; + + sum += *percentile_fee; + } + + let percentile_fee = sum / fee_history.reward.len(); + + Ok(percentile_fee) +} + +#[cfg(test)] +mod tests { + use ethers::types::{FeeHistory, U256}; + + use super::*; + + // Below are really just sample calculations + + #[test] + fn estimate_fees_optimism_108598243() { + const CONTENT: &str = include_str!( + "./gas_estimation/fee_history_optimism_108598243.json" + ); + + let fee_history: FeeHistory = serde_json::from_str(CONTENT).unwrap(); + + let estimates = estimate_percentile_fees(&fee_history).unwrap(); + let expected_estimates = FeesEstimate { + base_fee_per_gas: U256::from(87), + percentile_fees: vec![U256::from(12), U256::from(28504848)], + }; + + assert_eq!(expected_estimates, estimates); + } + + #[test] + fn estimate_fees_history_ethereum_17977856() { + const CONTENT: &str = + include_str!("./gas_estimation/fee_history_ethereum_17977856.json"); + + let fee_history: FeeHistory = serde_json::from_str(CONTENT).unwrap(); + + let estimates = estimate_percentile_fees(&fee_history).unwrap(); + let expected_estimates = FeesEstimate { + base_fee_per_gas: U256::from(23149734459u64), + percentile_fees: vec![ + U256::from(42461472u64), + U256::from(100000000u64), + U256::from(613437626u64), + U256::from(2152582987u64), + ], + }; + + assert_eq!(expected_estimates, estimates); + } +} diff --git a/src/broadcast_utils/gas_estimation/fee_history_ethereum_17977856.json b/src/broadcast_utils/gas_estimation/fee_history_ethereum_17977856.json new file mode 100644 index 0000000..102a382 --- /dev/null +++ b/src/broadcast_utils/gas_estimation/fee_history_ethereum_17977856.json @@ -0,0 +1,32 @@ +{ + "oldestBlock": "0x11251f6", + "reward": [ + ["0x19a1574", "0x5f5e100", "0x11e1a300", "0x9502f900"], + ["0x19a1574", "0x5f5e100", "0x3b9aca00", "0xaccc8076"], + ["0x19a1574", "0x5f5e100", "0x11e1a300", "0x59682f00"], + ["0x19a1574", "0x5f5e100", "0x3b9aca00", "0x77359400"], + ["0x19a1574", "0x5f5e100", "0x11e1a300", "0x77359400"], + ["0x5f5e100", "0x5f5e100", "0x3b9aca00", "0x77359400"], + ["0x5f5e100", "0x5f5e100", "0x3b9aca00", "0x8f1c3d24"], + ["0x19a1574", "0x5f5e100", "0x5f5e100", "0x77359400"], + ["0x19a1574", "0x5f5e100", "0x3b9aca00", "0x84aa4f5a"], + ["0x22cc319", "0x5f5e100", "0x8026b44", "0x77359400"] + ], + "baseFeePerGas": [ + "0x51e5e0e5e", + "0x523876ab9", + "0x510f87c34", + "0x5784f5262", + "0x5bebae124", + "0x5d36c1efc", + "0x5caa325fe", + "0x5a751f887", + "0x5890e34a6", + "0x57caeed5c", + "0x563d4ea3b" + ], + "gasUsedRatio": [ + 0.5157560691348435, 0.4435715140000797, 0.8187094666666667, 0.7011559, 0.5562777666666666, 0.47643846666666667, + 0.40471816666666666, 0.41635656666666665, 0.4650763666666667, 0.4292321333333333 + ] +} diff --git a/src/broadcast_utils/gas_estimation/fee_history_optimism_108598243.json b/src/broadcast_utils/gas_estimation/fee_history_optimism_108598243.json new file mode 100644 index 0000000..9f0737e --- /dev/null +++ b/src/broadcast_utils/gas_estimation/fee_history_optimism_108598243.json @@ -0,0 +1,20 @@ +{ + "oldestBlock": "0x67913da", + "reward": [ + ["0xb", "0x2d13bf"], + ["0xb", "0x10"], + ["0x10", "0x989688"], + ["0xa", "0x5f5e0a9"], + ["0x10", "0x12"], + ["0xb", "0x10"], + ["0x10", "0x5f5e0a8"], + ["0x10", "0x10"], + ["0x5", "0x44c12ba"], + ["0x10", "0x10"] + ], + "baseFeePerGas": ["0x56", "0x57", "0x57", "0x57", "0x58", "0x57", "0x58", "0x58", "0x58", "0x58", "0x57"], + "gasUsedRatio": [ + 0.17549803333333333, 0.0974671, 0.1120808, 0.21052336666666666, 0.0381233, 0.18064013333333334, 0.09819203333333333, + 0.07915883333333333, 0.10884046666666666, 0.06973963333333333 + ] +} diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/client.rs @@ -0,0 +1 @@ + diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..03af392 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,91 @@ +use std::net::SocketAddr; +use std::time::Duration; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct Config { + pub service: TxSitterConfig, + pub server: ServerConfig, + pub rpc: RpcConfig, + pub database: DatabaseConfig, + pub keys: KeysConfig, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct TxSitterConfig { + #[serde(with = "humantime_serde")] + pub escalation_interval: Duration, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct ServerConfig { + pub host: SocketAddr, + + #[serde(default)] + pub disable_auth: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct RpcConfig { + #[serde(default)] + pub rpcs: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct DatabaseConfig { + pub connection_string: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case", tag = "kind")] +pub enum KeysConfig { + Kms(KmsKeysConfig), + Local(LocalKeysConfig), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct KmsKeysConfig { + pub region: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct LocalKeysConfig {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sample() { + let config = Config { + service: TxSitterConfig { + escalation_interval: Duration::from_secs(60 * 60), + }, + server: ServerConfig { + host: SocketAddr::from(([127, 0, 0, 1], 3000)), + disable_auth: false, + }, + rpc: RpcConfig { + rpcs: vec!["hello".to_string()], + }, + database: DatabaseConfig { + connection_string: + "postgres://postgres:postgres@127.0.0.1:52804/database" + .to_string(), + }, + keys: KeysConfig::Local(LocalKeysConfig {}), + }; + + let toml = toml::to_string_pretty(&config).unwrap(); + + println!("{}", toml); + } +} diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..82a1e96 --- /dev/null +++ b/src/db.rs @@ -0,0 +1,494 @@ +use std::time::Duration; + +use ethers::types::{Address, H256, U256}; +use sqlx::migrate::{MigrateDatabase, Migrator}; +use sqlx::{Pool, Postgres, Row}; + +use crate::broadcast_utils::gas_estimation::FeesEstimate; +use crate::config::DatabaseConfig; + +pub mod data; + +use self::data::{AddressWrapper, ReadTxData}; +pub use self::data::{BlockTxStatus, TxForEscalation, UnsentTx}; + +// Statically link in migration files +static MIGRATOR: Migrator = sqlx::migrate!("db/migrations"); + +pub struct Database { + pub pool: Pool, +} + +impl Database { + pub async fn new(config: &DatabaseConfig) -> eyre::Result { + let pool = loop { + if !Postgres::database_exists(&config.connection_string).await? { + Postgres::create_database(&config.connection_string).await?; + } + + let pool = Pool::connect(&config.connection_string).await?; + + if let Err(err) = MIGRATOR.run(&pool).await { + tracing::error!("{err:?}"); + tracing::warn!("Migration mismatch dropping previosu db"); + drop(pool); + // Drop the DB if it's out of date - ONLY FOR TESTING + Postgres::drop_database(&config.connection_string).await?; + } else { + break pool; + } + }; + + Ok(Self { pool }) + } + + pub async fn create_relayer( + &self, + id: &str, + name: &str, + chain_id: u64, + key_id: &str, + address: Address, + ) -> eyre::Result<()> { + sqlx::query( + r#" + INSERT INTO relayers (id, name, chain_id, key_id, address, nonce, current_nonce) + VALUES ($1, $2, $3, $4, $5, 0, 0) + "#, + ) + .bind(id) + .bind(name) + .bind(chain_id as i64) + .bind(key_id) + .bind(address.as_bytes()) + .execute(&self.pool) + .await?; + + Ok(()) + } + + pub async fn create_transaction( + &self, + tx_id: &str, + to: Address, + data: &[u8], + value: U256, + gas_limit: U256, + relayer_id: &str, + ) -> eyre::Result<()> { + let mut tx = self.pool.begin().await?; + let mut value_bytes = [0u8; 32]; + value.to_big_endian(&mut value_bytes); + + let mut gas_limit_bytes = [0u8; 32]; + gas_limit.to_big_endian(&mut gas_limit_bytes); + + sqlx::query( + r#" + INSERT INTO transactions (id, tx_to, data, value, gas_limit, relayer_id, nonce) + VALUES ($1, $2, $3, $4, $5, $6, (SELECT nonce FROM relayers WHERE id = $6)) + "#, + ) + .bind(tx_id) + .bind(to.as_bytes()) + .bind(data) + .bind(value_bytes) + .bind(gas_limit_bytes) + .bind(relayer_id) + .execute(tx.as_mut()) + .await?; + + sqlx::query( + r#" + UPDATE relayers + SET nonce = nonce + 1, + updated_at = now() + WHERE id = $1 + "#, + ) + .bind(relayer_id) + .execute(tx.as_mut()) + .await?; + + tx.commit().await?; + + Ok(()) + } + + pub async fn get_unsent_txs( + &self, + max_inflight_txs: usize, + ) -> eyre::Result> { + Ok(sqlx::query_as( + r#" + SELECT t.id, t.tx_to, t.data, t.value, t.gas_limit, t.nonce, r.key_id, r.chain_id + FROM transactions t + LEFT JOIN sent_transactions s ON (t.id = s.tx_id) + INNER JOIN relayers r ON (t.relayer_id = r.id) + WHERE s.tx_id IS NULL + AND (t.nonce - r.current_nonce < $1); + "#, + ) + .bind(max_inflight_txs as i64) + .fetch_all(&self.pool) + .await?) + } + + pub async fn insert_tx_broadcast( + &self, + tx_id: &str, + tx_hash: H256, + initial_max_fee_per_gas: U256, + initial_max_priority_fee_per_gas: U256, + ) -> eyre::Result<()> { + let mut initial_max_fee_per_gas_bytes = [0u8; 32]; + initial_max_fee_per_gas + .to_big_endian(&mut initial_max_fee_per_gas_bytes); + + let mut initial_max_priority_fee_per_gas_bytes = [0u8; 32]; + initial_max_priority_fee_per_gas + .to_big_endian(&mut initial_max_priority_fee_per_gas_bytes); + + let mut tx = self.pool.begin().await?; + + sqlx::query( + r#" + INSERT INTO sent_transactions (tx_id, initial_max_fee_per_gas, initial_max_priority_fee_per_gas) + VALUES ($1, $2, $3) + "# + ) + .bind(tx_id) + .bind(initial_max_fee_per_gas_bytes) + .bind(initial_max_priority_fee_per_gas_bytes) + .execute(tx.as_mut()).await?; + + sqlx::query( + r#" + INSERT INTO tx_hashes (tx_id, tx_hash, max_fee_per_gas, max_priority_fee_per_gas) + VALUES ($1, $2, $3, $4) + "#, + ) + .bind(tx_id) + .bind(tx_hash.as_bytes()) + .bind(initial_max_fee_per_gas_bytes) + .bind(initial_max_priority_fee_per_gas_bytes) + .execute(tx.as_mut()) + .await?; + + tx.commit().await?; + + Ok(()) + } + + pub async fn get_latest_block_fees_by_chain_id( + &self, + chain_id: u64, + ) -> eyre::Result> { + let row = sqlx::query( + r#" + SELECT fee_estimate + FROM blocks + WHERE chain_id = $1 + AND status = $2 + AND fee_estimate IS NOT NULL + ORDER BY block_number DESC + LIMIT 1 + "#, + ) + .bind(chain_id as i64) + .bind(BlockTxStatus::Mined) + .fetch_optional(&self.pool) + .await?; + + let item = row + .map(|row| row.try_get::, _>(0)) + .transpose()?; + + Ok(item.map(|json_fee_estimate| json_fee_estimate.0)) + } + + pub async fn get_next_block_numbers( + &self, + ) -> eyre::Result> { + let rows: Vec<(i64, i64)> = sqlx::query_as( + r#" + SELECT MAX(block_number) + 1, chain_id + FROM blocks + WHERE status = $1 + GROUP BY chain_id + "#, + ) + .bind(BlockTxStatus::Mined) + .fetch_all(&self.pool) + .await?; + + Ok(rows + .into_iter() + .map(|(block_number, chain_id)| { + (block_number as u64, chain_id as u64) + }) + .collect()) + } + + pub async fn has_blocks_for_chain( + &self, + chain_id: u64, + ) -> eyre::Result { + let row = sqlx::query( + r#" + SELECT EXISTS ( + SELECT 1 + FROM blocks + WHERE chain_id = $1 + ) + "#, + ) + .bind(chain_id as i64) + .fetch_one(&self.pool) + .await?; + + Ok(row.try_get::(0)?) + } + + pub async fn save_block( + &self, + block_number: u64, + chain_id: u64, + txs: &[H256], + fee_estimates: &FeesEstimate, + status: BlockTxStatus, + ) -> eyre::Result<()> { + let mut db_tx = self.pool.begin().await?; + + // let fee_estimates = serde_json::to_string(&fee_estimates)?; + + let (block_id,): (i64,) = sqlx::query_as( + r#" + INSERT INTO blocks (block_number, chain_id, fee_estimate, status) + VALUES ($1, $2, $3, $4) + RETURNING id + "#, + ) + .bind(block_number as i64) + .bind(chain_id as i64) + .bind(sqlx::types::Json(fee_estimates)) + .bind(status) + .fetch_one(db_tx.as_mut()) + .await?; + + for tx_hash in txs { + sqlx::query( + r#" + INSERT INTO block_txs (block_id, tx_hash) + VALUES ($1, $2) + "#, + ) + .bind(block_id) + .bind(tx_hash.as_bytes()) + .execute(db_tx.as_mut()) + .await?; + } + + db_tx.commit().await?; + + Ok(()) + } + + pub async fn update_transactions( + &self, + status: BlockTxStatus, + ) -> eyre::Result<()> { + sqlx::query( + r#" + UPDATE tx_hashes h + SET status = $1 + FROM transactions t, block_txs bt, blocks b, relayers r + WHERE t.id = h.tx_id + AND b.id = bt.block_id + AND h.tx_hash = bt.tx_hash + AND r.chain_id = b.chain_id + AND r.id = t.relayer_id + AND h.status = $2 + AND b.status = $1 + "#, + ) + .bind(status) + .bind(status.previous()) + .execute(&self.pool) + .await?; + + Ok(()) + } + + pub async fn fetch_txs_for_escalation( + &self, + escalation_interval: Duration, + ) -> eyre::Result> { + Ok(sqlx::query_as( + r#" + SELECT t.id, t.tx_to, t.data, t.value, t.gas_limit, t.nonce, + r.key_id, r.chain_id, + s.initial_max_fee_per_gas, s.initial_max_priority_fee_per_gas, s.escalation_count + FROM transactions t + JOIN sent_transactions s ON t.id = s.tx_id + JOIN tx_hashes h ON t.id = h.tx_id + JOIN relayers r ON t.relayer_id = r.id + WHERE now() - h.created_at > $1 + AND h.status = $2 + AND NOT h.escalated + "#, + ) + .bind(escalation_interval) + .bind(BlockTxStatus::Pending) + .fetch_all(&self.pool) + .await?) + } + + pub async fn escalate_tx( + &self, + tx_id: &str, + tx_hash: H256, + max_fee_per_gas: U256, + max_priority_fee_per_gas: U256, + ) -> eyre::Result<()> { + let mut tx = self.pool.begin().await?; + + sqlx::query( + r#" + UPDATE tx_hashes + SET escalated = true + WHERE tx_id = $1 + "#, + ) + .bind(tx_id) + .execute(tx.as_mut()) + .await?; + + sqlx::query( + r#" + UPDATE sent_transactions + SET escalation_count = escalation_count + 1 + WHERE tx_id = $1 + "#, + ) + .bind(tx_id) + .execute(tx.as_mut()) + .await?; + + let mut max_fee_per_gas_bytes = [0u8; 32]; + max_fee_per_gas.to_big_endian(&mut max_fee_per_gas_bytes); + + let mut max_priority_fee_per_gas_bytes = [0u8; 32]; + max_priority_fee_per_gas + .to_big_endian(&mut max_priority_fee_per_gas_bytes); + + sqlx::query( + r#" + INSERT INTO tx_hashes (tx_id, tx_hash, max_fee_per_gas, max_priority_fee_per_gas) + VALUES ($1, $2, $3, $4) + "# + ) + .bind(tx_id) + .bind(tx_hash.as_bytes()) + .bind(max_fee_per_gas_bytes) + .bind(max_priority_fee_per_gas_bytes) + .execute(tx.as_mut()) + .await?; + + tx.commit().await?; + + Ok(()) + } + + pub async fn read_tx( + &self, + tx_id: &str, + ) -> eyre::Result> { + Ok(sqlx::query_as( + r#" + SELECT t.id as tx_id, t.tx_to as to, t.data, t.value, t.gas_limit, t.nonce, + h.tx_hash, h.status + FROM transactions t + LEFT JOIN tx_hashes h ON t.id = h.tx_id + WHERE t.id = $1 + ORDER BY h.created_at DESC, h.status DESC + LIMIT 1 + "#, + ) + .bind(tx_id) + .fetch_optional(&self.pool) + .await?) + } + + pub async fn fetch_relayer_addresses( + &self, + chain_id: u64, + ) -> eyre::Result> { + let items: Vec<(AddressWrapper,)> = sqlx::query_as( + r#" + SELECT address + FROM relayers + WHERE chain_id = $1 + "#, + ) + .bind(chain_id as i64) + .fetch_all(&self.pool) + .await?; + + Ok(items.into_iter().map(|(wrapper,)| wrapper.0).collect()) + } + + pub async fn update_relayer_nonce( + &self, + chain_id: u64, + relayer_address: Address, + nonce: u64, + ) -> eyre::Result<()> { + sqlx::query( + r#" + UPDATE relayers + SET current_nonce = $3, + updated_at = now() + WHERE chain_id = $1 + AND address = $2 + "#, + ) + .bind(chain_id as i64) + .bind(relayer_address.as_bytes()) + .bind(nonce as i64) + .execute(&self.pool) + .await?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use postgres_docker_utils::DockerContainerGuard; + + use super::*; + + async fn setup_db() -> eyre::Result<(Database, DockerContainerGuard)> { + let db_container = postgres_docker_utils::setup().await?; + let db_socket_addr = db_container.address(); + let url = + format!("postgres://postgres:postgres@{db_socket_addr}/database"); + + let db = Database::new(&DatabaseConfig { + connection_string: url, + }) + .await?; + + Ok((db, db_container)) + } + + #[tokio::test] + async fn basic() -> eyre::Result<()> { + let (_db, _db_container) = setup_db().await?; + + // db.create_relayer().await?; + + Ok(()) + } +} diff --git a/src/db/data.rs b/src/db/data.rs new file mode 100644 index 0000000..0513bba --- /dev/null +++ b/src/db/data.rs @@ -0,0 +1,169 @@ +use ethers::types::{Address, H256, U256}; +use serde::{Deserialize, Serialize}; +use sqlx::database::HasValueRef; +use sqlx::prelude::FromRow; +use sqlx::Database; + +#[derive(Debug, Clone, FromRow)] +pub struct UnsentTx { + pub id: String, + pub tx_to: AddressWrapper, + pub data: Vec, + pub value: U256Wrapper, + pub gas_limit: U256Wrapper, + #[sqlx(try_from = "i64")] + pub nonce: u64, + pub key_id: String, + #[sqlx(try_from = "i64")] + pub chain_id: u64, +} + +#[derive(Debug, Clone, FromRow)] +pub struct TxForEscalation { + pub id: String, + pub tx_to: AddressWrapper, + pub data: Vec, + pub value: U256Wrapper, + pub gas_limit: U256Wrapper, + #[sqlx(try_from = "i64")] + pub nonce: u64, + pub key_id: String, + #[sqlx(try_from = "i64")] + pub chain_id: u64, + pub initial_max_fee_per_gas: U256Wrapper, + pub initial_max_priority_fee_per_gas: U256Wrapper, + #[sqlx(try_from = "i64")] + pub escalation_count: usize, +} + +#[derive(Debug, Clone, FromRow)] +pub struct ReadTxData { + pub tx_id: String, + pub to: AddressWrapper, + pub data: Vec, + pub value: U256Wrapper, + pub gas_limit: U256Wrapper, + #[sqlx(try_from = "i64")] + pub nonce: u64, + + // Sent tx data + pub tx_hash: Option, + pub status: Option, +} + +#[derive(Debug, Clone)] +pub struct AddressWrapper(pub Address); +#[derive(Debug, Clone)] +pub struct U256Wrapper(pub U256); + +#[derive(Debug, Clone)] +pub struct H256Wrapper(pub H256); + +impl<'r, DB> sqlx::Decode<'r, DB> for AddressWrapper +where + DB: Database, + Vec: sqlx::Decode<'r, DB>, +{ + fn decode( + value: >::ValueRef, + ) -> Result { + let bytes = as sqlx::Decode>::decode(value)?; + + let address = Address::from_slice(&bytes); + + Ok(Self(address)) + } +} + +impl sqlx::Type for AddressWrapper +where + Vec: sqlx::Type, +{ + fn type_info() -> DB::TypeInfo { + as sqlx::Type>::type_info() + } + + fn compatible(ty: &DB::TypeInfo) -> bool { + *ty == Self::type_info() + } +} + +impl<'r, DB> sqlx::Decode<'r, DB> for U256Wrapper +where + DB: Database, + [u8; 32]: sqlx::Decode<'r, DB>, +{ + fn decode( + value: >::ValueRef, + ) -> Result { + let bytes = <[u8; 32] as sqlx::Decode>::decode(value)?; + + let value = U256::from_big_endian(&bytes); + + Ok(Self(value)) + } +} + +impl sqlx::Type for U256Wrapper +where + [u8; 32]: sqlx::Type, +{ + fn type_info() -> DB::TypeInfo { + <[u8; 32] as sqlx::Type>::type_info() + } + + fn compatible(ty: &DB::TypeInfo) -> bool { + *ty == Self::type_info() + } +} + +impl<'r, DB> sqlx::Decode<'r, DB> for H256Wrapper +where + DB: Database, + [u8; 32]: sqlx::Decode<'r, DB>, +{ + fn decode( + value: >::ValueRef, + ) -> Result { + let bytes = <[u8; 32] as sqlx::Decode>::decode(value)?; + + let value = H256::from_slice(&bytes); + + Ok(Self(value)) + } +} + +impl sqlx::Type for H256Wrapper +where + [u8; 32]: sqlx::Type, +{ + fn type_info() -> DB::TypeInfo { + <[u8; 32] as sqlx::Type>::type_info() + } + + fn compatible(ty: &DB::TypeInfo) -> bool { + *ty == Self::type_info() + } +} + +#[derive( + Debug, Clone, Serialize, Deserialize, Copy, PartialEq, Eq, sqlx::Type, +)] +#[sqlx(rename_all = "camelCase")] +#[sqlx(type_name = "block_tx_status")] +#[serde(rename_all = "camelCase")] +pub enum BlockTxStatus { + Pending = 0, + Mined = 1, + Finalized = 2, +} + +impl BlockTxStatus { + pub fn previous(self) -> Self { + match self { + Self::Pending => Self::Pending, + Self::Mined => Self::Pending, + Self::Finalized => Self::Mined, + } + } +} diff --git a/src/keys.rs b/src/keys.rs new file mode 100644 index 0000000..62f7155 --- /dev/null +++ b/src/keys.rs @@ -0,0 +1,131 @@ +use aws_sdk_kms::types::{KeySpec, KeyUsageType}; +use aws_types::region::Region; +use ethers::core::k256::ecdsa::SigningKey; +use ethers_signers::Wallet; +use eyre::{Context, ContextCompat}; +pub use universal_signer::UniversalSigner; + +use crate::aws::ethers_signer::AwsSigner; +use crate::config::{KmsKeysConfig, LocalKeysConfig}; + +mod universal_signer; + +#[async_trait::async_trait] +pub trait KeysSource: Send + Sync + 'static { + /// Returns a key id and signer + async fn new_signer(&self) -> eyre::Result<(String, UniversalSigner)>; + + /// Loads the key using the provided id + async fn load_signer(&self, id: String) -> eyre::Result; +} + +pub struct KmsKeys { + kms_client: aws_sdk_kms::Client, +} + +impl KmsKeys { + pub async fn new(config: &KmsKeysConfig) -> eyre::Result { + let aws_config = aws_config::from_env() + .region(Region::new(config.region.clone())) + .load() + .await; + + let kms_client = aws_sdk_kms::Client::new(&aws_config); + + Ok(Self { kms_client }) + } +} + +#[async_trait::async_trait] +impl KeysSource for KmsKeys { + async fn new_signer(&self) -> eyre::Result<(String, UniversalSigner)> { + let kms_key = self + .kms_client + .create_key() + .key_spec(KeySpec::EccSecgP256K1) + .key_usage(KeyUsageType::SignVerify) + .send() + .await + .context("AWS Error")?; + + let key_id = + kms_key.key_metadata.context("Missing key metadata")?.key_id; + + let signer = AwsSigner::new( + self.kms_client.clone(), + key_id.clone(), + 1, // TODO: get chain id from provider + ) + .await?; + + Ok((key_id, UniversalSigner::Aws(signer))) + } + + async fn load_signer(&self, id: String) -> eyre::Result { + let signer = AwsSigner::new( + self.kms_client.clone(), + id.clone(), + 1, // TODO: get chain id from provider + ) + .await?; + + Ok(UniversalSigner::Aws(signer)) + } +} + +pub struct LocalKeys { + rng: rand::rngs::OsRng, +} + +impl LocalKeys { + pub fn new(_config: &LocalKeysConfig) -> Self { + Self { + rng: rand::rngs::OsRng, + } + } +} + +#[async_trait::async_trait] +impl KeysSource for LocalKeys { + async fn new_signer(&self) -> eyre::Result<(String, UniversalSigner)> { + let signing_key = SigningKey::random(&mut self.rng.clone()); + + let key_id = signing_key.to_bytes().to_vec(); + let key_id = hex::encode(key_id); + + let signer = Wallet::from(signing_key); + + Ok((key_id, UniversalSigner::Local(signer))) + } + + async fn load_signer(&self, id: String) -> eyre::Result { + let key_id = hex::decode(id)?; + let signing_key = SigningKey::from_slice(key_id.as_slice())?; + + let signer = Wallet::from(signing_key); + + Ok(UniversalSigner::Local(signer)) + } +} + +#[cfg(test)] +mod tests { + use ethers_signers::Signer; + + use super::*; + + #[tokio::test] + async fn local_roundtrip() -> eyre::Result<()> { + let keys_source = LocalKeys::new(&LocalKeysConfig {}); + + let (id, signer) = keys_source.new_signer().await?; + + let address = signer.address(); + + let signer = keys_source.load_signer(id).await?; + + assert_eq!(address, signer.address()); + + Ok(()) + } +} diff --git a/src/keys/universal_signer.rs b/src/keys/universal_signer.rs new file mode 100644 index 0000000..226cbcf --- /dev/null +++ b/src/keys/universal_signer.rs @@ -0,0 +1,87 @@ +use ethers::core::k256::ecdsa::SigningKey; +use ethers::core::types::transaction::eip2718::TypedTransaction; +use ethers::core::types::transaction::eip712::Eip712; +use ethers::core::types::{Address, Signature as EthSig}; +use ethers::signers::Signer; +use ethers_signers::{Wallet, WalletError}; +use thiserror::Error; + +use crate::aws::ethers_signer::AwsSigner; + +#[derive(Debug)] +pub enum UniversalSigner { + Aws(AwsSigner), + Local(Wallet), +} + +#[derive(Debug, Error)] +pub enum UniversalError { + #[error("AWS Signer Error: {0}")] + Aws(::Error), + #[error("Local Signer Error: {0}")] + Local(#[from] WalletError), +} + +impl From<::Error> for UniversalError { + fn from(e: ::Error) -> Self { + Self::Aws(e) + } +} + +#[async_trait::async_trait] +impl Signer for UniversalSigner { + type Error = UniversalError; + + async fn sign_message>( + &self, + message: S, + ) -> Result { + Ok(match self { + Self::Aws(signer) => signer.sign_message(message).await?, + Self::Local(signer) => signer.sign_message(message).await?, + }) + } + + async fn sign_transaction( + &self, + tx: &TypedTransaction, + ) -> Result { + Ok(match self { + Self::Aws(signer) => signer.sign_transaction(tx).await?, + Self::Local(signer) => signer.sign_transaction(tx).await?, + }) + } + + async fn sign_typed_data( + &self, + payload: &T, + ) -> Result { + Ok(match self { + Self::Aws(signer) => signer.sign_typed_data(payload).await?, + Self::Local(signer) => signer.sign_typed_data(payload).await?, + }) + } + + fn address(&self) -> Address { + match self { + Self::Aws(signer) => signer.address(), + Self::Local(signer) => signer.address(), + } + } + + /// Returns the signer's chain id + fn chain_id(&self) -> u64 { + match self { + Self::Aws(signer) => signer.chain_id(), + Self::Local(signer) => signer.chain_id(), + } + } + + /// Sets the signer's chain id + fn with_chain_id>(self, chain_id: T) -> Self { + match self { + Self::Aws(signer) => Self::Aws(signer.with_chain_id(chain_id)), + Self::Local(signer) => Self::Local(signer.with_chain_id(chain_id)), + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ea42b6d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,12 @@ +pub mod app; +pub mod aws; +pub mod config; +pub mod db; +pub mod server; +pub mod task_backoff; +pub mod tasks; + +pub mod broadcast_utils; +pub mod client; +pub mod keys; +pub mod serde_utils; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..be7b9b8 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,66 @@ +use std::path::PathBuf; +use std::sync::Arc; + +use clap::Parser; +use config::FileFormat; +use service::app::App; +use service::config::Config; +use service::task_backoff::TaskRunner; +use service::tasks; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::EnvFilter; + +#[derive(Parser)] +#[clap(rename_all = "kebab-case")] +struct Args { + #[clap(short, long, default_value = "./config.toml")] + config: PathBuf, + + #[clap(short, long)] + env_file: Vec, +} + +#[tokio::main] +async fn main() -> eyre::Result<()> { + let args = Args::parse(); + + dotenv::dotenv().ok(); + + for path in &args.env_file { + dotenv::from_path(path)?; + } + + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer().pretty().compact()) + .with(EnvFilter::from_default_env()) + .init(); + + let settings = config::Config::builder() + .add_source( + config::File::from(args.config.as_ref()).format(FileFormat::Toml), + ) + .add_source( + config::Environment::with_prefix("TX_SITTER").separator("__"), + ) + .add_source( + config::Environment::with_prefix("TX_SITTER_EXT") + .separator("__") + .try_parsing(true) + .list_separator(","), + ) + .build()?; + + let config = settings.try_deserialize::()?; + + let app = Arc::new(App::new(config).await?); + + let task_runner = TaskRunner::new(app.clone()); + task_runner.add_task("Broadcast transactions", tasks::broadcast_txs); + task_runner.add_task("Index transactions", tasks::index_blocks); + task_runner.add_task("Escalate transactions", tasks::escalate_txs); + + service::server::serve(app).await?; + + Ok(()) +} diff --git a/src/serde_utils.rs b/src/serde_utils.rs new file mode 100644 index 0000000..7f1e57a --- /dev/null +++ b/src/serde_utils.rs @@ -0,0 +1 @@ +pub mod decimal_u256; diff --git a/src/serde_utils/decimal_u256.rs b/src/serde_utils/decimal_u256.rs new file mode 100644 index 0000000..f37e802 --- /dev/null +++ b/src/serde_utils/decimal_u256.rs @@ -0,0 +1,42 @@ +use ethers::types::U256; + +pub fn serialize(u256: &U256, serializer: S) -> Result +where + S: serde::Serializer, +{ + let s = u256.to_string(); + serializer.serialize_str(&s) +} + +pub fn deserialize<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let s: &str = serde::Deserialize::deserialize(deserializer)?; + let u256 = U256::from_dec_str(s).map_err(serde::de::Error::custom)?; + Ok(u256) +} + +#[cfg(test)] +mod tests { + use serde::{Deserialize, Serialize}; + + use super::*; + + #[derive(Debug, Clone, Serialize, Deserialize)] + struct Test { + #[serde(with = "super")] + v: U256, + } + + #[test] + fn test_u256_serde() { + let test = Test { v: U256::from(123) }; + + let s = serde_json::to_string(&test).unwrap(); + assert_eq!(s, r#"{"v":"123"}"#); + + let test: Test = serde_json::from_str(&s).unwrap(); + assert_eq!(test.v, U256::from(123)); + } +} diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..b4e0610 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,172 @@ +use std::sync::Arc; + +use axum::extract::{Json, Path, State}; +use axum::http::StatusCode; +use axum::response::IntoResponse; +use axum::routing::{get, post}; +use axum::{Router, TypedHeader}; +use ethers_signers::Signer; +use eyre::Result; +use middleware::AuthorizedRelayer; +use thiserror::Error; + +use self::data::{ + CreateRelayerRequest, CreateRelayerResponse, GetTxResponse, SendTxRequest, + SendTxResponse, +}; +use crate::app::App; + +mod data; +mod middleware; + +#[derive(Debug, Error)] +pub enum ApiError { + #[error("Invalid key encoding")] + KeyEncoding, + + #[error("Invalid key length")] + KeyLength, + + #[error("Unauthorized")] + Unauthorized, + + #[error("Invalid format")] + InvalidFormat, + + #[error("Missing tx")] + MissingTx, + + #[error("Internal error {0}")] + Eyre(#[from] eyre::Report), +} + +impl IntoResponse for ApiError { + fn into_response(self) -> axum::response::Response { + let status_code = match self { + Self::KeyLength | Self::KeyEncoding => StatusCode::BAD_REQUEST, + Self::Unauthorized => StatusCode::UNAUTHORIZED, + Self::Eyre(_) => StatusCode::INTERNAL_SERVER_ERROR, + Self::InvalidFormat => StatusCode::BAD_REQUEST, + Self::MissingTx => StatusCode::NOT_FOUND, + }; + + let message = self.to_string(); + + (status_code, message).into_response() + } +} + +async fn send_tx( + State(app): State>, + TypedHeader(authorized_relayer): TypedHeader, + Json(req): Json, +) -> Result, ApiError> { + if !authorized_relayer.is_authorized(&req.relayer_id) { + return Err(ApiError::Unauthorized); + } + + let tx_id = if let Some(id) = req.tx_id { + id + } else { + uuid::Uuid::new_v4().to_string() + }; + + app.db + .create_transaction( + &tx_id, + req.to, + req.data.as_ref().map(|d| &d[..]).unwrap_or(&[]), + req.value, + req.gas_limit, + &req.relayer_id, + ) + .await?; + + Ok(Json(SendTxResponse { tx_id })) +} + +async fn get_tx( + State(app): State>, + Path(tx_id): Path, +) -> Result, ApiError> { + let tx = app.db.read_tx(&tx_id).await?.ok_or(ApiError::MissingTx)?; + + let get_tx_response = GetTxResponse { + tx_id: tx.tx_id, + to: tx.to.0, + data: if tx.data.is_empty() { + None + } else { + Some(tx.data.into()) + }, + value: tx.value.0, + gas_limit: tx.gas_limit.0, + nonce: tx.nonce, + tx_hash: tx.tx_hash.map(|h| h.0), + status: tx.status, + }; + + Ok(Json(get_tx_response)) +} + +async fn create_relayer( + State(app): State>, + Json(req): Json, +) -> Result, ApiError> { + let (key_id, signer) = app.keys_source.new_signer().await?; + + let address = signer.address(); + + let relayer_id = uuid::Uuid::new_v4(); + let relayer_id = relayer_id.to_string(); + + app.db + .create_relayer(&relayer_id, &req.name, req.chain_id, &key_id, address) + .await?; + + Ok(Json(CreateRelayerResponse { + relayer_id, + address, + })) +} + +async fn get_relayer( + State(_app): State>, + Path(_relayer_id): Path, +) -> &'static str { + "Hello, World!" +} + +pub async fn serve(app: Arc) -> Result<(), hyper::Error> { + let tx_routes = Router::new() + .route("/send", post(send_tx)) + .route("/:tx_id", get(get_tx)) + .layer(axum::middleware::from_fn_with_state( + app.clone(), + middleware::auth, + )) + .with_state(app.clone()); + + let relayer_routes = Router::new() + .route("/create", post(create_relayer)) + .route("/:relayer_id", get(get_relayer)) + .with_state(app.clone()); + + // let network_routes = Router::new() + // .route("/"); + + let router = Router::new() + .nest("/1/tx", tx_routes) + .nest("/1/relayer", relayer_routes) + .layer(tower_http::trace::TraceLayer::new_for_http()) + .layer(axum::middleware::from_fn(middleware::log_response)); + + let server = axum::Server::bind(&app.config.server.host) + .serve(router.into_make_service()); + + tracing::info!("Listening on {}", server.local_addr()); + + server.await?; + + Ok(()) +} diff --git a/src/server/data.rs b/src/server/data.rs new file mode 100644 index 0000000..f0c8e24 --- /dev/null +++ b/src/server/data.rs @@ -0,0 +1,59 @@ +use ethers::types::{Address, Bytes, H256, U256}; +use serde::{Deserialize, Serialize}; + +use crate::db::BlockTxStatus; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SendTxRequest { + pub relayer_id: String, + pub to: Address, + #[serde(with = "crate::serde_utils::decimal_u256")] + pub value: U256, + #[serde(default)] + pub data: Option, + #[serde(with = "crate::serde_utils::decimal_u256")] + pub gas_limit: U256, + #[serde(default)] + pub tx_id: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SendTxResponse { + pub tx_id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetTxResponse { + pub tx_id: String, + pub to: Address, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub data: Option, + #[serde(with = "crate::serde_utils::decimal_u256")] + pub value: U256, + #[serde(with = "crate::serde_utils::decimal_u256")] + pub gas_limit: U256, + pub nonce: u64, + + // Sent tx data + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tx_hash: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub status: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateRelayerRequest { + pub name: String, + pub chain_id: u64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateRelayerResponse { + pub relayer_id: String, + pub address: Address, +} diff --git a/src/server/middleware.rs b/src/server/middleware.rs new file mode 100644 index 0000000..50ba8c4 --- /dev/null +++ b/src/server/middleware.rs @@ -0,0 +1,5 @@ +mod auth_middleware; +mod log_response_middleware; + +pub use self::auth_middleware::{auth, AuthorizedRelayer}; +pub use self::log_response_middleware::log_response; diff --git a/src/server/middleware/auth_middleware.rs b/src/server/middleware/auth_middleware.rs new file mode 100644 index 0000000..8935450 --- /dev/null +++ b/src/server/middleware/auth_middleware.rs @@ -0,0 +1,144 @@ +use std::sync::Arc; + +use axum::extract::{Query, State}; +use axum::http::{HeaderName, HeaderValue, Request}; +use axum::middleware::Next; +use axum::response::{IntoResponse, Response}; +use headers::Header; +use serde::{Deserialize, Serialize}; + +use crate::app::App; +use crate::server::ApiError; + +pub const AUTHORIZED_RELAYER: &str = "x-authorized-relayer"; +static HEADER_NAME: HeaderName = HeaderName::from_static(AUTHORIZED_RELAYER); + +pub enum AuthorizedRelayer { + Named(String), + Any, +} + +impl AuthorizedRelayer { + pub fn is_authorized(&self, relayer_id: &str) -> bool { + match self { + AuthorizedRelayer::Any => true, + AuthorizedRelayer::Named(name) => name == relayer_id, + } + } +} + +impl Header for AuthorizedRelayer { + fn name() -> &'static HeaderName { + &HEADER_NAME + } + + fn decode<'i, I>(values: &mut I) -> Result + where + Self: Sized, + I: Iterator, + { + let value = values.next().ok_or_else(headers::Error::invalid)?; + let value = value + .to_str() + .map_err(|_| headers::Error::invalid())? + .to_owned(); + + if value == "*" { + Ok(Self::Any) + } else { + Ok(Self::Named(value)) + } + } + + fn encode>(&self, values: &mut E) { + match self { + AuthorizedRelayer::Named(name) => values + .extend(std::iter::once(HeaderValue::from_str(name).unwrap())), + AuthorizedRelayer::Any => { + values.extend(std::iter::once(HeaderValue::from_static("*"))) + } + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AuthParams { + #[serde(default)] + api_key: Option, +} + +pub async fn auth( + State(context): State>, + Query(query): Query, + request: Request, + next: Next, +) -> Response { + let (mut parts, body) = request.into_parts(); + + if context.config.server.disable_auth { + parts + .headers + .insert(AUTHORIZED_RELAYER, HeaderValue::from_str("*").unwrap()); + } else { + let authorized_relayer = match auth_inner(context.clone(), query).await + { + Ok(relayer_id) => relayer_id, + Err(error) => return error.into_response(), + }; + + parts.headers.insert( + AUTHORIZED_RELAYER, + HeaderValue::from_str(&authorized_relayer).unwrap(), + ); + } + + let request = Request::from_parts(parts, body); + + next.run(request).await +} + +async fn auth_inner( + _app: Arc, + _query: AuthParams, +) -> Result { + todo!("Add tables to DB and implement") + // let mut api_key = None; + + // TODO: Support Bearer in auth header + // let auth_header = parts.headers.get(AUTHORIZATION); + // if let Some(auth_header) = auth_header { + // todo!() + // } + + // if let Some(api_key_from_query) = query.api_key { + // api_key = Some(api_key_from_query); + // } + + // let Some(api_key) = api_key else { + // return Err(ApiError::Unauthorized); + // }; + + // let api_key = hex::decode(&api_key).map_err(|err| { + // tracing::warn!(?err, "Error decoding api key"); + + // ApiError::KeyEncoding + // })?; + + // let api_key: [u8; 32] = + // api_key.try_into().map_err(|_| ApiError::KeyLength)?; + + // let api_key_hash = Sha3_256::digest(&api_key); + + // let api_key_hash = hex::encode(api_key_hash); + + // // let relayer_id = context + // // .api_keys_db + // // .get_relayer_id_by_hash(api_key_hash) + // // .await? + // // .ok_or_else(|| ApiError::Unauthorized)?; + + // let relayer_id = todo!(); + + // Ok(relayer_id) +} diff --git a/src/server/middleware/log_response_middleware.rs b/src/server/middleware/log_response_middleware.rs new file mode 100644 index 0000000..ecd8f42 --- /dev/null +++ b/src/server/middleware/log_response_middleware.rs @@ -0,0 +1,24 @@ +use axum::http::Request; +use axum::middleware::Next; +use axum::response::Response; +use hyper::Body; + +pub async fn log_response(request: Request, next: Next) -> Response { + let mut response = next.run(request).await; + + if !response.status().is_success() { + let body_bytes = hyper::body::to_bytes(response.body_mut()) + .await + .expect("Failed to read body"); + + let body_as_text = std::str::from_utf8(&body_bytes) + .unwrap_or("Failed to parse body as text"); + + let status_code = response.status(); + tracing::error!(?status_code, "{body_as_text}"); + + *response.body_mut() = axum::body::boxed(Body::from(body_bytes)); + } + + response +} diff --git a/src/task_backoff.rs b/src/task_backoff.rs new file mode 100644 index 0000000..4099e36 --- /dev/null +++ b/src/task_backoff.rs @@ -0,0 +1,80 @@ +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use futures::Future; + +use crate::app::App; + +const FAILURE_MONITORING_PERIOD: Duration = Duration::from_secs(60); + +pub struct TaskRunner { + app: Arc, +} + +impl TaskRunner { + pub fn new(app: Arc) -> Self { + Self { app } + } + + pub fn add_task(&self, label: S, task: C) + where + S: ToString, + C: Fn(Arc) -> F + Send + Sync + 'static, + F: Future> + Send + 'static, + { + let app = self.app.clone(); + let label = label.to_string(); + + tokio::spawn(async move { + let mut failures = vec![]; + + loop { + tracing::info!(label, "Running task"); + + let result = task(app.clone()).await; + + if let Err(err) = result { + tracing::error!(label, error = ?err, "Task failed"); + + failures.push(Instant::now()); + let backoff = determine_backoff(&failures); + + tokio::time::sleep(backoff).await; + + prune_failures(&mut failures); + } else { + tracing::info!(label, "Task finished"); + break; + } + } + }); + } +} + +fn determine_backoff(failures: &[Instant]) -> Duration { + let mut base_backoff = Duration::from_secs(5); + + let num_failures_within_period = failures + .iter() + .filter(|&instant| instant.elapsed() < FAILURE_MONITORING_PERIOD) + .count(); + + if num_failures_within_period < 5 { + // I second for each failure + base_backoff += Duration::from_secs(num_failures_within_period as u64); + } + + if num_failures_within_period > 5 { + base_backoff += Duration::from_secs(10); + } + + if num_failures_within_period > 10 { + base_backoff += Duration::from_secs(30); + } + + base_backoff +} + +fn prune_failures(failures: &mut Vec) { + failures.retain(|instant| instant.elapsed() < FAILURE_MONITORING_PERIOD); +} diff --git a/src/tasks.rs b/src/tasks.rs new file mode 100644 index 0000000..8839ce6 --- /dev/null +++ b/src/tasks.rs @@ -0,0 +1,7 @@ +pub mod broadcast; +pub mod escalate; +pub mod index; + +pub use self::broadcast::broadcast_txs; +pub use self::escalate::escalate_txs; +pub use self::index::index_blocks; diff --git a/src/tasks/broadcast.rs b/src/tasks/broadcast.rs new file mode 100644 index 0000000..b7c7f21 --- /dev/null +++ b/src/tasks/broadcast.rs @@ -0,0 +1,84 @@ +use std::sync::Arc; +use std::time::Duration; + +use ethers::providers::Middleware; +use ethers::types::transaction::eip2718::TypedTransaction; +use ethers::types::transaction::eip2930::AccessList; +use ethers::types::{Address, Eip1559TransactionRequest, NameOrAddress}; +use eyre::ContextCompat; + +use crate::app::App; +use crate::broadcast_utils::{ + calculate_gas_fees_from_estimates, calculate_max_base_fee_per_gas, +}; + +const MAX_IN_FLIGHT_TXS: usize = 5; + +pub async fn broadcast_txs(app: Arc) -> eyre::Result<()> { + loop { + let txs = app.db.get_unsent_txs(MAX_IN_FLIGHT_TXS).await?; + + // TODO: Parallelize per chain id? + for tx in txs { + tracing::info!(tx.id, "Sending tx"); + + let middleware = app + .fetch_signer_middleware(tx.chain_id, tx.key_id.clone()) + .await?; + + let estimates = app + .db + .get_latest_block_fees_by_chain_id(tx.chain_id) + .await? + .context("Missing block")?; + + let max_base_fee_per_gas = + calculate_max_base_fee_per_gas(&estimates)?; + + let (max_fee_per_gas, max_priority_fee_per_gas) = + calculate_gas_fees_from_estimates( + &estimates, + 2, // Priority - 50th percentile + max_base_fee_per_gas, + )?; + + let eip1559_tx = Eip1559TransactionRequest { + from: None, + to: Some(NameOrAddress::from(Address::from(tx.tx_to.0))), + gas: Some(tx.gas_limit.0), + value: Some(tx.value.0), + data: Some(tx.data.into()), + nonce: Some(tx.nonce.into()), + access_list: AccessList::default(), + max_priority_fee_per_gas: Some(max_priority_fee_per_gas), + max_fee_per_gas: Some(max_fee_per_gas), + chain_id: Some(tx.chain_id.into()), + }; + + tracing::debug!(?eip1559_tx, "Sending tx"); + + // TODO: Is it possible that we send a tx but don't store it in the DB? + // TODO: Be smarter about error handling - a tx can fail to be sent + // e.g. because the relayer is out of funds + // but we don't want to retry it forever + let pending_tx = middleware + .send_transaction(TypedTransaction::Eip1559(eip1559_tx), None) + .await?; + + let tx_hash = pending_tx.tx_hash(); + + tracing::info!(?tx.id, ?tx_hash, "Tx sent successfully"); + + app.db + .insert_tx_broadcast( + &tx.id, + tx_hash, + max_fee_per_gas, + max_priority_fee_per_gas, + ) + .await?; + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } +} diff --git a/src/tasks/escalate.rs b/src/tasks/escalate.rs new file mode 100644 index 0000000..b840144 --- /dev/null +++ b/src/tasks/escalate.rs @@ -0,0 +1,85 @@ +use std::sync::Arc; + +use ethers::providers::Middleware; +use ethers::types::transaction::eip2718::TypedTransaction; +use ethers::types::transaction::eip2930::AccessList; +use ethers::types::{Address, Eip1559TransactionRequest, NameOrAddress, U256}; +use eyre::ContextCompat; + +use crate::app::App; + +pub async fn escalate_txs(app: Arc) -> eyre::Result<()> { + loop { + let txs_for_escalation = app + .db + .fetch_txs_for_escalation(app.config.service.escalation_interval) + .await?; + + for tx in txs_for_escalation { + tracing::info!(tx.id, "Escalating tx"); + + let middleware = app + .fetch_signer_middleware(tx.chain_id, tx.key_id.clone()) + .await?; + + let escalation = tx.escalation_count + 1; + + let estimates = app + .db + .get_latest_block_fees_by_chain_id(tx.chain_id) + .await? + .context("Missing block")?; + + // Min increase of 20% on the priority fee required for a replacement tx + let increased_gas_price_percentage = + U256::from(100 + (10 * (1 + escalation))); + + let factor = U256::from(100); + + let max_priority_fee_per_gas_increase = + tx.initial_max_priority_fee_per_gas.0 + * increased_gas_price_percentage + / factor; + + // TODO: Add limits per network + let max_priority_fee_per_gas = + tx.initial_max_priority_fee_per_gas.0 + + max_priority_fee_per_gas_increase; + + let max_fee_per_gas = + estimates.base_fee_per_gas + max_priority_fee_per_gas; + + let eip1559_tx = Eip1559TransactionRequest { + from: None, + to: Some(NameOrAddress::from(Address::from(tx.tx_to.0))), + gas: Some(tx.gas_limit.0), + value: Some(tx.value.0), + data: Some(tx.data.into()), + nonce: Some(tx.nonce.into()), + access_list: AccessList::default(), + max_priority_fee_per_gas: Some(max_priority_fee_per_gas), + max_fee_per_gas: Some(max_fee_per_gas), + chain_id: Some(tx.chain_id.into()), + }; + + let pending_tx = middleware + .send_transaction(TypedTransaction::Eip1559(eip1559_tx), None) + .await?; + + let tx_hash = pending_tx.tx_hash(); + + tracing::info!(?tx.id, ?tx_hash, "Tx escalated"); + + app.db + .escalate_tx( + &tx.id, + tx_hash, + max_fee_per_gas, + max_priority_fee_per_gas, + ) + .await?; + } + + tokio::time::sleep(app.config.service.escalation_interval).await; + } +} diff --git a/src/tasks/index.rs b/src/tasks/index.rs new file mode 100644 index 0000000..e926e2a --- /dev/null +++ b/src/tasks/index.rs @@ -0,0 +1,127 @@ +use std::sync::Arc; + +use ethers::providers::{Http, Middleware, Provider}; +use ethers::types::{Block, BlockNumber, H256, U256}; +use eyre::ContextCompat; + +use crate::app::App; +use crate::broadcast_utils::gas_estimation::{ + estimate_percentile_fees, FeesEstimate, +}; +use crate::db::BlockTxStatus; + +const BLOCK_FEE_HISTORY_SIZE: usize = 10; +const TRAILING_BLOCK_OFFSET: u64 = 5; +const FEE_PERCENTILES: [f64; 5] = [5.0, 25.0, 50.0, 75.0, 95.0]; + +pub async fn index_blocks(app: Arc) -> eyre::Result<()> { + loop { + let next_block_numbers = app.db.get_next_block_numbers().await?; + + // TODO: Parallelize + for (block_number, chain_id) in next_block_numbers { + let chain_id = U256::from(chain_id); + let rpc = app + .rpcs + .get(&chain_id) + .context("Missing RPC for chain id")?; + + if let Some((block, fee_estimates)) = + fetch_block_with_fee_estimates(rpc, block_number).await? + { + app.db + .save_block( + block_number, + chain_id.as_u64(), + &block.transactions, + &fee_estimates, + BlockTxStatus::Mined, + ) + .await?; + + let relayer_addresses = + app.db.fetch_relayer_addresses(chain_id.as_u64()).await?; + + // TODO: Parallelize + for relayer_address in relayer_addresses { + let tx_count = rpc + .get_transaction_count(relayer_address, None) + .await?; + + app.db + .update_relayer_nonce( + chain_id.as_u64(), + relayer_address, + tx_count.as_u64(), + ) + .await?; + } + + if block_number > TRAILING_BLOCK_OFFSET { + let (block, fee_estimates) = + fetch_block_with_fee_estimates( + rpc, + block_number - TRAILING_BLOCK_OFFSET, + ) + .await? + .context("Missing trailing block")?; + + app.db + .save_block( + block_number, + chain_id.as_u64(), + &block.transactions, + &fee_estimates, + BlockTxStatus::Finalized, + ) + .await?; + } + } + } + + app.db.update_transactions(BlockTxStatus::Mined).await?; + app.db.update_transactions(BlockTxStatus::Finalized).await?; + } +} + +pub async fn fetch_block_with_fee_estimates( + rpc: &Provider, + block_id: impl Into, +) -> eyre::Result, FeesEstimate)>> { + let block_id = block_id.into(); + + let block = rpc.get_block(block_id).await?; + + let Some(block) = block else { + return Ok(None); + }; + + let fee_history = rpc + .fee_history(BLOCK_FEE_HISTORY_SIZE, block_id, &FEE_PERCENTILES) + .await?; + + let fee_estimates = estimate_percentile_fees(&fee_history)?; + + Ok(Some((block, fee_estimates))) +} + +pub async fn fetch_block( + rpc: &Provider, + block_id: impl Into, +) -> eyre::Result, FeesEstimate)>> { + let block_id = block_id.into(); + + let block = rpc.get_block(block_id).await?; + + let Some(block) = block else { + return Ok(None); + }; + + let fee_history = rpc + .fee_history(BLOCK_FEE_HISTORY_SIZE, block_id, &FEE_PERCENTILES) + .await?; + + let fee_estimates = estimate_percentile_fees(&fee_history)?; + + Ok(Some((block, fee_estimates))) +} From 04dbc13ffdd4f5910a3cb0c3ef97595287c6fe03 Mon Sep 17 00:00:00 2001 From: Dzejkop Date: Wed, 22 Nov 2023 10:37:10 +0100 Subject: [PATCH 2/4] Add basic tests --- Cargo.lock | 45 +++++++++++ Cargo.toml | 2 + crates/fake-rpc/Cargo.toml | 52 +++++++++++++ crates/fake-rpc/src/lib.rs | 144 ++++++++++++++++++++++++++++++++++++ crates/fake-rpc/src/main.rs | 30 ++++++++ manual_test.nu | 4 +- src/aws/ethers_signer.rs | 8 +- src/lib.rs | 1 + src/main.rs | 15 +--- src/server.rs | 25 +++++-- src/server/data.rs | 2 +- src/service.rs | 49 ++++++++++++ tests/common/mod.rs | 138 ++++++++++++++++++++++++++++++++++ tests/create_relayer.rs | 37 +++++++++ tests/send_tx.rs | 91 +++++++++++++++++++++++ 15 files changed, 614 insertions(+), 29 deletions(-) create mode 100644 crates/fake-rpc/Cargo.toml create mode 100644 crates/fake-rpc/src/lib.rs create mode 100644 crates/fake-rpc/src/main.rs create mode 100644 src/service.rs create mode 100644 tests/common/mod.rs create mode 100644 tests/create_relayer.rs create mode 100644 tests/send_tx.rs diff --git a/Cargo.lock b/Cargo.lock index 5e1ee40..984c18f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1660,6 +1660,43 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fake-rpc" +version = "0.1.0" +dependencies = [ + "async-trait", + "axum", + "chrono", + "clap", + "config", + "dotenv", + "ethers", + "ethers-signers", + "eyre", + "futures", + "headers", + "hex", + "hex-literal", + "humantime", + "humantime-serde", + "hyper", + "rand", + "reqwest", + "serde", + "serde_json", + "sha3", + "spki", + "sqlx", + "strum", + "thiserror", + "tokio", + "toml 0.8.8", + "tower-http", + "tracing", + "tracing-subscriber", + "uuid 0.8.2", +] + [[package]] name = "fastrand" version = "2.0.1" @@ -2039,6 +2076,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + [[package]] name = "hkdf" version = "0.12.3" @@ -3760,9 +3803,11 @@ dependencies = [ "ethers", "ethers-signers", "eyre", + "fake-rpc", "futures", "headers", "hex", + "hex-literal", "humantime", "humantime-serde", "hyper", diff --git a/Cargo.toml b/Cargo.toml index fcd9ad3..ffcd4c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ ethers = { version = "2.0.11" } ethers-signers = { version = "2.0.11" } eyre = "0.6.5" hex = "0.4.3" +hex-literal = "0.4.1" reqwest = { version = "0.11.13", default-features = false, features = [ "rustls-tls", ] } @@ -68,6 +69,7 @@ postgres-docker-utils = { path = "crates/postgres-docker-utils" } [dev-dependencies] test-case = "3.1.0" indoc = "2.0.3" +fake-rpc = { path = "crates/fake-rpc" } [[bin]] name = "bootstrap" diff --git a/crates/fake-rpc/Cargo.toml b/crates/fake-rpc/Cargo.toml new file mode 100644 index 0000000..da94d7a --- /dev/null +++ b/crates/fake-rpc/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "fake-rpc" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = "1.0.136" +axum = { version = "0.6.20", features = ["headers"] } +thiserror = "1.0.50" +headers = "0.3.9" +humantime = "2.1.0" +humantime-serde = "1.1.1" +hyper = "0.14.27" +dotenv = "0.15.0" +clap = { version = "4.3.0", features = ["env", "derive"] } +ethers = { version = "2.0.11" } +ethers-signers = { version = "2.0.11" } +eyre = "0.6.5" +hex = "0.4.3" +hex-literal = "0.4.1" +reqwest = { version = "0.11.13", default-features = false, features = [ + "rustls-tls", +] } +serde_json = "1.0.91" +strum = { version = "0.25.0", features = ["derive"] } +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = [ + "env-filter", + "std", + "fmt", + "json", + "ansi", +] } +tower-http = { version = "0.4.4", features = ["trace"] } +uuid = { version = "0.8", features = ["v4"] } +futures = "0.3" +chrono = "0.4" +rand = "0.8.5" +sha3 = "0.10.8" +config = "0.13.3" +toml = "0.8.8" +sqlx = { version = "0.7.2", features = [ + "runtime-tokio", + "tls-rustls", + "postgres", + "migrate", +] } +spki = "0.7.2" +async-trait = "0.1.74" diff --git a/crates/fake-rpc/src/lib.rs b/crates/fake-rpc/src/lib.rs new file mode 100644 index 0000000..2a9dbc0 --- /dev/null +++ b/crates/fake-rpc/src/lib.rs @@ -0,0 +1,144 @@ +use std::net::{Ipv4Addr, SocketAddr}; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; + +use axum::extract::State; +use axum::routing::{post, IntoMakeService}; +use axum::{Json, Router}; +use ethers::utils::{Anvil, AnvilInstance}; +use hyper::server::conn::AddrIncoming; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use tokio::sync::Mutex; + +pub struct DoubleAnvil { + main_anvil: Mutex, + reference_anvil: Mutex, + held_back_txs: Mutex>, + + auto_advance: AtomicBool, +} + +impl DoubleAnvil { + pub async fn drop_txs(&self) -> eyre::Result<()> { + let mut held_back_txs = self.held_back_txs.lock().await; + held_back_txs.clear(); + Ok(()) + } + + pub async fn advance(&self) -> eyre::Result<()> { + let mut held_back_txs = self.held_back_txs.lock().await; + + for req in held_back_txs.drain(..) { + tracing::info!(?req, "eth_sendRawTransaction"); + + let response = reqwest::Client::new() + .post(&self.main_anvil.lock().await.endpoint()) + .json(&req) + .send() + .await + .unwrap(); + + let resp = response.json::().await.unwrap(); + + tracing::info!(?resp, "eth_sendRawTransaction.response"); + } + + Ok(()) + } + + pub fn set_auto_advance(&self, auto_advance: bool) { + self.auto_advance + .store(auto_advance, std::sync::atomic::Ordering::SeqCst); + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +struct JsonRpcReq { + pub id: u64, + pub jsonrpc: String, + pub method: String, + #[serde(default)] + pub params: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct JsonRpcResponse { + pub id: u64, + pub jsonrpc: String, + pub result: Value, +} + +async fn advance(State(anvil): State>) { + anvil.advance().await.unwrap(); +} + +async fn rpc( + State(anvil): State>, + Json(req): Json, +) -> Json { + let method = req.method.as_str(); + let anvil_instance = match method { + "eth_sendRawTransaction" => { + anvil.held_back_txs.lock().await.push(req.clone()); + + if anvil.auto_advance.load(std::sync::atomic::Ordering::SeqCst) { + anvil.advance().await.unwrap(); + } + + anvil.reference_anvil.lock().await + } + "eth_getTransactionReceipt" => anvil.main_anvil.lock().await, + "eth_getTransactionByHash" => anvil.reference_anvil.lock().await, + _ => anvil.main_anvil.lock().await, + }; + + tracing::info!(?req, "{}", method); + + let response = reqwest::Client::new() + .post(&anvil_instance.endpoint()) + .json(&req) + .send() + .await + .unwrap(); + + let resp = response.json::().await.unwrap(); + + tracing::info!(?resp, "{}.response", method); + + Json(resp) +} + +pub async fn serve( + port: u16, +) -> ( + Arc, + axum::Server>, +) { + let main_anvil = Anvil::new().spawn(); + let reference_anvil = Anvil::new().spawn(); + + tracing::info!("Main anvil instance: {}", main_anvil.endpoint()); + tracing::info!("Reference anvil instance: {}", reference_anvil.endpoint()); + + let state = Arc::new(DoubleAnvil { + main_anvil: Mutex::new(main_anvil), + reference_anvil: Mutex::new(reference_anvil), + held_back_txs: Mutex::new(Vec::new()), + auto_advance: AtomicBool::new(true), + }); + + let router = Router::new() + .route("/", post(rpc)) + .route("/advance", post(advance)) + .with_state(state.clone()) + .layer(tower_http::trace::TraceLayer::new_for_http()); + + let host = Ipv4Addr::new(127, 0, 0, 1); + let socket_addr = SocketAddr::new(host.into(), port); + + let server = + axum::Server::bind(&socket_addr).serve(router.into_make_service()); + + (state, server) +} diff --git a/crates/fake-rpc/src/main.rs b/crates/fake-rpc/src/main.rs new file mode 100644 index 0000000..a20b425 --- /dev/null +++ b/crates/fake-rpc/src/main.rs @@ -0,0 +1,30 @@ +use clap::Parser; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::EnvFilter; + +#[derive(Debug, Clone, Parser)] +struct Args { + #[clap(short, long, default_value = "8545")] + port: u16, +} + +#[tokio::main] +async fn main() -> eyre::Result<()> { + dotenv::dotenv().ok(); + + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer().pretty().compact()) + .with(EnvFilter::from_default_env()) + .init(); + + let args = Args::parse(); + + let (_app, server) = fake_rpc::serve(args.port).await; + + tracing::info!("Serving fake RPC at {}", server.local_addr()); + + server.await?; + + Ok(()) +} diff --git a/manual_test.nu b/manual_test.nu index 6031671..e3849e9 100644 --- a/manual_test.nu +++ b/manual_test.nu @@ -6,8 +6,8 @@ http post -t application/json http://127.0.0.1:3000/1/relayer/create { "name": " # Send a transaction http post -t application/json http://127.0.0.1:3000/1/tx/send { - "relayerId": "a2d426a9-5b55-4048-812a-ef9e9f2b3a53", - "to": "0x14bf69c64d27e5a5b07188b2e19f7501baa79209", + "relayerId": "2c1c949e-22f5-4c9a-adb7-f2a7b17111e1", + "to": "0xe46e8850051fba970ce6108601df51abb460326f", "value": "10", "data": "" "gasLimit": "150000" diff --git a/src/aws/ethers_signer.rs b/src/aws/ethers_signer.rs index 70100d8..3296679 100644 --- a/src/aws/ethers_signer.rs +++ b/src/aws/ethers_signer.rs @@ -330,9 +330,7 @@ mod tests { .load() .await; - let kms_client = aws_sdk_kms::Client::new(&config); - - kms_client + aws_sdk_kms::Client::new(&config) } #[allow(dead_code)] @@ -342,9 +340,7 @@ mod tests { .load() .await; - let kms_client = aws_sdk_kms::Client::new(&config); - - kms_client + aws_sdk_kms::Client::new(&config) } #[tokio::test] diff --git a/src/lib.rs b/src/lib.rs index ea42b6d..b52f53c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ pub mod aws; pub mod config; pub mod db; pub mod server; +pub mod service; pub mod task_backoff; pub mod tasks; diff --git a/src/main.rs b/src/main.rs index be7b9b8..a0416ce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,9 @@ use std::path::PathBuf; -use std::sync::Arc; use clap::Parser; use config::FileFormat; -use service::app::App; use service::config::Config; -use service::task_backoff::TaskRunner; -use service::tasks; +use service::service::Service; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::EnvFilter; @@ -53,14 +50,8 @@ async fn main() -> eyre::Result<()> { let config = settings.try_deserialize::()?; - let app = Arc::new(App::new(config).await?); - - let task_runner = TaskRunner::new(app.clone()); - task_runner.add_task("Broadcast transactions", tasks::broadcast_txs); - task_runner.add_task("Index transactions", tasks::index_blocks); - task_runner.add_task("Escalate transactions", tasks::escalate_txs); - - service::server::serve(app).await?; + let service = Service::new(config).await?; + service.wait().await?; Ok(()) } diff --git a/src/server.rs b/src/server.rs index b4e0610..cfe06ee 100644 --- a/src/server.rs +++ b/src/server.rs @@ -3,10 +3,11 @@ use std::sync::Arc; use axum::extract::{Json, Path, State}; use axum::http::StatusCode; use axum::response::IntoResponse; -use axum::routing::{get, post}; +use axum::routing::{get, post, IntoMakeService}; use axum::{Router, TypedHeader}; use ethers_signers::Signer; use eyre::Result; +use hyper::server::conn::AddrIncoming; use middleware::AuthorizedRelayer; use thiserror::Error; @@ -16,7 +17,7 @@ use self::data::{ }; use crate::app::App; -mod data; +pub mod data; mod middleware; #[derive(Debug, Error)] @@ -137,7 +138,19 @@ async fn get_relayer( "Hello, World!" } -pub async fn serve(app: Arc) -> Result<(), hyper::Error> { +pub async fn serve(app: Arc) -> eyre::Result<()> { + let server = spawn_server(app).await?; + + tracing::info!("Listening on {}", server.local_addr()); + + server.await?; + + Ok(()) +} + +pub async fn spawn_server( + app: Arc, +) -> eyre::Result>> { let tx_routes = Router::new() .route("/send", post(send_tx)) .route("/:tx_id", get(get_tx)) @@ -164,9 +177,5 @@ pub async fn serve(app: Arc) -> Result<(), hyper::Error> { let server = axum::Server::bind(&app.config.server.host) .serve(router.into_make_service()); - tracing::info!("Listening on {}", server.local_addr()); - - server.await?; - - Ok(()) + Ok(server) } diff --git a/src/server/data.rs b/src/server/data.rs index f0c8e24..22b2485 100644 --- a/src/server/data.rs +++ b/src/server/data.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::db::BlockTxStatus; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SendTxRequest { pub relayer_id: String, diff --git a/src/service.rs b/src/service.rs new file mode 100644 index 0000000..b07c6c4 --- /dev/null +++ b/src/service.rs @@ -0,0 +1,49 @@ +use std::net::SocketAddr; +use std::sync::Arc; + +use tokio::task::JoinHandle; + +use crate::app::App; +use crate::config::Config; +use crate::task_backoff::TaskRunner; +use crate::tasks; + +pub struct Service { + _app: Arc, + local_addr: SocketAddr, + server_handle: JoinHandle>, +} + +impl Service { + pub async fn new(config: Config) -> eyre::Result { + let app = Arc::new(App::new(config).await?); + + let task_runner = TaskRunner::new(app.clone()); + task_runner.add_task("Broadcast transactions", tasks::broadcast_txs); + task_runner.add_task("Index transactions", tasks::index_blocks); + task_runner.add_task("Escalate transactions", tasks::escalate_txs); + + let server = crate::server::spawn_server(app.clone()).await?; + let local_addr = server.local_addr(); + let server_handle = tokio::spawn(async move { + server.await?; + Ok(()) + }); + + Ok(Self { + _app: app, + local_addr, + server_handle, + }) + } + + pub fn local_addr(&self) -> SocketAddr { + self.local_addr + } + + pub async fn wait(self) -> eyre::Result<()> { + self.server_handle.await??; + + Ok(()) + } +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs new file mode 100644 index 0000000..55bece6 --- /dev/null +++ b/tests/common/mod.rs @@ -0,0 +1,138 @@ +#![allow(dead_code)] // Needed because this module is imported as module by many test crates + +use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; +use std::sync::Arc; +use std::time::Duration; + +use ethers::core::k256::ecdsa::SigningKey; +use ethers::middleware::SignerMiddleware; +use ethers::providers::{Http, Middleware, Provider}; +use ethers::signers::LocalWallet; +use ethers::types::{Address, Eip1559TransactionRequest, H160, U256}; +use ethers_signers::Signer; +use fake_rpc::DoubleAnvil; +use postgres_docker_utils::DockerContainerGuard; +use service::config::{ + Config, DatabaseConfig, KeysConfig, LocalKeysConfig, RpcConfig, + ServerConfig, TxSitterConfig, +}; +use service::service::Service; +use tokio::task::JoinHandle; + +pub type AppMiddleware = SignerMiddleware>, LocalWallet>; + +pub const DEFAULT_ANVIL_ACCOUNT: Address = H160(hex_literal::hex!( + "f39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +)); + +pub const DEFAULT_ANVIL_PRIVATE_KEY: &[u8] = &hex_literal::hex!( + "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +); + +pub const ARBITRARY_ADDRESS: Address = H160(hex_literal::hex!( + "1Ed53d680B8890DAe2a63f673a85fFDE1FD5C7a2" +)); + +pub const DEFAULT_ANVIL_CHAIN_ID: u64 = 31337; + +pub struct DoubleAnvilHandle { + pub double_anvil: Arc, + local_addr: SocketAddr, + server_handle: JoinHandle>, +} + +impl DoubleAnvilHandle { + pub fn local_addr(&self) -> String { + self.local_addr.to_string() + } +} + +pub async fn setup_db() -> eyre::Result<(String, DockerContainerGuard)> { + let db_container = postgres_docker_utils::setup().await?; + let db_socket_addr = db_container.address(); + let url = format!("postgres://postgres:postgres@{db_socket_addr}/database"); + + Ok((url, db_container)) +} + +pub async fn setup_double_anvil() -> eyre::Result { + let (double_anvil, server) = fake_rpc::serve(0).await; + + let local_addr = server.local_addr(); + + let server_handle = tokio::spawn(async move { + server.await?; + Ok(()) + }); + + let middleware = setup_middleware( + format!("http://{local_addr}"), + DEFAULT_ANVIL_PRIVATE_KEY, + ) + .await?; + + // We need to seed some transactions so we can get fee estimates on the first block + middleware + .send_transaction( + Eip1559TransactionRequest { + to: Some(DEFAULT_ANVIL_ACCOUNT.into()), + value: Some(U256::from(100u64)), + ..Default::default() + }, + None, + ) + .await? + .await?; + + Ok(DoubleAnvilHandle { + double_anvil, + local_addr, + server_handle, + }) +} + +pub async fn setup_service( + rpc_url: &str, + db_connection_url: &str, + escalation_interval: Duration, +) -> eyre::Result { + println!("rpc_url.to_string() = {}", rpc_url); + + let config = Config { + service: TxSitterConfig { + escalation_interval, + }, + server: ServerConfig { + host: SocketAddr::V4(SocketAddrV4::new( + Ipv4Addr::new(127, 0, 0, 1), + 0, + )), + disable_auth: true, + }, + rpc: RpcConfig { + rpcs: vec![format!("http://{}", rpc_url.to_string())], + }, + database: DatabaseConfig { + connection_string: db_connection_url.to_string(), + }, + keys: KeysConfig::Local(LocalKeysConfig {}), + }; + + let service = Service::new(config).await?; + + Ok(service) +} + +pub async fn setup_middleware( + rpc_url: impl AsRef, + private_key: &[u8], +) -> eyre::Result { + let provider = Provider::::new(rpc_url.as_ref().parse()?); + + let wallet = LocalWallet::from(SigningKey::from_slice(private_key)?) + .with_chain_id(provider.get_chainid().await?.as_u64()); + + let middleware = SignerMiddleware::new(Arc::new(provider), wallet); + + Ok(middleware) +} diff --git a/tests/create_relayer.rs b/tests/create_relayer.rs new file mode 100644 index 0000000..a64df1f --- /dev/null +++ b/tests/create_relayer.rs @@ -0,0 +1,37 @@ +use std::time::Duration; + +use common::setup_db; +use service::server::data::{CreateRelayerRequest, CreateRelayerResponse}; + +use crate::common::{ + setup_double_anvil, setup_service, DEFAULT_ANVIL_CHAIN_ID, +}; + +mod common; + +const ESCALATION_INTERVAL: Duration = Duration::from_secs(30); + +#[tokio::test] +async fn create_relayer() -> eyre::Result<()> { + let (db_url, _db_container) = setup_db().await?; + let double_anvil = setup_double_anvil().await?; + + let service = + setup_service(&double_anvil.local_addr(), &db_url, ESCALATION_INTERVAL) + .await?; + + let addr = service.local_addr(); + + let response = reqwest::Client::new() + .post(&format!("http://{}/1/relayer/create", addr)) + .json(&CreateRelayerRequest { + name: "Test relayer".to_string(), + chain_id: DEFAULT_ANVIL_CHAIN_ID, + }) + .send() + .await?; + + let _response: CreateRelayerResponse = response.json().await?; + + Ok(()) +} diff --git a/tests/send_tx.rs b/tests/send_tx.rs new file mode 100644 index 0000000..462590a --- /dev/null +++ b/tests/send_tx.rs @@ -0,0 +1,91 @@ +use std::time::Duration; + +use ethers::providers::Middleware; +use ethers::types::{Eip1559TransactionRequest, U256}; +use ethers::utils::parse_units; +use service::server::data::{ + CreateRelayerRequest, CreateRelayerResponse, SendTxRequest, SendTxResponse, +}; + +mod common; + +use crate::common::*; + +const ESCALATION_INTERVAL: Duration = Duration::from_secs(30); + +#[tokio::test] +async fn send_tx() -> eyre::Result<()> { + let (db_url, _db_container) = setup_db().await?; + let double_anvil = setup_double_anvil().await?; + + let service = + setup_service(&double_anvil.local_addr(), &db_url, ESCALATION_INTERVAL) + .await?; + + let addr = service.local_addr(); + + let response = reqwest::Client::new() + .post(&format!("http://{}/1/relayer/create", addr)) + .json(&CreateRelayerRequest { + name: "Test relayer".to_string(), + chain_id: DEFAULT_ANVIL_CHAIN_ID, + }) + .send() + .await?; + + let response: CreateRelayerResponse = response.json().await?; + + // Fund the relayer + let middleware = setup_middleware( + format!("http://{}", double_anvil.local_addr()), + DEFAULT_ANVIL_PRIVATE_KEY, + ) + .await?; + + let amount: U256 = parse_units("100", "ether")?.into(); + + middleware + .send_transaction( + Eip1559TransactionRequest { + to: Some(response.address.into()), + value: Some(amount), + ..Default::default() + }, + None, + ) + .await? + .await?; + + let provider = middleware.provider(); + + let current_balance = provider.get_balance(response.address, None).await?; + assert_eq!(current_balance, amount); + + // Send a transaction + let value: U256 = parse_units("1", "ether")?.into(); + let response = reqwest::Client::new() + .post(&format!("http://{}/1/tx/send", addr)) + .json(&SendTxRequest { + relayer_id: response.relayer_id, + to: ARBITRARY_ADDRESS, + value, + gas_limit: U256::from(21_000), + ..Default::default() + }) + .send() + .await?; + + let _response: SendTxResponse = response.json().await?; + + for _ in 0..10 { + let balance = provider.get_balance(ARBITRARY_ADDRESS, None).await?; + + if balance == value { + return Ok(()); + } else { + tokio::time::sleep(Duration::from_secs(1)).await; + } + } + + panic!("Transaction was not sent") +} From cc02754263399bea83965827d5e97e03e0ceaa2a Mon Sep 17 00:00:00 2001 From: Dzejkop Date: Wed, 22 Nov 2023 10:49:05 +0100 Subject: [PATCH 3/4] Automate manual test --- manual_test.nu | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/manual_test.nu b/manual_test.nu index e3849e9..9fa8df3 100644 --- a/manual_test.nu +++ b/manual_test.nu @@ -1,26 +1,28 @@ -# Create relayer -http post -t application/json http://127.0.0.1:3000/1/relayer/create { "name": "My Relayer", "chainId": 31337 } +echo "Creating relayer" +let relayer = http post -t application/json http://127.0.0.1:3000/1/relayer/create { "name": "My Relayer", "chainId": 31337 } -# Relayer id: 4df4464e-d0af-4354-b08a-5a78978ab6e6 -# Address: 0x968420740442caccb5023b2220e79e811f5ca798 +echo "Funding relayer" +cast send --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --value 100ether $relayer.address '' -# Send a transaction -http post -t application/json http://127.0.0.1:3000/1/tx/send { - "relayerId": "2c1c949e-22f5-4c9a-adb7-f2a7b17111e1", - "to": "0xe46e8850051fba970ce6108601df51abb460326f", +echo "Sending transaction" +let tx = http post -t application/json http://127.0.0.1:3000/1/tx/send { + "relayerId": $relayer.relayerId, + "to": $relayer.address, "value": "10", "data": "" "gasLimit": "150000" } -http get http://127.0.0.1:3000/1/tx/682b734b-87d0-41ee-9210-15fcbe13cc3d - +echo "Wait until tx is mined" for i in 0..100 { - http post -t application/json http://127.0.0.1:3000/1/tx/send { - "relayerId": "34e611c6-9b17-463a-8ca9-cdb640381f38", - "to": "0x5ce4a426963abbf4f941c1af48594445fad99322", - "value": "10", - "data": "" - "gasLimit": "150000" + let txResponse = http get http://127.0.0.1:3000/1/tx/($tx.txId) + + if ($txResponse | get -i status) == "mined" { + echo $txResponse + break + } else { + sleep 1sec } } + +echo "Success!" From 6230f3209d4ef66207f0c28d2c4e59534fa5f1f6 Mon Sep 17 00:00:00 2001 From: Dzejkop Date: Wed, 22 Nov 2023 11:10:45 +0100 Subject: [PATCH 4/4] Add tests --- README.md | 11 ++++- TODO.md | 6 ++- src/tasks/index.rs | 3 ++ tests/common/mod.rs | 16 +++++++ tests/create_relayer.rs | 7 ++- tests/send_many_txs.rs | 101 ++++++++++++++++++++++++++++++++++++++++ tests/send_tx.rs | 2 + 7 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 tests/send_many_txs.rs diff --git a/README.md b/README.md index 8728317..27ceead 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# tx-sitter-service (proper name pending) +# Tx Sitter Monolith + +A monolithized version of the [tx-sitter](https://github.com/worldcoin/tx-sitter-aws/). ## Testing locally @@ -9,3 +11,10 @@ Copy `.env.example` to `.env` or set `RUST_LOG=info,service=debug` to have loggi 3. Start the service `cargo run` This will use the `config.toml` configuration. + +If you have [nushell](https://www.nushell.sh/) installed, `nu manual_test.nu` can be run to execute a basic test. + +## Running tests +While you obviously can run tests with `cargo test --workspace` some tests take quite a long time (due to spinning up an anvil node, sending txs, etc.). + +Therefore I recommend [cargo-nextest](https://nexte.st/) as it runs all the tests in parallel. Once installed `cargo nextest run --workspace` can be used instead. diff --git a/TODO.md b/TODO.md index 65f1b8e..2258c9d 100644 --- a/TODO.md +++ b/TODO.md @@ -8,4 +8,8 @@ 6. [ ] Metrics 7. [ ] Tracing (add telemetry-batteries) 8. [ ] Automated testing -9. [ ] Parallelization in a few places + 1. [x] Basic + 2. [ ] Basic with contracts + 3. [ ] Escalation testing + 4. [ ] Reorg testing (how?!?) +9. [ ] Parallelization in a few places diff --git a/src/tasks/index.rs b/src/tasks/index.rs index e926e2a..77fd7f5 100644 --- a/src/tasks/index.rs +++ b/src/tasks/index.rs @@ -1,4 +1,5 @@ use std::sync::Arc; +use std::time::Duration; use ethers::providers::{Http, Middleware, Provider}; use ethers::types::{Block, BlockNumber, H256, U256}; @@ -76,6 +77,8 @@ pub async fn index_blocks(app: Arc) -> eyre::Result<()> { ) .await?; } + } else { + tokio::time::sleep(Duration::from_secs(5)).await; } } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 55bece6..0b25bcc 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -18,6 +18,10 @@ use service::config::{ }; use service::service::Service; use tokio::task::JoinHandle; +use tracing::level_filters::LevelFilter; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::EnvFilter; pub type AppMiddleware = SignerMiddleware>, LocalWallet>; @@ -47,6 +51,18 @@ impl DoubleAnvilHandle { } } +pub fn setup_tracing() { + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer().pretty().compact()) + .with( + EnvFilter::builder() + .with_default_directive(LevelFilter::INFO.into()) + // Logging from fake_rpc can get very messy so we set it to warn only + .parse_lossy("info,fake_rpc=warn"), + ) + .init(); +} + pub async fn setup_db() -> eyre::Result<(String, DockerContainerGuard)> { let db_container = postgres_docker_utils::setup().await?; let db_socket_addr = db_container.address(); diff --git a/tests/create_relayer.rs b/tests/create_relayer.rs index a64df1f..4a0acf6 100644 --- a/tests/create_relayer.rs +++ b/tests/create_relayer.rs @@ -1,11 +1,8 @@ use std::time::Duration; -use common::setup_db; use service::server::data::{CreateRelayerRequest, CreateRelayerResponse}; -use crate::common::{ - setup_double_anvil, setup_service, DEFAULT_ANVIL_CHAIN_ID, -}; +use crate::common::*; mod common; @@ -13,6 +10,8 @@ const ESCALATION_INTERVAL: Duration = Duration::from_secs(30); #[tokio::test] async fn create_relayer() -> eyre::Result<()> { + setup_tracing(); + let (db_url, _db_container) = setup_db().await?; let double_anvil = setup_double_anvil().await?; diff --git a/tests/send_many_txs.rs b/tests/send_many_txs.rs new file mode 100644 index 0000000..6e2c23f --- /dev/null +++ b/tests/send_many_txs.rs @@ -0,0 +1,101 @@ +use std::time::Duration; + +use ethers::providers::Middleware; +use ethers::types::{Eip1559TransactionRequest, U256}; +use ethers::utils::parse_units; +use service::server::data::{ + CreateRelayerRequest, CreateRelayerResponse, SendTxRequest, SendTxResponse, +}; + +mod common; + +use crate::common::*; + +const ESCALATION_INTERVAL: Duration = Duration::from_secs(30); + +#[tokio::test] +async fn send_many_txs() -> eyre::Result<()> { + setup_tracing(); + + let (db_url, _db_container) = setup_db().await?; + let double_anvil = setup_double_anvil().await?; + + let service = + setup_service(&double_anvil.local_addr(), &db_url, ESCALATION_INTERVAL) + .await?; + + let addr = service.local_addr(); + + let response = reqwest::Client::new() + .post(&format!("http://{}/1/relayer/create", addr)) + .json(&CreateRelayerRequest { + name: "Test relayer".to_string(), + chain_id: DEFAULT_ANVIL_CHAIN_ID, + }) + .send() + .await?; + + let response: CreateRelayerResponse = response.json().await?; + + // Fund the relayer + let middleware = setup_middleware( + format!("http://{}", double_anvil.local_addr()), + DEFAULT_ANVIL_PRIVATE_KEY, + ) + .await?; + + let amount: U256 = parse_units("1000", "ether")?.into(); + + middleware + .send_transaction( + Eip1559TransactionRequest { + to: Some(response.address.into()), + value: Some(amount), + ..Default::default() + }, + None, + ) + .await? + .await?; + + let provider = middleware.provider(); + + let current_balance = provider.get_balance(response.address, None).await?; + assert_eq!(current_balance, amount); + + // Send a transaction + let value: U256 = parse_units("10", "ether")?.into(); + let num_transfers = 10; + let relayer_id = response.relayer_id; + + for _ in 0..num_transfers { + let response = reqwest::Client::new() + .post(&format!("http://{}/1/tx/send", addr)) + .json(&SendTxRequest { + relayer_id: relayer_id.clone(), + to: ARBITRARY_ADDRESS, + value, + gas_limit: U256::from(21_000), + ..Default::default() + }) + .send() + .await?; + + let _response: SendTxResponse = response.json().await?; + } + + let expected_balance = value * num_transfers; + for _ in 0..50 { + let balance = provider.get_balance(ARBITRARY_ADDRESS, None).await?; + + tracing::info!(?balance, ?expected_balance, "Checking balance"); + + if balance == expected_balance { + return Ok(()); + } else { + tokio::time::sleep(Duration::from_secs(5)).await; + } + } + + panic!("Transactions were not sent") +} diff --git a/tests/send_tx.rs b/tests/send_tx.rs index 462590a..a524a33 100644 --- a/tests/send_tx.rs +++ b/tests/send_tx.rs @@ -15,6 +15,8 @@ const ESCALATION_INTERVAL: Duration = Duration::from_secs(30); #[tokio::test] async fn send_tx() -> eyre::Result<()> { + setup_tracing(); + let (db_url, _db_container) = setup_db().await?; let double_anvil = setup_double_anvil().await?;