diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index db18bf8e5bc..ca65b27701c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -5,4 +5,7 @@ crates/fuel-gas-price-algorithm @xgreenx @Dentosal @MitchTurner @rafal-ch # Code owners for the transaction pool -crates/services/txpool_v2 @xgreenx @Dentosal @MitchTurner @AurelienFT \ No newline at end of file +crates/services/txpool_v2 @xgreenx @Dentosal @MitchTurner @AurelienFT + +# Code owners for the gas price service (v0 & v1) +crates/services/gas_price_service @xgreenx @Dentosal @MitchTurner @rymnc \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index adcff97b9ed..bf05f3f92a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,8 +27,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - [2361](https://github.com/FuelLabs/fuel-core/pull/2361): Add caches to the sync service to not reask for data it already fetched from the network. - [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. - [2033](https://github.com/FuelLabs/fuel-core/pull/2033): Remove `Option` in favor of `BlockHeightQuery` where applicable. +- [2439](https://github.com/FuelLabs/fuel-core/pull/2439): Add gas costs for the two new zk opcodes `ecop` and `eadd` and the benches that allow to calibrate them. - [2472](https://github.com/FuelLabs/fuel-core/pull/2472): Added the `amountU128` field to the `Balance` GraphQL schema, providing the total balance as a `U128`. The existing `amount` field clamps any balance exceeding `U64` to `u64::MAX`. +- [2526](https://github.com/FuelLabs/fuel-core/pull/2526): Add possibility to not have any cache set for RocksDB. Add an option to either load the RocksDB columns families on creation of the database or when the column is used. +- [2532](https://github.com/FuelLabs/fuel-core/pull/2532): Getters for inner rocksdb database handles. ### Fixed - [2365](https://github.com/FuelLabs/fuel-core/pull/2365): Fixed the error during dry run in the case of race condition. @@ -42,6 +46,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - [2511](https://github.com/FuelLabs/fuel-core/pull/2511): Fix backward compatibility of V0Metadata in gas price db. ### Changed +- [2501](https://github.com/FuelLabs/fuel-core/pull/2501): Use gas price from block for estimating future gas prices - [2468](https://github.com/FuelLabs/fuel-core/pull/2468): Abstract unrecorded blocks concept for V1 algorithm, create new storage impl. Introduce `TransactionableStorage` trait to allow atomic changes to the storage. - [2295](https://github.com/FuelLabs/fuel-core/pull/2295): `CombinedDb::from_config` now respects `state_rewind_policy` with tmp RocksDB. - [2378](https://github.com/FuelLabs/fuel-core/pull/2378): Use cached hash of the topic instead of calculating it on each publishing gossip message. @@ -57,6 +62,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - [2154](https://github.com/FuelLabs/fuel-core/pull/2154): Transaction graphql endpoints use `TransactionType` instead of `fuel_tx::Transaction`. - [2446](https://github.com/FuelLabs/fuel-core/pull/2446): Use graphiql instead of graphql-playground due to known vulnerability and stale development. - [2379](https://github.com/FuelLabs/fuel-core/issues/2379): Change `kv_store::Value` to be `Arc<[u8]>` instead of `Arc>`. +- [2526](https://github.com/FuelLabs/fuel-core/pull/2526): By default the cache of RocksDB is now disabled instead of being `1024 * 1024 * 1024`. ## [Version 0.40.2] diff --git a/Cargo.lock b/Cargo.lock index 6fa3ea715a0..06916aec7a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -230,7 +230,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", "synstructure", ] @@ -242,7 +242,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -381,7 +381,7 @@ dependencies = [ "proc-macro2", "quote", "strum 0.26.3", - "syn 2.0.91", + "syn 2.0.94", "thiserror 1.0.69", ] @@ -561,7 +561,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -572,13 +572,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "1b1244b10dcd56c92219da4e14caa97e312079e185f04ba3eea25061561dc0a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -663,7 +663,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -674,9 +674,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-config" -version = "1.5.11" +version = "1.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d1c2c88936a73c699225d0bc00684a534166b0cebc2659c3cdf08de8edc64c" +checksum = "c03a50b30228d3af8865ce83376b4e99e1ffa34728220fe2860e4df0bb5278d6" dependencies = [ "aws-credential-types", "aws-runtime", @@ -716,9 +716,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300a12520b4e6d08b73f77680f12c16e8ae43250d55100e0b2be46d78da16a48" +checksum = "b16d1aa50accc11a4b4d5c50f7fb81cc0cf60328259c587d0e6b0f11385bde46" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -741,9 +741,9 @@ dependencies = [ [[package]] name = "aws-sdk-kms" -version = "1.52.0" +version = "1.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ff4c717bf02350576b1542d7534edda68b95299b72700424978afd125b0b507" +checksum = "a6cf16c0e5853312995505557b876dd3f9fb9941e96d031383528ccef14ace57" dependencies = [ "aws-credential-types", "aws-runtime", @@ -763,9 +763,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.51.0" +version = "1.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74995133da38f109a0eb8e8c886f9e80c713b6e9f2e6e5a6a1ba4450ce2ffc46" +checksum = "1605dc0bf9f0a4b05b451441a17fcb0bda229db384f23bf5cead3adbab0664ac" dependencies = [ "aws-credential-types", "aws-runtime", @@ -785,9 +785,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.52.0" +version = "1.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7062a779685cbf3b2401eb36151e2c6589fd5f3569b8a6bc2d199e5aaa1d059" +checksum = "59f3f73466ff24f6ad109095e0f3f2c830bfb4cd6c8b12f744c8e61ebf4d3ba1" dependencies = [ "aws-credential-types", "aws-runtime", @@ -807,9 +807,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.52.0" +version = "1.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299dae7b1dc0ee50434453fa5a229dc4b22bd3ee50409ff16becf1f7346e0193" +checksum = "249b2acaa8e02fd4718705a9494e3eb633637139aa4bb09d70965b0448e865db" dependencies = [ "aws-credential-types", "aws-runtime", @@ -913,7 +913,7 @@ dependencies = [ "aws-smithy-types", "bytes", "fastrand 2.3.0", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "http-body 1.0.1", @@ -1001,7 +1001,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", @@ -1009,7 +1009,7 @@ dependencies = [ "http-body 0.4.6", "hyper 0.14.32", "itoa", - "matchit", + "matchit 0.5.0", "memchr", "mime", "percent-encoding", @@ -1025,6 +1025,61 @@ dependencies = [ "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.32", + "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.2.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.2", + "tower-layer", + "tower-service", +] + [[package]] name = "axum-core" version = "0.2.9" @@ -1041,6 +1096,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.2.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" @@ -1133,7 +1225,23 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.91", + "syn 2.0.94", +] + +[[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]] @@ -1245,9 +1353,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.1" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", "regex-automata 0.4.9", @@ -1352,9 +1460,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.5" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" +checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" dependencies = [ "jobserver", "libc", @@ -1499,7 +1607,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -1719,6 +1827,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.4", + "tendermint-proto 0.40.1", + "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.1", + "thiserror 1.0.69", +] + [[package]] name = "cpp_demangle" version = "0.4.4" @@ -2063,7 +2202,20 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", +] + +[[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]] @@ -2074,7 +2226,7 @@ checksum = "17334136b717249d58b05739a825e2c83f53b0beee0e1f714c332cf48eee7023" dependencies = [ "cynic-proc-macros", "ref-cast", - "reqwest 0.12.10", + "reqwest 0.12.12", "serde", "serde_json", "static_assertions", @@ -2094,7 +2246,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.10.0", - "syn 2.0.91", + "syn 2.0.94", "thiserror 1.0.69", ] @@ -2118,7 +2270,7 @@ dependencies = [ "cynic-codegen", "darling", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -2142,7 +2294,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -2153,7 +2305,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -2246,7 +2398,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -2266,7 +2418,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", "unicode-xid", ] @@ -2383,7 +2535,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -2449,6 +2601,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" @@ -2473,7 +2638,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -2564,7 +2729,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -2584,7 +2749,7 @@ checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -2604,7 +2769,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -2759,7 +2924,7 @@ dependencies = [ "reqwest 0.11.27", "serde", "serde_json", - "syn 2.0.91", + "syn 2.0.94", "toml 0.8.19", "walkdir", ] @@ -2777,7 +2942,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -2803,7 +2968,7 @@ dependencies = [ "serde", "serde_json", "strum 0.26.3", - "syn 2.0.91", + "syn 2.0.94", "tempfile", "thiserror 1.0.69", "tiny-keccak", @@ -2984,7 +3149,7 @@ dependencies = [ "futures", "hyper 0.14.32", "hyper-rustls 0.24.2", - "hyper-timeout", + "hyper-timeout 0.4.1", "log", "pin-project", "rand", @@ -3078,6 +3243,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" @@ -3168,8 +3343,9 @@ dependencies = [ "async-graphql", "async-graphql-value", "async-trait", - "axum", + "axum 0.5.17", "clap", + "cosmrs", "derive_more 0.99.18", "enum-iterator", "fuel-core", @@ -3186,6 +3362,7 @@ dependencies = [ "fuel-core-producer", "fuel-core-relayer", "fuel-core-services", + "fuel-core-shared-sequencer", "fuel-core-storage", "fuel-core-sync", "fuel-core-trace", @@ -3242,6 +3419,7 @@ dependencies = [ "fuel-core-sync", "fuel-core-types 0.40.2", "futures", + "hex", "itertools 0.12.1", "num_enum", "p256", @@ -3278,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.2", "hex", @@ -3342,7 +3520,7 @@ dependencies = [ "hyper-rustls 0.24.2", "insta", "itertools 0.12.1", - "reqwest 0.12.10", + "reqwest 0.12.12", "schemafy_lib", "serde", "serde_json", @@ -3455,7 +3633,7 @@ dependencies = [ "futures", "num_enum", "parking_lot", - "reqwest 0.12.10", + "reqwest 0.12.12", "serde", "strum 0.25.0", "strum_macros 0.25.3", @@ -3569,15 +3747,12 @@ version = "0.40.2" 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.2", - "k256", "mockall", "rand", "serde", @@ -3657,6 +3832,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "fuel-core-shared-sequencer" +version = "0.40.2" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.22.1", + "cosmos-sdk-proto", + "cosmrs", + "fuel-core-services", + "fuel-core-types 0.40.2", + "fuel-sequencer-proto", + "futures", + "postcard", + "prost 0.12.6", + "reqwest 0.12.12", + "serde", + "serde_json", + "tendermint-rpc", + "tokio", + "tracing", +] + [[package]] name = "fuel-core-storage" version = "0.40.2" @@ -3737,7 +3935,7 @@ dependencies = [ "primitive-types", "proptest", "rand", - "reqwest 0.12.10", + "reqwest 0.12.12", "rstest", "serde_json", "spki", @@ -3805,14 +4003,18 @@ name = "fuel-core-types" version = "0.40.2" dependencies = [ "anyhow", + "aws-config", + "aws-sdk-kms", "bs58", "derivative", "derive_more 0.99.18", "fuel-vm 0.59.1", + "k256", "rand", "secrecy", "serde", "tai64", + "tokio", "zeroize", ] @@ -3892,7 +4094,7 @@ checksum = "3f49fdbfc1615d88d2849650afc2b0ac2fecd69661ebadd31a073d8416747764" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", "synstructure", ] @@ -3904,7 +4106,7 @@ checksum = "8703ee10001e6a52ad9a0d8411ca5a92098de978ccfbdddd0ba185f3a7405b4c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", "synstructure", ] @@ -3949,6 +4151,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" @@ -4207,7 +4421,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -4334,9 +4548,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "gloo-timers" @@ -4392,6 +4606,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.2.0", + "indexmap 2.7.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.4.1" @@ -4738,7 +4971,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "httparse", @@ -4761,9 +4994,11 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2 0.4.7", "http 1.2.0", "http-body 1.0.1", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -4818,6 +5053,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.2", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.10" @@ -4975,7 +5223,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -5100,7 +5348,7 @@ dependencies = [ "autocfg", "impl-tools-lib", "proc-macro-error2", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -5112,7 +5360,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -5123,7 +5371,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -5840,7 +6088,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -6128,7 +6376,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax 0.8.5", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -6198,6 +6446,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" @@ -6627,7 +6881,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -6749,7 +7003,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -6901,6 +7155,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" @@ -6957,7 +7238,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -7021,7 +7302,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -7059,7 +7340,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -7301,7 +7582,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -7374,7 +7655,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", "version_check", "yansi 1.0.1", ] @@ -7399,7 +7680,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -7429,7 +7710,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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec" +dependencies = [ + "bytes", + "prost-derive 0.13.4", ] [[package]] @@ -7445,6 +7746,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.94", +] + +[[package]] +name = "prost-derive" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.94", +] + +[[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" @@ -7480,8 +7816,8 @@ dependencies = [ "libflate", "log", "names", - "prost", - "reqwest 0.12.10", + "prost 0.11.9", + "reqwest 0.12.12", "serde_json", "thiserror 1.0.69", "url", @@ -7502,9 +7838,9 @@ dependencies = [ [[package]] name = "quanta" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773ce68d0bb9bc7ef20be3536ffe94e223e1f365bd374108b2659fac0c65cfe6" +checksum = "3bd1fe6824cea6538803de3ff1bc0cf3949024db3d43c9643024bfb33a807c0e" dependencies = [ "crossbeam-utils", "libc", @@ -7768,7 +8104,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -7845,7 +8181,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.32", @@ -7858,6 +8194,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls 0.21.12", + "rustls-native-certs", "rustls-pemfile 1.0.4", "serde", "serde_json", @@ -7877,9 +8214,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.10" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3536321cfc54baa8cf3e273d5e1f63f889067829c4b410fcdbac8ca7b80994" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" dependencies = [ "base64 0.22.1", "bytes", @@ -8228,9 +8565,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "rusty-fork" @@ -8300,7 +8637,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -8460,22 +8797,31 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "serde" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" 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.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -8490,6 +8836,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.94", +] + [[package]] name = "serde_spanned" version = "0.6.8" @@ -8538,7 +8895,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -8868,7 +9225,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta-derive", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -8879,7 +9236,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -8932,7 +9289,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -8945,7 +9302,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -8967,6 +9324,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" @@ -9023,9 +9395,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.91" +version = "2.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" +checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3" dependencies = [ "proc-macro2", "quote", @@ -9055,7 +9427,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -9123,17 +9495,155 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.14.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand 2.3.0", + "getrandom", "once_cell", "rustix 0.38.42", "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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9703e34d940c2a293804752555107f8dbe2b84ec4c6dd5203831235868105d2" +dependencies = [ + "bytes", + "digest 0.10.7", + "ed25519", + "ed25519-consensus", + "flex-error", + "futures", + "k256", + "num-traits", + "once_cell", + "prost 0.13.4", + "ripemd", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.10.8", + "signature", + "subtle", + "subtle-encoding", + "tendermint-proto 0.40.1", + "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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9e1705aa0fa5ecb2c6aa7fb78c2313c4a31158ea5f02048bf318f849352eb" +dependencies = [ + "bytes", + "flex-error", + "prost 0.13.4", + "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" @@ -9190,7 +9700,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -9201,7 +9711,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", "test-case-core", ] @@ -9224,7 +9734,7 @@ dependencies = [ "futures", "itertools 0.12.1", "rand", - "reqwest 0.12.10", + "reqwest 0.12.12", "tempfile", "tokio", ] @@ -9238,7 +9748,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -9267,7 +9777,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -9278,7 +9788,7 @@ checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -9442,7 +9952,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -9558,6 +10068,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.32", + "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.2.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.2", + "hyper-timeout 0.5.2", + "hyper-util", + "percent-encoding", + "pin-project", + "prost 0.13.4", + "socket2 0.5.8", + "tokio", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower" version = "0.4.13" @@ -9566,8 +10133,11 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", + "indexmap 1.9.3", "pin-project", "pin-project-lite", + "rand", + "slab", "tokio", "tokio-util", "tower-layer", @@ -9673,7 +10243,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -10025,7 +10595,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", "wasm-bindgen-shared", ] @@ -10060,7 +10630,7 @@ checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10185,7 +10755,7 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", "wasmtime-component-util", "wasmtime-wit-bindgen", "wit-parser", @@ -10284,7 +10854,7 @@ checksum = "a2bde986038b819bc43a21fef0610aeb47aabfe3ea09ca3533a7b81023b84ec6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -10589,9 +11159,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980" dependencies = [ "memchr", ] @@ -10794,7 +11364,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", "synstructure", ] @@ -10816,7 +11386,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -10836,7 +11406,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", "synstructure", ] @@ -10857,7 +11427,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] @@ -10879,7 +11449,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.94", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1bdb62f1f66..6b49ff33df8 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", @@ -74,6 +75,7 @@ fuel-core-services = { version = "0.40.2", path = "./crates/services" } fuel-core-consensus-module = { version = "0.40.2", path = "./crates/services/consensus_module" } fuel-core-bft = { version = "0.40.2", path = "./crates/services/consensus_module/bft" } fuel-core-poa = { version = "0.40.2", path = "./crates/services/consensus_module/poa" } +fuel-core-shared-sequencer = { version = "0.40.2", path = "crates/services/shared-sequencer" } fuel-core-executor = { version = "0.40.2", path = "./crates/services/executor", default-features = false } fuel-core-importer = { version = "0.40.2", path = "./crates/services/importer" } fuel-core-gas-price-service = { version = "0.40.2", path = "crates/services/gas_price_service" } @@ -97,6 +99,7 @@ fuel-vm-private = { version = "0.59.1", 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/README.md b/README.md index 52f64d7d27b..b5b60613ddf 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ Before pushing any changes or creating pull request please run `source ci_checks ## Building +If you plan to use already pre-compiled binairies you can directly go to [Running a Ignition node](#running-a-ignition-node). + ### System Requirements There are several system requirements including clang. @@ -47,6 +49,50 @@ You'll need `wasm32-unknown-unknown` target installed. rustup target add wasm32-unknown-unknown ``` + +## Running a Ignition node + +If you want to participate in the Ignition network with your own node you can launch it following these simple commands. + +### From pre-compiled binaries + +Follow : https://docs.fuel.network/guides/running-a-node/running-a-mainnet-node/ + +### From source + +Clone the `fuel-core` repository : +``` +git clone https://github.com/FuelLabs/fuel-core.git +``` + +Go to the latest release tag for ignition on the `fuel-core` repository : +``` +git checkout v0.40.2 +``` + +Build your node binary: +```bash +cargo build --release --bin fuel-core --no-default-features --features production +``` + +To run the node follow : https://docs.fuel.network/guides/running-a-node/running-a-mainnet-node/ + +## Running a Local network from source + +Clone the `fuel-core` repository : +``` +git clone https://github.com/FuelLabs/fuel-core.git +``` + +Build your node binary: +```bash +cargo build --release --bin fuel-core --no-default-features --features production +``` + +To run the node follow : https://docs.fuel.network/guides/running-a-node/running-a-local-node/ + +## Setup to make contributions + ### Compiling We recommend using `xtask` to build fuel-core: @@ -111,47 +157,6 @@ $ ./target/debug/fuel-core run --poa-instant=false 2023-06-13T12:44:12.857763Z INFO fuel_core::cli::run: 232: Block production disabled ``` -## Running a Ignition node - -If you want to participate in the Ignition network with your own node you can launch it following these simple commands. - -Install the latest fuelup : -```bash -curl -fsSL https://install.fuel.network/ | sh -fuelup toolchain install latest -``` - -Clone the chain configuration folder : -``` -git clone https://github.com/FuelLabs/chain-configuration -``` - -Generate a keypair for your node: -```bash -fuel-core-keygen new --key-type peering -``` -and copy the secret key displayed. - -Run your node (change all variable with `{}` to your own personal variables): -```bash -fuel-core run \ ---enable-relayer \ ---service-name fuel-ignition-node \ ---keypair {KEYGEN_SECRET_KEY} \ ---relayer {ETHEREUM_RPC_ENDPOINT} \ ---ip=0.0.0.0 --port 4000 --peering-port 30333 \ ---db-path ~/.fuel-ignition \ ---snapshot {PATH_TO_CHAIN_CONFIGURATION_FOLDER}/ignition \ ---utxo-validation --poa-instant false --enable-p2p \ ---bootstrap-nodes /dnsaddr/mainnet.fuel.network \ ---sync-header-batch-size 100 \ ---relayer-v2-listening-contracts=0xAEB0c00D0125A8a788956ade4f4F12Ead9f65DDf \ ---relayer-da-deploy-height=20620434 \ ---relayer-log-page-size=100 \ ---sync-block-stream-buffer-size 30 -``` -Instead of directly placing your personal values on the command we advise you to use, for example, environment variables. - ### Troubleshooting #### Publishing diff --git a/benches/Cargo.toml b/benches/Cargo.toml index e4ee7db927b..b31c8cf948f 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -31,6 +31,7 @@ fuel-core-sync = { path = "./../crates/services/sync", features = [ ] } fuel-core-types = { path = "./../crates/types", features = ["test-helpers"] } futures = { workspace = true } +hex = "0.4.3" itertools = { workspace = true } num_enum = { workspace = true } p256 = { version = "0.13", default-features = false, features = [ @@ -63,7 +64,7 @@ harness = false name = "vm" [features] -default = ["fuel-core/rocksdb"] +default = ["fuel-core/rocksdb", "fuel-core/rocksdb-production"] [[bench]] harness = false diff --git a/benches/benches-outputs/src/lib.rs b/benches/benches-outputs/src/lib.rs index 5ff79c3ae70..505bbc6f3ba 100644 --- a/benches/benches-outputs/src/lib.rs +++ b/benches/benches-outputs/src/lib.rs @@ -1,5 +1,5 @@ use fuel_core_types::fuel_tx::{ - consensus_parameters::gas::GasCostsValuesV4, + consensus_parameters::gas::*, DependentCost, GasCostsValues, }; diff --git a/benches/benches/block_target_gas.rs b/benches/benches/block_target_gas.rs index fddd14a24c9..e1384e65a81 100644 --- a/benches/benches/block_target_gas.rs +++ b/benches/benches/block_target_gas.rs @@ -27,7 +27,13 @@ use fuel_core::{ Config, FuelService, }, - state::historical_rocksdb::StateRewindPolicy, + state::{ + historical_rocksdb::StateRewindPolicy, + rocks_db::{ + ColumnsPolicy, + DatabaseConfig, + }, + }, }; use fuel_core_benches::{ default_gas_costs::default_gas_costs, @@ -266,8 +272,15 @@ fn service_with_many_contracts( .build() .unwrap(); let _drop = rt.enter(); - let mut database = Database::rocksdb_temp(StateRewindPolicy::NoRewind) - .expect("Failed to create database"); + let mut database = Database::rocksdb_temp( + StateRewindPolicy::NoRewind, + DatabaseConfig { + cache_capacity: Some(16 * 1024 * 1024 * 1024), + max_fds: -1, + columns_policy: ColumnsPolicy::OnCreation, + }, + ) + .expect("Failed to create database"); let mut chain_config = ChainConfig::local_testnet(); diff --git a/benches/benches/state.rs b/benches/benches/state.rs index d08ecf2104c..e661ee9ded2 100644 --- a/benches/benches/state.rs +++ b/benches/benches/state.rs @@ -5,10 +5,19 @@ use criterion::{ BenchmarkGroup, Criterion, }; -use fuel_core::database::{ - database_description::on_chain::OnChain, - state::StateInitializer, - Database, +use fuel_core::{ + database::{ + database_description::on_chain::OnChain, + state::StateInitializer, + Database, + }, + state::{ + historical_rocksdb::StateRewindPolicy, + rocks_db::{ + ColumnsPolicy, + DatabaseConfig, + }, + }, }; use fuel_core_storage::{ transactional::{ @@ -71,7 +80,15 @@ fn insert_state_single_contract_database(c: &mut Criterion) { let mut bench_state = |group: &mut BenchmarkGroup, name: &str, n: usize| { group.bench_function(name, |b| { - let mut db = Database::::default(); + let mut db = Database::::rocksdb_temp( + StateRewindPolicy::NoRewind, + DatabaseConfig { + cache_capacity: Some(16 * 1024 * 1024 * 1024), + max_fds: -1, + columns_policy: ColumnsPolicy::OnCreation, + }, + ) + .unwrap(); let contract: ContractId = rng.gen(); setup(&mut db, &contract, n); let outer = db.write_transaction(); diff --git a/benches/benches/transaction_throughput.rs b/benches/benches/transaction_throughput.rs index 4475e9d3759..cafe8705000 100644 --- a/benches/benches/transaction_throughput.rs +++ b/benches/benches/transaction_throughput.rs @@ -10,7 +10,16 @@ use criterion::{ SamplingMode, }; use ed25519_dalek::Signer; -use fuel_core::service::config::Trigger; +use fuel_core::{ + service::{ + config::Trigger, + DbType, + }, + state::rocks_db::{ + ColumnsPolicy, + DatabaseConfig, + }, +}; use fuel_core_benches::*; use fuel_core_storage::transactional::AtomicView; use fuel_core_types::{ @@ -92,6 +101,12 @@ where test_builder.gas_limit = Some(10_000_000_000); test_builder.block_size_limit = Some(1_000_000_000_000); test_builder.max_txs = 100000; + test_builder.database_type = DbType::RocksDb; + test_builder.database_config = DatabaseConfig { + cache_capacity: Some(16 * 1024 * 1024 * 1024), + max_fds: -1, + columns_policy: ColumnsPolicy::OnCreation, + }; // spin up node let transactions: Vec = diff --git a/benches/benches/utils.rs b/benches/benches/utils.rs index 52b82cee529..d3d46efb694 100644 --- a/benches/benches/utils.rs +++ b/benches/benches/utils.rs @@ -36,7 +36,7 @@ pub fn get_state_size() -> u64 { } /// Allocates a byte array from heap and initializes it. Then points `reg` to it. -fn aloc_bytearray(reg: u8, v: [u8; S]) -> Vec { +pub fn aloc_bytearray(reg: u8, v: [u8; S]) -> Vec { let mut ops = vec![op::movi(reg, S as u32), op::aloc(reg)]; for (i, b) in v.iter().enumerate() { if *b != 0 { diff --git a/benches/benches/vm_set/blockchain.rs b/benches/benches/vm_set/blockchain.rs index cd51bf6dbc9..04ad9851cc8 100644 --- a/benches/benches/vm_set/blockchain.rs +++ b/benches/benches/vm_set/blockchain.rs @@ -20,7 +20,13 @@ use fuel_core::{ GenesisDatabase, }, service::Config, - state::historical_rocksdb::HistoricalRocksDB, + state::{ + historical_rocksdb::HistoricalRocksDB, + rocks_db::{ + ColumnsPolicy, + DatabaseConfig, + }, + }, }; use fuel_core_benches::*; use fuel_core_storage::{ @@ -47,8 +53,11 @@ use fuel_core_types::{ }, fuel_tx::{ ContractIdExt, + Finalizable, Input, Output, + Transaction, + TransactionBuilder, Word, }, fuel_types::*, @@ -65,6 +74,7 @@ pub struct BenchDb { db: GenesisDatabase, /// Used for RAII cleanup. Contents of this directory are deleted on drop. _tmp_dir: utils::ShallowTempDir, + latest_block: u32, } impl BenchDb { @@ -73,9 +83,12 @@ impl BenchDb { let db = HistoricalRocksDB::::default_open( tmp_dir.path(), - None, Default::default(), - -1, + DatabaseConfig { + cache_capacity: None, + max_fds: -1, + columns_policy: ColumnsPolicy::OnCreation, + }, ) .unwrap(); let db = Arc::new(db); @@ -122,18 +135,40 @@ impl BenchDb { &block.compress(&chain_config.consensus_parameters.chain_id()), ) .unwrap(); - Ok(Self { _tmp_dir: tmp_dir, db: database, + latest_block: 0, }) } + fn add_blocks(&mut self, nb_blocks: u32) { + for i in 1..=nb_blocks { + let mut block = + fuel_core::service::genesis::create_genesis_block(&Config::local_node()); + let transactions = block.transactions_mut(); + // Add a dummy transaction to the block to make the block bigger. + transactions.push(Transaction::Script( + TransactionBuilder::script(vec![], vec![1; 200_000]).finalize(), + )); + let config = Config::local_node(); + let chain_config = config.snapshot_reader.chain_config(); + self.db + .storage::() + .insert( + &i.into(), + &block.compress(&chain_config.consensus_parameters.chain_id()), + ) + .unwrap(); + } + self.latest_block = nb_blocks; + } + /// Creates a `VmDatabase` instance. fn to_vm_database(&self) -> VmStorage> { let consensus = ConsensusHeader { prev_root: Default::default(), - height: 1.into(), + height: self.latest_block.into(), time: Tai64::UNIX_EPOCH, generated: (), }; @@ -517,14 +552,25 @@ pub fn run(c: &mut Criterion) { VmBench::new(op::bhei(0x10)), ); + let mut db = + BenchDb::new(&VmBench::CONTRACT).expect("Unable to fill contract storage"); + db.add_blocks(10000); + run_group_ref( &mut c.benchmark_group("bhsh"), "bhsh", - VmBench::new(op::bhsh(0x10, RegId::ZERO)).with_prepare_script(vec![ - op::movi(0x10, Bytes32::LEN.try_into().unwrap()), - op::aloc(0x10), - op::move_(0x10, RegId::HP), - ]), + VmBench::new(op::bhsh(RegId::HP, 0x11)) + .prepend_prepare_script(vec![ + // Store number of bytes we want to alloc for the future result + op::movi(0x10, Bytes32::LEN.try_into().unwrap()), + // Allocate space for the future result + op::aloc(0x10), + // Add block height to 0x11 + op::movi(0x11, 0xc9), + ]) + // Random height + .with_height(0xca.into()) + .with_db(db.to_vm_database()), ); run_group_ref( diff --git a/benches/benches/vm_set/crypto.rs b/benches/benches/vm_set/crypto.rs index cb266a4df36..32db19b8017 100644 --- a/benches/benches/vm_set/crypto.rs +++ b/benches/benches/vm_set/crypto.rs @@ -1,3 +1,5 @@ +use crate::utils::aloc_bytearray; + use super::run_group_ref; use criterion::{ @@ -28,7 +30,7 @@ pub fn run(c: &mut Criterion) { "eck1", VmBench::new(op::eck1(RegId::HP, 0x20, 0x21)) .with_prepare_script(vec![ - op::gtf_args(0x20, 0x00, GTFArgs::ScriptData), + op::gtf_args(0x20, RegId::ZERO, GTFArgs::ScriptData), op::addi( 0x21, 0x20, @@ -55,7 +57,7 @@ pub fn run(c: &mut Criterion) { "ecr1", VmBench::new(op::ecr1(RegId::HP, 0x20, 0x21)) .with_prepare_script(vec![ - op::gtf_args(0x20, 0x00, GTFArgs::ScriptData), + op::gtf_args(0x20, RegId::ZERO, GTFArgs::ScriptData), op::addi( 0x21, 0x20, @@ -122,7 +124,7 @@ pub fn run(c: &mut Criterion) { format!("{i}"), VmBench::new(op::ed19(0x20, 0x21, RegId::ZERO, 0x10)) .with_prepare_script(vec![ - op::gtf_args(0x20, 0x00, GTFArgs::ScriptData), + op::gtf_args(0x20, RegId::ZERO, GTFArgs::ScriptData), op::addi( 0x21, 0x20, @@ -154,4 +156,61 @@ pub fn run(c: &mut Criterion) { ); } bench_ed19.finish(); + + // ecop testing mul as it's the most expensive operation + let mut points_bytearray = Vec::new(); + // X + points_bytearray.extend( + hex::decode("2bd3e6d0f3b142924f5ca7b49ce5b9d54c4703d7ae5648e61d02268b1a0a9fb7") + .unwrap(), + ); + // Y + points_bytearray.extend( + hex::decode("21611ce0a6af85915e2f1d70300909ce2e49dfad4a4619c8390cae66cefdb204") + .unwrap(), + ); + // Scalar + points_bytearray.extend( + hex::decode("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + .unwrap(), + ); + // 96 bytes = 1 point and 1 scalar + let prepare_points = aloc_bytearray::<96>(0x10, points_bytearray.try_into().unwrap()); + run_group_ref( + &mut c.benchmark_group("ecop"), + "ecop", + VmBench::new(op::ecop(0x10, RegId::ZERO, 0x01, 0x10)) + .with_prepare_script(prepare_points), + ); + + // ec pairing + let mut bench_epar = c.benchmark_group("epar"); + for i in [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] { + let mut points_bytearray = Vec::new(); + for _ in 0u32..i { + points_bytearray.extend( + hex::decode( + "0000000000000000000000000000000000000000000000000000000000000001\ + 0000000000000000000000000000000000000000000000000000000000000002\ + 198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2\ + 1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed\ + 090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b\ + 12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", + ) + .unwrap(), + ); + } + bench_epar.throughput(Throughput::Bytes(i as u64)); + run_group_ref( + &mut bench_epar, + format!("{i}"), + VmBench::new(op::epar(0x12, RegId::ZERO, 0x11, 0x10)) + .with_data(points_bytearray) + .with_prepare_script(vec![ + op::movi(0x11, i), + op::gtf_args(0x10, 0x00, GTFArgs::ScriptData), + ]), + ); + } + bench_epar.finish(); } diff --git a/benches/src/bin/collect.rs b/benches/src/bin/collect.rs index 3fe0b3a0850..4fb2a697eb8 100644 --- a/benches/src/bin/collect.rs +++ b/benches/src/bin/collect.rs @@ -1,6 +1,6 @@ use clap::Parser; use fuel_core_types::fuel_tx::{ - consensus_parameters::gas::GasCostsValuesV4, + consensus_parameters::gas::GasCostsValuesV5, ConsensusParameters, GasCosts, }; @@ -371,7 +371,7 @@ pub const GIT: &str = ""#, r#"";"#, r##" pub fn default_gas_costs() -> GasCostsValues { - GasCostsValuesV4 {"##, + GasCostsValuesV5 {"##, r##" }.into() } "##, @@ -495,7 +495,7 @@ impl State { ) } - fn to_gas_costs(&self) -> GasCostsValuesV4 { + fn to_gas_costs(&self) -> GasCostsValuesV5 { serde_yaml::from_value(self.to_yaml()).unwrap() } diff --git a/benches/src/db_lookup_times_utils/utils.rs b/benches/src/db_lookup_times_utils/utils.rs index 72f376e1e7e..1cbd2b0f7f7 100644 --- a/benches/src/db_lookup_times_utils/utils.rs +++ b/benches/src/db_lookup_times_utils/utils.rs @@ -5,7 +5,11 @@ use crate::db_lookup_times_utils::full_block_table::{ use anyhow::anyhow; use fuel_core::{ database::database_description::DatabaseDescription, - state::rocks_db::RocksDb, + state::rocks_db::{ + ColumnsPolicy, + DatabaseConfig, + RocksDb, + }, }; use fuel_core_storage::kv_store::{ KeyValueInspect, @@ -39,7 +43,14 @@ pub fn get_random_block_height( pub fn open_rocks_db( path: &Path, ) -> Result> { - let db = RocksDb::default_open(path, None, -1)?; + let db = RocksDb::default_open( + path, + DatabaseConfig { + cache_capacity: Some(16 * 1024 * 1024 * 1024), + max_fds: -1, + columns_policy: ColumnsPolicy::OnCreation, + }, + )?; Ok(db) } diff --git a/benches/src/default_gas_costs.rs b/benches/src/default_gas_costs.rs index dc09359b7ad..f770326ee47 100644 --- a/benches/src/default_gas_costs.rs +++ b/benches/src/default_gas_costs.rs @@ -8,7 +8,7 @@ pub fn default_gas_costs() -> GasCostsValues { andi: 2, bal: 274, bhei: 2, - bhsh: 2, + bhsh: 32, burn: 7566, cb: 2, cfsi: 2, @@ -90,12 +90,10 @@ pub fn default_gas_costs() -> GasCostsValues { wqmm: 6, xor: 2, xori: 2, - // TODO: Change to correct values - ecop: 2, - // TODO: Change to correct values + ecop: 3500, epar: DependentCost::HeavyOperation { - base: 2, - gas_per_unit: 2, + base: 69000, + gas_per_unit: 52000, }, aloc: DependentCost::LightOperation { base: 2, diff --git a/benches/src/lib.rs b/benches/src/lib.rs index 3bed0a964de..da36e5b7678 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -141,6 +141,8 @@ pub struct VmBenchPrepared { pub diff: diff::Diff, } +const TX_SIZE: u64 = 64 * 1024 * 1024; + impl VmBench { pub const SALT: Salt = Salt::zeroed(); pub const CONTRACT: ContractId = ContractId::zeroed(); @@ -148,10 +150,17 @@ impl VmBench { pub fn new(instruction: Instruction) -> Self { let mut consensus_params = ConsensusParameters::default(); consensus_params.set_tx_params( - TxParameters::default().with_max_gas_per_tx(LARGE_GAS_LIMIT + 1), + TxParameters::default() + .with_max_gas_per_tx(LARGE_GAS_LIMIT + 1) + .with_max_size(TX_SIZE), ); consensus_params.set_fee_params(FeeParameters::default().with_gas_per_byte(0)); consensus_params.set_gas_costs(GasCosts::free()); + consensus_params.set_script_params( + ScriptParameters::default() + .with_max_script_length(TX_SIZE) + .with_max_script_data_length(TX_SIZE), + ); Self { params: consensus_params, diff --git a/bin/e2e-test-client/src/tests/test_data/large_state/chain_config.json b/bin/e2e-test-client/src/tests/test_data/large_state/chain_config.json index 66747c21118..23adc22f3fc 100644 --- a/bin/e2e-test-client/src/tests/test_data/large_state/chain_config.json +++ b/bin/e2e-test-client/src/tests/test_data/large_state/chain_config.json @@ -48,7 +48,7 @@ "andi": 2, "bal": 366, "bhei": 2, - "bhsh": 2, + "bhsh": 32, "burn": 33949, "cb": 2, "cfei": 2, @@ -129,7 +129,7 @@ "wqmm": 11, "xor": 2, "xori": 2, - "ecop": 2, + "ecop": 3500, "call": { "LightOperation": { "base": 21687, @@ -240,8 +240,8 @@ }, "epar": { "HeavyOperation": { - "base": 2, - "gas_per_unit": 2 + "base": 69000, + "gas_per_unit": 52000 } }, "contract_root": { diff --git a/bin/fuel-core/Cargo.toml b/bin/fuel-core/Cargo.toml index 41d05c2b69a..880a69f9296 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/chainspec/local-testnet/chain_config.json b/bin/fuel-core/chainspec/local-testnet/chain_config.json index 41766b5b468..43c2927aa34 100644 --- a/bin/fuel-core/chainspec/local-testnet/chain_config.json +++ b/bin/fuel-core/chainspec/local-testnet/chain_config.json @@ -47,7 +47,7 @@ "andi": 2, "bal": 274, "bhei": 2, - "bhsh": 2, + "bhsh": 32, "burn": 7566, "cb": 2, "cfsi": 2, @@ -126,7 +126,7 @@ "wqmm": 6, "xor": 2, "xori": 2, - "ecop": 2, + "ecop": 3500, "aloc": { "LightOperation": { "base": 2, @@ -273,8 +273,8 @@ }, "epar": { "HeavyOperation": { - "base": 2, - "gas_per_unit": 2 + "base": 69000, + "gas_per_unit": 52000 } }, "contract_root": { diff --git a/bin/fuel-core/src/cli/rollback.rs b/bin/fuel-core/src/cli/rollback.rs index 12e18a0547b..52b7ea3b2b8 100644 --- a/bin/fuel-core/src/cli/rollback.rs +++ b/bin/fuel-core/src/cli/rollback.rs @@ -3,7 +3,10 @@ use anyhow::Context; use clap::Parser; use fuel_core::{ combined_database::CombinedDatabase, - state::historical_rocksdb::StateRewindPolicy, + state::{ + historical_rocksdb::StateRewindPolicy, + rocks_db::DatabaseConfig, + }, }; use rlimit::{ getrlimit, @@ -51,9 +54,12 @@ pub async fn exec(command: Command) -> anyhow::Result<()> { let path = command.database_path.as_path(); let db = CombinedDatabase::open( path, - 64 * 1024 * 1024, StateRewindPolicy::RewindFullRange, - command.rocksdb_max_fds, + DatabaseConfig { + cache_capacity: Some(64 * 1024 * 1024), + max_fds: command.rocksdb_max_fds, + columns_policy: Default::default(), + }, ) .map_err(Into::::into) .context(format!("failed to open combined database at path {path:?}"))?; diff --git a/bin/fuel-core/src/cli/run.rs b/bin/fuel-core/src/cli/run.rs index 06d00219b50..763ffdef897 100644 --- a/bin/fuel-core/src/cli/run.rs +++ b/bin/fuel-core/src/cli/run.rs @@ -34,6 +34,10 @@ use fuel_core::{ RelayerConsensusConfig, VMConfig, }, + state::rocks_db::{ + ColumnsPolicy, + DatabaseConfig, + }, txpool::config::{ BlackList, Config as TxPoolConfig, @@ -55,8 +59,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, @@ -85,11 +91,12 @@ use tracing::{ #[cfg(feature = "rocksdb")] use fuel_core::state::historical_rocksdb::StateRewindPolicy; -use super::DEFAULT_DATABASE_CACHE_SIZE; - #[cfg(feature = "p2p")] mod p2p; +#[cfg(feature = "shared-sequencer")] +mod shared_sequencer; + mod consensus; mod graphql; mod profiling; @@ -105,12 +112,8 @@ pub struct Command { pub service_name: String, /// The maximum database cache size in bytes. - #[arg( - long = "max-database-cache-size", - default_value_t = DEFAULT_DATABASE_CACHE_SIZE, - env - )] - pub max_database_cache_size: usize, + #[arg(long = "max-database-cache-size", env)] + pub max_database_cache_size: Option, #[clap( name = "DB_PATH", @@ -256,6 +259,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 +323,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 +404,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())); } @@ -456,11 +466,17 @@ impl Command { let combined_db_config = CombinedDatabaseConfig { database_path, database_type, - max_database_cache_size, #[cfg(feature = "rocksdb")] - state_rewind_policy, + database_config: DatabaseConfig { + max_fds: rocksdb_max_fds, + cache_capacity: max_database_cache_size, + #[cfg(feature = "production")] + columns_policy: ColumnsPolicy::OnCreation, + #[cfg(not(feature = "production"))] + columns_policy: ColumnsPolicy::Lazy, + }, #[cfg(feature = "rocksdb")] - max_fds: rocksdb_max_fds, + state_rewind_policy, }; let block_importer = fuel_core::service::config::fuel_core_importer::Config::new( @@ -600,6 +616,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..698a4955b05 --- /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 + /// (e.g. "http://127.0.0.1:26657") + #[clap(long = "ss-tendermint-api", env)] + tendermint_api: Option, + /// The REST address of the sequencer chain + /// (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/bin/fuel-core/src/cli/snapshot.rs b/bin/fuel-core/src/cli/snapshot.rs index 3f562e38733..060dd9a87a2 100644 --- a/bin/fuel-core/src/cli/snapshot.rs +++ b/bin/fuel-core/src/cli/snapshot.rs @@ -6,7 +6,13 @@ use clap::{ }; use fuel_core::{ combined_database::CombinedDatabase, - state::historical_rocksdb::StateRewindPolicy, + state::{ + historical_rocksdb::StateRewindPolicy, + rocks_db::{ + ColumnsPolicy, + DatabaseConfig, + }, + }, types::fuel_types::ContractId, }; use fuel_core_chain_config::ChainConfig; @@ -209,9 +215,12 @@ fn open_db( ) -> anyhow::Result { CombinedDatabase::open( path, - capacity.unwrap_or(1024 * 1024 * 1024), StateRewindPolicy::NoRewind, - max_fds, + DatabaseConfig { + cache_capacity: Some(capacity.unwrap_or(1024 * 1024 * 1024)), + max_fds, + columns_policy: ColumnsPolicy::OnCreation, + }, ) .map_err(Into::::into) .context(format!("failed to open combined database at path {path:?}",)) diff --git a/crates/fuel-core/Cargo.toml b/crates/fuel-core/Cargo.toml index f1bf18fd60b..79c05ec33e6 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 } @@ -97,6 +99,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", @@ -106,6 +109,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/proptest-regressions/service/adapters.txt b/crates/fuel-core/proptest-regressions/service/adapters.txt new file mode 100644 index 00000000000..b83038b4a5f --- /dev/null +++ b/crates/fuel-core/proptest-regressions/service/adapters.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 87eb4d9c7c90c2a0ee11edef3ee1b01303db0a6ba1fd34d7fc6146a2c36387f4 # shrinks to gas_price = 1, starting_height = 0, block_horizon = 1, percentage = 100 diff --git a/crates/fuel-core/src/combined_database.rs b/crates/fuel-core/src/combined_database.rs index c0b6d291af1..b8ff591e274 100644 --- a/crates/fuel-core/src/combined_database.rs +++ b/crates/fuel-core/src/combined_database.rs @@ -1,5 +1,9 @@ #[cfg(feature = "rocksdb")] -use crate::state::historical_rocksdb::StateRewindPolicy; +use crate::state::{ + historical_rocksdb::StateRewindPolicy, + rocks_db::DatabaseConfig, +}; + use crate::{ database::{ database_description::{ @@ -36,11 +40,9 @@ use std::path::PathBuf; pub struct CombinedDatabaseConfig { pub database_path: PathBuf, pub database_type: DbType, - pub max_database_cache_size: usize, #[cfg(feature = "rocksdb")] + pub database_config: DatabaseConfig, pub state_rewind_policy: StateRewindPolicy, - #[cfg(feature = "rocksdb")] - pub max_fds: i32, } /// A database that combines the on-chain, off-chain and relayer databases into one entity. @@ -79,24 +81,49 @@ impl CombinedDatabase { #[cfg(feature = "rocksdb")] pub fn open( path: &std::path::Path, - capacity: usize, state_rewind_policy: StateRewindPolicy, - max_fds: i32, + database_config: DatabaseConfig, ) -> crate::database::Result { // Split the fds in equitable manner between the databases - let max_fds = match max_fds { + + let max_fds = match database_config.max_fds { -1 => -1, - _ => max_fds.saturating_div(4), + _ => database_config.max_fds.saturating_div(4), }; + // TODO: Use different cache sizes for different databases - let on_chain = - Database::open_rocksdb(path, capacity, state_rewind_policy, max_fds)?; - let off_chain = - Database::open_rocksdb(path, capacity, state_rewind_policy, max_fds)?; - let relayer = - Database::open_rocksdb(path, capacity, StateRewindPolicy::NoRewind, max_fds)?; - let gas_price = - Database::open_rocksdb(path, capacity, state_rewind_policy, max_fds)?; + let on_chain = Database::open_rocksdb( + path, + state_rewind_policy, + DatabaseConfig { + max_fds, + ..database_config + }, + )?; + let off_chain = Database::open_rocksdb( + path, + state_rewind_policy, + DatabaseConfig { + max_fds, + ..database_config + }, + )?; + let relayer = Database::open_rocksdb( + path, + StateRewindPolicy::NoRewind, + DatabaseConfig { + max_fds, + ..database_config + }, + )?; + let gas_price = Database::open_rocksdb( + path, + state_rewind_policy, + DatabaseConfig { + max_fds, + ..database_config + }, + )?; Ok(Self { on_chain, off_chain, @@ -109,10 +136,11 @@ impl CombinedDatabase { #[cfg(feature = "rocksdb")] pub fn temp_database_with_state_rewind_policy( state_rewind_policy: StateRewindPolicy, + database_config: DatabaseConfig, ) -> DatabaseResult { Ok(Self { - on_chain: Database::rocksdb_temp(state_rewind_policy)?, - off_chain: Database::rocksdb_temp(state_rewind_policy)?, + on_chain: Database::rocksdb_temp(state_rewind_policy, database_config)?, + off_chain: Database::rocksdb_temp(state_rewind_policy, database_config)?, relayer: Default::default(), gas_price: Default::default(), }) @@ -129,19 +157,19 @@ impl CombinedDatabase { ); CombinedDatabase::temp_database_with_state_rewind_policy( config.state_rewind_policy, + config.database_config, )? } else { tracing::info!( - "Opening database {:?} with cache size \"{}\" and state rewind policy \"{:?}\"", + "Opening database {:?} with cache size \"{:?}\" and state rewind policy \"{:?}\"", config.database_path, - config.max_database_cache_size, + config.database_config.cache_capacity, config.state_rewind_policy, ); CombinedDatabase::open( &config.database_path, - config.max_database_cache_size, config.state_rewind_policy, - config.max_fds, + config.database_config, )? } } diff --git a/crates/fuel-core/src/database.rs b/crates/fuel-core/src/database.rs index 41d0451f221..3886062e4fe 100644 --- a/crates/fuel-core/src/database.rs +++ b/crates/fuel-core/src/database.rs @@ -78,7 +78,10 @@ use crate::state::{ HistoricalRocksDB, StateRewindPolicy, }, - rocks_db::RocksDb, + rocks_db::{ + DatabaseConfig, + RocksDb, + }, }; #[cfg(feature = "rocksdb")] use std::path::Path; @@ -201,16 +204,27 @@ where #[cfg(feature = "rocksdb")] pub fn open_rocksdb( path: &Path, - capacity: impl Into>, state_rewind_policy: StateRewindPolicy, - max_fds: i32, + database_config: DatabaseConfig, ) -> Result { + let db = + Self::open_as_historical_rocksdb(path, state_rewind_policy, database_config)?; + + Ok(Self::new(Arc::new(db))) + } + + #[cfg(feature = "rocksdb")] + pub fn open_as_historical_rocksdb( + path: &Path, + state_rewind_policy: StateRewindPolicy, + database_config: DatabaseConfig, + ) -> Result> { use anyhow::Context; + let db = HistoricalRocksDB::::default_open( path, - capacity.into(), state_rewind_policy, - max_fds, + database_config, ) .map_err(Into::::into) .with_context(|| { @@ -220,7 +234,7 @@ where ) })?; - Ok(Self::new(Arc::new(db))) + Ok(db) } /// Converts the regular database to an unchecked database. @@ -255,9 +269,14 @@ where } #[cfg(feature = "rocksdb")] - pub fn rocksdb_temp(rewind_policy: StateRewindPolicy) -> Result { - let db = RocksDb::>::default_open_temp(None)?; - let historical_db = HistoricalRocksDB::new(db, rewind_policy)?; + pub fn rocksdb_temp( + state_rewind_policy: StateRewindPolicy, + database_config: DatabaseConfig, + ) -> Result { + let db = RocksDb::>::default_open_temp_with_params( + database_config, + )?; + let historical_db = HistoricalRocksDB::new(db, state_rewind_policy)?; let data = Arc::new(historical_db); Ok(Self::from_storage(DataSource::new(data, Stage::default()))) } @@ -278,8 +297,15 @@ where } #[cfg(feature = "rocksdb")] { - Self::rocksdb_temp(StateRewindPolicy::NoRewind) - .expect("Failed to create a temporary database") + Self::rocksdb_temp( + StateRewindPolicy::NoRewind, + DatabaseConfig { + cache_capacity: None, + max_fds: 512, + columns_policy: Default::default(), + }, + ) + .expect("Failed to create a temporary database") } } } @@ -1107,9 +1133,8 @@ mod tests { let db = Database::::open_rocksdb( temp_dir.path(), - 1024 * 1024 * 1024, Default::default(), - 512, + DatabaseConfig::config_for_tests(), ) .unwrap(); // rocks db fails diff --git a/crates/fuel-core/src/graphql_api/indexation.rs b/crates/fuel-core/src/graphql_api/indexation.rs index 1b490c2f13d..d515504863c 100644 --- a/crates/fuel-core/src/graphql_api/indexation.rs +++ b/crates/fuel-core/src/graphql_api/indexation.rs @@ -238,6 +238,7 @@ mod tests { MessageBalances, }, }, + state::rocks_db::DatabaseConfig, }; impl PartialEq for IndexationError { @@ -352,9 +353,12 @@ mod tests { fn balances_enabled_flag_is_respected() { use tempfile::TempDir; let tmp_dir = TempDir::new().unwrap(); - let mut db: Database = - Database::open_rocksdb(tmp_dir.path(), None, Default::default(), 512) - .unwrap(); + let mut db: Database = Database::open_rocksdb( + tmp_dir.path(), + Default::default(), + DatabaseConfig::config_for_tests(), + ) + .unwrap(); let mut tx = db.write_transaction(); const BALANCES_ARE_DISABLED: bool = false; @@ -409,9 +413,12 @@ mod tests { fn coins() { use tempfile::TempDir; let tmp_dir = TempDir::new().unwrap(); - let mut db: Database = - Database::open_rocksdb(tmp_dir.path(), None, Default::default(), 512) - .unwrap(); + let mut db: Database = Database::open_rocksdb( + tmp_dir.path(), + Default::default(), + DatabaseConfig::config_for_tests(), + ) + .unwrap(); let mut tx = db.write_transaction(); const BALANCES_ARE_ENABLED: bool = true; @@ -481,9 +488,12 @@ mod tests { fn messages() { use tempfile::TempDir; let tmp_dir = TempDir::new().unwrap(); - let mut db: Database = - Database::open_rocksdb(tmp_dir.path(), None, Default::default(), 512) - .unwrap(); + let mut db: Database = Database::open_rocksdb( + tmp_dir.path(), + Default::default(), + DatabaseConfig::config_for_tests(), + ) + .unwrap(); let mut tx = db.write_transaction(); const BALANCES_ARE_ENABLED: bool = true; @@ -589,9 +599,12 @@ mod tests { fn coin_balance_overflow_does_not_error() { use tempfile::TempDir; let tmp_dir = TempDir::new().unwrap(); - let mut db: Database = - Database::open_rocksdb(tmp_dir.path(), None, Default::default(), 512) - .unwrap(); + let mut db: Database = Database::open_rocksdb( + tmp_dir.path(), + Default::default(), + DatabaseConfig::config_for_tests(), + ) + .unwrap(); let mut tx = db.write_transaction(); const BALANCES_ARE_ENABLED: bool = true; @@ -623,9 +636,12 @@ mod tests { fn message_balance_overflow_does_not_error() { use tempfile::TempDir; let tmp_dir = TempDir::new().unwrap(); - let mut db: Database = - Database::open_rocksdb(tmp_dir.path(), None, Default::default(), 512) - .unwrap(); + let mut db: Database = Database::open_rocksdb( + tmp_dir.path(), + Default::default(), + DatabaseConfig::config_for_tests(), + ) + .unwrap(); let mut tx = db.write_transaction(); const BALANCES_ARE_ENABLED: bool = true; @@ -661,9 +677,12 @@ mod tests { fn coin_balance_underflow_causes_error() { use tempfile::TempDir; let tmp_dir = TempDir::new().unwrap(); - let mut db: Database = - Database::open_rocksdb(tmp_dir.path(), None, Default::default(), 512) - .unwrap(); + let mut db: Database = Database::open_rocksdb( + tmp_dir.path(), + Default::default(), + DatabaseConfig::config_for_tests(), + ) + .unwrap(); let mut tx = db.write_transaction(); const BALANCES_ARE_ENABLED: bool = true; diff --git a/crates/fuel-core/src/p2p_test_helpers.rs b/crates/fuel-core/src/p2p_test_helpers.rs index 3a5f6e78009..e8e184d1877 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 1f751f1de5e..535ebbba86c 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..cb6c5301115 100644 --- a/crates/fuel-core/src/service/adapters.rs +++ b/crates/fuel-core/src/service/adapters.rs @@ -1,13 +1,25 @@ +use crate::{ + database::{ + database_description::relayer::Relayer, + Database, + }, + fuel_core_graphql_api::ports::GasPriceEstimate, + service::{ + sub_services::{ + BlockProducerService, + TxPoolSharedState, + }, + vm_pool::MemoryPool, + }, +}; use fuel_core_consensus_module::{ block_verifier::Verifier, RelayerConsensusConfig, }; use fuel_core_executor::executor::OnceTransactionsSource; +use fuel_core_gas_price_service::v1::service::LatestGasPrice; 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,9 +28,13 @@ 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, + fuel_types::BlockHeight, services::{ block_importer::SharedImportResult, block_producer::Components, @@ -27,25 +43,12 @@ use fuel_core_types::{ UncommittedResult, }, }, + signer::SignMode, tai64::Tai64, }; use fuel_core_upgradable_executor::executor::Executor; use std::sync::Arc; -use crate::{ - database::{ - database_description::relayer::Relayer, - Database, - }, - service::{ - sub_services::{ - BlockProducerService, - TxPoolSharedState, - }, - vm_pool::MemoryPool, - }, -}; - pub mod block_importer; pub mod consensus_module; pub mod consensus_parameters_provider; @@ -59,6 +62,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; @@ -85,6 +90,162 @@ impl StaticGasPrice { } } +#[cfg(test)] +mod arc_gas_price_estimate_tests { + #![allow(non_snake_case)] + + use super::*; + use proptest::proptest; + + async fn _worst_case__correctly_calculates_value( + gas_price: u64, + starting_height: u32, + block_horizon: u32, + percentage: u16, + ) { + // given + let subject = ArcGasPriceEstimate::new(starting_height, gas_price, percentage); + + // when + let target_height = starting_height.saturating_add(block_horizon); + let estimated = subject + .worst_case_gas_price(target_height.into()) + .await + .unwrap(); + + // then + let mut actual = gas_price; + + for _ in 0..block_horizon { + let change_amount = + actual.saturating_mul(percentage as u64).saturating_div(100); + actual = actual.saturating_add(change_amount); + } + + assert!(estimated >= actual); + } + + proptest! { + #[test] + fn worst_case_gas_price__correctly_calculates_value( + gas_price: u64, + starting_height: u32, + block_horizon in 0..10_000u32, + percentage: u16, + ) { + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(_worst_case__correctly_calculates_value( + gas_price, + starting_height, + block_horizon, + percentage, + )); + } + } + + proptest! { + #[test] + fn worst_case_gas_price__never_overflows( + gas_price: u64, + starting_height: u32, + block_horizon in 0..10_000u32, + percentage: u16 + ) { + let rt = tokio::runtime::Runtime::new().unwrap(); + + // given + let subject = ArcGasPriceEstimate::new(starting_height, gas_price, percentage); + + // when + let target_height = starting_height.saturating_add(block_horizon); + + let _ = rt.block_on(subject.worst_case_gas_price(target_height.into())); + + // then + // doesn't panic with an overflow + } + } +} + +/// Allows communication from other service with more recent gas price data +/// `Height` refers to the height of the block at which the gas price was last updated +/// `GasPrice` refers to the gas price at the last updated block +#[allow(dead_code)] +pub struct ArcGasPriceEstimate { + /// Shared state of latest gas price data + latest_gas_price: LatestGasPrice, + /// The max percentage the gas price can increase per block + percentage: u16, +} + +impl ArcGasPriceEstimate { + #[cfg(test)] + pub fn new(height: Height, price: GasPrice, percentage: u16) -> Self { + let latest_gas_price = LatestGasPrice::new(height, price); + Self { + latest_gas_price, + percentage, + } + } + + pub fn new_from_inner( + inner: LatestGasPrice, + percentage: u16, + ) -> Self { + Self { + latest_gas_price: inner, + percentage, + } + } +} + +impl ArcGasPriceEstimate { + fn get_height_and_gas_price(&self) -> (Height, GasPrice) { + self.latest_gas_price.get() + } +} + +#[async_trait::async_trait] +impl GasPriceEstimate for ArcGasPriceEstimate { + async fn worst_case_gas_price(&self, height: BlockHeight) -> Option { + let (best_height, best_gas_price) = self.get_height_and_gas_price(); + let percentage = self.percentage; + + let worst = cumulative_percentage_change( + best_gas_price, + best_height, + percentage as u64, + height.into(), + ); + Some(worst) + } +} + +#[allow(clippy::cast_possible_truncation)] +pub(crate) fn cumulative_percentage_change( + start_gas_price: u64, + best_height: u32, + percentage: u64, + target_height: u32, +) -> u64 { + let blocks = target_height.saturating_sub(best_height) as f64; + let percentage_as_decimal = percentage as f64 / 100.0; + let multiple = (1.0f64 + percentage_as_decimal).powf(blocks); + let mut approx = start_gas_price as f64 * multiple; + // Account for rounding errors and take a slightly higher value + // Around the `ROUNDING_ERROR_CUTOFF` the rounding errors will cause the estimate to be too low. + // We increase by `ROUNDING_ERROR_COMPENSATION` to account for this. + // This is an unlikely situation in practice, but we want to guarantee that the actual + // gas price is always equal or less than the estimate given here + const ROUNDING_ERROR_CUTOFF: f64 = 16948547188989277.0; + if approx > ROUNDING_ERROR_CUTOFF { + const ROUNDING_ERROR_COMPENSATION: f64 = 2000.0; + approx += ROUNDING_ERROR_COMPENSATION; + } + // `f64` over `u64::MAX` are cast to `u64::MAX` + approx.ceil() as u64 +} + #[derive(Clone)] pub struct PoAAdapter { shared_state: Option, @@ -226,7 +387,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 0e093c2b8b6..02c463334f1 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, @@ -104,6 +108,8 @@ impl Config { #[cfg(feature = "test-helpers")] pub fn local_node_with_reader(snapshot_reader: SnapshotReader) -> Self { + use crate::state::rocks_db::DatabaseConfig; + let block_importer = fuel_core_importer::Config::new(false); let latest_block = snapshot_reader.last_block_config(); // In tests, we always want to use the native executor as a default configuration. @@ -117,7 +123,12 @@ impl Config { let combined_db_config = CombinedDatabaseConfig { // Set the cache for tests = 10MB - max_database_cache_size: 10 * 1024 * 1024, + #[cfg(feature = "rocksdb")] + database_config: DatabaseConfig { + cache_capacity: Some(10 * 1024 * 1024), + columns_policy: Default::default(), + max_fds: 512, + }, database_path: Default::default(), #[cfg(feature = "rocksdb")] database_type: DbType::RocksDb, @@ -126,8 +137,6 @@ impl Config { #[cfg(feature = "rocksdb")] state_rewind_policy: crate::state::historical_rocksdb::StateRewindPolicy::RewindFullRange, - #[cfg(feature = "rocksdb")] - max_fds: 512, }; let starting_gas_price = 0; let gas_price_change_percent = 0; @@ -182,6 +191,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(), )), @@ -234,7 +245,7 @@ pub struct VMConfig { } #[derive( - Clone, Debug, Display, Eq, PartialEq, EnumString, EnumVariantNames, ValueEnum, + Clone, Copy, Debug, Display, Eq, PartialEq, EnumString, EnumVariantNames, ValueEnum, )] #[strum(serialize_all = "kebab_case")] pub enum DbType { diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 03003319235..9bd53e072a8 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/fuel-core/src/state/historical_rocksdb.rs b/crates/fuel-core/src/state/historical_rocksdb.rs index 059fb35f12b..6422e2a1c47 100644 --- a/crates/fuel-core/src/state/historical_rocksdb.rs +++ b/crates/fuel-core/src/state/historical_rocksdb.rs @@ -64,6 +64,8 @@ use std::{ path::Path, }; +use super::rocks_db::DatabaseConfig; + pub mod description; pub mod modifications_history; pub mod view_at_height; @@ -104,14 +106,16 @@ where }) } + pub fn inner(&self) -> &RocksDb> { + &self.db + } + pub fn default_open>( path: P, - capacity: Option, state_rewind_policy: StateRewindPolicy, - max_fds: i32, + database_config: DatabaseConfig, ) -> DatabaseResult { - let db = - RocksDb::>::default_open(path, capacity, max_fds)?; + let db = RocksDb::>::default_open(path, database_config)?; Ok(Self { state_rewind_policy, db, diff --git a/crates/fuel-core/src/state/rocks_db.rs b/crates/fuel-core/src/state/rocks_db.rs index 3df5c7127e5..3f43333c152 100644 --- a/crates/fuel-core/src/state/rocks_db.rs +++ b/crates/fuel-core/src/state/rocks_db.rs @@ -96,10 +96,40 @@ impl Drop for DropResources { } } +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)] +/// Defined behaviour for opening the columns of the database. +pub enum ColumnsPolicy { + #[cfg_attr(not(feature = "rocksdb-production"), default)] + // Open a new column only when a database interaction is done with it. + Lazy, + #[cfg_attr(feature = "rocksdb-production", default)] + // Open all columns on creation on the service. + OnCreation, +} + +/// Configuration to create a database +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct DatabaseConfig { + pub cache_capacity: Option, + pub max_fds: i32, + pub columns_policy: ColumnsPolicy, +} + +#[cfg(feature = "test-helpers")] +impl DatabaseConfig { + pub fn config_for_tests() -> Self { + Self { + cache_capacity: None, + max_fds: 512, + columns_policy: ColumnsPolicy::Lazy, + } + } +} + pub struct RocksDb { read_options: ReadOptions, db: Arc, - create_family: Arc>>, + create_family: Option>>>, snapshot: Option>, metrics: Arc, // used for RAII @@ -125,14 +155,28 @@ impl RocksDb where Description: DatabaseDescription, { + /// Allows consumers to get the inner db handle + pub fn inner(&self) -> &DB { + &self.db + } + pub fn default_open_temp(capacity: Option) -> DatabaseResult { + Self::default_open_temp_with_params(DatabaseConfig { + cache_capacity: capacity, + max_fds: 512, + columns_policy: Default::default(), + }) + } + + pub fn default_open_temp_with_params( + database_config: DatabaseConfig, + ) -> DatabaseResult { let tmp_dir = TempDir::new().unwrap(); let path = tmp_dir.path(); let result = Self::open( path, enum_iterator::all::().collect::>(), - capacity, - 512, + database_config, ); let mut db = result?; @@ -151,14 +195,12 @@ where pub fn default_open>( path: P, - capacity: Option, - max_fds: i32, + database_config: DatabaseConfig, ) -> DatabaseResult { Self::open( path, enum_iterator::all::().collect::>(), - capacity, - max_fds, + database_config, ) } @@ -172,18 +214,16 @@ where pub fn open>( path: P, columns: Vec, - capacity: Option, - max_fds: i32, + database_config: DatabaseConfig, ) -> DatabaseResult { - Self::open_with(DB::open_cf_descriptors, path, columns, capacity, max_fds) + Self::open_with(DB::open_cf_descriptors, path, columns, database_config) } pub fn open_read_only>( path: P, columns: Vec, - capacity: Option, error_if_log_file_exist: bool, - max_fds: i32, + database_config: DatabaseConfig, ) -> DatabaseResult { Self::open_with( |options, primary_path, cfs| { @@ -196,8 +236,7 @@ where }, path, columns, - capacity, - max_fds, + database_config, ) } @@ -205,8 +244,7 @@ where path: PrimaryPath, secondary_path: SecondaryPath, columns: Vec, - capacity: Option, - max_fds: i32, + database_config: DatabaseConfig, ) -> DatabaseResult where PrimaryPath: AsRef, @@ -223,8 +261,7 @@ where }, path, columns, - capacity, - max_fds, + database_config, ) } @@ -232,8 +269,7 @@ where opener: F, path: P, columns: Vec, - capacity: Option, - max_fds: i32, + database_config: DatabaseConfig, ) -> DatabaseResult where F: Fn( @@ -257,7 +293,7 @@ where // See https://github.com/facebook/rocksdb/blob/a1523efcdf2f0e8133b9a9f6e170a0dad49f928f/include/rocksdb/table.h#L246-L271 for details on what the format versions are/do. block_opts.set_format_version(5); - if let Some(capacity) = capacity { + if let Some(capacity) = database_config.cache_capacity { // Set cache size 1/3 of the capacity as recommended by // https://github.com/facebook/rocksdb/wiki/Setup-Options-and-Basic-Tuning#block-cache-size let block_cache_size = capacity / 3; @@ -281,7 +317,7 @@ where let cpu_number = i32::try_from(num_cpus::get()).expect("The number of CPU can't exceed `i32`"); opts.increase_parallelism(cmp::max(1, cpu_number / 2)); - if let Some(capacity) = capacity { + if let Some(capacity) = database_config.cache_capacity { // Set cache size 1/3 of the capacity. Another 1/3 is // used by block cache and the last 1 / 3 remains for other purposes: // @@ -292,7 +328,7 @@ where } opts.set_max_background_jobs(6); opts.set_bytes_per_sync(1048576); - opts.set_max_open_files(max_fds); + opts.set_max_open_files(database_config.max_fds); let existing_column_families = DB::list_cf(&opts, &path).unwrap_or_default(); @@ -308,7 +344,10 @@ where } } - if cf_descriptors_to_open.is_empty() { + if database_config.columns_policy == ColumnsPolicy::OnCreation + || (database_config.columns_policy == ColumnsPolicy::Lazy + && cf_descriptors_to_open.is_empty()) + { opts.create_if_missing(true); } @@ -349,8 +388,17 @@ where } .map_err(|e| DatabaseError::Other(e.into()))?; + let create_family = match database_config.columns_policy { + ColumnsPolicy::OnCreation => { + for (name, opt) in cf_descriptors_to_create { + db.create_cf(name, &opt) + .map_err(|e| DatabaseError::Other(e.into()))?; + } + None + } + ColumnsPolicy::Lazy => Some(Arc::new(Mutex::new(cf_descriptors_to_create))), + }; let db = Arc::new(db); - let create_family = Arc::new(Mutex::new(cf_descriptors_to_create)); let rocks_db = RocksDb { read_options: Self::generate_read_options(&None), @@ -424,26 +472,29 @@ where match family { None => { - let mut lock = self - .create_family - .lock() - .expect("The create family lock should be available"); - - let name = Self::col_name(column); - let Some(family) = lock.remove(&name) else { - return self - .db - .cf_handle(&Self::col_name(column)) - .expect("No column family found"); - }; - - self.db - .create_cf(&name, &family) - .expect("Couldn't create column family"); - - let family = self.db.cf_handle(&name).expect("invalid column state"); - - family + if let Some(create_family) = &self.create_family { + let mut lock = create_family + .lock() + .expect("The create family lock should be available"); + + let name = Self::col_name(column); + let Some(family) = lock.remove(&name) else { + return self + .db + .cf_handle(&Self::col_name(column)) + .expect("No column family found"); + }; + + self.db + .create_cf(&name, &family) + .expect("Couldn't create column family"); + + let family = self.db.cf_handle(&name).expect("invalid column state"); + + family + } else { + panic!("Columns in the DB should have been created on DB opening"); + } } Some(family) => family, } @@ -926,7 +977,8 @@ mod tests { fn create_db() -> (RocksDb, TempDir) { let tmp_dir = TempDir::new().unwrap(); ( - RocksDb::default_open(tmp_dir.path(), None, 512).unwrap(), + RocksDb::default_open(tmp_dir.path(), DatabaseConfig::config_for_tests()) + .unwrap(), tmp_dir, ) } @@ -938,17 +990,24 @@ mod tests { // Given let old_columns = vec![Column::Coins, Column::Messages, Column::UploadedBytecodes]; - let database_with_old_columns = - RocksDb::::open(tmp_dir.path(), old_columns.clone(), None, 512) - .expect("Failed to open database with old columns"); + let database_with_old_columns = RocksDb::::open( + tmp_dir.path(), + old_columns.clone(), + DatabaseConfig::config_for_tests(), + ) + .expect("Failed to open database with old columns"); drop(database_with_old_columns); // When let mut new_columns = old_columns; new_columns.push(Column::ContractsAssets); new_columns.push(Column::Metadata); - let database_with_new_columns = - RocksDb::::open(tmp_dir.path(), new_columns, None, 512).map(|_| ()); + let database_with_new_columns = RocksDb::::open( + tmp_dir.path(), + new_columns, + DatabaseConfig::config_for_tests(), + ) + .map(|_| ()); // Then assert_eq!(Ok(()), database_with_new_columns); @@ -1117,7 +1176,11 @@ mod tests { // When let columns = enum_iterator::all::<::Column>() .collect::>(); - let result = RocksDb::::open(tmp_dir.path(), columns, None, 512); + let result = RocksDb::::open( + tmp_dir.path(), + columns, + DatabaseConfig::config_for_tests(), + ); // Then assert!(result.is_err()); @@ -1134,9 +1197,8 @@ mod tests { let result = RocksDb::::open_read_only( tmp_dir.path(), old_columns.clone(), - None, false, - 512, + DatabaseConfig::config_for_tests(), ) .map(|_| ()); @@ -1157,8 +1219,7 @@ mod tests { tmp_dir.path(), secondary_temp.path(), old_columns.clone(), - None, - 512, + DatabaseConfig::config_for_tests(), ) .map(|_| ()); @@ -1250,8 +1311,11 @@ mod tests { enum_iterator::all::<::Column>() .skip(1) .collect::>(); - let open_with_part_of_columns = - RocksDb::::open(tmp_dir.path(), part_of_columns, None, 512); + let open_with_part_of_columns = RocksDb::::open( + tmp_dir.path(), + part_of_columns, + DatabaseConfig::config_for_tests(), + ); // Then let _ = open_with_part_of_columns diff --git a/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/.gitignore b/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/.gitignore new file mode 100644 index 00000000000..a3253ee3df3 --- /dev/null +++ b/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/.gitignore @@ -0,0 +1,2 @@ +Cargo.lock +data diff --git a/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/Cargo.toml b/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/Cargo.toml new file mode 100644 index 00000000000..88119f5f5f5 --- /dev/null +++ b/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "fuel-gas-price-data-fetcher" +version = "0.0.1" +edition = "2021" +publish = false + +[workspace] + +[dependencies] +anyhow = "1.0.86" +clap = { version = "4.5.16", features = ["derive"] } +csv = "1.3.0" +fuel-gas-price-algorithm = { path = ".." } +futures = "0.3.30" +plotters = "0.3.5" +rand = "0.8.5" +rand_distr = "0.4.3" +serde = { version = "1.0.209", features = ["derive"] } +tokio = { version = "1.40.0", features = ["macros", "rt", "rt-multi-thread"] } +reqwest = { version = "0.12.11", features = ["json"] } +serde_json = { version = "1.0.134" } +fuel-core-client = { version = "0.40.2" } # locked to whatever version you're supposed to be fetching data from +fuel-core-types = { version = "0.40.2" } +postcard = { version = "1.0" } +tracing = { version = "0.1.41" } +tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } + +async-trait = "0.1" +cynic = { version = "2.2", features = ["http-reqwest"] } +itertools = { version = "0.13" } + +[build-dependencies] +fuel-core-client = { version = "0.40.2" } diff --git a/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/README.md b/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/README.md new file mode 100644 index 00000000000..935451cb9f9 --- /dev/null +++ b/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/README.md @@ -0,0 +1,10 @@ +# Gas Price Analysis Data Fetcher + +Binary allowing retrieveing the L1 blob and L2 block data needed by the gas price simulation binary. +It requires being able to connect to the block committer, and either being able to connect to a sentry node or access to the database of a mainnet synched node. + +## Usage + +``` +cargo run -- --block-committer-endpoint ${BLOCK_COMMITTER_URL} --block-range 0 1000 --db-path ${FUEL_MAINNET_DB_PATH} +``` diff --git a/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/build.rs b/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/build.rs new file mode 100644 index 00000000000..f215fc0766e --- /dev/null +++ b/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/build.rs @@ -0,0 +1,14 @@ +#![deny(clippy::arithmetic_side_effects)] +#![deny(clippy::cast_possible_truncation)] +#![deny(unused_crate_dependencies)] +#![deny(warnings)] + +use std::fs; + +fn main() { + fs::create_dir_all("target").expect("Unable to create target directory"); + fs::write("target/schema.sdl", fuel_core_client::SCHEMA_SDL) + .expect("Unable to write schema file"); + + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/src/client_ext.rs b/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/src/client_ext.rs new file mode 100644 index 00000000000..dc8aa8ee78f --- /dev/null +++ b/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/src/client_ext.rs @@ -0,0 +1,236 @@ +#![deny(clippy::arithmetic_side_effects)] +#![deny(clippy::cast_possible_truncation)] +#![deny(warnings)] + +// This was copied from https://github.com/FuelLabs/fuel-core-client-ext/blob/b792ef76cbcf82eda45a944b15433682fe094fee/src/lib.rs + +use cynic::QueryBuilder; +use fuel_core_client::{ + client, + client::{ + pagination::{ + PaginatedResult, + PaginationRequest, + }, + schema::{ + block::{ + BlockByHeightArgs, + Consensus, + Header, + }, + schema, + tx::OpaqueTransactionWithStatus, + ConnectionArgs, + PageInfo, + }, + types::{ + TransactionResponse, + TransactionStatus, + }, + FuelClient, + }, +}; +use fuel_core_types::{ + blockchain::{ + self, + block::Block, + header::{ + ApplicationHeader, + ConsensusHeader, + PartialBlockHeader, + }, + SealedBlock, + }, + fuel_tx::{ + Bytes32, + Receipt, + }, +}; +use itertools::Itertools; + +#[derive(cynic::QueryFragment, Debug)] +#[cynic( + schema_path = "./target/schema.sdl", + graphql_type = "Query", + variables = "ConnectionArgs" +)] +pub struct FullBlocksQuery { + #[arguments(after: $after, before: $before, first: $first, last: $last)] + pub blocks: FullBlockConnection, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(schema_path = "./target/schema.sdl", graphql_type = "BlockConnection")] +pub struct FullBlockConnection { + pub edges: Vec, + pub page_info: PageInfo, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(schema_path = "./target/schema.sdl", graphql_type = "BlockEdge")] +pub struct FullBlockEdge { + pub cursor: String, + pub node: FullBlock, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic( + schema_path = "./target/schema.sdl", + graphql_type = "Query", + variables = "BlockByHeightArgs" +)] +pub struct FullBlockByHeightQuery { + #[arguments(height: $height)] + pub block: Option, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(schema_path = "./target/schema.sdl", graphql_type = "Block")] +pub struct FullBlock { + pub header: Header, + pub consensus: Consensus, + pub transactions: Vec, +} + +impl From for PaginatedResult { + fn from(conn: FullBlockConnection) -> Self { + PaginatedResult { + cursor: conn.page_info.end_cursor, + has_next_page: conn.page_info.has_next_page, + has_previous_page: conn.page_info.has_previous_page, + results: conn.edges.into_iter().map(|e| e.node).collect(), + } + } +} + +#[async_trait::async_trait] +pub trait ClientExt { + async fn full_blocks( + &self, + request: PaginationRequest, + ) -> std::io::Result>; +} + +#[async_trait::async_trait] +impl ClientExt for FuelClient { + async fn full_blocks( + &self, + request: PaginationRequest, + ) -> std::io::Result> { + let query = FullBlocksQuery::build(request.into()); + let blocks = self.query(query).await?.blocks.into(); + Ok(blocks) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SealedBlockWithMetadata { + pub block: SealedBlock, + pub receipts: Vec>>, +} + +impl TryFrom for SealedBlockWithMetadata { + type Error = anyhow::Error; + + fn try_from(full_block: FullBlock) -> Result { + let transactions: Vec = full_block + .transactions + .into_iter() + .map(TryInto::try_into) + .try_collect()?; + + let receipts = transactions + .iter() + .map(|tx| &tx.status) + .map(|status| match status { + TransactionStatus::Success { receipts, .. } => Some(receipts.clone()), + _ => None, + }) + .collect_vec(); + + let messages = receipts + .iter() + .flatten() + .flat_map(|receipt| receipt.iter().filter_map(|r| r.message_id())) + .collect_vec(); + + let transactions = transactions + .into_iter() + .map(|tx| tx.transaction) + .collect_vec(); + + let partial_header = PartialBlockHeader { + application: ApplicationHeader { + da_height: full_block.header.da_height.0.into(), + consensus_parameters_version: full_block + .header + .consensus_parameters_version + .into(), + state_transition_bytecode_version: full_block + .header + .state_transition_bytecode_version + .into(), + generated: Default::default(), + }, + consensus: ConsensusHeader { + prev_root: full_block.header.prev_root.into(), + height: full_block.header.height.into(), + time: full_block.header.time.into(), + generated: Default::default(), + }, + }; + + let header = partial_header + .generate( + &transactions, + &messages, + full_block.header.event_inbox_root.into(), + ) + .map_err(|e| anyhow::anyhow!(e))?; + + let actual_id: Bytes32 = full_block.header.id.into(); + let expected_id: Bytes32 = header.id().into(); + if expected_id != actual_id { + return Err(anyhow::anyhow!("Header id mismatch")); + } + + let block = Block::try_from_executed(header, transactions) + .ok_or(anyhow::anyhow!("Failed to create block from transactions"))?; + + let consensus: client::types::Consensus = full_block.consensus.into(); + + let consensus = match consensus { + client::types::Consensus::Genesis(genesis) => { + use blockchain::consensus as core_consensus; + core_consensus::Consensus::Genesis(core_consensus::Genesis { + chain_config_hash: genesis.chain_config_hash, + coins_root: genesis.coins_root, + contracts_root: genesis.contracts_root, + messages_root: genesis.messages_root, + transactions_root: genesis.transactions_root, + }) + } + client::types::Consensus::PoAConsensus(poa) => { + use blockchain::consensus as core_consensus; + core_consensus::Consensus::PoA(core_consensus::poa::PoAConsensus { + signature: poa.signature, + }) + } + client::types::Consensus::Unknown => { + return Err(anyhow::anyhow!("Unknown consensus type")); + } + }; + + let sealed = SealedBlock { + entity: block, + consensus, + }; + + let sealed = SealedBlockWithMetadata { + block: sealed, + receipts, + }; + + Ok(sealed) + } +} diff --git a/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/src/layer1.rs b/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/src/layer1.rs new file mode 100644 index 00000000000..cbb618c3281 --- /dev/null +++ b/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/src/layer1.rs @@ -0,0 +1,94 @@ +use std::ops::Range; + +use fuel_core_types::fuel_types::BlockHeight; +use reqwest::{ + header::{ + HeaderMap, + CONTENT_TYPE, + }, + Url, +}; + +use crate::types::BlockCommitterCosts; + +pub struct BlockCommitterDataFetcher { + client: reqwest::Client, + endpoint: Url, + num_responses: usize, +} + +impl BlockCommitterDataFetcher { + pub fn new(endpoint: Url, num_responses: usize) -> anyhow::Result { + let mut content_type_json_header = HeaderMap::new(); + content_type_json_header.insert( + CONTENT_TYPE, + "application/json" + .parse() + .expect("Content-Type header value is valid"), + ); + let client = reqwest::ClientBuilder::new() + .default_headers(content_type_json_header) + .build()?; + Ok(Self { + client, + endpoint, + num_responses, + }) + } + + // TODO: Better error type; qed + async fn fetch_blob_data( + &self, + from_height: u64, + ) -> anyhow::Result> { + let query = self.endpoint.join("v1/costs")?.join(&format!( + "?variant=specific&value={}&limit={}", + from_height, self.num_responses + ))?; + + tracing::debug!("Query: {}", query.as_str()); + + let response = self.client.get(query).send().await?; + if !response.status().is_success() { + return Err(anyhow::anyhow!( + "Failed to fetch data from block committer: {}", + response.status(), + ) + .into()); + } + + let block_committer_costs = response.json::>().await?; + Ok(block_committer_costs) + } + + pub async fn fetch_l1_block_costs( + &self, + blocks: Range, + ) -> Result, anyhow::Error> { + let mut block_costs = vec![]; + let mut current_block_height = blocks.start; + while current_block_height < blocks.end { + let Ok(mut costs) = + self.fetch_blob_data((*current_block_height).into()).await + else { + Err(anyhow::anyhow!( + "Could not fetch data for block {}", + current_block_height + ))? + }; + + if costs.is_empty() { + // Might be that the block committer doesn't have data for the block, in which case we return prematurely. + // If this happens, we should increase the value of results returned by the block committer in the query. + break; + } + + // Block committer will return the data for the block in the next batch, hence we don't increment the height of the last + // block. + current_block_height = (*costs.last().unwrap().end_height).into(); + block_costs.append(&mut costs); + } + + Ok(block_costs) + } +} diff --git a/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/src/layer2.rs b/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/src/layer2.rs new file mode 100644 index 00000000000..754ca64af38 --- /dev/null +++ b/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/src/layer2.rs @@ -0,0 +1,214 @@ +use std::{ + collections::{ + HashMap, + HashSet, + }, + ops::Range, +}; + +use crate::types::{ + BytesSize, + GasUnits, + Layer2BlockData, +}; + +use super::client_ext::{ + ClientExt, + SealedBlockWithMetadata, +}; +use fuel_core_client::client::{ + pagination::{ + PageDirection, + PaginationRequest, + }, + FuelClient, +}; +use fuel_core_types::{ + fuel_tx::{ + Chargeable, + ConsensusParameters, + }, + fuel_types::BlockHeight, +}; +use itertools::Itertools; + +#[derive(Clone)] +pub struct BlockFetcher { + client: FuelClient, +} + +impl BlockFetcher { + pub fn new(url: impl AsRef) -> anyhow::Result { + let client = FuelClient::new(url)?; + Ok(Self { client }) + } +} + +impl BlockFetcher { + pub async fn blocks_for( + &self, + range: Range, + ) -> anyhow::Result> { + if range.is_empty() { + return Ok(vec![]); + } + + let start = range.start.saturating_sub(1); + let size = i32::try_from(range.len()).expect("Should be a valid i32"); + + let request = PaginationRequest { + cursor: Some(start.to_string()), + results: size, + direction: PageDirection::Forward, + }; + let response = self.client.full_blocks(request).await?; + let blocks = response + .results + .into_iter() + .map(TryInto::try_into) + .try_collect()?; + Ok(blocks) + } + + pub async fn get_l2_block_data( + &self, + range: Range, + num_results: usize, + ) -> anyhow::Result> { + let range: Range = range.start.try_into()?..range.end.try_into()?; + let mut ranges: Vec> = + Vec::with_capacity(range.len().saturating_div(num_results)); + + for start in range.clone().step_by(num_results) { + let end = start.saturating_add(num_results as u32).min(range.end); + ranges.push(start..end); + } + + let mut blocks = Vec::with_capacity(range.len()); + for range in ranges { + tracing::info!("Fetching blocks for range {:?}", range); + let blocks_for_range = self.blocks_for(range).await?; + blocks.extend(blocks_for_range); + } + + let consensus_parameters_versions = blocks + .iter() + .map(|b| b.block.entity.header().consensus_parameters_version) + .collect::>(); + + tracing::debug!( + "Consensus parameter versions: {:?}", + consensus_parameters_versions + ); + + let mut consensus_parameters: HashMap = HashMap::new(); + for consensus_parameters_version in consensus_parameters_versions { + let cp = self + .client + .consensus_parameters(consensus_parameters_version.try_into()?) + .await?; + + if let Some(cp) = cp { + tracing::debug!( + "Found consensus parameters for version {}: {:?}", + consensus_parameters_version, + cp + ); + consensus_parameters.insert(consensus_parameters_version, cp); + } + } + + let mut block_data = HashMap::with_capacity(range.len()); + + for b in blocks { + let block_height = height(&b); + let consensus_parameters = consensus_parameters + .get(&b.block.entity.header().consensus_parameters_version) + .ok_or(anyhow::anyhow!( + "Consensus parameters not found for block {}", + block_height + ))?; + let block_size = + BytesSize(postcard::to_allocvec(&b.block)?.len().try_into()?); + + let gas_consumed = total_gas_consumed(&b, consensus_parameters)?; + let capacity = GasUnits(consensus_parameters.block_gas_limit()); + let bytes_capacity = + BytesSize(consensus_parameters.block_transaction_size_limit()); + let transactions_count = b.block.entity.transactions().len(); + + block_data.insert( + block_height, + Layer2BlockData { + block_height, + block_size, + gas_consumed, + capacity, + bytes_capacity, + transactions_count, + }, + ); + } + + Ok(block_data) + } +} + +fn height(block: &SealedBlockWithMetadata) -> BlockHeight { + *block.block.entity.header().height() +} + +fn total_gas_consumed( + block: &SealedBlockWithMetadata, + consensus_parameters: &ConsensusParameters, +) -> Result { + let min_gas: u64 = block + .block + .entity + .transactions() + .iter() + .filter_map(|tx| match tx { + fuel_core_types::fuel_tx::Transaction::Script(chargeable_transaction) => { + Some(chargeable_transaction.min_gas( + consensus_parameters.gas_costs(), + consensus_parameters.fee_params(), + )) + } + fuel_core_types::fuel_tx::Transaction::Create(chargeable_transaction) => { + Some(chargeable_transaction.min_gas( + consensus_parameters.gas_costs(), + consensus_parameters.fee_params(), + )) + } + fuel_core_types::fuel_tx::Transaction::Mint(_mint) => None, + fuel_core_types::fuel_tx::Transaction::Upgrade(chargeable_transaction) => { + Some(chargeable_transaction.min_gas( + consensus_parameters.gas_costs(), + consensus_parameters.fee_params(), + )) + } + fuel_core_types::fuel_tx::Transaction::Upload(chargeable_transaction) => { + Some(chargeable_transaction.min_gas( + consensus_parameters.gas_costs(), + consensus_parameters.fee_params(), + )) + } + fuel_core_types::fuel_tx::Transaction::Blob(chargeable_transaction) => { + Some(chargeable_transaction.min_gas( + consensus_parameters.gas_costs(), + consensus_parameters.fee_params(), + )) + } + }) + .sum(); + let gas_consumed = block + .receipts + .iter() + .flatten() + .map(|r| r.iter().filter_map(|r| r.gas_used()).sum::()) + .sum(); + let total_gas = min_gas + .checked_add(gas_consumed) + .ok_or(anyhow::anyhow!("Gas overflow"))?; + Ok(GasUnits(total_gas)) +} diff --git a/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/src/main.rs b/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/src/main.rs new file mode 100644 index 00000000000..e8abaeefd28 --- /dev/null +++ b/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/src/main.rs @@ -0,0 +1,170 @@ +use std::{ + env, + path::PathBuf, +}; + +use fuel_core_types::fuel_types::BlockHeight; +use layer1::BlockCommitterDataFetcher; +use reqwest::Url; +use tracing_subscriber::EnvFilter; +use types::Layer2BlockData; + +use clap::{ + Args, + Parser, +}; + +use tracing_subscriber::prelude::*; + +pub mod client_ext; +mod layer1; +mod layer2; +mod summary; +mod types; + +// const SENTRY_NODE_GRAPHQL_RESULTS_PER_QUERY: usize = 5_000; +const SENTRY_NODE_GRAPHQL_RESULTS_PER_QUERY: usize = 40; + +#[derive(Parser)] +#[command(version, about, long_about = None)] +struct Arg { + #[clap(flatten)] + l2_block_data_source: L2BlockDataSource, + #[arg(short, long)] + /// Endpoint of the block committer to fetch L1 blobs data + block_committer_endpoint: Url, + #[arg( + short = 'r', + long, + num_args = 2..=2, + required = true + )] + /// Range of blocks to fetch the data for. Lower bound included, Upper bound excluded. + block_range: Vec, + + #[arg(required = true)] + /// The output CSV file where Block and Blob data will be written to + output_file: PathBuf, +} + +#[derive(Debug, Args)] +#[group(required = true, multiple = false)] +struct L2BlockDataSource { + #[arg(short, long)] + /// Path of the database stored by a fuel-node to retrieve L2 block data. Alternatively, the endpoint of a sentry node can be provided using --sentry-node-endpoint. + db_path: Option, + #[arg(short, long)] + /// Endpoint of the sentry node to fetch L2 block data. Alternatively, the path of the database stored by a fuel-node can be provided using --db-path. + sentry_node_endpoint: Option, +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let Arg { + block_committer_endpoint, + l2_block_data_source, + block_range, + output_file, + } = Arg::parse(); + + let filter = match env::var_os("RUST_LOG") { + Some(_) => { + EnvFilter::try_from_default_env().expect("Invalid `RUST_LOG` provided") + } + None => EnvFilter::new("info"), + }; + + let fmt = tracing_subscriber::fmt::Layer::default() + .with_level(true) + .boxed(); + + tracing_subscriber::registry().with(fmt).with(filter).init(); + + // Safety: The block range is always a vector of length 2 + let start_block_included = BlockHeight::from(block_range[0]); + // When requested a set of results, the block committer will fetch the data for the next blob which + // might not include the current height. Each blob contains 3_600 blocks, hence we subtract + // this amount from the block height we use for the first request. + let end_block_excluded = BlockHeight::from(block_range[1]); + let block_range = start_block_included..end_block_excluded; + + if end_block_excluded < start_block_included { + return Err(anyhow::anyhow!( + "Invalid block range - start block must be lower than end block: {}..{}", + start_block_included, + end_block_excluded + )); + } + + let block_committer_data_fetcher = + BlockCommitterDataFetcher::new(block_committer_endpoint, 10)?; + + let block_costs = block_committer_data_fetcher + .fetch_l1_block_costs(block_range) + .await?; + + tracing::debug!("{:?}", block_costs); + match l2_block_data_source { + L2BlockDataSource { + db_path: Some(_db_path), + sentry_node_endpoint: None, + } => { + todo!(); + } + L2BlockDataSource { + db_path: None, + sentry_node_endpoint: Some(sentry_node_endpoint), + } => { + tracing::info!( + "Retrieving L2 data from sentry node: {}", + sentry_node_endpoint + ); + let sentry_node_client = layer2::BlockFetcher::new(sentry_node_endpoint)?; + let blocks_range = start_block_included..end_block_excluded; + + tracing::info!( + "Retrieving L2 data for blocks: {}..{}", + start_block_included, + end_block_excluded + ); + let res = sentry_node_client + .get_l2_block_data(blocks_range, SENTRY_NODE_GRAPHQL_RESULTS_PER_QUERY) + .await; + tracing::info!("results: {:?}", res); + + let blocks_with_gas_consumed = res?; + for ( + _block_height, + Layer2BlockData { + block_height, + block_size, + gas_consumed, + capacity, + bytes_capacity, + transactions_count, + }, + ) in &blocks_with_gas_consumed + { + tracing::debug!( + "Block Height: {}, Block Size: {}, Gas Consumed: {}, Capacity: {}, Bytes Capacity: {}, Transactions count: {}", + block_height, **block_size, **gas_consumed, **capacity, **bytes_capacity, transactions_count + ); + } + summary::summarise_available_data( + &output_file, + &block_costs, + &blocks_with_gas_consumed, + ) + .inspect_err(|e| { + tracing::error!("Failed to write to CSV file: {:?}, {:?}", output_file, e) + })?; + } + _ => { + return Err(anyhow::anyhow!( + "Either db-path or sentry-node-endpoint must be provided" + )); + } + }; + + Ok(()) +} diff --git a/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/src/summary.rs b/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/src/summary.rs new file mode 100644 index 00000000000..bdc1ba7ccd9 --- /dev/null +++ b/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/src/summary.rs @@ -0,0 +1,73 @@ +use std::{ + collections::HashMap, + fs::File, + path::Path, +}; + +use fuel_core_types::fuel_types::BlockHeight; + +use crate::types::{ + BlockCommitterCosts, + Layer2BlockData, +}; + +#[derive(Debug, serde::Serialize)] +struct BlockSummary { + l1_block_number: u64, + l1_blob_fee_wei: u128, + l2_block_number: u32, + l2_gas_fullness: u64, + l2_gas_capacity: u64, + l2_byte_size: u64, + l2_byte_capacity: u64, + l2_block_transactions_count: usize, +} + +fn summarise_data_for_block_committer_costs( + costs: &BlockCommitterCosts, + l2_data: &HashMap, +) -> Vec { + tracing::info!("Building summary for: {:?}", costs); + let block_range_len: usize = costs.len().try_into().unwrap_or_default(); + let mut summaries = Vec::with_capacity(block_range_len); + for block_height in costs.iter() { + let Ok(block_height): Result = block_height.try_into() else { + continue + }; + let l2_data = l2_data.get(&block_height); + if let Some(l2_data) = l2_data { + summaries.push(BlockSummary { + l1_block_number: *costs.da_block_height, + l1_blob_fee_wei: *costs.cost, + l2_block_number: block_height, + l2_gas_fullness: *l2_data.gas_consumed, + l2_gas_capacity: *l2_data.capacity, + l2_byte_size: *l2_data.block_size, + l2_byte_capacity: *l2_data.bytes_capacity, + l2_block_transactions_count: l2_data.transactions_count, + }); + } + } + summaries +} + +pub fn summarise_available_data( + output_file_path: &Path, + costs: &[BlockCommitterCosts], + l2_data: &HashMap, +) -> Result<(), anyhow::Error> { + let file_writer = File::create(output_file_path)?; + let mut writer = csv::WriterBuilder::new() + .has_headers(true) + .from_writer(file_writer); + for block_costs_entry in costs { + let summaries = + summarise_data_for_block_committer_costs(block_costs_entry, l2_data); + for summary in summaries { + tracing::debug!("Serializing record: {:?}", summary); + writer.serialize(summary)?; + } + } + writer.flush()?; + Ok(()) +} diff --git a/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/src/types.rs b/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/src/types.rs new file mode 100644 index 00000000000..ecbc1642373 --- /dev/null +++ b/crates/fuel-gas-price-algorithm/gas-price-data-fetcher/src/types.rs @@ -0,0 +1,103 @@ +use std::{ + iter::Map, + ops::{ + Deref, + RangeInclusive, + }, +}; + +use fuel_core_types::{ + blockchain::primitives::DaBlockHeight, + fuel_types::BlockHeight, +}; +use serde::{ + Deserialize, + Serialize, +}; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Wei(u128); + +impl Deref for Wei { + type Target = u128; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[serde(transparent)] +pub struct GasUnits(pub u64); + +impl Deref for GasUnits { + type Target = u64; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct BytesSize(pub u64); + +impl Deref for BytesSize { + type Target = u64; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct BlockCommitterCosts { + /// The cost of the block, supposedly in Wei but need to check + pub cost: Wei, + pub size: BytesSize, + pub da_block_height: DaBlockHeight, + pub start_height: BlockHeight, + pub end_height: BlockHeight, +} + +impl BlockCommitterCosts { + pub fn iter(&self) -> impl Iterator { + let start_height = *self.start_height; + let end_height = *self.end_height; + (start_height..=end_height) + .map(|raw_block_height| BlockHeight::from(raw_block_height)) + } + + pub fn len(&self) -> u32 { + // Remove 1 from end height because range is inclusive. + self.end_height + .saturating_sub(1) + .saturating_sub(*self.start_height) + } +} + +impl IntoIterator for BlockCommitterCosts { + type Item = BlockHeight; + + // `impl Trait` in associated types is unstable + // see issue #63063 for more information + // We must specify the concrete type here. + type IntoIter = Map, fn(u32) -> BlockHeight>; + + fn into_iter(self) -> Self::IntoIter { + let start_height = *self.start_height; + let end_height = *self.end_height; + (start_height..=end_height) + .map(|raw_block_height| BlockHeight::from(raw_block_height)) + } +} + +#[derive(Debug)] +pub struct Layer2BlockData { + pub block_height: BlockHeight, + pub block_size: BytesSize, + pub gas_consumed: GasUnits, + pub capacity: GasUnits, + pub bytes_capacity: BytesSize, + pub transactions_count: usize, +} diff --git a/crates/fuel-gas-price-algorithm/gas-price-data-reader/.gitignore b/crates/fuel-gas-price-algorithm/gas-price-data-reader/.gitignore new file mode 100644 index 00000000000..ae872b4d900 --- /dev/null +++ b/crates/fuel-gas-price-algorithm/gas-price-data-reader/.gitignore @@ -0,0 +1,2 @@ +Cargo.lock +data \ No newline at end of file diff --git a/crates/fuel-gas-price-algorithm/gas-price-data-reader/Cargo.toml b/crates/fuel-gas-price-algorithm/gas-price-data-reader/Cargo.toml new file mode 100644 index 00000000000..baa9e89a078 --- /dev/null +++ b/crates/fuel-gas-price-algorithm/gas-price-data-reader/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "gas-price-data-reader" +version = "0.1.0" +edition = "2021" + +[dependencies] +csv = "1.3.1" +serde = { version = "1.0.217", features = ["derive"] } + +[workspace] diff --git a/crates/fuel-gas-price-algorithm/gas-price-data-reader/src/main.rs b/crates/fuel-gas-price-algorithm/gas-price-data-reader/src/main.rs new file mode 100644 index 00000000000..5e9accdeafc --- /dev/null +++ b/crates/fuel-gas-price-algorithm/gas-price-data-reader/src/main.rs @@ -0,0 +1,84 @@ +use std::collections::HashMap; + +const WEI_PER_ETH: f64 = 1_000_000_000_000_000_000.; +// l1_block_number,l1_blob_fee_wei,l2_block_number,l2_gas_fullness,l2_gas_capacity,l2_byte_size,l2_byte_capacity +// 21403864,509018984154240,9099900,0,30000000,488,260096 +// 21403864,509018984154240,9099901,1073531,30000000,3943,260096 +// 21403864,509018984154240,9099902,0,30000000,488,260096 +// parse data +#[derive(Debug, serde::Deserialize, Eq, PartialEq, Hash)] +struct Record { + l1_block_number: u64, + l1_blob_fee_wei: u128, + l2_block_number: u64, + l2_gas_fullness: u64, + l2_gas_capacity: u64, + l2_byte_size: u64, + l2_byte_capacity: u64, +} +fn get_records_from_csv_file(file_path: &str) -> Vec { + let mut rdr = csv::ReaderBuilder::new() + .has_headers(true) + .from_path(file_path) + .unwrap(); + let headers = csv::StringRecord::from(vec![ + "l1_block_number", + "l1_blob_fee_wei", + "l2_block_number", + "l2_gas_fullness", + "l2_gas_capacity", + "l2_byte_size", + "l2_byte_capacity", + ]); + let records = rdr + .records() + .skip(1) + .map(|r| r.unwrap().deserialize(Some(&headers)).unwrap()) + .collect::>(); + records +} + +const ENV_VAR_NAME: &str = "BLOCK_HISTORY_FILE"; +fn get_path_to_file() -> String { + if let Some(path) = std::env::var_os(ENV_VAR_NAME) { + return path.to_str().unwrap().to_string(); + } else { + let maybe_path = std::env::args().nth(1); + if let Some(path) = maybe_path { + return path; + } else { + panic!("Please provide a path to the file or set the {ENV_VAR_NAME} environment variable"); + } + } +} + +fn main() { + let path = get_path_to_file(); + let records = get_records_from_csv_file(&path); + let length = records.len(); + let costs = records + .iter() + .map(|r| (r.l1_block_number, r.l1_blob_fee_wei)) + .collect::>(); + let total_costs: u128 = costs.values().sum(); + let total_l2_gas = records.iter().map(|r| r.l2_gas_fullness).sum::(); + + // println!("Average cost: {}", average); + println!("Length: {}", length); + println!("Total cost: {}", total_costs); + println!("Total cost (ETH): {}", total_costs as f64 / WEI_PER_ETH); + println!( + "Average cost per l2 block: {}", + total_costs / length as u128 + ); + println!( + "Average cost per l2 block (ETH): {}", + (total_costs as f64 / length as f64) / WEI_PER_ETH + ); + // get cost per l2 gas fullness + let average_cost_per_l2_gas_fullness = total_costs / total_l2_gas as u128; + println!( + "Average cost per l2 gas: {}", + average_cost_per_l2_gas_fullness + ); +} 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 dc09c2d4dc2..abd916d0b92 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 { @@ -613,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 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/gas_price_service/src/common/fuel_core_storage_adapter.rs b/crates/services/gas_price_service/src/common/fuel_core_storage_adapter.rs index 74bec1207d2..506885fe78b 100644 --- a/crates/services/gas_price_service/src/common/fuel_core_storage_adapter.rs +++ b/crates/services/gas_price_service/src/common/fuel_core_storage_adapter.rs @@ -178,6 +178,7 @@ pub fn get_block_info( block_gas_capacity: block_gas_limit, block_bytes: Postcard::encode(block).len() as u64, block_fees: fee, + gas_price, }; Ok(info) } diff --git a/crates/services/gas_price_service/src/common/utils.rs b/crates/services/gas_price_service/src/common/utils.rs index b57fc2608a2..5c944bd9fb1 100644 --- a/crates/services/gas_price_service/src/common/utils.rs +++ b/crates/services/gas_price_service/src/common/utils.rs @@ -42,5 +42,7 @@ pub enum BlockInfo { block_bytes: u64, // The fees the block has collected block_fees: u64, + // The gas price used in the block + gas_price: u64, }, } diff --git a/crates/services/gas_price_service/src/v0/service.rs b/crates/services/gas_price_service/src/v0/service.rs index c4237b1141e..e0a794c0da9 100644 --- a/crates/services/gas_price_service/src/v0/service.rs +++ b/crates/services/gas_price_service/src/v0/service.rs @@ -250,6 +250,7 @@ mod tests { block_gas_capacity: 100, block_bytes: 100, block_fees: 100, + gas_price: 100, }; let (l2_block_sender, l2_block_receiver) = mpsc::channel(1); diff --git a/crates/services/gas_price_service/src/v0/tests.rs b/crates/services/gas_price_service/src/v0/tests.rs index 492016d37eb..85aeb4fe36a 100644 --- a/crates/services/gas_price_service/src/v0/tests.rs +++ b/crates/services/gas_price_service/src/v0/tests.rs @@ -157,6 +157,7 @@ async fn next_gas_price__affected_by_new_l2_block() { block_gas_capacity: 100, block_bytes: 100, block_fees: 100, + gas_price: 100, }; let (l2_block_sender, l2_block_receiver) = tokio::sync::mpsc::channel(1); let l2_block_source = FakeL2BlockSource { @@ -200,6 +201,7 @@ async fn next__new_l2_block_saves_old_metadata() { block_gas_capacity: 100, block_bytes: 100, block_fees: 100, + gas_price: 100, }; let (l2_block_sender, l2_block_receiver) = tokio::sync::mpsc::channel(1); let l2_block_source = FakeL2BlockSource { diff --git a/crates/services/gas_price_service/src/v1/service.rs b/crates/services/gas_price_service/src/v1/service.rs index df0c91a5c93..0e9b4c2f741 100644 --- a/crates/services/gas_price_service/src/v1/service.rs +++ b/crates/services/gas_price_service/src/v1/service.rs @@ -1,5 +1,3 @@ -use std::num::NonZeroU64; - use crate::{ common::{ gas_price_algorithm::SharedGasPriceAlgo, @@ -58,12 +56,41 @@ use fuel_gas_price_algorithm::{ }, }; use futures::FutureExt; +use std::{ + num::NonZeroU64, + sync::Arc, +}; use tokio::sync::broadcast::Receiver; +#[derive(Debug, Clone)] +pub struct LatestGasPrice { + inner: Arc>, +} + +impl LatestGasPrice { + pub fn new(height: Height, price: GasPrice) -> Self { + let pair = (height, price); + let inner = Arc::new(parking_lot::RwLock::new(pair)); + Self { inner } + } + + pub fn set(&mut self, height: Height, price: GasPrice) { + *self.inner.write() = (height, price); + } +} + +impl LatestGasPrice { + pub fn get(&self) -> (Height, GasPrice) { + *self.inner.read() + } +} + /// The service that updates the gas price algorithm. pub struct GasPriceServiceV1 { /// The algorithm that can be used in the next block shared_algo: SharedV1Algorithm, + /// The latest gas price + latest_gas_price: LatestGasPrice, /// The L2 block source l2_block_source: L2, /// The algorithm updater @@ -78,6 +105,21 @@ pub struct GasPriceServiceV1 { storage_tx_provider: StorageTxProvider, } +impl GasPriceServiceV1 { + pub(crate) fn update_latest_gas_price(&mut self, block_info: &BlockInfo) { + match block_info { + BlockInfo::GenesisBlock => { + // do nothing + } + BlockInfo::Block { + height, gas_price, .. + } => { + self.latest_gas_price.set(*height, *gas_price); + } + } + } +} + impl GasPriceServiceV1 where L2: L2BlockSource, @@ -91,6 +133,7 @@ where tracing::info!("Received L2 block result: {:?}", l2_block_res); let block = l2_block_res?; + self.update_latest_gas_price(&block); tracing::debug!("Updating gas price algorithm"); self.apply_block_info_to_gas_algorithm(block).await?; Ok(()) @@ -105,6 +148,7 @@ where pub fn new( l2_block_source: L2, shared_algo: SharedV1Algorithm, + latest_gas_price: LatestGasPrice, algorithm_updater: AlgorithmUpdaterV1, da_source_adapter_handle: DaSourceService, storage_tx_provider: AtomicStorage, @@ -113,6 +157,7 @@ where da_source_adapter_handle.shared_data().clone().subscribe(); Self { shared_algo, + latest_gas_price, l2_block_source, algorithm_updater, da_source_adapter_handle, @@ -218,6 +263,7 @@ where block_gas_capacity, block_bytes, block_fees, + .. } => { self.handle_normal_block( height, @@ -344,6 +390,7 @@ mod tests { }; use fuel_core_storage::{ structured_storage::test::InMemoryStorage, + tables::merkle::DenseMetadataKey::Latest, transactional::{ IntoTransaction, StorageTransaction, @@ -386,6 +433,7 @@ mod tests { service::{ initialize_algorithm, GasPriceServiceV1, + LatestGasPrice, }, uninitialized_task::fuel_storage_unrecorded_blocks::FuelStorageUnrecordedBlocks, }, @@ -445,6 +493,7 @@ mod tests { block_gas_capacity: 100, block_bytes: 100, block_fees: 100, + gas_price: 100, }; let (l2_block_sender, l2_block_receiver) = mpsc::channel(1); @@ -481,10 +530,12 @@ mod tests { ), None, ); + let latest_gas_price = LatestGasPrice::new(0, 0); let mut service = GasPriceServiceV1::new( l2_block_source, shared_algo, + latest_gas_price, algo_updater, dummy_da_source, inner, @@ -513,6 +564,7 @@ mod tests { block_gas_capacity: 100, block_bytes: 100, block_fees: 100, + gas_price: 100, }; let (l2_block_sender, l2_block_receiver) = mpsc::channel(1); @@ -564,10 +616,12 @@ mod tests { Some(Duration::from_millis(1)), ); let mut watcher = StateWatcher::started(); + let latest_gas_price = LatestGasPrice::new(0, 0); let mut service = GasPriceServiceV1::new( l2_block_source, shared_algo, + latest_gas_price, algo_updater, da_source, inner, @@ -627,6 +681,7 @@ mod tests { block_gas_capacity: 100, block_bytes: 100, block_fees: 100, + gas_price: 100, }; let (l2_block_sender, l2_block_receiver) = mpsc::channel(1); @@ -664,10 +719,12 @@ mod tests { Some(Duration::from_millis(1)), ); let mut watcher = StateWatcher::started(); + let latest_gas_price = LatestGasPrice::new(0, 0); let mut service = GasPriceServiceV1::new( l2_block_source, shared_algo, + latest_gas_price, algo_updater, da_source, inner, diff --git a/crates/services/gas_price_service/src/v1/tests.rs b/crates/services/gas_price_service/src/v1/tests.rs index f5524508edc..edbbbe9d6b8 100644 --- a/crates/services/gas_price_service/src/v1/tests.rs +++ b/crates/services/gas_price_service/src/v1/tests.rs @@ -43,6 +43,7 @@ use crate::{ service::{ initialize_algorithm, GasPriceServiceV1, + LatestGasPrice, }, uninitialized_task::{ fuel_storage_unrecorded_blocks::AsUnrecordedBlocks, @@ -355,6 +356,7 @@ async fn next_gas_price__affected_by_new_l2_block() { block_gas_capacity: 100, block_bytes: 100, block_fees: 100, + gas_price: 100, }; let (l2_block_sender, l2_block_receiver) = tokio::sync::mpsc::channel(1); let l2_block_source = FakeL2BlockSource { @@ -369,9 +371,11 @@ async fn next_gas_price__affected_by_new_l2_block() { initialize_algorithm(&config, height, &metadata_storage).unwrap(); let da_source = FakeDABlockCost::never_returns(); let da_source_service = DaSourceService::new(da_source, None); + let latest_gas_price = LatestGasPrice::new(0, 0); let mut service = GasPriceServiceV1::new( l2_block_source, shared_algo, + latest_gas_price, algo_updater, da_source_service, inner, @@ -401,6 +405,7 @@ async fn run__new_l2_block_saves_old_metadata() { block_gas_capacity: 100, block_bytes: 100, block_fees: 100, + gas_price: 100, }; let (l2_block_sender, l2_block_receiver) = tokio::sync::mpsc::channel(1); let l2_block_source = FakeL2BlockSource { @@ -413,9 +418,11 @@ async fn run__new_l2_block_saves_old_metadata() { let shared_algo = SharedV1Algorithm::new_with_algorithm(algo_updater.algorithm()); let da_source = FakeDABlockCost::never_returns(); let da_source_service = DaSourceService::new(da_source, None); + let latest_gas_price = LatestGasPrice::new(0, 0); let mut service = GasPriceServiceV1::new( l2_block_source, shared_algo, + latest_gas_price, algo_updater, da_source_service, inner, @@ -438,6 +445,54 @@ async fn run__new_l2_block_saves_old_metadata() { service.shutdown().await.unwrap(); } +#[tokio::test] +async fn run__new_l2_block_updates_latest_gas_price_arc() { + // given + let height = 1; + let gas_price = 40; + let l2_block = BlockInfo::Block { + height, + gas_used: 60, + block_gas_capacity: 100, + block_bytes: 100, + block_fees: 100, + gas_price, + }; + let (l2_block_sender, l2_block_receiver) = tokio::sync::mpsc::channel(1); + let l2_block_source = FakeL2BlockSource { + l2_block: l2_block_receiver, + }; + + let config = zero_threshold_arbitrary_config(); + let inner = database(); + let algo_updater = updater_from_config(&config); + let shared_algo = SharedV1Algorithm::new_with_algorithm(algo_updater.algorithm()); + let da_source = FakeDABlockCost::never_returns(); + let da_source_service = DaSourceService::new(da_source, None); + let latest_gas_price = LatestGasPrice::new(0, 0); + let mut service = GasPriceServiceV1::new( + l2_block_source, + shared_algo, + latest_gas_price.clone(), + algo_updater, + da_source_service, + inner, + ); + let mut watcher = StateWatcher::started(); + + // when + l2_block_sender.send(l2_block).await.unwrap(); + service.run(&mut watcher).await; + + // then + let expected = (height, gas_price); + let actual = latest_gas_price.get(); + assert_eq!(expected, actual); + + // cleanup + service.shutdown().await.unwrap(); +} + #[derive(Clone)] struct FakeSettings; @@ -506,7 +561,7 @@ impl L2Data for FakeL2Data { &self, _height: &BlockHeight, ) -> StorageResult>> { - unimplemented!() + Ok(None) } } impl AtomicView for FakeOnChainDb { diff --git a/crates/services/gas_price_service/src/v1/uninitialized_task.rs b/crates/services/gas_price_service/src/v1/uninitialized_task.rs index 96da25427d4..571f3e9f84c 100644 --- a/crates/services/gas_price_service/src/v1/uninitialized_task.rs +++ b/crates/services/gas_price_service/src/v1/uninitialized_task.rs @@ -43,6 +43,7 @@ use crate::{ service::{ initialize_algorithm, GasPriceServiceV1, + LatestGasPrice, }, uninitialized_task::fuel_storage_unrecorded_blocks::{ AsUnrecordedBlocks, @@ -78,6 +79,7 @@ use fuel_gas_price_algorithm::v1::{ AlgorithmUpdaterV1, UnrecordedBlocks, }; +use std::sync::Arc; pub mod fuel_storage_unrecorded_blocks; @@ -90,6 +92,7 @@ pub struct UninitializedTask, pub(crate) shared_algo: SharedV1Algorithm, + pub(crate) latest_gas_price: LatestGasPrice, pub(crate) algo_updater: AlgorithmUpdaterV1, pub(crate) da_source: DA, } @@ -119,10 +122,20 @@ where .latest_height() .unwrap_or(genesis_block_height) .into(); + let latest_gas_price = on_chain_db + .latest_view()? + .get_block(&latest_block_height.into())? + .and_then(|block| { + let (_, gas_price) = mint_values(&block).ok()?; + Some(gas_price) + }) + .unwrap_or(0); let (algo_updater, shared_algo) = initialize_algorithm(&config, latest_block_height, &gas_price_db)?; + let latest_gas_price = LatestGasPrice::new(latest_block_height, latest_gas_price); + let task = Self { config, gas_metadata_height, @@ -132,6 +145,7 @@ where on_chain_db, block_stream, algo_updater, + latest_gas_price, shared_algo, da_source, }; @@ -181,6 +195,7 @@ where let service = GasPriceServiceV1::new( l2_block_source, self.shared_algo, + self.latest_gas_price, self.algo_updater, da_service, self.gas_price_db, @@ -201,6 +216,7 @@ where let service = GasPriceServiceV1::new( l2_block_source, self.shared_algo, + self.latest_gas_price, self.algo_updater, da_service, self.gas_price_db, @@ -221,12 +237,12 @@ where SettingsProvider: GasPriceSettingsProvider + 'static, { const NAME: &'static str = "GasPriceServiceV1"; - type SharedData = SharedV1Algorithm; + type SharedData = (SharedV1Algorithm, LatestGasPrice); type Task = GasPriceServiceV1, DA, AtomicStorage>; type TaskParams = (); fn shared_data(&self) -> Self::SharedData { - self.shared_algo.clone() + (self.shared_algo.clone(), self.latest_gas_price.clone()) } async fn into_task( 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..fb3eb767d5a --- /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 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..96233f01895 --- /dev/null +++ b/crates/services/shared-sequencer/src/http_api.rs @@ -0,0 +1,189 @@ +use anyhow::Context; +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).with_context(|| format!("response text {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).with_context(|| format!("response text {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).with_context(|| format!("response text {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).with_context(|| format!("response text {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).with_context(|| format!("response text {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).with_context(|| format!("response text {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).with_context(|| format!("response text {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..8dd3b488602 --- /dev/null +++ b/crates/services/shared-sequencer/src/lib.rs @@ -0,0 +1,269 @@ +//! 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], + ss_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 ss_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, + ss_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 what 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.ss_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..7b632f35e5b --- /dev/null +++ b/crates/services/shared-sequencer/src/service.rs @@ -0,0 +1,250 @@ +//! 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>, + interval: tokio::time::Interval, +} + +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 { + interval: tokio::time::interval(self.config.block_posting_frequency), + shared_sequencer_client, + config: self.config, + signer: self.signer, + account_metadata: None, + prev_order: None, + blobs, + }) + } +} + +impl Task +where + S: Signer, +{ + /// Fetch latest account metadata if it's not set + async fn ensure_account_metadata(&mut self) -> anyhow::Result<()> { + if self.account_metadata.is_some() { + return Ok(()); + } + let ss = self + .shared_sequencer_client + .as_ref() + .expect("Shared sequencer client is not set"); + self.account_metadata = Some(ss.get_account_meta(self.signer.as_ref()).await?); + Ok(()) + } + + /// Fetch previous order in the topic if it's not set + async fn ensure_prev_order(&mut self) -> anyhow::Result<()> { + if self.prev_order.is_some() { + return Ok(()); + } + let ss = self + .shared_sequencer_client + .as_ref() + .expect("Shared sequencer client is not set"); + self.prev_order = ss.get_topic().await?.map(|f| f.order); + Ok(()) + } +} + +#[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; + } + + if let Err(err) = self.ensure_account_metadata().await { + // We don't want to spam the RPC endpoint with a lot of queries, + // so wait for one second before sending the next one. + tokio::time::sleep(Duration::from_secs(1)).await; + return TaskNextAction::ErrorContinue(err) + } + if let Err(err) = self.ensure_prev_order().await { + return TaskNextAction::ErrorContinue(err) + }; + + tokio::select! { + biased; + _ = watcher.while_started() => { + TaskNextAction::Stop + }, + _ = self.interval.tick() => { + 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; + return TaskNextAction::Continue; + }; + + let mut account = self.account_metadata.take().expect("Account metadata is not set"); + let next_order = self.prev_order.map(|prev| prev.wrapping_add(1)).unwrap_or(0); + let ss = self.shared_sequencer_client + .as_ref().expect("Shared sequencer client is not set"); + let blobs_bytes = postcard::to_allocvec(&blobs).expect("Failed to serialize SSBlob"); + + if let Err(err) = ss.send(self.signer.as_ref(), account, next_order, blobs_bytes).await { + return TaskNextAction::ErrorContinue(err); + } + + 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 + }, + } + } + + 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/storage/Cargo.toml b/crates/storage/Cargo.toml index 8750f3ae8b0..4d5ab6b2265 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -24,7 +24,7 @@ fuel-core-types = { workspace = true, default-features = false, features = [ "serde", ] } fuel-vm-private = { workspace = true, default-features = false } -impl-tools = "0.10" +impl-tools = "0.10.3" itertools = { workspace = true, features = ["use_alloc"] } mockall = { workspace = true, optional = true } num_enum = { workspace = true } diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index 246952eef4f..cbb69a1beda 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -18,6 +18,7 @@ version = { workspace = true } [dependencies] anyhow = { workspace = true } +aws-sdk-kms = { workspace = true, optional = true } bs58 = { version = "0.5", optional = true } derivative = { version = "2", default-features = false, optional = true, features = [ "use_core", @@ -26,6 +27,7 @@ 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 } @@ -33,6 +35,10 @@ serde = { workspace = true, features = ["derive"], optional = true } tai64 = { version = "=4.0.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", "derivative"] @@ -41,3 +47,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/proptest-regressions/recovery.txt b/tests/proptest-regressions/recovery.txt index 964abdad7e9..28f5541dd30 100644 --- a/tests/proptest-regressions/recovery.txt +++ b/tests/proptest-regressions/recovery.txt @@ -6,3 +6,4 @@ # everyone who runs the test benefits from these saved cases. cc 7c4e472cb1f611f337e2b9e029d79cad58b6e80786135c0cea9e37808467f109 # shrinks to (height, lower_height) = (53, 0) cc 34f1effd503ddfe54ad3da854765812601969db5335e6bf5c385a99a7ef59e90 # shrinks to (height, lower_height) = (64, 27) +cc 46741826ecf4f441d17d822657a94fb33b941f19c222d181ccbb865b0b62b7fc # shrinks to (height, lower_height) = (4, 3) diff --git a/tests/test-helpers/src/builder.rs b/tests/test-helpers/src/builder.rs index 73d70eeb2a0..cb615cea811 100644 --- a/tests/test-helpers/src/builder.rs +++ b/tests/test-helpers/src/builder.rs @@ -13,6 +13,7 @@ use fuel_core::{ DbType, FuelService, }, + state::rocks_db::DatabaseConfig, }; use fuel_core_client::client::FuelClient; use fuel_core_poa::Trigger; @@ -100,6 +101,8 @@ pub struct TestSetupBuilder { pub base_asset_id: AssetId, pub trigger: Trigger, pub max_txs: usize, + pub database_type: DbType, + pub database_config: DatabaseConfig, } impl TestSetupBuilder { @@ -241,14 +244,14 @@ impl TestSetupBuilder { }; txpool.heavy_work.size_of_verification_queue = self.max_txs; - let config = Config { + let mut config = Config { utxo_validation: self.utxo_validation, txpool, block_production: self.trigger, starting_gas_price: self.starting_gas_price, ..Config::local_node_with_configs(chain_conf, state) }; - assert_eq!(config.combined_db_config.database_type, DbType::RocksDb); + config.combined_db_config.database_config = self.database_config; let srv = FuelService::new_node(config).await.unwrap(); let client = FuelClient::from(srv.bound_address); @@ -276,6 +279,8 @@ impl Default for TestSetupBuilder { base_asset_id: AssetId::BASE, trigger: Trigger::Instant, max_txs: 100000, + database_type: DbType::RocksDb, + database_config: DatabaseConfig::config_for_tests(), } } } diff --git a/tests/tests/aws_kms.rs b/tests/tests/aws_kms.rs index 31dcb8e0448..e832a4afc2c 100644 --- a/tests/tests/aws_kms.rs +++ b/tests/tests/aws_kms.rs @@ -1,4 +1,7 @@ -use fuel_core::combined_database::CombinedDatabase; +use fuel_core::{ + combined_database::CombinedDatabase, + state::rocks_db::DatabaseConfig, +}; use fuel_core_storage::transactional::AtomicView; use fuel_core_types::blockchain::consensus::Consensus; use test_helpers::fuel_core_driver::FuelCoreDriver; @@ -47,8 +50,12 @@ async fn can_get_sealed_block_from_poa_produced_block_when_signing_with_kms() { // stop the node and just grab the database let db_path = driver.kill().await; - let db = CombinedDatabase::open(db_path.path(), 1024 * 1024, Default::default(), 512) - .unwrap(); + let db = CombinedDatabase::open( + db_path.path(), + Default::default(), + DatabaseConfig::config_for_tests(), + ) + .unwrap(); let view = db.on_chain().latest_view().unwrap(); diff --git a/tests/tests/blocks.rs b/tests/tests/blocks.rs index 923d457af93..ac367a62f08 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 b4f82b39a53..cd0d1430375 100644 --- a/tests/tests/da_compression.rs +++ b/tests/tests/da_compression.rs @@ -23,7 +23,6 @@ use fuel_core_compression::{ decompress::decompress, VersionedCompressedBlock, }; -use fuel_core_poa::signer::SignMode; use fuel_core_storage::transactional::{ HistoricalView, IntoTransaction, @@ -42,6 +41,7 @@ use fuel_core_types::{ TxPointer, }, secrecy::Secret, + signer::SignMode, }; use rand::{ rngs::StdRng, diff --git a/tests/tests/health.rs b/tests/tests/health.rs index d0b241fcb5a..231628493f1 100644 --- a/tests/tests/health.rs +++ b/tests/tests/health.rs @@ -5,6 +5,7 @@ use fuel_core::{ Config, FuelService, }, + state::rocks_db::DatabaseConfig, types::fuel_tx::Transaction, }; use fuel_core_client::client::FuelClient; @@ -28,9 +29,12 @@ async fn can_restart_node() { // start node once { - let database = - Database::open_rocksdb(tmp_dir.path(), None, Default::default(), 512) - .unwrap(); + let database = Database::open_rocksdb( + tmp_dir.path(), + Default::default(), + DatabaseConfig::config_for_tests(), + ) + .unwrap(); let first_startup = FuelService::from_database(database, Config::local_node()) .await .unwrap(); @@ -41,9 +45,12 @@ async fn can_restart_node() { } { - let database = - Database::open_rocksdb(tmp_dir.path(), None, Default::default(), 512) - .unwrap(); + let database = Database::open_rocksdb( + tmp_dir.path(), + Default::default(), + DatabaseConfig::config_for_tests(), + ) + .unwrap(); let _second_startup = FuelService::from_database(database, Config::local_node()) .await .unwrap(); @@ -52,14 +59,16 @@ async fn can_restart_node() { #[tokio::test] async fn can_restart_node_with_transactions() { - let capacity = 1024 * 1024; let tmp_dir = tempfile::TempDir::new().unwrap(); { // Given - let database = - CombinedDatabase::open(tmp_dir.path(), capacity, Default::default(), 512) - .unwrap(); + let database = CombinedDatabase::open( + tmp_dir.path(), + Default::default(), + DatabaseConfig::config_for_tests(), + ) + .unwrap(); let service = FuelService::from_combined_database(database, Config::local_node()) .await .unwrap(); @@ -76,9 +85,12 @@ async fn can_restart_node_with_transactions() { { // When - let database = - CombinedDatabase::open(tmp_dir.path(), capacity, Default::default(), 512) - .unwrap(); + let database = CombinedDatabase::open( + tmp_dir.path(), + Default::default(), + DatabaseConfig::config_for_tests(), + ) + .unwrap(); let service = FuelService::from_combined_database(database, Config::local_node()) .await .unwrap(); diff --git a/tests/tests/poa.rs b/tests/tests/poa.rs index b360d0d89fa..0dc28a58387 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/recovery.rs b/tests/tests/recovery.rs index 60806b58f05..07a2c9797d1 100644 --- a/tests/tests/recovery.rs +++ b/tests/tests/recovery.rs @@ -171,7 +171,12 @@ async fn _gas_price_updater__can_recover_on_startup_when_gas_price_db_is_behind( for _ in 0..diff { let _ = database.gas_price().rollback_last_block(); } - assert!(database.on_chain().latest_height() > database.gas_price().latest_height()); + assert!( + database.on_chain().latest_height() > database.gas_price().latest_height(), + "on_chain: {:?}, gas_price: {:?}", + database.on_chain().latest_height(), + database.gas_price().latest_height() + ); let temp_dir = driver.kill().await; // When diff --git a/tests/tests/relayer.rs b/tests/tests/relayer.rs index e92e335b3fc..1e9c2dd542f 100644 --- a/tests/tests/relayer.rs +++ b/tests/tests/relayer.rs @@ -15,6 +15,7 @@ use fuel_core::{ Config, FuelService, }, + state::rocks_db::DatabaseConfig, }; use fuel_core_client::client::{ pagination::{ @@ -332,14 +333,16 @@ async fn can_restart_node_with_relayer_data() { .try_into() .unwrap()]); - let capacity = 1024 * 1024; let tmp_dir = tempfile::TempDir::new().unwrap(); { // Given - let database = - CombinedDatabase::open(tmp_dir.path(), capacity, Default::default(), 512) - .unwrap(); + let database = CombinedDatabase::open( + tmp_dir.path(), + Default::default(), + DatabaseConfig::config_for_tests(), + ) + .unwrap(); let service = FuelService::from_combined_database(database, config.clone()) .await @@ -357,9 +360,12 @@ async fn can_restart_node_with_relayer_data() { { // When - let database = - CombinedDatabase::open(tmp_dir.path(), capacity, Default::default(), 512) - .unwrap(); + let database = CombinedDatabase::open( + tmp_dir.path(), + Default::default(), + DatabaseConfig::config_for_tests(), + ) + .unwrap(); let service = FuelService::from_combined_database(database, config) .await .unwrap(); 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,