From ac721a390a2fad985e79bf614a776663ab6f4c4b Mon Sep 17 00:00:00 2001 From: green Date: Fri, 22 Nov 2024 23:30:23 -0500 Subject: [PATCH] Squashed https://github.com/FuelLabs/fuel-core/pull/1922/ into one commit --- CHANGELOG.md | 2 +- Cargo.lock | 614 +++++++++++++++++- Cargo.toml | 3 + bin/fuel-core/Cargo.toml | 9 +- bin/fuel-core/src/cli/run.rs | 22 +- bin/fuel-core/src/cli/run/shared_sequencer.rs | 56 ++ ci_checks.sh | 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 | 33 + .../services/shared-sequencer/src/http_api.rs | 181 ++++++ crates/services/shared-sequencer/src/lib.rs | 280 ++++++++ crates/services/shared-sequencer/src/ports.rs | 25 + .../services/shared-sequencer/src/service.rs | 263 ++++++++ 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 +- 38 files changed, 1740 insertions(+), 110 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 fdd6424972..3f0dfd2dd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - [2376](https://github.com/FuelLabs/fuel-core/pull/2376): Add a way to fetch transactions in P2P without specifying a peer. - [2327](https://github.com/FuelLabs/fuel-core/pull/2327): Add more services tests and more checks of the pool. Also add an high level documentation for users of the pool and contributors. - [2416](https://github.com/FuelLabs/fuel-core/issues/2416): Define the `GasPriceServiceV1` task. - +- [1922](https://github.com/FuelLabs/fuel-core/pull/1922): Added support for posting blocks to the shared sequencer. ### Fixed - [2366](https://github.com/FuelLabs/fuel-core/pull/2366): The `importer_gas_price_for_block` metric is properly collected. diff --git a/Cargo.lock b/Cargo.lock index a3d5b110de..e2f906d2e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -912,7 +912,7 @@ dependencies = [ "aws-smithy-types", "bytes", "fastrand 2.2.0", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "http-body 1.0.1", @@ -1000,7 +1000,7 @@ 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", @@ -1008,7 +1008,7 @@ dependencies = [ "http-body 0.4.6", "hyper 0.14.31", "itoa", - "matchit", + "matchit 0.5.0", "memchr", "mime", "percent-encoding", @@ -1018,12 +1018,67 @@ dependencies = [ "serde_urlencoded", "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.31", + "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.1", + "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.1", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -1135,6 +1227,22 @@ dependencies = [ "syn 2.0.87", ] +[[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" @@ -1742,6 +1850,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 1.0.69", +] + [[package]] name = "cpp_demangle" version = "0.4.4" @@ -2089,6 +2228,19 @@ dependencies = [ "syn 2.0.87", ] +[[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 = "3.9.0" @@ -2471,6 +2623,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" @@ -2985,7 +3150,7 @@ dependencies = [ "futures", "hyper 0.14.31", "hyper-rustls 0.24.2", - "hyper-timeout", + "hyper-timeout 0.4.1", "log", "pin-project", "rand", @@ -3079,6 +3244,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" @@ -3169,8 +3344,9 @@ dependencies = [ "async-graphql", "async-graphql-value", "async-trait", - "axum", + "axum 0.5.17", "clap 4.5.21", + "cosmrs", "derive_more 0.99.18", "enum-iterator", "fuel-core", @@ -3187,6 +3363,7 @@ dependencies = [ "fuel-core-producer", "fuel-core-relayer", "fuel-core-services", + "fuel-core-shared-sequencer", "fuel-core-storage", "fuel-core-sync", "fuel-core-trace", @@ -3217,7 +3394,7 @@ dependencies = [ "tokio-rayon", "tokio-stream", "tokio-util", - "tower", + "tower 0.4.13", "tower-http 0.4.4", "tracing", "uuid 1.11.0", @@ -3279,7 +3456,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", @@ -3570,15 +3747,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", @@ -3658,6 +3832,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" @@ -3806,14 +4003,18 @@ name = "fuel-core-types" version = "0.40.0" dependencies = [ "anyhow", + "aws-config", + "aws-sdk-kms", "bs58", "derivative", "derive_more 0.99.18", "fuel-vm 0.58.2", + "k256", "rand", "secrecy", "serde", "tai64", + "tokio", "zeroize", ] @@ -3949,6 +4150,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" @@ -4391,6 +4604,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" @@ -4723,7 +4955,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "httparse", @@ -4746,9 +4978,11 @@ 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", @@ -4803,6 +5037,19 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper 1.5.0", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.10" @@ -6191,6 +6438,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" @@ -6904,6 +7157,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" @@ -7422,7 +7702,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]] @@ -7438,6 +7738,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.87", +] + +[[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.87", +] + +[[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" @@ -7474,7 +7809,7 @@ dependencies = [ "libflate", "log", "names", - "prost", + "prost 0.11.9", "reqwest 0.11.27", "thiserror 1.0.69", "url", @@ -7838,7 +8173,7 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "hyper 0.14.31", @@ -7851,6 +8186,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls 0.21.12", + "rustls-native-certs", "rustls-pemfile 1.0.4", "serde", "serde_json", @@ -8455,6 +8791,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.215" @@ -8478,6 +8823,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.87", +] + [[package]] name = "serde_spanned" version = "0.6.8" @@ -8942,6 +9298,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" @@ -9088,6 +9459,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 1.0.69", + "time", + "tokio", + "tracing", + "url", + "uuid 1.11.0", + "walkdir", +] + [[package]] name = "term" version = "0.7.0" @@ -9519,6 +10027,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.31", + "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.0", + "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" @@ -9527,8 +10092,11 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", + "indexmap 1.9.3", "pin-project", "pin-project-lite", + "rand", + "slab", "tokio", "tokio-util", "tower-layer", @@ -9536,6 +10104,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" @@ -9550,7 +10132,7 @@ dependencies = [ "http-body 0.4.6", "http-range-header", "pin-project-lite", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", ] diff --git a/Cargo.toml b/Cargo.toml index 0a711e83ad..3dec718584 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 = "3.1.0", features = ["http-reqwest"] } clap = "4.4" derivative = { version = "2" } diff --git a/bin/fuel-core/Cargo.toml b/bin/fuel-core/Cargo.toml index 41d05c2b69..880a69f929 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" @@ -61,9 +61,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"] @@ -74,6 +78,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 06d00219b5..7f963281dc 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, @@ -90,6 +92,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; @@ -256,6 +261,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, @@ -316,6 +325,8 @@ impl Command { p2p_args, #[cfg(feature = "p2p")] sync_args, + #[cfg(feature = "shared-sequencer")] + shared_sequencer_args, disabled_metrics, max_da_lag, max_wait_time, @@ -395,8 +406,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())); } @@ -600,6 +612,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 0000000000..74f5b078ad --- /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, + /// If set to true, new blocks will be posted to the shared sequencer chain + #[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/ci_checks.sh b/ci_checks.sh index fa5454366d..e27eef801c 100755 --- a/ci_checks.sh +++ b/ci_checks.sh @@ -31,7 +31,7 @@ cargo make check --all-features --locked && cargo make check --locked && OVERRIDE_CHAIN_CONFIGS=true cargo test --test integration_tests local_node && cargo nextest run --workspace && -FUEL_ALWAYS_USE_WASM=true cargo nextest run --all-features --workspace && +cargo nextest run --all-features --workspace && cargo nextest run -p fuel-core --no-default-features && cargo nextest run -p fuel-core-client --no-default-features && cargo nextest run -p fuel-core-chain-config --no-default-features && diff --git a/crates/fuel-core/Cargo.toml b/crates/fuel-core/Cargo.toml index cac6a58150..8de4a1e31b 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 3a5f6e7800..e8e184d187 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 1f751f1de5..535ebbba86 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 f55d457233..73a1b87689 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 0000000000..611a725c17 --- /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 0e093c2b8b..a72fedcfce 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, @@ -182,6 +186,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 4fd45534b2..4f6478096f 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -42,10 +42,7 @@ 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, structured_storage::StructuredStorage, @@ -53,6 +50,7 @@ use fuel_core_storage::{ }; #[cfg(feature = "relayer")] use fuel_core_types::blockchain::primitives::DaBlockHeight; +use fuel_core_types::signer::SignMode; use std::sync::Arc; use tokio::sync::Mutex; @@ -245,9 +243,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, @@ -255,7 +266,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, ) @@ -353,6 +364,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 5d71379853..3a04386767 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 c2a11ff1fc..9336ff672e 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 19dfeb826c..7b5e32871f 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 795f4ced78..abd916d0b9 100644 --- a/crates/services/consensus_module/poa/src/service.rs +++ b/crates/services/consensus_module/poa/src/service.rs @@ -123,9 +123,8 @@ pub(crate) enum RequestType { Manual, Trigger, } - pub struct MainTask { - signer: S, + signer: Arc, block_producer: B, block_importer: I, txpool: T, @@ -157,7 +156,7 @@ where block_producer: B, block_importer: I, p2p_port: P, - signer: S, + signer: Arc, predefined_blocks: PB, clock: C, ) -> Self { @@ -360,8 +359,6 @@ where consensus: seal, }; - block.entity.header().time(); - // Import the sealed block self.block_importer .commit_result(Uncommitted::new( @@ -615,7 +612,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 5230e03f42..99ff0c1aa7 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 0000000000..f10f592dcf --- /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 0000000000..fb3eb767d5 --- /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 tendermint API + /// (e.g. "http://127.0.0.1:26657") + pub tendermint_rpc_api: String, + /// The REST address of the sequencer chain tendermint API + /// (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 0000000000..19c4b9e16a --- /dev/null +++ b/crates/services/shared-sequencer/src/error.rs @@ -0,0 +1,33 @@ +use core::fmt; + +use cosmrs::ErrorReport; + +#[derive(Debug)] +pub struct CosmosError(pub ErrorReport); + +impl fmt::Display for CosmosError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "CosmosError: {:?}", self.0) + } +} + +impl std::error::Error for CosmosError {} + +impl From for CosmosError { + fn from(e: ErrorReport) -> Self { + Self(e) + } +} + +#[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 0000000000..b78a4266fe --- /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 0000000000..b11697e17e --- /dev/null +++ b/crates/services/shared-sequencer/src/lib.rs @@ -0,0 +1,280 @@ +//! 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::{ + CosmosError, + 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( + latest_height.saturating_add(100), + 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<()> { + 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()?) + } + + /// Return all blob messages in the given blob + pub async fn read_block_msgs(&self, height: u32) -> anyhow::Result> { + let mut result = Vec::new(); + + let block = self.tendermint()?.block(height).await?; + + for item in block.block.data.iter().skip(1) { + let tx = cosmrs::Tx::from_bytes(item).map_err(CosmosError)?; + for msg in tx.body.messages.iter() { + if msg.type_url == "/fuelsequencer.sequencing.v1.MsgPostBlob" { + let msg: MsgPostBlob = prost::Message::decode(msg.value.as_slice())?; + result.push(msg); + } + } + } + Ok(result) + } +} diff --git a/crates/services/shared-sequencer/src/ports.rs b/crates/services/shared-sequencer/src/ports.rs new file mode 100644 index 0000000000..2fb3ea08e8 --- /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 0000000000..7bed83acc6 --- /dev/null +++ b/crates/services/shared-sequencer/src/service.rs @@ -0,0 +1,263 @@ +//! 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, + TaskNextAction, +}; +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, +{ + 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) => { + 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); + } + + tokio::time::sleep(self.config.block_posting_frequency).await; + + let blobs = { + let mut lock = self.blobs.lock().await; + core::mem::take(&mut *lock) + }; + + if blobs.is_empty() { + tokio::time::sleep(Duration::from_secs(1)).await; + Ok(None) + } else { + Ok(Some(blobs)) + } + } +} + +#[async_trait] +impl RunnableTask for Task +where + S: Signer + 'static, +{ + async fn run(&mut self, watcher: &mut StateWatcher) -> TaskNextAction { + if !self.config.enabled { + let _ = watcher.while_started().await; + return TaskNextAction::Stop; + } + + tokio::select! { + biased; + _ = watcher.while_started() => { + TaskNextAction::Stop + }, + + blobs = self.blobs() => { + let blobs = match blobs { + Ok(blobs) => blobs, + Err(err) => return TaskNextAction::ErrorContinue(err), + }; + + 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); + TaskNextAction::Continue + } + Err(err) => { + TaskNextAction::ErrorContinue(err) + } + } + } else { + TaskNextAction::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 deb1b2f8ab..586f0ad86b 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.4", default-features = false, features = ["ecdsa"] } rand = { workspace = true, optional = true } secrecy = "0.8" serde = { workspace = true, features = ["derive"], optional = true } tai64 = { version = "4.1", 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 f05826e186..066ab8e933 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 7bf1d2b24e..41bfbe4207 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 948e804045..2ed0a64626 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 0000000000..e7e5bb2212 --- /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 876a9d6b5a..221ab35147 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 923d457af9..ac367a62f0 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 43fce2a27e..287e2c734f 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 2de2ee4e82..b37f233503 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 df18da6019..848dd7e2af 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 b928e54c35..bf09c7083c 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 ac412583ef..63a3ceff85 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,