From ff7cf60d1a3d93aea87e5992600a829172a10876 Mon Sep 17 00:00:00 2001 From: Brandon Vrooman Date: Tue, 17 Oct 2023 14:40:31 -0400 Subject: [PATCH] feat: Support no_std and WASM compilation for `fuel-core` crates (#1411) Related issues: - Closes https://github.com/FuelLabs/fuel-core/issues/1370 Adds no_std compilation support for crates: - `fuel-core-types` - `fuel-core-storage` - `fuel-core-client` - `fuel-core-chain-config` --------- Co-authored-by: xgreenx --- .github/workflows/ci.yml | 12 ++++ CHANGELOG.md | 1 + Cargo.lock | 8 +-- Cargo.toml | 2 +- benches/src/lib.rs | 22 +++---- bin/fuel-core/src/cli/snapshot.rs | 10 ++-- bin/keygen/src/keygen.rs | 6 +- crates/chain-config/Cargo.toml | 19 ++++--- crates/chain-config/src/config.rs | 3 + crates/chain-config/src/config/chain.rs | 57 +++++++++++++++---- crates/chain-config/src/lib.rs | 15 +++-- crates/client/Cargo.toml | 2 +- crates/client/src/client.rs | 34 +++++++---- crates/database/Cargo.toml | 2 +- crates/database/src/lib.rs | 28 ++++----- crates/fuel-core/src/database.rs | 2 +- crates/fuel-core/src/schema/tx/receipt.rs | 5 +- crates/services/executor/src/refs/contract.rs | 9 ++- crates/services/importer/Cargo.toml | 2 +- crates/services/importer/src/importer.rs | 46 ++++++++------- crates/services/producer/Cargo.toml | 2 +- .../services/producer/src/block_producer.rs | 20 +++++-- crates/services/relayer/src/log.rs | 3 +- crates/services/relayer/src/service.rs | 4 +- crates/storage/Cargo.toml | 6 +- crates/storage/src/lib.rs | 21 ++++--- crates/types/Cargo.toml | 5 +- crates/types/src/blockchain/consensus.rs | 5 +- crates/types/src/services/executor.rs | 50 +++++++++------- 29 files changed, 251 insertions(+), 150 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de27fa8b31c..d6e2700a769 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -119,6 +119,17 @@ jobs: args: --manifest-path version-compatibility/Cargo.toml --workspace - command: build args: -p fuel-core-bin --no-default-features --features production + + # WASM compatibility checks + - command: check + args: -p fuel-core-types --target wasm32-unknown-unknown --no-default-features + - command: check + args: -p fuel-core-storage --target wasm32-unknown-unknown --no-default-features + - command: check + args: -p fuel-core-client --target wasm32-unknown-unknown --no-default-features + - command: check + args: -p fuel-core-chain-config --target wasm32-unknown-unknown --no-default-features + # disallow any job that takes longer than 45 minutes timeout-minutes: 45 continue-on-error: ${{ matrix.skip-error || false }} @@ -127,6 +138,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_VERSION }} + targets: "wasm32-unknown-unknown" components: "clippy" - name: Install Cargo Make uses: davidB/rust-cargo-make@v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 11dcee1e9e9..2883c41b466 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Description of the upcoming release here. ### Added +- [#1411](https://github.com/FuelLabs/fuel-core/pull/1411) Added WASM and `no_std` compatibility - [#1371](https://github.com/FuelLabs/fuel-core/pull/1371): Add new client function for querying the `MessageStatus` for a specific message (by `Nonce`) - [#1356](https://github.com/FuelLabs/fuel-core/pull/1356): Add peer reputation reporting to heartbeat code - [#1355](https://github.com/FuelLabs/fuel-core/pull/1355): Added new metrics related to block importing, such as tps, sync delays etc diff --git a/Cargo.lock b/Cargo.lock index ebac2174247..9a6ae94a152 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2881,10 +2881,10 @@ name = "fuel-core-database" version = "0.20.4" dependencies = [ "anyhow", + "derive_more", "fuel-core-storage", "fuel-core-trace", "fuel-core-types", - "thiserror", ] [[package]] @@ -2928,13 +2928,13 @@ name = "fuel-core-importer" version = "0.20.4" dependencies = [ "anyhow", + "derive_more", "fuel-core-metrics", "fuel-core-storage", "fuel-core-trace", "fuel-core-types", "mockall", "test-case", - "thiserror", "tokio", "tracing", ] @@ -3032,12 +3032,12 @@ version = "0.20.4" dependencies = [ "anyhow", "async-trait", + "derive_more", "fuel-core-producer", "fuel-core-storage", "fuel-core-trace", "fuel-core-types", "rand 0.8.5", - "thiserror", "tokio", "tokio-rayon", "tracing", @@ -3090,10 +3090,10 @@ name = "fuel-core-storage" version = "0.20.4" dependencies = [ "anyhow", + "derive_more", "fuel-core-types", "fuel-vm", "mockall", - "thiserror", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a0bb9971f98..9632b05413f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,7 +75,7 @@ fuel-core-tests = { version = "0.0.0", path = "./tests" } fuel-core-xtask = { version = "0.0.0", path = "./xtask" } # Fuel dependencies -fuel-vm-private = { version = "0.38.0", package = "fuel-vm" } +fuel-vm-private = { version = "0.38.0", package = "fuel-vm", default-features = false } # Common dependencies anyhow = "1.0" diff --git a/benches/src/lib.rs b/benches/src/lib.rs index 63af76148a3..72738c67af7 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -30,10 +30,7 @@ use fuel_core_types::{ }; pub use rand::Rng; -use std::{ - io, - iter, -}; +use std::iter; const LARGE_GAS_LIMIT: u64 = u64::MAX - 1001; @@ -95,7 +92,7 @@ pub struct VmBench { pub prepare_call: Option, pub dummy_contract: Option, pub contract_code: Option, - pub prepare_db: Option io::Result>>, + pub prepare_db: Option anyhow::Result>>, } #[derive(Debug, Clone)] @@ -138,7 +135,7 @@ impl VmBench { } } - pub fn contract(rng: &mut R, instruction: Instruction) -> io::Result + pub fn contract(rng: &mut R, instruction: Instruction) -> anyhow::Result where R: Rng, { @@ -276,21 +273,21 @@ impl VmBench { pub fn with_prepare_db(mut self, prepare_db: F) -> Self where - F: FnMut(VmDatabase) -> io::Result + 'static, + F: FnMut(VmDatabase) -> anyhow::Result + 'static, { self.prepare_db.replace(Box::new(prepare_db)); self } - pub fn prepare(self) -> io::Result { + pub fn prepare(self) -> anyhow::Result { self.try_into() } } impl TryFrom for VmBenchPrepared { - type Error = io::Error; + type Error = anyhow::Error; - fn try_from(case: VmBench) -> io::Result { + fn try_from(case: VmBench) -> anyhow::Result { let VmBench { params, gas_price, @@ -317,8 +314,7 @@ impl TryFrom for VmBenchPrepared { .iter() .any(|op| matches!(op, Instruction::RET(_))) { - return Err(io::Error::new( - io::ErrorKind::Other, + return Err(anyhow::anyhow!( "a prepare script should not call/return into different contexts.", )) } @@ -423,7 +419,7 @@ impl TryFrom for VmBenchPrepared { let PrepareCall { ra, rb, rc, rd } = p; vm.prepare_call(ra, rb, rc, rd) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; + .map_err(anyhow::Error::msg)?; for instruction in post_call { vm.instruction(instruction).unwrap(); } diff --git a/bin/fuel-core/src/cli/snapshot.rs b/bin/fuel-core/src/cli/snapshot.rs index 6ee6c9912d2..95e5b2936a7 100644 --- a/bin/fuel-core/src/cli/snapshot.rs +++ b/bin/fuel-core/src/cli/snapshot.rs @@ -60,10 +60,12 @@ pub async fn exec(command: Command) -> anyhow::Result<()> { database::Database, }; let path = command.database_path; - let data_source = - fuel_core::state::rocks_db::RocksDb::default_open(&path, None).context( - format!("failed to open database at path {}", path.display()), - )?; + let data_source = fuel_core::state::rocks_db::RocksDb::default_open(&path, None) + .map_err(Into::::into) + .context(format!( + "failed to open database at path {}", + path.display() + ))?; let db = Database::new(std::sync::Arc::new(data_source)); match command.subcommand { diff --git a/bin/keygen/src/keygen.rs b/bin/keygen/src/keygen.rs index eec28b78608..151461c16f7 100644 --- a/bin/keygen/src/keygen.rs +++ b/bin/keygen/src/keygen.rs @@ -98,7 +98,8 @@ pub struct ParseSecret { impl ParseSecret { pub fn exec(&self) -> anyhow::Result<()> { - let secret = SecretKey::from_str(&self.secret)?; + let secret = SecretKey::from_str(&self.secret) + .map_err(|_| anyhow::anyhow!("invalid secret key"))?; match self.key_type { KeyType::BlockProduction => { let address = Input::owner(&secret.public_key()); @@ -130,7 +131,8 @@ fn print_value(output: serde_json::Value, pretty: bool) -> anyhow::Result<()> { serde_json::to_string_pretty(&output) } else { serde_json::to_string(&output) - }; + } + .map_err(anyhow::Error::msg); println!("{}", output?); Ok(()) } diff --git a/crates/chain-config/Cargo.toml b/crates/chain-config/Cargo.toml index 5ead7c99ce2..2f1ba39147c 100644 --- a/crates/chain-config/Cargo.toml +++ b/crates/chain-config/Cargo.toml @@ -14,18 +14,23 @@ description = "Fuel Chain config types" anyhow = { workspace = true } bech32 = "0.9.0" fuel-core-storage = { workspace = true } -fuel-core-types = { workspace = true, features = [ - "serde", - "random", -] } +fuel-core-types = { workspace = true, default-features = false, features = ["serde"] } hex = { version = "0.4", features = ["serde"] } itertools = { workspace = true } -postcard = { version = "1.0", features = ["use-std"] } -rand = { workspace = true } +postcard = { version = "1.0", features = ["alloc"] } +rand = { workspace = true, optional = true } serde = { workspace = true, features = ["derive", "rc"] } -serde_json = { version = "1.0", features = ["raw_value"] } +serde_json = { version = "1.0", features = ["raw_value"], optional = true } serde_with = "1.11" tracing = "0.1" [dev-dependencies] +fuel-core-types = { workspace = true, default-features = false, features = ["random", "serde"] } insta = { workspace = true } +rand = { workspace = true } +serde_json = { version = "1.0", features = ["raw_value"] } + +[features] +default = ["std"] +random = ["dep:rand", "fuel-core-types/random"] +std = ["dep:serde_json", "fuel-core-types/std", "anyhow/std"] diff --git a/crates/chain-config/src/config.rs b/crates/chain-config/src/config.rs index e1550e9dfc1..2ec36889f42 100644 --- a/crates/chain-config/src/config.rs +++ b/crates/chain-config/src/config.rs @@ -33,6 +33,7 @@ mod tests { RngCore, SeedableRng, }; + #[cfg(feature = "std")] use std::{ env::temp_dir, fs::write, @@ -47,6 +48,7 @@ mod tests { state::StateConfig, }; + #[cfg(feature = "std")] #[test] fn from_str_loads_from_file() { // setup chain config in a temp file @@ -320,6 +322,7 @@ mod tests { } } + #[cfg(feature = "std")] fn tmp_path() -> PathBuf { let mut path = temp_dir(); path.push(rand::random::().to_string()); diff --git a/crates/chain-config/src/config/chain.rs b/crates/chain-config/src/config/chain.rs index 7b9b43c2be7..20c7c7d9419 100644 --- a/crates/chain-config/src/config/chain.rs +++ b/crates/chain-config/src/config/chain.rs @@ -2,6 +2,7 @@ use bech32::{ ToBase32, Variant::Bech32m, }; +use core::str::FromStr; use fuel_core_storage::MerkleRoot; use fuel_core_types::{ fuel_crypto::Hasher, @@ -19,10 +20,6 @@ use fuel_core_types::{ fuel_vm::SecretKey, }; use itertools::Itertools; -use rand::{ - rngs::StdRng, - SeedableRng, -}; use serde::{ Deserialize, Serialize, @@ -31,10 +28,10 @@ use serde_with::{ serde_as, skip_serializing_none, }; +#[cfg(feature = "std")] use std::{ io::ErrorKind, path::PathBuf, - str::FromStr, }; use crate::{ @@ -83,10 +80,49 @@ impl ChainConfig { pub fn local_testnet() -> Self { // endow some preset accounts with an initial balance tracing::info!("Initial Accounts"); - let mut rng = StdRng::seed_from_u64(10); + let secrets = [ + "0xde97d8624a438121b86a1956544bd72ed68cd69f2c99555b08b1e8c51ffd511c", + "0x37fa81c84ccd547c30c176b118d5cb892bdb113e8e80141f266519422ef9eefd", + "0x862512a2363db2b3a375c0d4bbbd27172180d89f23f2e259bac850ab02619301", + "0x976e5c3fa620092c718d852ca703b6da9e3075b9f2ecb8ed42d9f746bf26aafb", + "0x7f8a325504e7315eda997db7861c9447f5c3eff26333b20180475d94443a10c6", + ]; + let initial_coins = secrets + .into_iter() + .map(|secret| { + let secret = SecretKey::from_str(secret).expect("Expected valid secret"); + let address = Address::from(*secret.public_key().hash()); + let bech32_data = Bytes32::new(*address).to_base32(); + let bech32_encoding = + bech32::encode(FUEL_BECH32_HRP, bech32_data, Bech32m).unwrap(); + tracing::info!( + "PrivateKey({:#x}), Address({:#x} [bech32: {}]), Balance({})", + secret, + address, + bech32_encoding, + TESTNET_INITIAL_BALANCE + ); + Self::initial_coin(secret, TESTNET_INITIAL_BALANCE, None) + }) + .collect_vec(); + + Self { + chain_name: LOCAL_TESTNET.to_string(), + initial_state: Some(StateConfig { + coins: Some(initial_coins), + ..StateConfig::default() + }), + ..Default::default() + } + } + + #[cfg(feature = "random")] + pub fn random_testnet() -> Self { + tracing::info!("Initial Accounts"); + let mut rng = rand::thread_rng(); let initial_coins = (0..5) .map(|_| { - let secret = fuel_core_types::fuel_crypto::SecretKey::random(&mut rng); + let secret = SecretKey::random(&mut rng); let address = Address::from(*secret.public_key().hash()); let bech32_data = Bytes32::new(*address).to_base32(); let bech32_encoding = @@ -132,6 +168,7 @@ impl ChainConfig { } } +#[cfg(feature = "std")] impl FromStr for ChainConfig { type Err = std::io::Error; @@ -184,7 +221,7 @@ impl GenesisCommitment for ChainConfig { impl GenesisCommitment for ConsensusParameters { fn root(&self) -> anyhow::Result { // TODO: Define hash algorithm for `ConsensusParameters` - let bytes = postcard::to_stdvec(&self)?; + let bytes = postcard::to_allocvec(&self).map_err(anyhow::Error::msg)?; let params_hash = Hasher::default().chain(bytes).finalize(); Ok(params_hash.into()) @@ -194,7 +231,7 @@ impl GenesisCommitment for ConsensusParameters { impl GenesisCommitment for GasCosts { fn root(&self) -> anyhow::Result { // TODO: Define hash algorithm for `GasCosts` - let bytes = postcard::to_stdvec(&self)?; + let bytes = postcard::to_allocvec(&self).map_err(anyhow::Error::msg)?; let hash = Hasher::default().chain(bytes).finalize(); Ok(hash.into()) @@ -204,7 +241,7 @@ impl GenesisCommitment for GasCosts { impl GenesisCommitment for ConsensusConfig { fn root(&self) -> anyhow::Result { // TODO: Define hash algorithm for `ConsensusConfig` - let bytes = postcard::to_stdvec(&self)?; + let bytes = postcard::to_allocvec(&self).map_err(anyhow::Error::msg)?; let hash = Hasher::default().chain(bytes).finalize(); Ok(hash.into()) diff --git a/crates/chain-config/src/lib.rs b/crates/chain-config/src/lib.rs index 87e4faf8c9a..a59e2474209 100644 --- a/crates/chain-config/src/lib.rs +++ b/crates/chain-config/src/lib.rs @@ -11,8 +11,15 @@ pub use genesis::GenesisCommitment; /// A default secret key to use for testing purposes only pub fn default_consensus_dev_key() -> SecretKey { - const DEV_KEY_PHRASE: &str = - "winner alley monkey elephant sun off boil hope toward boss bronze dish"; - SecretKey::new_from_mnemonic_phrase_with_path(DEV_KEY_PHRASE, "m/44'/60'/0'/0/0") - .expect("valid key") + // Derived from: + // - Mnemonic phrase: "winner alley monkey elephant sun off boil hope toward boss bronze dish" + // - Path: "m/44'/60'/0'/0/0" + // Equivalent to: + // `SecretKey::new_from_mnemonic_phrase_with_path(..)` + let bytes: [u8; 32] = [ + 0xfb, 0xe4, 0x91, 0x78, 0xda, 0xc2, 0xdf, 0x5f, 0xde, 0xa7, 0x4a, 0x11, 0xa9, + 0x0f, 0x99, 0x77, 0x62, 0x5f, 0xe0, 0x23, 0xcd, 0xf6, 0x41, 0x4b, 0xfd, 0x63, + 0x9d, 0x32, 0x7a, 0x2e, 0x9d, 0xdb, + ]; + SecretKey::try_from(bytes.as_slice()).expect("valid key") } diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index 4c70704d3a4..ac5aa411602 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -29,7 +29,7 @@ thiserror = "1.0" tracing = "0.1" [dev-dependencies] -fuel-core-types = { workspace = true, features = ["serde", "test-helpers"] } +fuel-core-types = { workspace = true, features = ["serde", "std", "test-helpers"] } insta = { workspace = true } [build-dependencies] diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index aa6c2123c32..2097c7e5440 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -98,7 +98,6 @@ use std::{ self, FromStr, }, - sync::Arc, }; use tai64::Tai64; use tracing as _; @@ -120,7 +119,7 @@ pub mod types; pub struct FuelClient { client: reqwest::Client, #[cfg(feature = "subscriptions")] - cookie: Arc, + cookie: std::sync::Arc, url: reqwest::Url, } @@ -134,18 +133,29 @@ impl FromStr for FuelClient { } let mut url = reqwest::Url::parse(&raw_url) + .map_err(anyhow::Error::msg) .with_context(|| format!("Invalid fuel-core URL: {str}"))?; url.set_path("/graphql"); - let cookie = Arc::new(reqwest::cookie::Jar::default()); - let client = reqwest::Client::builder() - .cookie_provider(cookie.clone()) - .build()?; - Ok(Self { - client, - #[cfg(feature = "subscriptions")] - cookie, - url, - }) + + #[cfg(feature = "subscriptions")] + { + let cookie = std::sync::Arc::new(reqwest::cookie::Jar::default()); + let client = reqwest::Client::builder() + .cookie_provider(cookie.clone()) + .build() + .map_err(anyhow::Error::msg)?; + Ok(Self { + client, + cookie, + url, + }) + } + + #[cfg(not(feature = "subscriptions"))] + { + let client = reqwest::Client::new(); + Ok(Self { client, url }) + } } } diff --git a/crates/database/Cargo.toml b/crates/database/Cargo.toml index 80a97ec648b..793d6d1d5ba 100644 --- a/crates/database/Cargo.toml +++ b/crates/database/Cargo.toml @@ -12,9 +12,9 @@ description = "The crates contains databases used by Fuel core protocol." [dependencies] anyhow = { workspace = true } +derive_more = { workspace = true } fuel-core-storage = { workspace = true } fuel-core-types = { workspace = true } -thiserror = "1.0" [dev-dependencies] fuel-core-trace = { path = "../trace" } diff --git a/crates/database/src/lib.rs b/crates/database/src/lib.rs index 2cf3ce23421..f30e91c0cb5 100644 --- a/crates/database/src/lib.rs +++ b/crates/database/src/lib.rs @@ -10,40 +10,40 @@ use fuel_core_storage::Error as StorageError; use fuel_core_types::services::executor::Error as ExecutorError; -use std::{ - array::TryFromSliceError, - io::ErrorKind, -}; +use std::array::TryFromSliceError; /// The error occurred during work with any of databases. -#[derive(thiserror::Error, Debug)] +#[derive(Debug, derive_more::Display, derive_more::From)] #[non_exhaustive] pub enum Error { /// Error occurred during serialization or deserialization of the entity. - #[error("error performing serialization or deserialization")] + #[display(fmt = "error performing serialization or deserialization")] Codec, /// Chain can be initialized once. - #[error("Failed to initialize chain")] + #[display(fmt = "Failed to initialize chain")] ChainAlreadyInitialized, /// Chain should be initialized before usage. - #[error("Chain is not yet initialized")] + #[display(fmt = "Chain is not yet initialized")] ChainUninitialized, /// The version of database or data is invalid (possibly not migrated). - #[error("Invalid database version, expected {expected:#x}, found {found:#x}")] + #[display( + fmt = "Invalid database version, expected {expected:#x}, found {found:#x}" + )] InvalidDatabaseVersion { /// the current database version found: u32, /// the database version expected by this build of fuel-core expected: u32, }, + /// Not related to database error. - #[error(transparent)] - Other(#[from] anyhow::Error), + #[from] + Other(anyhow::Error), } -impl From for std::io::Error { - fn from(e: Error) -> Self { - std::io::Error::new(ErrorKind::Other, e) +impl From for anyhow::Error { + fn from(error: Error) -> Self { + anyhow::Error::msg(error) } } diff --git a/crates/fuel-core/src/database.rs b/crates/fuel-core/src/database.rs index 46cc8f31391..7059d92ba46 100644 --- a/crates/fuel-core/src/database.rs +++ b/crates/fuel-core/src/database.rs @@ -194,7 +194,7 @@ impl Database { #[cfg(feature = "rocksdb")] pub fn open(path: &Path, capacity: impl Into>) -> DatabaseResult { use anyhow::Context; - let db = RocksDb::default_open(path, capacity.into()).context("Failed to open rocksdb, you may need to wipe a pre-existing incompatible db `rm -rf ~/.fuel/db`")?; + let db = RocksDb::default_open(path, capacity.into()).map_err(Into::::into).context("Failed to open rocksdb, you may need to wipe a pre-existing incompatible db `rm -rf ~/.fuel/db`")?; Ok(Database { data: Arc::new(db), diff --git a/crates/fuel-core/src/schema/tx/receipt.rs b/crates/fuel-core/src/schema/tx/receipt.rs index b46d3bb46c2..84489b732ee 100644 --- a/crates/fuel-core/src/schema/tx/receipt.rs +++ b/crates/fuel-core/src/schema/tx/receipt.rs @@ -14,13 +14,14 @@ use async_graphql::{ Enum, Object, }; -use derive_more::Display; use fuel_core_types::{ fuel_asm::Word, fuel_tx, }; -#[derive(Copy, Clone, Debug, Display, Enum, Eq, PartialEq, strum_macros::EnumIter)] +#[derive( + Copy, Clone, Debug, derive_more::Display, Enum, Eq, PartialEq, strum_macros::EnumIter, +)] pub enum ReceiptType { Call, Return, diff --git a/crates/services/executor/src/refs/contract.rs b/crates/services/executor/src/refs/contract.rs index 36fb996f949..b78cccdb524 100644 --- a/crates/services/executor/src/refs/contract.rs +++ b/crates/services/executor/src/refs/contract.rs @@ -1,3 +1,4 @@ +use core::fmt; use fuel_core_chain_config::GenesisCommitment; use fuel_core_storage::{ not_found, @@ -23,10 +24,7 @@ use fuel_core_types::{ Result as ExecutorResult, }, }; -use std::{ - borrow::Cow, - error::Error as StdError, -}; +use std::borrow::Cow; /// The wrapper around `contract_id` to simplify work with `Contract` in the database. pub struct ContractRef { @@ -116,12 +114,13 @@ pub trait ContractStorageTrait: + MerkleRootStorage + MerkleRootStorage { - type InnerError: StdError + Send + Sync + 'static; + type InnerError: fmt::Debug + fmt::Display + Send + Sync + 'static; } impl<'a, Database> GenesisCommitment for ContractRef<&'a mut Database> where Database: ContractStorageTrait, + anyhow::Error: From, { fn root(&self) -> anyhow::Result { let contract_id = *self.contract_id(); diff --git a/crates/services/importer/Cargo.toml b/crates/services/importer/Cargo.toml index 4423268ace6..a8b359ceb76 100644 --- a/crates/services/importer/Cargo.toml +++ b/crates/services/importer/Cargo.toml @@ -11,10 +11,10 @@ description = "Fuel Block Importer" [dependencies] anyhow = { workspace = true } +derive_more = { workspace = true } fuel-core-metrics = { workspace = true } fuel-core-storage = { workspace = true } fuel-core-types = { workspace = true } -thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } tracing = { workspace = true } diff --git a/crates/services/importer/src/importer.rs b/crates/services/importer/src/importer.rs index 433bf0e7cba..ed5a51d2e11 100644 --- a/crates/services/importer/src/importer.rs +++ b/crates/services/importer/src/importer.rs @@ -49,39 +49,47 @@ use tokio::sync::{ #[cfg(test)] pub mod test; -#[derive(thiserror::Error, Debug)] +#[derive(Debug, derive_more::Display, derive_more::From)] pub enum Error { - #[error("The commit is already in the progress: {0}.")] + #[display(fmt = "The commit is already in the progress: {_0}.")] SemaphoreError(TryAcquireError), - #[error("The wrong state of database during insertion of the genesis block.")] - InvalidUnderlyingDatabaseGenesisState, - #[error( - "The wrong state of database after execution of the block.\ - The actual height is {1}, when the next expected height is {0}." + #[display( + fmt = "The wrong state of database during insertion of the genesis block." )] + InvalidUnderlyingDatabaseGenesisState, + #[display(fmt = "The wrong state of database after execution of the block.\ + The actual height is {_1}, when the next expected height is {_0}.")] InvalidDatabaseStateAfterExecution(BlockHeight, BlockHeight), - #[error("Got overflow during increasing the height.")] + #[display(fmt = "Got overflow during increasing the height.")] Overflow, - #[error("The non-generic block can't have zero height.")] + #[display(fmt = "The non-generic block can't have zero height.")] ZeroNonGenericHeight, - #[error("The actual height is {1}, when the next expected height is {0}.")] + #[display(fmt = "The actual height is {_1}, when the next expected height is {_0}.")] IncorrectBlockHeight(BlockHeight, BlockHeight), - #[error( - "Got another block id after validation of the block. Expected {0} != Actual {1}" + #[display( + fmt = "Got another block id after validation of the block. Expected {_0} != Actual {_1}" )] BlockIdMismatch(BlockId, BlockId), - #[error("Some of the block fields are not valid: {0}.")] + #[display(fmt = "Some of the block fields are not valid: {_0}.")] FailedVerification(anyhow::Error), - #[error("The execution of the block failed: {0}.")] + #[display(fmt = "The execution of the block failed: {_0}.")] FailedExecution(executor::Error), - #[error("It is not possible to skip transactions during importing of the block.")] + #[display( + fmt = "It is not possible to skip transactions during importing of the block." + )] SkippedTransactionsNotEmpty, - #[error("It is not possible to execute the genesis block.")] + #[display(fmt = "It is not possible to execute the genesis block.")] ExecuteGenesis, - #[error("The database already contains the data at the height {0}.")] + #[display(fmt = "The database already contains the data at the height {_0}.")] NotUnique(BlockHeight), - #[error(transparent)] - StorageError(#[from] StorageError), + #[from] + StorageError(StorageError), +} + +impl From for anyhow::Error { + fn from(error: Error) -> Self { + anyhow::Error::msg(error) + } } #[cfg(test)] diff --git a/crates/services/producer/Cargo.toml b/crates/services/producer/Cargo.toml index abcd62c1ddc..fee135e14b1 100644 --- a/crates/services/producer/Cargo.toml +++ b/crates/services/producer/Cargo.toml @@ -12,9 +12,9 @@ version = { workspace = true } [dependencies] anyhow = { workspace = true } async-trait = { workspace = true } +derive_more = { workspace = true } fuel-core-storage = { workspace = true } fuel-core-types = { workspace = true } -thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } tokio-rayon = { workspace = true } tracing = { workspace = true } diff --git a/crates/services/producer/src/block_producer.rs b/crates/services/producer/src/block_producer.rs index 16bda6fbd79..bd3d1c82629 100644 --- a/crates/services/producer/src/block_producer.rs +++ b/crates/services/producer/src/block_producer.rs @@ -33,28 +33,35 @@ use fuel_core_types::{ tai64::Tai64, }; use std::sync::Arc; -use thiserror::Error; use tokio::sync::Mutex; use tracing::debug; #[cfg(test)] mod tests; -#[derive(Error, Debug)] +#[derive(Debug, derive_more::Display)] pub enum Error { - #[error( - "0 is an invalid block height for production. It is reserved for genesis data." + #[display( + fmt = "0 is an invalid block height for production. It is reserved for genesis data." )] GenesisBlock, - #[error("Previous block height {0} doesn't exist")] + #[display(fmt = "Previous block height {_0} doesn't exist")] MissingBlock(BlockHeight), - #[error("Best finalized da_height {best} is behind previous block da_height {previous_block}")] + #[display( + fmt = "Best finalized da_height {best} is behind previous block da_height {previous_block}" + )] InvalidDaFinalizationState { best: DaBlockHeight, previous_block: DaBlockHeight, }, } +impl From for anyhow::Error { + fn from(error: Error) -> Self { + anyhow::Error::msg(error) + } +} + pub struct Producer { pub config: Config, pub db: Database, @@ -107,6 +114,7 @@ where let result = self .executor .execute_without_commit(component) + .map_err(Into::::into) .context(context_string)?; debug!("Produced block with result: {:?}", result.result()); diff --git a/crates/services/relayer/src/log.rs b/crates/services/relayer/src/log.rs index 3fe4a54eeb2..723459f7ac3 100644 --- a/crates/services/relayer/src/log.rs +++ b/crates/services/relayer/src/log.rs @@ -68,7 +68,8 @@ impl TryFrom<&Log> for EthEventLog { data: log.data.to_vec(), }; - let message = abi::bridge::MessageSentFilter::decode_log(&raw_log)?; + let message = abi::bridge::MessageSentFilter::decode_log(&raw_log) + .map_err(anyhow::Error::msg)?; let amount = message.amount; let data = message.data.to_vec(); let mut nonce = Nonce::zeroed(); diff --git a/crates/services/relayer/src/service.rs b/crates/services/relayer/src/service.rs index 4d6b4280db3..7e6521754ba 100644 --- a/crates/services/relayer/src/service.rs +++ b/crates/services/relayer/src/service.rs @@ -304,7 +304,7 @@ impl SharedState { where D: RelayerDb + 'static, { - Ok(self.database.get_finalized_da_height()?) + self.database.get_finalized_da_height().map_err(Into::into) } } @@ -322,7 +322,7 @@ where Err(anyhow::anyhow!("The relayer got a stop signal")) }, block = self.eth_node.get_block(ethers_core::types::BlockNumber::Finalized) => { - let block_number = block? + let block_number = block.map_err(anyhow::Error::msg)? .and_then(|block| block.number) .ok_or(anyhow::anyhow!("Block pending"))? .as_u64(); diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index 5322d503b32..707e4962241 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -18,10 +18,10 @@ version = { workspace = true } [dependencies] anyhow = { workspace = true } -fuel-core-types = { workspace = true } -fuel-vm-private = { workspace = true } +derive_more = { workspace = true } +fuel-core-types = { workspace = true, default-features = false } +fuel-vm-private = { workspace = true, default-features = false } mockall = { workspace = true, optional = true } -thiserror = { workspace = true } [features] test-helpers = ["dep:mockall"] diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index 771d40d75fc..9636d00fb27 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -9,7 +9,6 @@ #![deny(warnings)] use fuel_core_types::services::executor::Error as ExecutorError; -use std::io::ErrorKind; pub use fuel_vm_private::{ fuel_storage::*, @@ -33,28 +32,28 @@ pub use fuel_vm_private::storage::{ /// The storage result alias. pub type Result = core::result::Result; -#[derive(thiserror::Error, Debug)] +#[derive(Debug, derive_more::Display, derive_more::From)] #[non_exhaustive] /// Error occurring during interaction with storage pub enum Error { /// Error occurred during serialization or deserialization of the entity. - #[error("error performing serialization or deserialization")] + #[display(fmt = "error performing serialization or deserialization")] Codec, /// Error occurred during interaction with database. - #[error("error occurred in the underlying datastore `{0}`")] - DatabaseError(Box), + #[display(fmt = "error occurred in the underlying datastore `{_0:?}`")] + DatabaseError(Box), /// This error should be created with `not_found` macro. - #[error("resource of type `{0}` was not found at the: {1}")] + #[display(fmt = "resource of type `{_0}` was not found at the: {_1}")] NotFound(&'static str, &'static str), // TODO: Do we need this type at all? /// Unknown or not expected(by architecture) error. - #[error(transparent)] - Other(#[from] anyhow::Error), + #[from] + Other(anyhow::Error), } -impl From for std::io::Error { - fn from(e: Error) -> Self { - std::io::Error::new(ErrorKind::Other, e) +impl From for anyhow::Error { + fn from(error: Error) -> Self { + anyhow::Error::msg(error) } } diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index d488b3e51eb..d3cde9bb4cf 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -19,7 +19,7 @@ version = { workspace = true } [dependencies] anyhow = { workspace = true } derive_more = { version = "0.99" } -fuel-vm-private = { workspace = true } +fuel-vm-private = { workspace = true, default-features = false, features = ["alloc"] } secrecy = "0.8" serde = { workspace = true, features = ["derive"], optional = true } tai64 = { version = "4.0", features = ["serde"] } @@ -27,7 +27,8 @@ thiserror = "1.0" zeroize = "1.5" [features] -default = [] +default = ["std"] serde = ["dep:serde", "fuel-vm-private/serde"] +std = ["fuel-vm-private/std"] random = ["fuel-vm-private/random"] test-helpers = ["random", "fuel-vm-private/test-helpers"] diff --git a/crates/types/src/blockchain/consensus.rs b/crates/types/src/blockchain/consensus.rs index 905124f1a50..791361ee22b 100644 --- a/crates/types/src/blockchain/consensus.rs +++ b/crates/types/src/blockchain/consensus.rs @@ -35,7 +35,10 @@ impl Consensus { match &self { Consensus::Genesis(_) => Ok(Address::zeroed()), Consensus::PoA(poa_data) => { - let public_key = poa_data.signature.recover(block_id.as_message())?; + let public_key = poa_data + .signature + .recover(block_id.as_message()) + .map_err(|e| anyhow::anyhow!("Can't recover public key: {:?}", e))?; let address = Input::owner(&public_key); Ok(address) } diff --git a/crates/types/src/services/executor.rs b/crates/types/src/services/executor.rs index 00d29aac25c..c3e436c395e 100644 --- a/crates/types/src/services/executor.rs +++ b/crates/types/src/services/executor.rs @@ -242,58 +242,64 @@ impl ExecutionKind { } #[allow(missing_docs)] -#[derive(thiserror::Error, Debug)] +#[derive(Debug, derive_more::Display, derive_more::From)] #[non_exhaustive] pub enum Error { - #[error("Transaction id was already used: {0:#x}")] + #[display(fmt = "Transaction id was already used: {_0:#x}")] TransactionIdCollision(Bytes32), - #[error("output already exists")] + #[display(fmt = "output already exists")] OutputAlreadyExists, - #[error("The computed fee caused an integer overflow")] + #[display(fmt = "The computed fee caused an integer overflow")] FeeOverflow, - #[error("Not supported transaction: {0:?}")] + #[display(fmt = "Not supported transaction: {_0:?}")] NotSupportedTransaction(TxId), - #[error("The first transaction in the block is not `Mint` - coinbase.")] + #[display(fmt = "The first transaction in the block is not `Mint` - coinbase.")] CoinbaseIsNotFirstTransaction, - #[error("Coinbase should have one output.")] + #[display(fmt = "Coinbase should have one output.")] CoinbaseSeveralOutputs, - #[error("Coinbase outputs is invalid.")] + #[display(fmt = "Coinbase outputs is invalid.")] CoinbaseOutputIsInvalid, - #[error("Coinbase amount mismatches with expected.")] + #[display(fmt = "Coinbase amount mismatches with expected.")] CoinbaseAmountMismatch, - #[error("Invalid transaction: {0}")] - TransactionValidity(#[from] TransactionValidityError), + #[from] + TransactionValidity(TransactionValidityError), // TODO: Replace with `fuel_core_storage::Error` when execution error will live in the // `fuel-core-executor`. - #[error("got error during work with storage {0}")] + #[display(fmt = "got error during work with storage {_0}")] StorageError(anyhow::Error), - #[error("got error during work with relayer {0}")] + #[display(fmt = "got error during work with relayer {_0}")] RelayerError(Box), - #[error("Transaction({transaction_id:#x}) execution error: {error:?}")] + #[display(fmt = "Transaction({transaction_id:#x}) execution error: {error:?}")] VmExecution { // TODO: Replace with `fuel_core_storage::Error` when execution error will live in the // `fuel-core-executor`. error: InterpreterError, transaction_id: Bytes32, }, - #[error("{0:?}")] + #[display(fmt = "{_0:?}")] InvalidTransaction(CheckError), - #[error("Execution error with backtrace")] + #[display(fmt = "Execution error with backtrace")] Backtrace(Box), - #[error("Transaction doesn't match expected result: {transaction_id:#x}")] + #[display(fmt = "Transaction doesn't match expected result: {transaction_id:#x}")] InvalidTransactionOutcome { transaction_id: Bytes32 }, - #[error("The amount of charged fees is invalid")] + #[display(fmt = "The amount of charged fees is invalid")] InvalidFeeAmount, - #[error("Block id is invalid")] + #[display(fmt = "Block id is invalid")] InvalidBlockId, - #[error("No matching utxo for contract id ${0:#x}")] + #[display(fmt = "No matching utxo for contract id ${_0:#x}")] ContractUtxoMissing(ContractId), - #[error("message already spent {0:#x}")] + #[display(fmt = "message already spent {_0:#x}")] MessageAlreadySpent(Nonce), - #[error("Expected input of type {0}")] + #[display(fmt = "Expected input of type {_0}")] InputTypeMismatch(String), } +impl From for anyhow::Error { + fn from(error: Error) -> Self { + anyhow::Error::msg(error) + } +} + impl From for Error { fn from(e: Backtrace) -> Self { Error::Backtrace(Box::new(e))