From 68edae4a35c667b23b482c2837766622bd4906f5 Mon Sep 17 00:00:00 2001 From: Green Baneling Date: Mon, 25 Nov 2024 11:58:03 -0500 Subject: [PATCH] Shared sequencer integration for `fuel-core 0.40.0` (#2451) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description This PR duplicates https://github.com/FuelLabs/fuel-core/pull/1922, but on top of the `fuel-core 0.40.0`. Since the current `master` is not ready to be released, but we want this feature in the testnet, we will create a minor release with added off-chain logic that posts data to the SS. All changes are the same as in the original PR, except `reqwest` is bumped to `0.12`, because it has a fix important for SS. ### Before requesting review - [x] I have reviewed the code myself --------- Co-authored-by: MÃ¥rten Blankfors --- CHANGELOG.md | 2 + Cargo.lock | 1168 ++++++++++++++++- Cargo.toml | 5 +- bin/fuel-core/Cargo.toml | 9 +- bin/fuel-core/src/cli/run.rs | 22 +- bin/fuel-core/src/cli/run/shared_sequencer.rs | 56 + crates/client/Cargo.toml | 5 +- .../client/assets/debugAdapterProtocol.json | 2 +- crates/fuel-core/Cargo.toml | 4 + crates/fuel-core/src/p2p_test_helpers.rs | 2 +- crates/fuel-core/src/service.rs | 4 + crates/fuel-core/src/service/adapters.rs | 42 +- .../src/service/adapters/shared_sequencer.rs | 10 + crates/fuel-core/src/service/config.rs | 10 +- crates/fuel-core/src/service/sub_services.rs | 25 +- .../services/consensus_module/poa/Cargo.toml | 4 - .../consensus_module/poa/src/config.rs | 7 +- .../services/consensus_module/poa/src/lib.rs | 1 - .../consensus_module/poa/src/service.rs | 9 +- .../consensus_module/poa/src/service_test.rs | 24 +- crates/services/shared-sequencer/Cargo.toml | 33 + .../services/shared-sequencer/src/config.rs | 37 + crates/services/shared-sequencer/src/error.rs | 14 + .../services/shared-sequencer/src/http_api.rs | 181 +++ crates/services/shared-sequencer/src/lib.rs | 264 ++++ crates/services/shared-sequencer/src/ports.rs | 25 + .../services/shared-sequencer/src/service.rs | 266 ++++ crates/types/Cargo.toml | 7 + crates/types/src/blockchain/primitives.rs | 6 + crates/types/src/lib.rs | 1 + crates/types/src/services.rs | 1 + crates/types/src/services/shared_sequencer.rs | 19 + .../poa => types}/src/signer.rs | 86 +- tests/tests/blocks.rs | 6 +- tests/tests/da_compression.rs | 2 +- tests/tests/poa.rs | 2 +- tests/tests/trigger_integration/instant.rs | 6 +- tests/tests/trigger_integration/interval.rs | 6 +- tests/tests/trigger_integration/never.rs | 6 +- 39 files changed, 2221 insertions(+), 158 deletions(-) create mode 100644 bin/fuel-core/src/cli/run/shared_sequencer.rs create mode 100644 crates/fuel-core/src/service/adapters/shared_sequencer.rs create mode 100644 crates/services/shared-sequencer/Cargo.toml create mode 100644 crates/services/shared-sequencer/src/config.rs create mode 100644 crates/services/shared-sequencer/src/error.rs create mode 100644 crates/services/shared-sequencer/src/http_api.rs create mode 100644 crates/services/shared-sequencer/src/lib.rs create mode 100644 crates/services/shared-sequencer/src/ports.rs create mode 100644 crates/services/shared-sequencer/src/service.rs create mode 100644 crates/types/src/services/shared_sequencer.rs rename crates/{services/consensus_module/poa => types}/src/signer.rs (82%) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8dce9c8ab2..c36866da444 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +- [2450](https://github.com/FuelLabs/fuel-core/pull/2450): Added support for posting blocks to the shared sequencer. + ## [Version 0.40.0] ### Added diff --git a/Cargo.lock b/Cargo.lock index bc6071f4082..bfaa3012a58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -912,13 +912,13 @@ dependencies = [ "aws-smithy-types", "bytes", "fastrand 2.1.1", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "http-body 1.0.1", "httparse", - "hyper", - "hyper-rustls", + "hyper 0.14.30", + "hyper-rustls 0.24.2", "once_cell", "pin-project-lite", "pin-utils", @@ -1000,15 +1000,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43" dependencies = [ "async-trait", - "axum-core", + "axum-core 0.2.9", "bitflags 1.3.2", "bytes", "futures-util", "http 0.2.12", "http-body 0.4.6", - "hyper", + "hyper 0.14.30", "itoa", - "matchit", + "matchit 0.5.0", "memchr", "mime", "percent-encoding", @@ -1016,14 +1016,69 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 0.1.2", "tokio", - "tower", + "tower 0.4.13", "tower-http 0.3.5", "tower-layer", "tower-service", ] +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core 0.3.4", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", + "itoa", + "matchit 0.7.3", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper 0.1.2", + "tower 0.4.13", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core 0.4.5", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "itoa", + "matchit 0.7.3", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper 1.0.2", + "tower 0.5.1", + "tower-layer", + "tower-service", +] + [[package]] name = "axum-core" version = "0.2.9" @@ -1040,6 +1095,43 @@ dependencies = [ "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 0.2.12", + "http-body 0.4.6", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 1.0.2", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -1129,6 +1221,22 @@ dependencies = [ "syn 2.0.79", ] +[[package]] +name = "bip32" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa13fae8b6255872fd86f7faf4b41168661d7d78609f7bfe6771b85c6739a15b" +dependencies = [ + "bs58", + "hmac 0.12.1", + "k256", + "rand_core", + "ripemd", + "sha2 0.10.8", + "subtle", + "zeroize", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -1706,13 +1814,24 @@ dependencies = [ "version_check", ] +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "cookie_store" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "387461abbc748185c3a6e1673d826918b450b87ff22639429c694619a83b6cf6" dependencies = [ - "cookie", + "cookie 0.17.0", "idna 0.3.0", "log", "publicsuffix", @@ -1723,6 +1842,24 @@ dependencies = [ "url", ] +[[package]] +name = "cookie_store" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9" +dependencies = [ + "cookie 0.18.1", + "document-features", + "idna 1.0.3", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -1748,6 +1885,37 @@ dependencies = [ "memchr", ] +[[package]] +name = "cosmos-sdk-proto" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462e1f6a8e005acc8835d32d60cbd7973ed65ea2a8d8473830e675f050956427" +dependencies = [ + "prost 0.13.3", + "tendermint-proto 0.40.0", + "tonic 0.12.3", +] + +[[package]] +name = "cosmrs" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "210fbe6f98594963b46cc980f126a9ede5db9a3848ca65b71303bebdb01afcd9" +dependencies = [ + "bip32", + "cosmos-sdk-proto", + "ecdsa", + "eyre", + "k256", + "rand_core", + "serde", + "serde_json", + "signature", + "subtle-encoding", + "tendermint 0.40.0", + "thiserror", +] + [[package]] name = "counter" version = "0.5.7" @@ -2104,6 +2272,19 @@ dependencies = [ "syn 2.0.79", ] +[[package]] +name = "curve25519-dalek-ng" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core", + "subtle-ng", + "zeroize", +] + [[package]] name = "cynic" version = "2.2.8" @@ -2111,7 +2292,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1afa0591b1021e427e548a1f0f147fe6168f6c7c7f7006bace77f28856051b8" dependencies = [ "cynic-proc-macros", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "static_assertions", @@ -2429,6 +2610,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "document-features" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +dependencies = [ + "litrs", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -2477,6 +2667,19 @@ dependencies = [ "signature", ] +[[package]] +name = "ed25519-consensus" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8465edc8ee7436ffea81d21a019b16676ee3db267aa8d5a8d729581ecf998b" +dependencies = [ + "curve25519-dalek-ng", + "hex", + "rand_core", + "sha2 0.9.9", + "zeroize", +] + [[package]] name = "ed25519-dalek" version = "2.1.1" @@ -2752,7 +2955,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "syn 2.0.79", @@ -2814,7 +3017,7 @@ checksum = "e79e5973c26d4baf0ce55520bd732314328cabe53193286671b47144145b9649" dependencies = [ "chrono", "ethers-core", - "reqwest", + "reqwest 0.11.27", "semver", "serde", "serde_json", @@ -2839,7 +3042,7 @@ dependencies = [ "futures-locks", "futures-util", "instant", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "thiserror", @@ -2872,7 +3075,7 @@ dependencies = [ "jsonwebtoken", "once_cell", "pin-project", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "thiserror", @@ -2989,9 +3192,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43ddc25e1ad2cc0106d5e2d967397b4fb2068a66677ee9b0eea4600e5cfe8fb4" dependencies = [ "futures", - "hyper", - "hyper-rustls", - "hyper-timeout", + "hyper 0.14.30", + "hyper-rustls 0.24.2", + "hyper-timeout 0.4.1", "log", "pin-project", "rand", @@ -3085,6 +3288,16 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flex-error" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" +dependencies = [ + "eyre", + "paste", +] + [[package]] name = "float-cmp" version = "0.9.0" @@ -3175,8 +3388,9 @@ dependencies = [ "async-graphql", "async-graphql-value", "async-trait", - "axum", + "axum 0.5.17", "clap 4.5.20", + "cosmrs", "derive_more", "enum-iterator", "fuel-core", @@ -3193,6 +3407,7 @@ dependencies = [ "fuel-core-producer", "fuel-core-relayer", "fuel-core-services", + "fuel-core-shared-sequencer", "fuel-core-storage", "fuel-core-sync", "fuel-core-trace", @@ -3201,7 +3416,7 @@ dependencies = [ "fuel-core-upgradable-executor", "futures", "hex", - "hyper", + "hyper 0.14.30", "indicatif", "itertools 0.12.1", "mockall", @@ -3223,7 +3438,7 @@ dependencies = [ "tokio-rayon", "tokio-stream", "tokio-util", - "tower", + "tower 0.4.13", "tower-http 0.4.4", "tracing", "uuid 1.10.0", @@ -3285,7 +3500,7 @@ dependencies = [ "fuel-core-chain-config", "fuel-core-compression", "fuel-core-metrics", - "fuel-core-poa", + "fuel-core-shared-sequencer", "fuel-core-storage", "fuel-core-types 0.40.0", "hex", @@ -3345,10 +3560,10 @@ dependencies = [ "fuel-core-types 0.40.0", "futures", "hex", - "hyper-rustls", + "hyper-rustls 0.24.2", "insta", "itertools 0.12.1", - "reqwest", + "reqwest 0.11.27", "schemafy_lib", "serde", "serde_json", @@ -3461,7 +3676,7 @@ dependencies = [ "futures", "num_enum", "parking_lot", - "reqwest", + "reqwest 0.12.9", "serde", "strum 0.25.0", "strum_macros 0.25.3", @@ -3572,15 +3787,12 @@ version = "0.40.0" dependencies = [ "anyhow", "async-trait", - "aws-config", - "aws-sdk-kms", "fuel-core-chain-config", "fuel-core-poa", "fuel-core-services", "fuel-core-storage", "fuel-core-trace", "fuel-core-types 0.40.0", - "k256", "mockall", "rand", "serde", @@ -3660,6 +3872,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "fuel-core-shared-sequencer" +version = "0.40.0" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.22.1", + "cosmos-sdk-proto", + "cosmrs", + "fuel-core-services", + "fuel-core-types 0.40.0", + "fuel-sequencer-proto", + "futures", + "postcard", + "prost 0.12.6", + "reqwest 0.12.9", + "serde", + "serde_json", + "tendermint-rpc", + "tokio", + "tracing", +] + [[package]] name = "fuel-core-storage" version = "0.40.0" @@ -3731,7 +3966,7 @@ dependencies = [ "fuel-core-upgradable-executor", "futures", "hex", - "hyper", + "hyper 0.14.30", "insta", "itertools 0.12.1", "k256", @@ -3740,7 +3975,7 @@ dependencies = [ "primitive-types", "proptest", "rand", - "reqwest", + "reqwest 0.12.9", "rstest", "serde_json", "spki", @@ -3807,14 +4042,18 @@ name = "fuel-core-types" version = "0.40.0" dependencies = [ "anyhow", + "aws-config", + "aws-sdk-kms", "bs58", "derivative", "derive_more", "fuel-vm 0.58.2", + "k256", "rand", "secrecy", "serde", "tai64", + "tokio", "zeroize", ] @@ -3951,6 +4190,18 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "fuel-sequencer-proto" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbdd607c9c70921cc016becde659e5062ae460b7bb3f525a1dd65f8209c0083" +dependencies = [ + "prost 0.12.6", + "prost-types", + "regex", + "tonic 0.11.0", +] + [[package]] name = "fuel-storage" version = "0.56.0" @@ -4300,8 +4551,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -4401,6 +4654,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap 2.6.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.4.1" @@ -4733,7 +5005,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "httparse", @@ -4747,6 +5019,27 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.7", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -4755,13 +5048,31 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper", + "hyper 0.14.30", "log", "rustls 0.21.12", "rustls-native-certs", "tokio", - "tokio-rustls", - "webpki-roots", + "tokio-rustls 0.24.1", + "webpki-roots 0.25.4", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.5.1", + "hyper-util", + "rustls 0.23.14", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", + "webpki-roots 0.26.7", ] [[package]] @@ -4770,24 +5081,56 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper", + "hyper 0.14.30", "pin-project-lite", "tokio", "tokio-io-timeout", ] [[package]] -name = "iana-time-zone" -version = "0.1.61" +name = "hyper-timeout" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core 0.52.0", + "hyper 1.5.1", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.5.1", + "pin-project-lite", + "socket2 0.5.7", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core 0.52.0", ] [[package]] @@ -4799,6 +5142,124 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "id-arena" version = "2.2.1" @@ -4841,6 +5302,27 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "if-addrs" version = "0.10.2" @@ -4882,7 +5364,7 @@ dependencies = [ "bytes", "futures", "http 0.2.12", - "hyper", + "hyper 0.14.30", "log", "rand", "tokio", @@ -5752,7 +6234,7 @@ dependencies = [ "thiserror", "tracing", "url", - "webpki-roots", + "webpki-roots 0.25.4", ] [[package]] @@ -5895,6 +6377,18 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + [[package]] name = "lock_api" version = "0.4.12" @@ -5972,6 +6466,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" +[[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" @@ -6693,6 +7193,33 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +[[package]] +name = "peg" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "295283b02df346d1ef66052a757869b2876ac29a6bb0ac3f5f7cd44aebe40e8f" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdad6a1d9cf116a059582ce415d5f5566aabcd4008646779dab7fdc2a9a9d426" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3aeb8f54c078314c2065ee649a7241f46b9d8e418e1a9581ba0546657d7aa3a" + [[package]] name = "pem" version = "1.1.1" @@ -7177,7 +7704,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.11.9", +] + +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive 0.12.6", +] + +[[package]] +name = "prost" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" +dependencies = [ + "bytes", + "prost-derive 0.13.3", ] [[package]] @@ -7193,6 +7740,41 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "prost-derive" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost 0.12.6", +] + [[package]] name = "psl-types" version = "2.0.11" @@ -7229,8 +7811,8 @@ dependencies = [ "libflate", "log", "names", - "prost", - "reqwest", + "prost 0.11.9", + "reqwest 0.11.27", "thiserror", "url", "winapi", @@ -7554,16 +8136,16 @@ checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "base64 0.21.7", "bytes", - "cookie", - "cookie_store", + "cookie 0.17.0", + "cookie_store 0.20.0", "encoding_rs", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", - "hyper", - "hyper-rustls", + "hyper 0.14.30", + "hyper-rustls 0.24.2", "ipnet", "js-sys", "log", @@ -7572,23 +8154,68 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls 0.21.12", - "rustls-pemfile", + "rustls-native-certs", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 0.1.2", "system-configuration", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 0.25.4", "winreg", ] +[[package]] +name = "reqwest" +version = "0.12.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +dependencies = [ + "base64 0.22.1", + "bytes", + "cookie 0.18.1", + "cookie_store 0.21.1", + "futures-core", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.1", + "hyper-rustls 0.27.3", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.14", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.26.7", + "windows-registry", +] + [[package]] name = "resolv-conf" version = "0.7.0" @@ -7829,7 +8456,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "schannel", "security-framework", ] @@ -7843,6 +8470,15 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.9.0" @@ -8111,6 +8747,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.210" @@ -8134,6 +8779,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "serde_spanned" version = "0.6.8" @@ -8598,6 +9254,21 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "subtle-encoding" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" +dependencies = [ + "zeroize", +] + +[[package]] +name = "subtle-ng" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" + [[package]] name = "svm-rs" version = "0.3.5" @@ -8608,7 +9279,7 @@ dependencies = [ "fs2", "hex", "once_cell", - "reqwest", + "reqwest 0.11.27", "semver", "serde", "serde_json", @@ -8669,6 +9340,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.1" @@ -8735,6 +9415,143 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "tendermint" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b50aae6ec24c3429149ad59b5b8d3374d7804d4c7d6125ceb97cb53907fb68d" +dependencies = [ + "bytes", + "digest 0.10.7", + "ed25519", + "ed25519-consensus", + "flex-error", + "futures", + "num-traits", + "once_cell", + "prost 0.12.6", + "prost-types", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.10.8", + "signature", + "subtle", + "subtle-encoding", + "tendermint-proto 0.36.0", + "time", + "zeroize", +] + +[[package]] +name = "tendermint" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d513ce7f9e41c67ab2dd3d554ef65f36fbcc61745af1e1f93eafdeefa1ce37" +dependencies = [ + "bytes", + "digest 0.10.7", + "ed25519", + "ed25519-consensus", + "flex-error", + "futures", + "k256", + "num-traits", + "once_cell", + "prost 0.13.3", + "ripemd", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.10.8", + "signature", + "subtle", + "subtle-encoding", + "tendermint-proto 0.40.0", + "time", + "zeroize", +] + +[[package]] +name = "tendermint-config" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e07b383dc8780ebbec04cfb603f3fdaba6ea6663d8dd861425b1ffa7761fe90d" +dependencies = [ + "flex-error", + "serde", + "serde_json", + "tendermint 0.36.0", + "toml 0.8.19", + "url", +] + +[[package]] +name = "tendermint-proto" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f193d04afde6592c20fd70788a10b8cb3823091c07456db70d8a93f5fb99c1" +dependencies = [ + "bytes", + "flex-error", + "prost 0.12.6", + "prost-types", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + +[[package]] +name = "tendermint-proto" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c81ba1b023ec00763c3bc4f4376c67c0047f185cccf95c416c7a2f16272c4cbb" +dependencies = [ + "bytes", + "flex-error", + "prost 0.13.3", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + +[[package]] +name = "tendermint-rpc" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e3c231a3632cab53f92ad4161c730c468c08cfe4f0aa5a6735b53b390aecbd" +dependencies = [ + "async-trait", + "bytes", + "flex-error", + "futures", + "getrandom", + "peg", + "pin-project", + "rand", + "reqwest 0.11.27", + "semver", + "serde", + "serde_bytes", + "serde_json", + "subtle", + "subtle-encoding", + "tendermint 0.36.0", + "tendermint-config", + "tendermint-proto 0.36.0", + "thiserror", + "time", + "tokio", + "tracing", + "url", + "uuid 1.10.0", + "walkdir", +] + [[package]] name = "term" version = "0.7.0" @@ -8825,7 +9642,7 @@ dependencies = [ "futures", "itertools 0.12.1", "rand", - "reqwest", + "reqwest 0.12.9", "tempfile", "tokio", ] @@ -8958,6 +9775,16 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -9042,6 +9869,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.14", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.16" @@ -9064,9 +9902,9 @@ dependencies = [ "log", "rustls 0.21.12", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "tungstenite", - "webpki-roots", + "webpki-roots 0.25.4", ] [[package]] @@ -9125,6 +9963,63 @@ dependencies = [ "winnow", ] +[[package]] +name = "tonic" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" +dependencies = [ + "async-stream", + "async-trait", + "axum 0.6.20", + "base64 0.21.7", + "bytes", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", + "hyper-timeout 0.4.1", + "percent-encoding", + "pin-project", + "prost 0.12.6", + "tokio", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-stream", + "async-trait", + "axum 0.7.9", + "base64 0.22.1", + "bytes", + "h2 0.4.7", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.1", + "hyper-timeout 0.5.2", + "hyper-util", + "percent-encoding", + "pin-project", + "prost 0.13.3", + "socket2 0.5.7", + "tokio", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower" version = "0.4.13" @@ -9133,8 +10028,11 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", + "indexmap 1.9.3", "pin-project", "pin-project-lite", + "rand", + "slab", "tokio", "tokio-util", "tower-layer", @@ -9142,6 +10040,20 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 0.1.2", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-http" version = "0.3.5" @@ -9156,7 +10068,7 @@ dependencies = [ "http-body 0.4.6", "http-range-header", "pin-project-lite", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", ] @@ -9467,6 +10379,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -9889,6 +10813,15 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "webpki-roots" +version = "0.26.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "widestring" version = "1.1.0" @@ -9954,6 +10887,36 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -10139,6 +11102,18 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "ws_stream_wasm" version = "0.7.4" @@ -10277,6 +11252,30 @@ dependencies = [ "time", ] +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -10298,6 +11297,27 @@ dependencies = [ "syn 2.0.79", ] +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.1" @@ -10318,6 +11338,28 @@ dependencies = [ "syn 2.0.79", ] +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "zip" version = "0.6.6" diff --git a/Cargo.toml b/Cargo.toml index d80bf4f5bdc..be62fa73d95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ members = [ "crates/services/p2p", "crates/services/producer", "crates/services/relayer", + "crates/services/shared-sequencer", "crates/services/sync", "crates/services/txpool_v2", "crates/services/upgradable-executor", @@ -69,6 +70,7 @@ fuel-core-services = { version = "0.40.0", path = "./crates/services" } fuel-core-consensus-module = { version = "0.40.0", path = "./crates/services/consensus_module" } fuel-core-bft = { version = "0.40.0", path = "./crates/services/consensus_module/bft" } fuel-core-poa = { version = "0.40.0", path = "./crates/services/consensus_module/poa" } +fuel-core-shared-sequencer = { version = "0.40.0", path = "crates/services/shared-sequencer" } fuel-core-executor = { version = "0.40.0", path = "./crates/services/executor", default-features = false } fuel-core-importer = { version = "0.40.0", path = "./crates/services/importer" } fuel-core-gas-price-service = { version = "0.40.0", path = "crates/services/gas_price_service" } @@ -92,6 +94,7 @@ fuel-vm-private = { version = "0.58.2", package = "fuel-vm", default-features = # Common dependencies anyhow = "1.0" async-trait = "0.1" +aws-sdk-kms = "1.37" cynic = { version = "2.2.1", features = ["http-reqwest"] } clap = "4.4" derivative = { version = "2" } @@ -119,7 +122,7 @@ serde_with = { version = "3.4", default-features = false } strum = { version = "0.25" } strum_macros = "0.25" # enable cookie store to support L7 sticky sessions -reqwest = { version = "0.11.16", default-features = false, features = [ +reqwest = { version = "0.12", default-features = false, features = [ "rustls-tls", "cookies", ] } diff --git a/bin/fuel-core/Cargo.toml b/bin/fuel-core/Cargo.toml index 76bbb6d0367..cd95fc990df 100644 --- a/bin/fuel-core/Cargo.toml +++ b/bin/fuel-core/Cargo.toml @@ -29,7 +29,7 @@ fuel-core = { workspace = true, features = ["wasm-executor"] } fuel-core-chain-config = { workspace = true } fuel-core-compression = { workspace = true } fuel-core-metrics = { workspace = true } -fuel-core-poa = { workspace = true } +fuel-core-shared-sequencer = { workspace = true, optional = true } fuel-core-types = { workspace = true, features = ["std"] } hex = { workspace = true } humantime = "2.1" @@ -60,9 +60,13 @@ test-case = { workspace = true } [features] default = ["env", "relayer", "rocksdb"] -aws-kms = ["dep:aws-config", "dep:aws-sdk-kms", "fuel-core-poa/aws-kms"] +aws-kms = ["dep:aws-config", "dep:aws-sdk-kms", "fuel-core-types/aws-kms"] env = ["dep:dotenvy"] p2p = ["fuel-core/p2p", "const_format"] +shared-sequencer = [ + "dep:fuel-core-shared-sequencer", + "fuel-core/shared-sequencer", +] relayer = ["fuel-core/relayer", "dep:url"] parquet = ["fuel-core-chain-config/parquet", "fuel-core-types/serde"] rocksdb = ["fuel-core/rocksdb"] @@ -73,6 +77,7 @@ production = [ "relayer", "rocksdb-production", "p2p", + "shared-sequencer", "parquet", "aws-kms", ] diff --git a/bin/fuel-core/src/cli/run.rs b/bin/fuel-core/src/cli/run.rs index 6dc0349f4f8..712b2895292 100644 --- a/bin/fuel-core/src/cli/run.rs +++ b/bin/fuel-core/src/cli/run.rs @@ -55,8 +55,10 @@ use fuel_core_metrics::config::{ DisableConfig, Module, }; -use fuel_core_poa::signer::SignMode; -use fuel_core_types::blockchain::header::StateTransitionBytecodeVersion; +use fuel_core_types::{ + blockchain::header::StateTransitionBytecodeVersion, + signer::SignMode, +}; use pyroscope::{ pyroscope::PyroscopeAgentRunning, PyroscopeAgent, @@ -86,6 +88,9 @@ use super::DEFAULT_DATABASE_CACHE_SIZE; #[cfg(feature = "p2p")] mod p2p; +#[cfg(feature = "shared-sequencer")] +mod shared_sequencer; + mod consensus; mod graphql; mod profiling; @@ -240,6 +245,10 @@ pub struct Command { #[cfg(feature = "p2p")] pub sync_args: p2p::SyncArgs, + #[cfg_attr(feature = "shared-sequencer", clap(flatten))] + #[cfg(feature = "shared-sequencer")] + pub shared_sequencer_args: shared_sequencer::Args, + #[arg(long = "disable-metrics", value_delimiter = ',', help = fuel_core_metrics::config::help_string(), env)] pub disabled_metrics: Vec, @@ -298,6 +307,8 @@ impl Command { p2p_args, #[cfg(feature = "p2p")] sync_args, + #[cfg(feature = "shared-sequencer")] + shared_sequencer_args, disabled_metrics: metrics, max_da_lag, max_wait_time, @@ -377,8 +388,9 @@ impl Command { // if consensus key is not configured, fallback to dev consensus key let key = default_consensus_dev_key(); warn!( - "Fuel Core is using an insecure test key for consensus. Public key: {}", - key.public_key() + "Fuel Core is using an insecure test key for consensus. Public key: {}, SecretKey: {}", + key.public_key(), + key ); consensus_signer = SignMode::Key(Secret::new(key.into())); } @@ -579,6 +591,8 @@ impl Command { p2p: p2p_cfg, #[cfg(feature = "p2p")] sync: sync_args.into(), + #[cfg(feature = "shared-sequencer")] + shared_sequencer: shared_sequencer_args.try_into()?, consensus_signer, name, relayer_consensus_config: verifier, diff --git a/bin/fuel-core/src/cli/run/shared_sequencer.rs b/bin/fuel-core/src/cli/run/shared_sequencer.rs new file mode 100644 index 00000000000..9030a87fc11 --- /dev/null +++ b/bin/fuel-core/src/cli/run/shared_sequencer.rs @@ -0,0 +1,56 @@ +use fuel_core_types::fuel_types::Bytes32; + +#[derive(Debug, Clone, clap::Args)] +pub struct Args { + /// If set to true, new blocks will be posted to the shared sequencer chain. + #[clap(long = "enable-ss", action)] + enable: bool, + /// The frequency at which to post blocks to the shared sequencer. + #[clap(long = "ss-block-posting-frequency", env, default_value = "12s")] + block_posting_frequency: humantime::Duration, + /// The RPC address of the sequencer chain tendermint API + /// (e.g. "http://127.0.0.1:26657") + #[clap(long = "ss-tendermint-api", env)] + tendermint_api: Option, + /// The REST address of the sequencer chain blockchain/rest API + /// (e.g. "http://127.0.0.1:1317") + #[clap(long = "ss-blockchain-api", env)] + blockchain_api: Option, + /// Topic to post blocks to + /// (e.g. "1111111111111111111111111111111111111111111111111111111111111111") + #[clap( + long = "ss-topic", + env, + default_value = "0000000000000000000000000000000000000000000000000000000000000000" + )] + topic: Bytes32, +} + +#[cfg(feature = "shared-sequencer")] +impl TryFrom for fuel_core_shared_sequencer::Config { + type Error = anyhow::Error; + + fn try_from(val: Args) -> anyhow::Result { + let endpoints = match (val.tendermint_api, val.blockchain_api) { + (Some(tendermint_api), Some(blockchain_api)) => { + Some(fuel_core_shared_sequencer::Endpoints { + tendermint_rpc_api: tendermint_api, + blockchain_rest_api: blockchain_api, + }) + } + (None, None) => None, + _ => { + return Err(anyhow::anyhow!( + "Both tendermint and blockchain API must be set or unset" + )) + } + }; + + Ok(fuel_core_shared_sequencer::Config { + enabled: val.enable, + block_posting_frequency: val.block_posting_frequency.into(), + endpoints, + topic: *val.topic, + }) + } +} diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index ad8f7d48078..a6bb5c1e3dc 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -24,7 +24,10 @@ hyper-rustls = { version = "0.24", features = [ "webpki-tokio", ], optional = true } itertools = { workspace = true } -reqwest = { workspace = true } +reqwest = { version = "0.11.16", default-features = false, features = [ + "rustls-tls", + "cookies", +] } serde = { workspace = true, features = ["derive"] } serde_json = { version = "1.0", features = ["raw_value"] } tai64 = { version = "4.0", features = ["serde"] } diff --git a/crates/client/assets/debugAdapterProtocol.json b/crates/client/assets/debugAdapterProtocol.json index 44a0c2eed9c..b435aef0f85 100644 --- a/crates/client/assets/debugAdapterProtocol.json +++ b/crates/client/assets/debugAdapterProtocol.json @@ -1440,7 +1440,7 @@ { "$ref": "#/definitions/Request" }, { "type": "object", - "description": "Replaces all existing instruction breakpoints. Typically, instruction breakpoints would be set from a diassembly window. \nTo clear all instruction breakpoints, specify an empty array.\nWhen an instruction breakpoint is hit, a 'stopped' event (with reason 'instruction breakpoint') is generated.\nClients should only call this request if the capability 'supportsInstructionBreakpoints' is true.", + "description": "Replaces all existing instruction breakpoints. Typically, instruction breakpoints would be set from a disassembly window. \nTo clear all instruction breakpoints, specify an empty array.\nWhen an instruction breakpoint is hit, a 'stopped' event (with reason 'instruction breakpoint') is generated.\nClients should only call this request if the capability 'supportsInstructionBreakpoints' is true.", "properties": { "command": { "type": "string", diff --git a/crates/fuel-core/Cargo.toml b/crates/fuel-core/Cargo.toml index cac6a58150b..8de4a1e31bb 100644 --- a/crates/fuel-core/Cargo.toml +++ b/crates/fuel-core/Cargo.toml @@ -20,6 +20,7 @@ async-graphql-value = "7.0.11" async-trait = { workspace = true } axum = { workspace = true } clap = { workspace = true, features = ["derive"] } +cosmrs = { version = "0.21", optional = true } derive_more = { version = "0.99" } enum-iterator = { workspace = true } fuel-core-chain-config = { workspace = true, features = ["std"] } @@ -35,6 +36,7 @@ fuel-core-poa = { workspace = true } fuel-core-producer = { workspace = true } fuel-core-relayer = { workspace = true, optional = true } fuel-core-services = { workspace = true } +fuel-core-shared-sequencer = { workspace = true, optional = true } fuel-core-storage = { workspace = true } fuel-core-sync = { workspace = true, optional = true } fuel-core-txpool = { workspace = true } @@ -93,6 +95,7 @@ smt = [ ] p2p = ["dep:fuel-core-p2p", "dep:fuel-core-sync"] relayer = ["dep:fuel-core-relayer"] +shared-sequencer = ["dep:fuel-core-shared-sequencer", "dep:cosmrs"] rocksdb = ["dep:rocksdb", "dep:tempfile", "dep:num_cpus", "dep:postcard"] test-helpers = [ "fuel-core-database/test-helpers", @@ -102,6 +105,7 @@ test-helpers = [ "fuel-core-compression/test-helpers", "fuel-core-txpool/test-helpers", "fuel-core-services/test-helpers", + "fuel-core-shared-sequencer?/test-helpers", "fuel-core-importer/test-helpers", ] # features to enable in production, but increase build times diff --git a/crates/fuel-core/src/p2p_test_helpers.rs b/crates/fuel-core/src/p2p_test_helpers.rs index fbc9f32afb4..276bb694caa 100644 --- a/crates/fuel-core/src/p2p_test_helpers.rs +++ b/crates/fuel-core/src/p2p_test_helpers.rs @@ -34,7 +34,6 @@ use fuel_core_p2p::{ }; use fuel_core_poa::{ ports::BlockImporter, - signer::SignMode, Trigger, }; use fuel_core_storage::{ @@ -61,6 +60,7 @@ use fuel_core_types::{ }, secrecy::Secret, services::p2p::GossipsubMessageAcceptance, + signer::SignMode, }; use futures::StreamExt; use rand::{ diff --git a/crates/fuel-core/src/service.rs b/crates/fuel-core/src/service.rs index 6b42dd3960c..db6ea7da662 100644 --- a/crates/fuel-core/src/service.rs +++ b/crates/fuel-core/src/service.rs @@ -519,6 +519,10 @@ mod tests { // p2p & sync expected_services += 2; } + #[cfg(feature = "shared-sequencer")] + { + expected_services += 1; + } // # Dev-note: Update the `expected_services` when we add/remove a new/old service. assert_eq!(i, expected_services); diff --git a/crates/fuel-core/src/service/adapters.rs b/crates/fuel-core/src/service/adapters.rs index f55d4572335..73a1b876896 100644 --- a/crates/fuel-core/src/service/adapters.rs +++ b/crates/fuel-core/src/service/adapters.rs @@ -4,10 +4,7 @@ use fuel_core_consensus_module::{ }; use fuel_core_executor::executor::OnceTransactionsSource; use fuel_core_importer::ImporterResult; -use fuel_core_poa::{ - ports::BlockSigner, - signer::SignMode, -}; +use fuel_core_poa::ports::BlockSigner; use fuel_core_services::stream::BoxStream; use fuel_core_storage::transactional::Changes; use fuel_core_txpool::BorrowedTxPool; @@ -16,7 +13,10 @@ use fuel_core_types::services::p2p::peer_reputation::AppScore; use fuel_core_types::{ blockchain::{ block::Block, - consensus::Consensus, + consensus::{ + poa::PoAConsensus, + Consensus, + }, }, fuel_tx::Transaction, services::{ @@ -27,6 +27,7 @@ use fuel_core_types::{ UncommittedResult, }, }, + signer::SignMode, tai64::Tai64, }; use fuel_core_upgradable_executor::executor::Executor; @@ -59,6 +60,8 @@ pub mod p2p; pub mod producer; #[cfg(feature = "relayer")] pub mod relayer; +#[cfg(feature = "shared-sequencer")] +pub mod shared_sequencer; #[cfg(feature = "p2p")] pub mod sync; pub mod txpool; @@ -226,7 +229,34 @@ impl FuelBlockSigner { #[async_trait::async_trait] impl BlockSigner for FuelBlockSigner { async fn seal_block(&self, block: &Block) -> anyhow::Result { - self.mode.seal_block(block).await + let block_hash = block.id(); + let message = block_hash.into_message(); + let signature = self.mode.sign_message(message).await?; + Ok(Consensus::PoA(PoAConsensus::new(signature))) + } + + fn is_available(&self) -> bool { + self.mode.is_available() + } +} + +#[cfg(feature = "shared-sequencer")] +#[async_trait::async_trait] +impl fuel_core_shared_sequencer::ports::Signer for FuelBlockSigner { + async fn sign( + &self, + data: &[u8], + ) -> anyhow::Result { + Ok(self.mode.sign(data).await?) + } + + fn public_key(&self) -> cosmrs::crypto::PublicKey { + let pubkey = self + .mode + .verifying_key() + .expect("Invalid public key") + .expect("Public key not available"); + cosmrs::crypto::PublicKey::from(pubkey) } fn is_available(&self) -> bool { diff --git a/crates/fuel-core/src/service/adapters/shared_sequencer.rs b/crates/fuel-core/src/service/adapters/shared_sequencer.rs new file mode 100644 index 00000000000..611a725c172 --- /dev/null +++ b/crates/fuel-core/src/service/adapters/shared_sequencer.rs @@ -0,0 +1,10 @@ +use crate::service::adapters::BlockImporterAdapter; +use fuel_core_services::stream::BoxStream; +use fuel_core_shared_sequencer::ports::BlocksProvider; +use fuel_core_types::services::block_importer::SharedImportResult; + +impl BlocksProvider for BlockImporterAdapter { + fn subscribe(&self) -> BoxStream { + self.events_shared_result() + } +} diff --git a/crates/fuel-core/src/service/config.rs b/crates/fuel-core/src/service/config.rs index 2dd6602a979..9ab9dcee013 100644 --- a/crates/fuel-core/src/service/config.rs +++ b/crates/fuel-core/src/service/config.rs @@ -4,7 +4,6 @@ use std::{ }; use clap::ValueEnum; -use fuel_core_poa::signer::SignMode; use strum_macros::{ Display, EnumString, @@ -28,7 +27,10 @@ pub use fuel_core_poa::Trigger; #[cfg(feature = "relayer")] use fuel_core_relayer::Config as RelayerConfig; use fuel_core_txpool::config::Config as TxPoolConfig; -use fuel_core_types::blockchain::header::StateTransitionBytecodeVersion; +use fuel_core_types::{ + blockchain::header::StateTransitionBytecodeVersion, + signer::SignMode, +}; use crate::{ combined_database::CombinedDatabaseConfig, @@ -69,6 +71,8 @@ pub struct Config { pub p2p: Option>, #[cfg(feature = "p2p")] pub sync: fuel_core_sync::Config, + #[cfg(feature = "shared-sequencer")] + pub shared_sequencer: fuel_core_shared_sequencer::Config, pub consensus_signer: SignMode, pub name: String, pub relayer_consensus_config: fuel_core_consensus_module::RelayerConsensusConfig, @@ -180,6 +184,8 @@ impl Config { p2p: Some(P2PConfig::::default("test_network")), #[cfg(feature = "p2p")] sync: fuel_core_sync::Config::default(), + #[cfg(feature = "shared-sequencer")] + shared_sequencer: fuel_core_shared_sequencer::Config::local_node(), consensus_signer: SignMode::Key(fuel_core_types::secrecy::Secret::new( fuel_core_chain_config::default_consensus_dev_key().into(), )), diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 13c9f6a9d01..d733204af61 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -42,16 +42,14 @@ use fuel_core_gas_price_service::v0::uninitialized_task::{ new_gas_price_service_v0, AlgorithmV0, }; -use fuel_core_poa::{ - signer::SignMode, - Trigger, -}; +use fuel_core_poa::Trigger; use fuel_core_storage::{ self, transactional::AtomicView, }; #[cfg(feature = "relayer")] use fuel_core_types::blockchain::primitives::DaBlockHeight; +use fuel_core_types::signer::SignMode; use std::sync::Arc; use tokio::sync::Mutex; @@ -241,9 +239,22 @@ pub fn init_sub_services( tracing::info!("Enabled manual block production because of `debug` flag"); } + let signer = Arc::new(FuelBlockSigner::new(config.consensus_signer.clone())); + + #[cfg(feature = "shared-sequencer")] + let shared_sequencer = { + let config = config.shared_sequencer.clone(); + + fuel_core_shared_sequencer::service::new_service( + importer_adapter.clone(), + config, + signer.clone(), + )? + }; + let predefined_blocks = InDirectoryPredefinedBlocks::new(config.predefined_blocks_path.clone()); - let poa = (production_enabled).then(|| { + let poa = production_enabled.then(|| { fuel_core_poa::new_service( &last_block_header, poa_config, @@ -251,7 +262,7 @@ pub fn init_sub_services( producer_adapter.clone(), importer_adapter.clone(), p2p_adapter.clone(), - FuelBlockSigner::new(config.consensus_signer.clone()), + signer, predefined_blocks, SystemTime, ) @@ -349,6 +360,8 @@ pub fn init_sub_services( services.push(Box::new(sync)); } } + #[cfg(feature = "shared-sequencer")] + services.push(Box::new(shared_sequencer)); services.push(Box::new(graph_ql)); services.push(Box::new(graphql_worker)); diff --git a/crates/services/consensus_module/poa/Cargo.toml b/crates/services/consensus_module/poa/Cargo.toml index 5d71379853d..3a04386767e 100644 --- a/crates/services/consensus_module/poa/Cargo.toml +++ b/crates/services/consensus_module/poa/Cargo.toml @@ -12,12 +12,10 @@ version = { workspace = true } [dependencies] anyhow = { workspace = true } async-trait = { workspace = true } -aws-sdk-kms = { version = "1.37.0", optional = true } fuel-core-chain-config = { workspace = true } fuel-core-services = { workspace = true } fuel-core-storage = { workspace = true, features = ["std"] } fuel-core-types = { workspace = true, features = ["std"] } -k256 = { version = "0.13.3", features = ["ecdsa-core"], optional = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } tokio = { workspace = true, features = ["full"] } @@ -25,7 +23,6 @@ tokio-stream = { workspace = true } tracing = { workspace = true } [dev-dependencies] -aws-config = { version = "1.1.7", features = ["behavior-version-latest"] } fuel-core-poa = { path = ".", features = ["test-helpers"] } fuel-core-services = { workspace = true, features = ["test-helpers"] } fuel-core-storage = { path = "./../../../storage", features = ["test-helpers"] } @@ -37,7 +34,6 @@ test-case = { workspace = true } tokio = { workspace = true, features = ["full", "test-util"] } [features] -aws-kms = ["dep:aws-sdk-kms", "dep:k256"] test-helpers = [ "fuel-core-storage/test-helpers", "fuel-core-types/test-helpers", diff --git a/crates/services/consensus_module/poa/src/config.rs b/crates/services/consensus_module/poa/src/config.rs index c2a11ff1fca..9336ff672e0 100644 --- a/crates/services/consensus_module/poa/src/config.rs +++ b/crates/services/consensus_module/poa/src/config.rs @@ -1,8 +1,9 @@ -use fuel_core_types::fuel_types::ChainId; +use fuel_core_types::{ + fuel_types::ChainId, + signer::SignMode, +}; use tokio::time::Duration; -use crate::signer::SignMode; - #[derive(Debug, Clone)] pub struct Config { pub trigger: Trigger, diff --git a/crates/services/consensus_module/poa/src/lib.rs b/crates/services/consensus_module/poa/src/lib.rs index 19dfeb826cf..7b5e32871fe 100644 --- a/crates/services/consensus_module/poa/src/lib.rs +++ b/crates/services/consensus_module/poa/src/lib.rs @@ -13,7 +13,6 @@ mod service_test; pub mod config; pub mod ports; pub mod service; -pub mod signer; pub mod verifier; pub use config::{ diff --git a/crates/services/consensus_module/poa/src/service.rs b/crates/services/consensus_module/poa/src/service.rs index ffb88f8db2a..8ba66e0666a 100644 --- a/crates/services/consensus_module/poa/src/service.rs +++ b/crates/services/consensus_module/poa/src/service.rs @@ -122,9 +122,8 @@ pub(crate) enum RequestType { Manual, Trigger, } - pub struct MainTask { - signer: S, + signer: Arc, block_producer: B, block_importer: I, txpool: T, @@ -156,7 +155,7 @@ where block_producer: B, block_importer: I, p2p_port: P, - signer: S, + signer: Arc, predefined_blocks: PB, clock: C, ) -> Self { @@ -359,8 +358,6 @@ where consensus: seal, }; - block.entity.header().time(); - // Import the sealed block self.block_importer .commit_result(Uncommitted::new( @@ -607,7 +604,7 @@ pub fn new_service( block_producer: B, block_importer: I, p2p_port: P, - block_signer: S, + block_signer: Arc, predefined_blocks: PB, clock: C, ) -> Service diff --git a/crates/services/consensus_module/poa/src/service_test.rs b/crates/services/consensus_module/poa/src/service_test.rs index 5230e03f42b..99ff0c1aa7c 100644 --- a/crates/services/consensus_module/poa/src/service_test.rs +++ b/crates/services/consensus_module/poa/src/service_test.rs @@ -15,7 +15,6 @@ use crate::{ TransactionsSource, }, service::MainTask, - signer::SignMode, Config, Service, Trigger, @@ -31,7 +30,10 @@ use fuel_core_storage::transactional::Changes; use fuel_core_types::{ blockchain::{ block::Block, - consensus::Consensus, + consensus::{ + poa::PoAConsensus, + Consensus, + }, header::{ BlockHeader, PartialBlockHeader, @@ -51,6 +53,7 @@ use fuel_core_types::{ ExecutionResult, UncommittedResult, }, + signer::SignMode, tai64::{ Tai64, Tai64N, @@ -184,7 +187,7 @@ impl TestContextBuilder { producer, importer, p2p_port, - FakeBlockSigner { succeeds: true }, + FakeBlockSigner { succeeds: true }.into(), predefined_blocks, watch, ); @@ -201,9 +204,12 @@ struct FakeBlockSigner { impl BlockSigner for FakeBlockSigner { async fn seal_block(&self, block: &Block) -> anyhow::Result { if self.succeeds { - SignMode::Key(Secret::new(default_consensus_dev_key().into())) - .seal_block(block) - .await + let signature = + SignMode::Key(Secret::new(default_consensus_dev_key().into())) + .sign_message(block.id().into_message()) + .await?; + + Ok(Consensus::PoA(PoAConsensus::new(signature))) } else { Err(anyhow::anyhow!("failed to sign block")) } @@ -368,7 +374,7 @@ async fn remove_skipped_transactions() { block_producer, block_importer, p2p_port, - FakeBlockSigner { succeeds: true }, + FakeBlockSigner { succeeds: true }.into(), predefined_blocks, time.watch(), ); @@ -487,7 +493,7 @@ async fn consensus_service__run__will_include_sequential_predefined_blocks_befor block_producer, block_importer, generate_p2p_port(), - FakeBlockSigner { succeeds: true }, + FakeBlockSigner { succeeds: true }.into(), InMemoryPredefinedBlocks::new(blocks_map), time.watch(), ); @@ -551,7 +557,7 @@ async fn consensus_service__run__will_insert_predefined_blocks_in_correct_order( block_producer, block_importer, generate_p2p_port(), - FakeBlockSigner { succeeds: true }, + FakeBlockSigner { succeeds: true }.into(), InMemoryPredefinedBlocks::new(predefined_blocks_map), time.watch(), ); diff --git a/crates/services/shared-sequencer/Cargo.toml b/crates/services/shared-sequencer/Cargo.toml new file mode 100644 index 00000000000..f10f592dcf5 --- /dev/null +++ b/crates/services/shared-sequencer/Cargo.toml @@ -0,0 +1,33 @@ +[package] +authors = { workspace = true } +categories = ["cryptography::cryptocurrencies"] +description = "The service responsible for communication with the shared sequencer." +edition = { workspace = true } +homepage = { workspace = true } +keywords = ["blockchain", "cryptocurrencies", "fuel-client", "fuel-core"] +license = { workspace = true } +name = "fuel-core-shared-sequencer" +repository = { workspace = true } +version = { workspace = true } + +[dependencies] +anyhow = { workspace = true } +async-trait = { workspace = true } +base64 = "0.22" +cosmos-sdk-proto = { version = "0.26", features = ["grpc"] } +cosmrs = "0.21" +fuel-core-services = { workspace = true } +fuel-core-types = { workspace = true, features = ["std", "serde"] } +fuel-sequencer-proto = { version = "0.1.0" } +futures = { workspace = true } +postcard = { workspace = true } +prost = "0.12" +reqwest = { version = "0.12", features = ["json"], default-features = false } +serde = { workspace = true, features = ["derive"] } +serde_json = "1.0" +tendermint-rpc = { version = "0.36", features = ["http-client"] } +tokio = { workspace = true } +tracing = { workspace = true } + +[features] +test-helpers = [] diff --git a/crates/services/shared-sequencer/src/config.rs b/crates/services/shared-sequencer/src/config.rs new file mode 100644 index 00000000000..ca118d95278 --- /dev/null +++ b/crates/services/shared-sequencer/src/config.rs @@ -0,0 +1,37 @@ +use std::time::Duration; + +/// Endpoints for the shared sequencer client. +#[derive(Debug, Clone)] +pub struct Endpoints { + /// The RPC address of the sequencer chain + /// (e.g. "http://127.0.0.1:26657") + pub tendermint_rpc_api: String, + /// The REST address of the sequencer chain + /// (e.g. "http://127.0.0.1:1317") + pub blockchain_rest_api: String, +} + +/// Configuration for the shared sequencer client +#[derive(Debug, Clone)] +pub struct Config { + /// Whether the sequencer is enabled. + pub enabled: bool, + /// The frequency at which to post blocks to the shared sequencer. + pub block_posting_frequency: Duration, + /// Endpoints for the shared sequencer client. + pub endpoints: Option, + /// Topic to post blocks to + pub topic: [u8; 32], +} + +impl Config { + /// Default configuration for locally running shared sequencer node + pub fn local_node() -> Self { + Self { + enabled: false, + block_posting_frequency: Duration::from_secs(12), + endpoints: None, + topic: [0u8; 32], + } + } +} diff --git a/crates/services/shared-sequencer/src/error.rs b/crates/services/shared-sequencer/src/error.rs new file mode 100644 index 00000000000..78ef1c0d10d --- /dev/null +++ b/crates/services/shared-sequencer/src/error.rs @@ -0,0 +1,14 @@ +use core::fmt; + +#[derive(Debug)] +pub struct PostBlobError { + pub message: String, +} + +impl fmt::Display for PostBlobError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "PostBlobError: {:?}", self.message) + } +} + +impl std::error::Error for PostBlobError {} diff --git a/crates/services/shared-sequencer/src/http_api.rs b/crates/services/shared-sequencer/src/http_api.rs new file mode 100644 index 00000000000..b78a4266fe0 --- /dev/null +++ b/crates/services/shared-sequencer/src/http_api.rs @@ -0,0 +1,181 @@ +use base64::prelude::*; +use cosmrs::AccountId; + +mod api_types { + use serde::Deserialize; + + #[derive(Debug, Deserialize)] + pub struct AccountResponse { + pub account: AccountInfo, + } + + #[derive(Debug, Deserialize)] + pub struct AccountInfo { + pub account_number: String, + pub sequence: String, + } + + #[derive(Debug, Deserialize)] + pub struct AccountPrefix { + pub bech32_prefix: String, + } + + #[derive(Debug, Deserialize)] + pub struct NodeInfo { + pub default_node_info: DefaultNodeInfo, + } + + #[derive(Debug, Deserialize)] + pub struct DefaultNodeInfo { + pub network: String, + } + + #[derive(Debug, Deserialize)] + pub struct StakingParams { + pub params: StakingParamsInner, + } + + #[derive(Debug, Deserialize)] + pub struct StakingParamsInner { + pub bond_denom: String, + } + + #[derive(Debug, Deserialize)] + pub struct TopicResponse { + pub topic: TopicInfo, + } + + #[derive(Debug, Deserialize)] + pub struct TopicInfo { + pub owner: String, + pub order: String, + } + + #[derive(Clone, Debug, Deserialize)] + pub struct SimulateResponse { + pub gas_info: GasInfo, + } + + #[derive(Clone, Debug, Deserialize)] + pub struct GasInfo { + pub gas_used: String, + } + + #[derive(Clone, Debug, Deserialize)] + pub struct Config { + pub minimum_gas_price: String, + } +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct AccountMetadata { + pub account_number: u64, + pub sequence: u64, +} + +#[derive(Clone, Debug, serde::Serialize)] +pub struct SimulateRequest { + pub tx_bytes: String, +} + +pub async fn estimate_transaction( + api_url: &str, + tx_bytes: Vec, +) -> anyhow::Result { + let tx_bytes = BASE64_STANDARD.encode(&tx_bytes); + let request = SimulateRequest { + tx_bytes: tx_bytes.to_string(), + }; + let r = reqwest::Client::new() + .post(format!("{api_url}/cosmos/tx/v1beta1/simulate")) + .json(&request) + .send() + .await?; + let text = r.text().await?; + let resp: api_types::SimulateResponse = serde_json::from_str(&text)?; + Ok(resp.gas_info.gas_used.parse()?) +} + +pub async fn get_account_prefix(api_url: &str) -> anyhow::Result { + let r = reqwest::get(format!("{api_url}/cosmos/auth/v1beta1/bech32")).await?; + let text = r.text().await?; + let resp: api_types::AccountPrefix = serde_json::from_str(&text)?; + Ok(resp.bech32_prefix) +} + +pub async fn chain_id(api_url: &str) -> anyhow::Result { + let r = reqwest::get(format!( + "{api_url}/cosmos/base/tendermint/v1beta1/node_info" + )) + .await?; + let text = r.text().await?; + let resp: api_types::NodeInfo = serde_json::from_str(&text)?; + Ok(resp.default_node_info.network) +} + +pub async fn config(api_url: &str) -> anyhow::Result { + let r = reqwest::get(format!("{api_url}/cosmos/base/node/v1beta1/config")).await?; + let text = r.text().await?; + let resp: api_types::Config = serde_json::from_str(&text)?; + Ok(resp) +} + +pub async fn coin_denom(api_url: &str) -> anyhow::Result { + let r = reqwest::get(format!("{api_url}/cosmos/staking/v1beta1/params")).await?; + let text = r.text().await?; + let resp: api_types::StakingParams = serde_json::from_str(&text)?; + Ok(resp.params.bond_denom) +} + +pub async fn get_account( + api_url: &str, + id: AccountId, +) -> anyhow::Result { + let r = reqwest::get(format!("{api_url}/cosmos/auth/v1beta1/accounts/{id}")).await?; + let text = r.text().await?; + let resp: api_types::AccountResponse = serde_json::from_str(&text)?; + let account_number = resp + .account + .account_number + .parse() + .map_err(|_| anyhow::anyhow!("Invalid account_number"))?; + let sequence = resp + .account + .sequence + .parse() + .map_err(|_| anyhow::anyhow!("Invalid sequence"))?; + Ok(AccountMetadata { + account_number, + sequence, + }) +} + +#[derive(Debug)] +pub struct TopicInfo { + pub owner: AccountId, + pub order: u64, +} + +pub async fn get_topic(api_url: &str, id: [u8; 32]) -> anyhow::Result> { + let id_b64 = BASE64_STANDARD.encode(id); + let r = reqwest::get(format!( + "{api_url}/fuelsequencer/sequencing/v1/topic/{id_b64}" + )) + .await?; + if r.status() == 404 { + return Ok(None); + } + let text = r.text().await?; + let resp: api_types::TopicResponse = serde_json::from_str(&text)?; + let owner = resp + .topic + .owner + .parse() + .map_err(|_| anyhow::anyhow!("Invalid owner"))?; + let order = resp + .topic + .order + .parse() + .map_err(|_| anyhow::anyhow!("Invalid order"))?; + Ok(Some(TopicInfo { owner, order })) +} diff --git a/crates/services/shared-sequencer/src/lib.rs b/crates/services/shared-sequencer/src/lib.rs new file mode 100644 index 00000000000..4a351f321c0 --- /dev/null +++ b/crates/services/shared-sequencer/src/lib.rs @@ -0,0 +1,264 @@ +//! Shared sequencer client + +#![deny(clippy::arithmetic_side_effects)] +#![deny(clippy::cast_possible_truncation)] +#![deny(unused_crate_dependencies)] +#![deny(missing_docs)] + +use anyhow::anyhow; +use cosmrs::{ + tendermint::chain::Id, + tx::{ + self, + Fee, + MessageExt, + SignDoc, + SignerInfo, + }, + AccountId, + Coin, + Denom, +}; +use error::PostBlobError; +use fuel_sequencer_proto::protos::fuelsequencer::sequencing::v1::MsgPostBlob; +use http_api::{ + AccountMetadata, + TopicInfo, +}; +use ports::Signer; +use prost::Message; +use tendermint_rpc::Client as _; + +// Re-exports +pub use config::{ + Config, + Endpoints, +}; +pub use prost::bytes::Bytes; + +mod config; +mod error; +mod http_api; +pub mod ports; +pub mod service; + +/// Shared sequencer client +pub struct Client { + endpoints: Endpoints, + topic: [u8; 32], + chain_id: Id, + gas_price: u128, + coin_denom: Denom, + account_prefix: String, +} + +impl Client { + /// Create a new shared sequencer client from config. + pub async fn new(endpoints: Endpoints, topic: [u8; 32]) -> anyhow::Result { + let coin_denom = http_api::coin_denom(&endpoints.blockchain_rest_api) + .await? + .parse() + .map_err(|e| anyhow::anyhow!("{e:?}"))?; + let account_prefix = + http_api::get_account_prefix(&endpoints.blockchain_rest_api).await?; + let chain_id = http_api::chain_id(&endpoints.blockchain_rest_api) + .await? + .parse() + .map_err(|e| anyhow::anyhow!("{e:?}"))?; + let ss_config = http_api::config(&endpoints.blockchain_rest_api).await?; + + let mut minimum_gas_price = ss_config.minimum_gas_price; + + if let Some(index) = minimum_gas_price.find('.') { + minimum_gas_price.truncate(index); + } + let gas_price: u128 = minimum_gas_price.parse()?; + // Ceil the gas price to the next integer. + let gas_price = gas_price.saturating_add(1); + + Ok(Self { + topic, + endpoints, + account_prefix, + coin_denom, + chain_id, + gas_price, + }) + } + + /// Returns the Cosmos account ID of the sender. + pub fn sender_account_id(&self, signer: &S) -> anyhow::Result { + let sender_public_key = signer.public_key(); + let sender_account_id = sender_public_key + .account_id(&self.account_prefix) + .map_err(|err| anyhow!("{err:?}"))?; + + Ok(sender_account_id) + } + + fn tendermint(&self) -> anyhow::Result { + Ok(tendermint_rpc::HttpClient::new( + &*self.endpoints.tendermint_rpc_api, + )?) + } + + /// Retrieve latest block height + pub async fn latest_block_height(&self) -> anyhow::Result { + Ok(self + .tendermint()? + .abci_info() + .await? + .last_block_height + .value() + .try_into()?) + } + + /// Retrieve account metadata by its ID + pub async fn get_account_meta( + &self, + signer: &S, + ) -> anyhow::Result { + let sender_account_id = self.sender_account_id(signer)?; + http_api::get_account(&self.endpoints.blockchain_rest_api, sender_account_id) + .await + } + + /// Retrieve the topic info, if it exists + pub async fn get_topic(&self) -> anyhow::Result> { + http_api::get_topic(&self.endpoints.blockchain_rest_api, self.topic).await + } + + /// Post a sealed block to the sequencer chain using some + /// reasonable defaults and the config. + /// This is a convenience wrapper for `send_raw`. + pub async fn send( + &self, + signer: &S, + account: AccountMetadata, + order: u64, + blob: Vec, + ) -> anyhow::Result<()> { + let latest_height = self.latest_block_height().await?; + + self.send_raw( + // We don't want our transactions to be stay in the mempool for a long time, + // so we set the timeout height to be 64 blocks ahead of the latest block height. + // The 64 is an arbitrary number. + latest_height.saturating_add(64), + signer, + account, + order, + self.topic, + Bytes::from(blob), + ) + .await + } + + /// Post a blob of raw data to the sequencer chain + #[allow(clippy::too_many_arguments)] + pub async fn send_raw( + &self, + timeout_height: u32, + signer: &S, + account: AccountMetadata, + order: u64, + topic: [u8; 32], + data: Bytes, + ) -> anyhow::Result<()> { + // We want to estimate the transaction to know hat amount and fee to use. + // We use a dummy amount and fee to estimate the gas, and based on the result + // we calculate the actual amount and fee to use in real transaction. + let dummy_amount = Coin { + amount: 0, + denom: self.coin_denom.clone(), + }; + let dummy_fee = Fee::from_amount_and_gas(dummy_amount, 0u64); + + let dummy_payload = self + .make_payload( + timeout_height, + dummy_fee, + signer, + account, + order, + topic, + data.clone(), + ) + .await?; + + let used_gas = http_api::estimate_transaction( + &self.endpoints.blockchain_rest_api, + dummy_payload, + ) + .await?; + + let used_gas = used_gas.saturating_mul(2); // Add some buffer + + let amount = Coin { + amount: self.gas_price.saturating_mul(used_gas as u128), + denom: self.coin_denom.clone(), + }; + + let fee = Fee::from_amount_and_gas(amount, used_gas); + let payload = self + .make_payload(timeout_height, fee, signer, account, order, topic, data) + .await?; + + let r = self.tendermint()?.broadcast_tx_sync(payload).await?; + if r.code.is_err() { + return Err(PostBlobError { message: r.log }.into()); + } + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + async fn make_payload( + &self, + timeout_height: u32, + fee: Fee, + signer: &S, + account: AccountMetadata, + order: u64, + topic: [u8; 32], + data: Bytes, + ) -> anyhow::Result> { + let sender_account_id = self.sender_account_id(signer)?; + + let msg = MsgPostBlob { + from: sender_account_id.to_string(), + order: order.to_string(), + topic: Bytes::from(topic.to_vec()), + data, + }; + let any_msg = cosmrs::Any { + type_url: "/fuelsequencer.sequencing.v1.MsgPostBlob".to_owned(), + value: msg.encode_to_vec(), + }; + let tx_body = tx::Body::new(vec![any_msg], "", timeout_height); + + let sender_public_key = signer.public_key(); + let signer_info = + SignerInfo::single_direct(Some(sender_public_key), account.sequence); + let auth_info = signer_info.auth_info(fee); + let sign_doc = + SignDoc::new(&tx_body, &auth_info, &self.chain_id, account.account_number) + .map_err(|err| anyhow!("{err:?}"))?; + + let sign_doc_bytes = sign_doc + .clone() + .into_bytes() + .map_err(|err| anyhow!("{err:?}"))?; + let signature = signer.sign(&sign_doc_bytes).await?; + + // Convert the signature to non-normalized form + let mut signature_bytes = *signature; + signature_bytes[32] &= 0x7f; + + Ok(cosmos_sdk_proto::cosmos::tx::v1beta1::TxRaw { + body_bytes: sign_doc.body_bytes, + auth_info_bytes: sign_doc.auth_info_bytes, + signatures: vec![signature_bytes.to_vec()], + } + .to_bytes()?) + } +} diff --git a/crates/services/shared-sequencer/src/ports.rs b/crates/services/shared-sequencer/src/ports.rs new file mode 100644 index 00000000000..2fb3ea08e84 --- /dev/null +++ b/crates/services/shared-sequencer/src/ports.rs @@ -0,0 +1,25 @@ +//! Ports used by the shared sequencer to access the outside world + +use cosmrs::crypto::PublicKey; +use fuel_core_services::stream::BoxStream; +use fuel_core_types::{ + fuel_crypto::Signature, + services::block_importer::SharedImportResult, +}; + +/// A signer that can sign arbitrary data +#[async_trait::async_trait] +pub trait Signer: Send + Sync { + /// Sign data using a key + async fn sign(&self, data: &[u8]) -> anyhow::Result; + /// Get the public key of the signer. Panics if the key is not available. + fn public_key(&self) -> PublicKey; + /// Check if the signer is available + fn is_available(&self) -> bool; +} + +/// Provider of the blocks. +pub trait BlocksProvider { + /// Subscribe to new blocks. + fn subscribe(&self) -> BoxStream; +} diff --git a/crates/services/shared-sequencer/src/service.rs b/crates/services/shared-sequencer/src/service.rs new file mode 100644 index 00000000000..d80092495d3 --- /dev/null +++ b/crates/services/shared-sequencer/src/service.rs @@ -0,0 +1,266 @@ +//! Defines the logic how to interact with the shared sequencer. + +use crate::{ + http_api::AccountMetadata, + ports::{ + BlocksProvider, + Signer, + }, + Client, + Config, +}; +use async_trait::async_trait; +use core::time::Duration; +use fuel_core_services::{ + stream::BoxStream, + EmptyShared, + RunnableService, + RunnableTask, + ServiceRunner, + StateWatcher, +}; +use fuel_core_types::services::{ + block_importer::SharedImportResult, + shared_sequencer::{ + SSBlob, + SSBlobs, + }, +}; +use futures::StreamExt; +use std::sync::Arc; + +/// Non-initialized shared sequencer task. +pub struct NonInitializedTask { + config: Config, + signer: Arc, + blocks_events: BoxStream, +} + +/// Initialized shared sequencer task. +pub struct Task { + /// The client that communicates with shared sequencer. + shared_sequencer_client: Option, + config: Config, + signer: Arc, + account_metadata: Option, + prev_order: Option, + blobs: Arc>, +} + +impl NonInitializedTask { + /// Create a new shared sequencer task. + fn new( + config: Config, + blocks_events: BoxStream, + signer: Arc, + ) -> anyhow::Result { + if config.enabled && config.endpoints.is_none() { + return Err(anyhow::anyhow!( + "Shared sequencer is enabled but no endpoints are set" + )); + } + + Ok(Self { + config, + blocks_events, + signer, + }) + } +} + +#[async_trait] +impl RunnableService for NonInitializedTask +where + S: Signer + 'static, +{ + const NAME: &'static str = "SharedSequencer"; + + type SharedData = EmptyShared; + type Task = Task; + type TaskParams = (); + + fn shared_data(&self) -> Self::SharedData { + EmptyShared + } + + async fn into_task( + mut self, + _: &StateWatcher, + _: Self::TaskParams, + ) -> anyhow::Result { + let shared_sequencer_client = if let Some(endpoints) = &self.config.endpoints { + let ss = Client::new(endpoints.clone(), self.config.topic).await?; + + if self.signer.is_available() { + let cosmos_public_address = ss.sender_account_id(self.signer.as_ref())?; + + tracing::info!( + "Shared sequencer uses account ID: {}", + cosmos_public_address + ); + } + + Some(ss) + } else { + None + }; + + let blobs = Arc::new(tokio::sync::Mutex::new(SSBlobs::new())); + + if self.config.enabled { + let mut block_events = self.blocks_events; + + tokio::task::spawn({ + let blobs = blobs.clone(); + async move { + while let Some(block) = block_events.next().await { + let blob = SSBlob { + block_height: *block.sealed_block.entity.header().height(), + block_id: block.sealed_block.entity.id(), + }; + blobs.lock().await.push(blob); + } + } + }); + } + + Ok(Task { + shared_sequencer_client, + config: self.config, + signer: self.signer, + account_metadata: None, + prev_order: None, + blobs, + }) + } +} + +impl Task +where + S: Signer, +{ + // This function is not cancel-safe because it calls `sleep` inside. + async fn blobs(&mut self) -> anyhow::Result> { + let ss = self + .shared_sequencer_client + .as_ref() + .expect("Shared sequencer client is not set; qed"); + + if self.account_metadata.is_none() { + // If the account is not funded, this code will fail + // because we can't sign the transaction without account metadata. + let account_metadata = ss.get_account_meta(self.signer.as_ref()).await; + + match account_metadata { + Ok(account_metadata) => { + self.account_metadata = Some(account_metadata); + } + Err(err) => { + // We don't want to spam the RPC endpoint with a lot of queries, + // so wait for one second before sending the next on. + tokio::time::sleep(Duration::from_secs(1)).await; + return Err(err); + } + } + } + + if self.prev_order.is_none() { + self.prev_order = ss.get_topic().await?.map(|f| f.order); + } + + let blobs = { + let mut lock = self.blobs.lock().await; + core::mem::take(&mut *lock) + }; + + if blobs.is_empty() { + tokio::time::sleep(self.config.block_posting_frequency).await; + Ok(None) + } else { + Ok(Some(blobs)) + } + } +} + +const CONTINUE: bool = true; +const STOP: bool = false; + +#[async_trait] +impl RunnableTask for Task +where + S: Signer + 'static, +{ + async fn run(&mut self, watcher: &mut StateWatcher) -> anyhow::Result { + if !self.config.enabled { + let _ = watcher.while_started().await; + return Ok(STOP); + } + + tokio::select! { + biased; + _ = watcher.while_started() => { + Ok(STOP) + }, + + // The `blobs` function is not cancel safe, as it calls sleep inside. + // If someone add new logic into the `tokio::select`, please + // rework the `blobs` function to be cancel safe and use interval inside. + blobs = self.blobs() => { + let blobs = blobs?; + + if let Some(blobs) = blobs { + let mut account = self.account_metadata.take().expect("Account metadata is not set; qed"); + let next_order = if let Some(prev_order) = self.prev_order { + prev_order.wrapping_add(1) + } else { + 0 + }; + + let ss = self.shared_sequencer_client + .as_ref().expect("Shared sequencer client is not set; qed"); + let blobs_bytes = postcard::to_allocvec(&blobs).expect("Failed to serialize SSBlob"); + let result = ss.send(self.signer.as_ref(), account, next_order, blobs_bytes).await; + + match result { + Ok(_) => { + tracing::info!("Posted block to shared sequencer {blobs:?}"); + account.sequence = account.sequence.saturating_add(1); + self.prev_order = Some(next_order); + self.account_metadata = Some(account); + Ok(CONTINUE) + } + Err(err) => { + Err(err) + } + } + } else { + Ok(CONTINUE) + } + }, + } + } + + async fn shutdown(self) -> anyhow::Result<()> { + // Nothing to shut down because we don't have any temporary state that should be dumped, + // and we don't spawn any sub-tasks that we need to finish or await. + Ok(()) + } +} + +/// Creates an instance of runnable shared sequencer service. +pub fn new_service( + block_provider: B, + config: Config, + signer: Arc, +) -> anyhow::Result>> +where + B: BlocksProvider, + S: Signer, +{ + let blocks_events = block_provider.subscribe(); + Ok(ServiceRunner::new(NonInitializedTask::new( + config, + blocks_events, + signer, + )?)) +} diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index 58b9268e6c1..505975b8ebe 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -18,18 +18,24 @@ version = { workspace = true } [dependencies] anyhow = { workspace = true } +aws-sdk-kms = { workspace = true, optional = true } bs58 = { version = "0.5", optional = true } derivative = { version = "2" } derive_more = { version = "0.99" } fuel-vm-private = { workspace = true, default-features = false, features = [ "alloc", ] } +k256 = { version = "0.13", default-features = false, features = ["ecdsa"] } rand = { workspace = true, optional = true } secrecy = "0.8" serde = { workspace = true, features = ["derive"], optional = true } tai64 = { version = "4.0", features = ["serde"] } zeroize = "1.5" +[dev-dependencies] +aws-config = { version = "1.1.7", features = ["behavior-version-latest"] } +tokio = { workspace = true, features = ["macros"] } + [features] default = ["std"] alloc = ["fuel-vm-private/alloc"] @@ -38,3 +44,4 @@ da-compression = ["fuel-vm-private/da-compression"] std = ["alloc", "fuel-vm-private/std", "bs58"] random = ["dep:rand", "fuel-vm-private/random"] test-helpers = ["random", "fuel-vm-private/test-helpers"] +aws-kms = ["dep:aws-sdk-kms"] diff --git a/crates/types/src/blockchain/primitives.rs b/crates/types/src/blockchain/primitives.rs index f05826e186e..066ab8e9335 100644 --- a/crates/types/src/blockchain/primitives.rs +++ b/crates/types/src/blockchain/primitives.rs @@ -172,6 +172,12 @@ impl From<[u8; 32]> for BlockId { } } +impl AsRef for SecretKeyWrapper { + fn as_ref(&self) -> &SecretKey { + &self.0 + } +} + impl TryFrom<&'_ [u8]> for BlockId { type Error = TryFromSliceError; diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 7bf1d2b24ec..41bfbe42079 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -31,6 +31,7 @@ pub use tai64; pub mod blockchain; pub mod entities; pub mod services; +pub mod signer; /// Re-export of some fuel-vm types pub mod fuel_vm { diff --git a/crates/types/src/services.rs b/crates/types/src/services.rs index 948e804045f..2ed0a646263 100644 --- a/crates/types/src/services.rs +++ b/crates/types/src/services.rs @@ -7,6 +7,7 @@ pub mod graphql_api; #[cfg(feature = "std")] pub mod p2p; pub mod relayer; +pub mod shared_sequencer; #[cfg(feature = "std")] pub mod txpool; diff --git a/crates/types/src/services/shared_sequencer.rs b/crates/types/src/services/shared_sequencer.rs new file mode 100644 index 00000000000..e7e5bb2212d --- /dev/null +++ b/crates/types/src/services/shared_sequencer.rs @@ -0,0 +1,19 @@ +//! Module defines types for shared sequencer. + +use crate::{ + blockchain::primitives::BlockId, + fuel_types::BlockHeight, +}; + +/// The blob posted to the shared sequencer. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct SSBlob { + /// The Fuel block height. + pub block_height: BlockHeight, + /// The block ID that corresponds to the block height. + pub block_id: BlockId, +} + +/// The blobs posted to the shared sequencer. +pub type SSBlobs = alloc::vec::Vec; diff --git a/crates/services/consensus_module/poa/src/signer.rs b/crates/types/src/signer.rs similarity index 82% rename from crates/services/consensus_module/poa/src/signer.rs rename to crates/types/src/signer.rs index 876a9d6b5a7..221ab351472 100644 --- a/crates/services/consensus_module/poa/src/signer.rs +++ b/crates/types/src/signer.rs @@ -1,24 +1,11 @@ -use anyhow::anyhow; -#[cfg(feature = "aws-kms")] -use aws_sdk_kms::{ - primitives::Blob, - types::{ - MessageType, - SigningAlgorithmSpec, - }, -}; -#[cfg(feature = "aws-kms")] -use fuel_core_types::fuel_crypto::Message; -use fuel_core_types::{ - blockchain::{ - block::Block, - consensus::{ - poa::PoAConsensus, - Consensus, - }, - primitives::SecretKeyWrapper, +//! Block and generic data signing using a secret key or AWS KMS + +use crate::{ + blockchain::primitives::SecretKeyWrapper, + fuel_crypto::{ + Message, + PublicKey, }, - fuel_crypto::PublicKey, fuel_tx::{ Address, Input, @@ -29,7 +16,16 @@ use fuel_core_types::{ Secret, }, }; -use std::ops::Deref; +use anyhow::anyhow; +#[cfg(feature = "aws-kms")] +use aws_sdk_kms::{ + primitives::Blob, + types::{ + MessageType, + SigningAlgorithmSpec, + }, +}; +use core::ops::Deref; /// How the block is signed #[derive(Clone, Debug)] @@ -41,8 +37,11 @@ pub enum SignMode { /// Sign using AWS KMS #[cfg(feature = "aws-kms")] Kms { + /// The key ID in AWS KMS. key_id: String, + /// The AWS KMS client. client: aws_sdk_kms::Client, + /// The cached public key bytes. cached_public_key_bytes: Vec, }, } @@ -53,12 +52,9 @@ impl SignMode { !matches!(self, SignMode::Unavailable) } - /// Sign a block - pub async fn seal_block(&self, block: &Block) -> anyhow::Result { - let block_hash = block.id(); - let message = block_hash.into_message(); - - let poa_signature = match self { + /// Sign a prehashed message + pub async fn sign_message(&self, message: Message) -> anyhow::Result { + let signature = match self { SignMode::Unavailable => return Err(anyhow!("no PoA signing key configured")), SignMode::Key(key) => { let signing_key = key.expose_secret().deref(); @@ -71,7 +67,12 @@ impl SignMode { cached_public_key_bytes, } => sign_with_kms(client, key_id, cached_public_key_bytes, message).await?, }; - Ok(Consensus::PoA(PoAConsensus::new(poa_signature))) + Ok(signature) + } + + /// Sign a blob of data + pub async fn sign(&self, data: &[u8]) -> anyhow::Result { + self.sign_message(Message::new(data)).await } /// Returns the public key of the block producer, if any @@ -96,6 +97,31 @@ impl SignMode { } } + /// Returns the verifying key of the block producer, if any + pub fn verifying_key(&self) -> anyhow::Result> { + match self { + SignMode::Unavailable => Ok(None), + SignMode::Key(secret_key) => { + let secret: k256::SecretKey = secret_key.expose_secret().as_ref().into(); + let public_key = secret.public_key(); + + Ok(Some(public_key.into())) + } + + #[cfg(feature = "aws-kms")] + SignMode::Kms { + cached_public_key_bytes, + .. + } => { + use k256::pkcs8::DecodePublicKey; + + let k256_public_key = + k256::PublicKey::from_public_key_der(cached_public_key_bytes)?; + Ok(Some(k256_public_key.into())) + } + } + } + /// Returns the address of the block producer, if any pub fn address(&self) -> anyhow::Result> { let address = self.public_key()?.as_ref().map(Input::owner); @@ -126,7 +152,7 @@ async fn sign_with_kms( .message(Blob::new(*message)) .send() .await - .inspect_err(|err| tracing::error!("Failed to sign with AWS KMS: {err:?}"))?; + .map_err(|err| anyhow::anyhow!("Failed to sign with AWS KMS: {err:?}"))?; let signature_der = reply .signature .ok_or_else(|| anyhow!("no signature returned from AWS KMS"))? @@ -176,7 +202,7 @@ mod tests { use std::str::FromStr; use super::*; - use fuel_core_types::fuel_crypto::SecretKey; + use crate::fuel_crypto::SecretKey; use rand::{ rngs::StdRng, SeedableRng, diff --git a/tests/tests/blocks.rs b/tests/tests/blocks.rs index 7a5ba4688d5..f57d525a478 100644 --- a/tests/tests/blocks.rs +++ b/tests/tests/blocks.rs @@ -17,10 +17,7 @@ use fuel_core_client::client::{ types::TransactionStatus, FuelClient, }; -use fuel_core_poa::{ - signer::SignMode, - Trigger, -}; +use fuel_core_poa::Trigger; use fuel_core_storage::{ tables::{ FuelBlocks, @@ -37,6 +34,7 @@ use fuel_core_types::{ }, fuel_tx::*, secrecy::ExposeSecret, + signer::SignMode, tai64::Tai64, }; use itertools::{ diff --git a/tests/tests/da_compression.rs b/tests/tests/da_compression.rs index 43fce2a27e6..287e2c734fb 100644 --- a/tests/tests/da_compression.rs +++ b/tests/tests/da_compression.rs @@ -13,7 +13,6 @@ use fuel_core_client::client::{ FuelClient, }; use fuel_core_compression::VersionedCompressedBlock; -use fuel_core_poa::signer::SignMode; use fuel_core_types::{ fuel_asm::{ op, @@ -26,6 +25,7 @@ use fuel_core_types::{ TransactionBuilder, }, secrecy::Secret, + signer::SignMode, }; use rand::{ rngs::StdRng, diff --git a/tests/tests/poa.rs b/tests/tests/poa.rs index 792064fbade..38e6da5445f 100644 --- a/tests/tests/poa.rs +++ b/tests/tests/poa.rs @@ -12,13 +12,13 @@ use fuel_core_client::client::{ types::TransactionStatus, FuelClient, }; -use fuel_core_poa::signer::SignMode; use fuel_core_storage::transactional::AtomicView; use fuel_core_types::{ blockchain::consensus::Consensus, fuel_crypto::SecretKey, fuel_tx::Transaction, secrecy::Secret, + signer::SignMode, }; use rand::{ rngs::StdRng, diff --git a/tests/tests/trigger_integration/instant.rs b/tests/tests/trigger_integration/instant.rs index df18da60190..848dd7e2aff 100644 --- a/tests/tests/trigger_integration/instant.rs +++ b/tests/tests/trigger_integration/instant.rs @@ -12,15 +12,13 @@ use fuel_core_client::client::{ }, FuelClient, }; -use fuel_core_poa::{ - signer::SignMode, - Trigger, -}; +use fuel_core_poa::Trigger; use fuel_core_types::{ fuel_asm::*, fuel_crypto::SecretKey, fuel_tx::TransactionBuilder, secrecy::Secret, + signer::SignMode, }; use rand::{ rngs::StdRng, diff --git a/tests/tests/trigger_integration/interval.rs b/tests/tests/trigger_integration/interval.rs index b928e54c35d..bf09c7083ca 100644 --- a/tests/tests/trigger_integration/interval.rs +++ b/tests/tests/trigger_integration/interval.rs @@ -12,15 +12,13 @@ use fuel_core_client::client::{ }, FuelClient, }; -use fuel_core_poa::{ - signer::SignMode, - Trigger, -}; +use fuel_core_poa::Trigger; use fuel_core_types::{ fuel_asm::*, fuel_crypto::SecretKey, fuel_tx::TransactionBuilder, secrecy::Secret, + signer::SignMode, }; use rand::{ rngs::StdRng, diff --git a/tests/tests/trigger_integration/never.rs b/tests/tests/trigger_integration/never.rs index ac412583ef4..63a3ceff85a 100644 --- a/tests/tests/trigger_integration/never.rs +++ b/tests/tests/trigger_integration/never.rs @@ -12,15 +12,13 @@ use fuel_core_client::client::{ }, FuelClient, }; -use fuel_core_poa::{ - signer::SignMode, - Trigger, -}; +use fuel_core_poa::Trigger; use fuel_core_types::{ fuel_asm::op, fuel_crypto::SecretKey, fuel_tx::TransactionBuilder, secrecy::Secret, + signer::SignMode, }; use rand::{ rngs::StdRng,