From 4c65bd2e9a840e7a82823ec77f3df7ae45b25325 Mon Sep 17 00:00:00 2001 From: Green Baneling Date: Mon, 23 Jan 2023 04:20:15 +0000 Subject: [PATCH] External block verifier (#916) Refs: https://github.com/FuelLabs/fuel-core/issues/883 It is the implementation of the block verifier. Right now it provides only one function to verify the block's fields. But in the future, we can add verification of the seals(it is why it holds a relayer) and reuse this verifier in the `fuel-core-sync`. Along with the verifier, I changed the `fuel_core_poa::Config` and `ChainConfig` to have rules for the block's time of the network and separate rules for block production. - Added `NodeRole::Producer` and `NodeRole::Validator`. Based on the role, we will create a `fuel_core_poa::Config`. - Removed `Never` from `BlockProduction` from `ChainConfig` because it is the block production information of the `PoA`. No unit tests right now. Co-authored-by: Tom Co-authored-by: Voxelot --- Cargo.lock | 44 +++--- Cargo.toml | 1 + README.md | 9 ++ bin/fuel-core/Cargo.toml | 7 +- bin/fuel-core/src/cli/run.rs | 73 ++++++--- bin/fuel-core/src/cli/run/consensus.rs | 148 ++++++++++++++++++ bin/fuel-core/src/cli/run/relayer.rs | 4 +- bin/fuel-core/src/cli/snapshot.rs | 2 +- crates/chain-config/Cargo.toml | 1 - crates/chain-config/src/config/chain.rs | 14 -- crates/chain-config/src/lib.rs | 2 + ...s__snapshot_configurable_block_height.snap | 5 - ...ests__snapshot_contract_with_balances.snap | 5 - ...__tests__snapshot_contract_with_state.snap | 5 - ..._tests__snapshot_local_testnet_config.snap | 5 - ...ig__tests__snapshot_simple_coin_state.snap | 5 - ...nfig__tests__snapshot_simple_contract.snap | 5 - ..._tests__snapshot_simple_message_state.snap | 5 - crates/fuel-core/Cargo.toml | 2 + crates/fuel-core/src/service/config.rs | 22 ++- crates/fuel-core/src/service/sub_services.rs | 28 ++-- crates/services/consensus_module/Cargo.toml | 20 +++ .../services/consensus_module/poa/Cargo.toml | 6 +- .../consensus_module/poa/src/config.rs | 16 +- .../services/consensus_module/poa/src/lib.rs | 1 + .../consensus_module/poa/src/service.rs | 3 + .../consensus_module/poa/src/verifier.rs | 78 +++++++++ .../poa/src/verifier/tests.rs | 111 +++++++++++++ .../consensus_module/src/block_verifier.rs | 98 ++++++++++++ .../src/block_verifier/config.rs | 24 +++ .../src/block_verifier/tests.rs | 69 ++++++++ crates/services/consensus_module/src/lib.rs | 7 + tests/tests/trigger_integration/hybrid.rs | 22 ++- tests/tests/trigger_integration/instant.rs | 6 +- tests/tests/trigger_integration/interval.rs | 14 +- tests/tests/trigger_integration/never.rs | 6 +- tests/tests/tx/txn_status_subscription.rs | 9 +- 37 files changed, 712 insertions(+), 170 deletions(-) create mode 100644 bin/fuel-core/src/cli/run/consensus.rs create mode 100644 crates/services/consensus_module/Cargo.toml create mode 100644 crates/services/consensus_module/poa/src/verifier.rs create mode 100644 crates/services/consensus_module/poa/src/verifier/tests.rs create mode 100644 crates/services/consensus_module/src/block_verifier.rs create mode 100644 crates/services/consensus_module/src/block_verifier/config.rs create mode 100644 crates/services/consensus_module/src/block_verifier/tests.rs create mode 100644 crates/services/consensus_module/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 72524ee2b0f..adf1e1fe8da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -934,12 +934,12 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.32" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39" +checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2" dependencies = [ "bitflags", - "clap_derive 4.0.21", + "clap_derive 4.1.0", "clap_lex 0.3.0", "is-terminal", "once_cell", @@ -962,9 +962,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.0.21" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" dependencies = [ "heck", "proc-macro-error", @@ -2273,11 +2273,13 @@ dependencies = [ "async-trait", "axum", "bincode", + "clap 4.1.1", "derive_more", "enum-iterator", "fuel-core-chain-config", "fuel-core-database", "fuel-core-executor", + "fuel-core-importer", "fuel-core-metrics", "fuel-core-p2p", "fuel-core-poa", @@ -2314,7 +2316,7 @@ dependencies = [ name = "fuel-core-benches" version = "0.0.0" dependencies = [ - "clap 4.0.32", + "clap 4.1.1", "criterion", "ctrlc", "fuel-core", @@ -2340,12 +2342,13 @@ name = "fuel-core-bin" version = "0.15.1" dependencies = [ "anyhow", - "clap 3.2.23", + "clap 4.1.1", "dirs", "fuel-core", + "humantime", "lazy_static", "serde_json", - "strum", + "test-case", "tokio", "tracing", "tracing-subscriber", @@ -2359,7 +2362,6 @@ dependencies = [ "anyhow", "bech32 0.9.1", "bincode", - "fuel-core-poa", "fuel-core-storage", "fuel-core-types", "hex", @@ -2405,6 +2407,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "fuel-core-consensus-module" +version = "0.15.1" +dependencies = [ + "anyhow", + "fuel-core-chain-config", + "fuel-core-poa", + "fuel-core-types", + "test-case", +] + [[package]] name = "fuel-core-database" version = "0.15.1" @@ -2510,10 +2523,9 @@ dependencies = [ "fuel-core-services", "fuel-core-storage", "fuel-core-types", - "humantime-serde", "mockall", "rand 0.8.5", - "serde", + "test-case", "tokio", "tokio-stream", "tracing", @@ -3189,16 +3201,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" -[[package]] -name = "humantime-serde" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" -dependencies = [ - "humantime", - "serde", -] - [[package]] name = "hyper" version = "0.14.23" diff --git a/Cargo.toml b/Cargo.toml index 4ab74ed885c..2ac74e0d7be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "crates/fuel-core", "crates/metrics", "crates/services", + "crates/services/consensus_module", "crates/services/consensus_module/bft", "crates/services/consensus_module/poa", "crates/services/executor", diff --git a/README.md b/README.md index 3d9b4b85500..32270b9c96a 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,15 @@ $ ./target/debug/fuel-core run --db-type in-memory Jul 12 23:28:47.238 INFO fuel_core: Binding GraphQL provider to 127.0.0.1:4000 ``` +To disable block production on your local node, set `--poa-instant=false` + +#### Example + +```console +$ ./target/debug/fuel-core run --poa-instant=false +2023-01-23T02:25:18.787401Z INFO fuel_core::cli::run: 173: Block production disabled. +``` + #### Troubleshooting ##### Outdated database diff --git a/bin/fuel-core/Cargo.toml b/bin/fuel-core/Cargo.toml index 2688e719866..e0dc2dde841 100644 --- a/bin/fuel-core/Cargo.toml +++ b/bin/fuel-core/Cargo.toml @@ -17,12 +17,12 @@ path = "src/main.rs" [dependencies] anyhow = "1.0" -clap = { version = "3.2", features = ["derive", "env"] } +clap = { version = "4.1", features = ["derive", "env"] } dirs = "4.0" fuel-core = { path = "../../crates/fuel-core", version = "0.15.1" } +humantime = "2.1" lazy_static = "1.4" serde_json = { version = "1.0", features = ["raw_value"], optional = true } -strum = "0.24" tokio = { version = "1.21", features = ["macros", "rt-multi-thread"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = [ @@ -32,6 +32,9 @@ tracing-subscriber = { version = "0.3", features = [ ] } url = { version = "2.2", optional = true } +[dev-dependencies] +test-case = "2.2" + [features] debug = ["fuel-core/debug"] default = ["debug", "metrics", "relayer", "rocksdb"] diff --git a/bin/fuel-core/src/cli/run.rs b/bin/fuel-core/src/cli/run.rs index 04a292b6ec7..25d9fecba9b 100644 --- a/bin/fuel-core/src/cli/run.rs +++ b/bin/fuel-core/src/cli/run.rs @@ -1,6 +1,9 @@ #![allow(unused_variables)] use crate::{ - cli::DEFAULT_DB_PATH, + cli::{ + run::consensus::PoATriggerArgs, + DEFAULT_DB_PATH, + }, FuelService, }; use anyhow::{ @@ -12,7 +15,10 @@ use fuel_core::{ chain_config::ChainConfig, producer::Config as ProducerConfig, service::{ - config::default_consensus_dev_key, + config::{ + default_consensus_dev_key, + Trigger, + }, Config, DbType, ServiceTrait, @@ -36,7 +42,6 @@ use std::{ path::PathBuf, str::FromStr, }; -use strum::VariantNames; use tracing::{ info, log::warn, @@ -48,13 +53,14 @@ pub const CONSENSUS_KEY_ENV: &str = "CONSENSUS_KEY_SECRET"; #[cfg(feature = "p2p")] mod p2p; +mod consensus; #[cfg(feature = "relayer")] mod relayer; /// Run the Fuel client node locally. #[derive(Debug, Clone, Parser)] pub struct Command { - #[clap(long = "ip", default_value = "127.0.0.1", parse(try_from_str))] + #[clap(long = "ip", default_value = "127.0.0.1", value_parser)] pub ip: net::IpAddr, #[clap(long = "port", default_value = "4000")] @@ -63,49 +69,58 @@ pub struct Command { #[clap( name = "DB_PATH", long = "db-path", - parse(from_os_str), + value_parser, default_value = (*DEFAULT_DB_PATH).to_str().unwrap() )] pub database_path: PathBuf, - #[clap(long = "db-type", default_value = "rocks-db", possible_values = &*DbType::VARIANTS, ignore_case = true)] + #[clap( + long = "db-type", + default_value = "rocks-db", + value_enum, + ignore_case = true + )] pub database_type: DbType, /// Specify either an alias to a built-in configuration or filepath to a JSON file. - #[clap(name = "CHAIN_CONFIG", long = "chain", default_value = "local_testnet")] + #[arg(name = "CHAIN_CONFIG", long = "chain", default_value = "local_testnet")] pub chain_config: String, /// Allows GraphQL Endpoints to arbitrarily advanced blocks. Should be used for local development only - #[clap(long = "manual_blocks_enabled")] + #[arg(long = "manual_blocks_enabled")] pub manual_blocks_enabled: bool, /// Enable logging of backtraces from vm errors - #[clap(long = "vm-backtrace")] + #[arg(long = "vm-backtrace")] pub vm_backtrace: bool, /// Enable full utxo stateful validation /// disabled by default until downstream consumers stabilize - #[clap(long = "utxo-validation")] + #[arg(long = "utxo-validation")] pub utxo_validation: bool, /// The minimum allowed gas price - #[clap(long = "min-gas-price", default_value = "0")] + #[arg(long = "min-gas-price", default_value = "0")] pub min_gas_price: u64, /// The signing key used when producing blocks. /// Setting via the `CONSENSUS_KEY_SECRET` ENV var is preferred. - #[clap(long = "consensus-key")] + #[arg(long = "consensus-key")] pub consensus_key: Option, + /// A new block is produced instantly when transactions are available. + #[clap(flatten)] + pub poa_trigger: PoATriggerArgs, + /// Use a default insecure consensus key for testing purposes. /// This will not be enabled by default in the future. - #[clap(long = "dev-keys", default_value = "true")] + #[arg(long = "dev-keys", default_value = "true")] pub consensus_dev_key: bool, /// The block's fee recipient public key. /// /// If not set, `consensus_key` is used as the provider of the `Address`. - #[clap(long = "coinbase-recipient")] + #[arg(long = "coinbase-recipient")] pub coinbase_recipient: Option, #[cfg(feature = "relayer")] @@ -116,7 +131,7 @@ pub struct Command { #[cfg(feature = "p2p")] pub p2p_args: p2p::P2PArgs, - #[clap(long = "metrics")] + #[arg(long = "metrics")] pub metrics: bool, } @@ -133,6 +148,7 @@ impl Command { utxo_validation, min_gas_price, consensus_key, + poa_trigger, consensus_dev_key, coinbase_recipient, #[cfg(feature = "relayer")] @@ -149,9 +165,17 @@ impl Command { #[cfg(feature = "p2p")] let p2p_cfg = p2p_args.into_config(metrics)?; + let trigger: Trigger = poa_trigger.into(); + + if trigger != Trigger::Never { + info!("Block production mode: {:?}", &trigger); + } else { + info!("Block production disabled"); + } + // if consensus key is not configured, fallback to dev consensus key let consensus_key = load_consensus_key(consensus_key)?.or_else(|| { - if consensus_dev_key { + if consensus_dev_key && trigger != Trigger::Never { let key = default_consensus_dev_key(); warn!( "Fuel Core is using an insecure test key for consensus. Public key: {}", @@ -164,16 +188,21 @@ impl Command { } }); + if consensus_key.is_some() && trigger == Trigger::Never { + warn!("Consensus key configured but block production is disabled!") + } + let coinbase_recipient = if let Some(coinbase_recipient) = coinbase_recipient { Address::from_str(coinbase_recipient.as_str()).map_err(|err| anyhow!(err))? } else { - let consensus_key = consensus_key + consensus_key .as_ref() .cloned() - .unwrap_or_else(|| Secret::new(SecretKeyWrapper::default())); - - let sk = consensus_key.expose_secret().deref(); - Address::from(*sk.public_key().hash()) + .map(|key| { + let sk = key.expose_secret().deref(); + Address::from(*sk.public_key().hash()) + }) + .unwrap_or_default() }; Ok(Config { @@ -183,6 +212,7 @@ impl Command { chain_conf: chain_conf.clone(), utxo_validation, manual_blocks_enabled, + block_production: trigger, vm: VMConfig { backtrace: vm_backtrace, }, @@ -193,6 +223,7 @@ impl Command { metrics, }, block_executor: Default::default(), + block_importer: Default::default(), #[cfg(feature = "relayer")] relayer: relayer_args.into(), #[cfg(feature = "p2p")] diff --git a/bin/fuel-core/src/cli/run/consensus.rs b/bin/fuel-core/src/cli/run/consensus.rs new file mode 100644 index 00000000000..378614015cd --- /dev/null +++ b/bin/fuel-core/src/cli/run/consensus.rs @@ -0,0 +1,148 @@ +//! Clap configuration related to consensus parameters + +use clap::{ + ArgGroup, + ValueEnum, +}; +use fuel_core::service::config::Trigger as PoATrigger; +use humantime::Duration; + +#[derive(Debug, Clone, clap::Args)] +pub struct PoATriggerArgs { + #[clap(flatten)] + instant: Instant, + #[clap(flatten)] + hybrid: Hybrid, + #[clap(flatten)] + interval: Interval, +} + +// Convert from arg struct to PoATrigger enum +impl From for PoATrigger { + fn from(value: PoATriggerArgs) -> Self { + match value { + PoATriggerArgs { + hybrid: + Hybrid { + idle_time: Some(idle_time), + min_time: Some(min_time), + max_time: Some(max_time), + }, + .. + } => PoATrigger::Hybrid { + min_block_time: min_time.into(), + max_tx_idle_time: idle_time.into(), + max_block_time: max_time.into(), + }, + PoATriggerArgs { + interval: Interval { period: Some(p) }, + .. + } => PoATrigger::Interval { + block_time: p.into(), + }, + PoATriggerArgs { instant, .. } if instant.instant == Boolean::True => { + PoATrigger::Instant + } + _ => PoATrigger::Never, + } + } +} + +#[derive(Debug, Clone, clap::Args)] +#[clap( + group = ArgGroup::new("instant-mode").args(&["instant"]).conflicts_with_all(&["interval-mode", "hybrid-mode"]), +)] +struct Instant { + /// Use instant block production mode. + /// Newly submitted txs will immediately trigger the production of the next block. + /// Cannot be combined with other poa flags. + #[arg(long = "poa-instant", default_value = "true", value_parser)] + instant: Boolean, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +enum Boolean { + True, + False, +} + +#[derive(Debug, Clone, clap::Args)] +#[clap( + group = ArgGroup::new("hybrid-mode") + .args(&["min_time", "idle_time", "max_time"]) + .multiple(true) + .conflicts_with_all(&["interval-mode", "instant-mode"]), +)] +struct Hybrid { + /// Hybrid trigger option. + /// Sets a minimum lower bound between blocks. This should be set high enough to ensure + /// peers can sync the blockchain. + /// Cannot be combined with other poa mode options (instant or interval). + #[arg(long = "poa-hybrid-min-time", requires_all = ["idle_time", "max_time"])] + min_time: Option, + /// Hybrid trigger option. + /// Sets the max time block production will wait after a period of inactivity before producing + /// a new block. If there are txs available but not enough for a full block, + /// this is how long the trigger will wait for more txs. + /// This ensures that if a burst of transactions are submitted, + /// they will all be included into the next block instead of making a new block immediately and + /// then waiting for the minimum block time to process the rest. + /// Cannot be combined with other poa mode options (instant or interval). + #[arg(long = "poa-hybrid-idle-time", requires_all = ["min_time", "max_time"])] + idle_time: Option, + /// Hybrid trigger option. + /// Sets the maximum time block production will wait to produce a block (even if empty). This + /// ensures that there is a regular cadence even under sustained load. + /// Cannot be combined with other poa mode options (instant or interval). + #[arg(long = "poa-hybrid-max-time", requires_all = ["min_time", "idle_time"])] + max_time: Option, +} + +#[derive(Debug, Clone, clap::Args)] +#[clap( + group = ArgGroup::new("interval-mode").args(&["period"]).conflicts_with_all(&["instant-mode", "hybrid-mode"]), +)] +struct Interval { + /// Interval trigger option. + /// Produces blocks on a fixed interval regardless of txpool activity. + /// Cannot be combined with other poa flags. + #[clap(long = "poa-interval-period")] + pub period: Option, +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::Parser; + use fuel_core::service::config::Trigger; + use std::time::Duration as StdDuration; + use test_case::test_case; + + #[derive(Debug, Clone, Parser)] + pub struct Command { + #[clap(flatten)] + trigger: PoATriggerArgs, + } + + pub fn hybrid(min_t: u64, mi_t: u64, max_t: u64) -> Trigger { + Trigger::Hybrid { + min_block_time: StdDuration::from_secs(min_t), + max_tx_idle_time: StdDuration::from_secs(mi_t), + max_block_time: StdDuration::from_secs(max_t), + } + } + + #[test_case(&[] => Ok(Trigger::Instant); "defaults to instant trigger")] + #[test_case(&["", "--poa-instant=false"] => Ok(Trigger::Never); "never trigger if instant is explicitly disabled")] + #[test_case(&["", "--poa-interval-period=1s"] => Ok(Trigger::Interval { block_time: StdDuration::from_secs(1)}); "uses interval mode if set")] + #[test_case(&["", "--poa-hybrid-min-time=1s", "--poa-hybrid-idle-time=2s", "--poa-hybrid-max-time=3s"] => Ok(hybrid(1,2,3)); "uses hybrid mode if set")] + #[test_case(&["", "--poa-interval-period=1s", "--poa-hybrid-min-time=1s", "--poa-hybrid-idle-time=2s", "--poa-hybrid-max-time=3s"] => Err(()); "can't set hybrid and interval at the same time")] + #[test_case(&["", "--poa-instant=true", "--poa-hybrid-min-time=1s", "--poa-hybrid-idle-time=2s", "--poa-hybrid-max-time=3s"] => Err(()); "can't set hybrid and instant at the same time")] + #[test_case(&["", "--poa-instant=true", "--poa-interval-period=1s"] => Err(()); "can't set interval and instant at the same time")] + #[test_case(&["", "--poa-hybrid-min-time=1s"] => Err(()); "can't set hybrid min time without idle and max")] + fn parse(args: &[&str]) -> Result { + Command::try_parse_from(args) + .map_err(|_| ()) + .map(|c| c.trigger.into()) + } +} diff --git a/bin/fuel-core/src/cli/run/relayer.rs b/bin/fuel-core/src/cli/run/relayer.rs index a4c1f93f98b..d8b67b72c06 100644 --- a/bin/fuel-core/src/cli/run/relayer.rs +++ b/bin/fuel-core/src/cli/run/relayer.rs @@ -13,11 +13,11 @@ use std::str::FromStr; pub struct RelayerArgs { /// Uri address to ethereum client. It can be in format of `http://localhost:8545/` or `ws://localhost:8545/`. /// If not set relayer will not start. - #[clap(long = "relayer")] + #[arg(long = "relayer")] pub eth_client: Option, /// Ethereum contract address. Create EthAddress into fuel_types - #[clap(long = "relayer-v2-listening-contracts", parse(try_from_str = parse_h160))] + #[arg(long = "relayer-v2-listening-contracts", value_parser = parse_h160)] pub eth_v2_listening_contracts: Vec, /// Number of da block after which messages/stakes/validators become finalized. diff --git a/bin/fuel-core/src/cli/snapshot.rs b/bin/fuel-core/src/cli/snapshot.rs index 94b6e95956f..fab0747f95a 100644 --- a/bin/fuel-core/src/cli/snapshot.rs +++ b/bin/fuel-core/src/cli/snapshot.rs @@ -8,7 +8,7 @@ pub struct Command { #[clap( name = "DB_PATH", long = "db-path", - parse(from_os_str), + value_parser, default_value = (*DEFAULT_DB_PATH).to_str().unwrap() )] pub database_path: PathBuf, diff --git a/crates/chain-config/Cargo.toml b/crates/chain-config/Cargo.toml index 4878f8c6302..6bb668dccc2 100644 --- a/crates/chain-config/Cargo.toml +++ b/crates/chain-config/Cargo.toml @@ -14,7 +14,6 @@ description = "Fuel Chain config types" anyhow = "1.0" bech32 = "0.9.0" bincode = "1.3" -fuel-core-poa = { path = "../services/consensus_module/poa", version = "0.15.1" } fuel-core-storage = { path = "../storage", version = "0.15.1" } fuel-core-types = { path = "../types", version = "0.15.1", features = [ "serde", diff --git a/crates/chain-config/src/config/chain.rs b/crates/chain-config/src/config/chain.rs index 359490fcd71..9e32834ddc7 100644 --- a/crates/chain-config/src/config/chain.rs +++ b/crates/chain-config/src/config/chain.rs @@ -56,7 +56,6 @@ pub const TESTNET_INITIAL_BALANCE: u64 = 10_000_000; #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] pub struct ChainConfig { pub chain_name: String, - pub block_production: BlockProduction, pub block_gas_limit: u64, #[serde(default)] pub initial_state: Option, @@ -70,9 +69,6 @@ impl Default for ChainConfig { fn default() -> Self { Self { chain_name: "local".into(), - block_production: BlockProduction::ProofOfAuthority { - trigger: fuel_core_poa::Trigger::Instant, - }, block_gas_limit: ConsensusParameters::DEFAULT.max_gas_per_tx * 10, /* TODO: Pick a sensible default */ transaction_parameters: ConsensusParameters::DEFAULT, initial_state: None, @@ -173,13 +169,3 @@ impl GenesisCommitment for ConsensusParameters { Ok(params_hash.into()) } } - -/// Block production mode and settings -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum BlockProduction { - /// Proof-of-authority modes - ProofOfAuthority { - #[serde(flatten)] - trigger: fuel_core_poa::Trigger, - }, -} diff --git a/crates/chain-config/src/lib.rs b/crates/chain-config/src/lib.rs index 4c7b079a044..55077defe51 100644 --- a/crates/chain-config/src/lib.rs +++ b/crates/chain-config/src/lib.rs @@ -1,3 +1,5 @@ +#![deny(unused_crate_dependencies)] + pub mod config; mod genesis; mod serialization; diff --git a/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_configurable_block_height.snap b/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_configurable_block_height.snap index 65abfd83912..656bdb35c55 100644 --- a/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_configurable_block_height.snap +++ b/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_configurable_block_height.snap @@ -4,11 +4,6 @@ expression: json --- { "chain_name": "local_testnet", - "block_production": { - "ProofOfAuthority": { - "trigger": "instant" - } - }, "block_gas_limit": 1000000000, "initial_state": { "height": "0x0000000014c8be1f" diff --git a/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_contract_with_balances.snap b/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_contract_with_balances.snap index 6c6415c6697..330ac915e79 100644 --- a/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_contract_with_balances.snap +++ b/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_contract_with_balances.snap @@ -4,11 +4,6 @@ expression: json --- { "chain_name": "local_testnet", - "block_production": { - "ProofOfAuthority": { - "trigger": "instant" - } - }, "block_gas_limit": 1000000000, "initial_state": { "contracts": [ diff --git a/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_contract_with_state.snap b/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_contract_with_state.snap index 06941596264..c49532980c5 100644 --- a/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_contract_with_state.snap +++ b/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_contract_with_state.snap @@ -4,11 +4,6 @@ expression: json --- { "chain_name": "local_testnet", - "block_production": { - "ProofOfAuthority": { - "trigger": "instant" - } - }, "block_gas_limit": 1000000000, "initial_state": { "contracts": [ diff --git a/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_local_testnet_config.snap b/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_local_testnet_config.snap index 9986277be69..916d1dca28d 100644 --- a/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_local_testnet_config.snap +++ b/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_local_testnet_config.snap @@ -4,11 +4,6 @@ expression: json --- { "chain_name": "local_testnet", - "block_production": { - "ProofOfAuthority": { - "trigger": "instant" - } - }, "block_gas_limit": 1000000000, "initial_state": { "coins": [ diff --git a/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_simple_coin_state.snap b/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_simple_coin_state.snap index 8e224bdf9c2..17f12e00e2a 100644 --- a/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_simple_coin_state.snap +++ b/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_simple_coin_state.snap @@ -4,11 +4,6 @@ expression: json --- { "chain_name": "local_testnet", - "block_production": { - "ProofOfAuthority": { - "trigger": "instant" - } - }, "block_gas_limit": 1000000000, "initial_state": { "coins": [ diff --git a/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_simple_contract.snap b/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_simple_contract.snap index 6d21d0fb5e8..6cf41e70223 100644 --- a/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_simple_contract.snap +++ b/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_simple_contract.snap @@ -4,11 +4,6 @@ expression: json --- { "chain_name": "local_testnet", - "block_production": { - "ProofOfAuthority": { - "trigger": "instant" - } - }, "block_gas_limit": 1000000000, "initial_state": { "contracts": [ diff --git a/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_simple_message_state.snap b/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_simple_message_state.snap index a9f90bb91c1..22f6b9c9411 100644 --- a/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_simple_message_state.snap +++ b/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_simple_message_state.snap @@ -4,11 +4,6 @@ expression: json --- { "chain_name": "local_testnet", - "block_production": { - "ProofOfAuthority": { - "trigger": "instant" - } - }, "block_gas_limit": 1000000000, "initial_state": { "messages": [ diff --git a/crates/fuel-core/Cargo.toml b/crates/fuel-core/Cargo.toml index cb8958efba3..254fa7bc944 100644 --- a/crates/fuel-core/Cargo.toml +++ b/crates/fuel-core/Cargo.toml @@ -18,11 +18,13 @@ async-graphql = { version = "4.0", features = [ async-trait = "0.1" axum = { version = "0.5" } bincode = "1.3" +clap = { version = "4.1", features = ["derive"] } derive_more = { version = "0.99" } enum-iterator = "1.2" fuel-core-chain-config = { path = "../chain-config", version = "0.15.1" } fuel-core-database = { path = "../database", version = "0.15.1" } fuel-core-executor = { path = "../services/executor", version = "0.15.1" } +fuel-core-importer = { path = "../services/importer", version = "0.15.1" } fuel-core-metrics = { path = "../metrics", version = "0.15.1", optional = true } fuel-core-p2p = { path = "../services/p2p", version = "0.15.1", optional = true } fuel-core-poa = { path = "../services/consensus_module/poa", version = "0.15.1" } diff --git a/crates/fuel-core/src/service/config.rs b/crates/fuel-core/src/service/config.rs index 96997bc3e8b..7ab21114b05 100644 --- a/crates/fuel-core/src/service/config.rs +++ b/crates/fuel-core/src/service/config.rs @@ -1,3 +1,4 @@ +use clap::ValueEnum; use fuel_core_chain_config::ChainConfig; use fuel_core_types::{ blockchain::primitives::SecretKeyWrapper, @@ -23,6 +24,8 @@ use fuel_core_p2p::config::{ NotInitialized, }; +pub use fuel_core_poa::Trigger; + #[derive(Clone, Debug)] pub struct Config { pub addr: SocketAddr, @@ -32,10 +35,12 @@ pub struct Config { // default to false until downstream consumers stabilize pub utxo_validation: bool, pub manual_blocks_enabled: bool, + pub block_production: Trigger, pub vm: VMConfig, pub txpool: fuel_core_txpool::Config, pub block_producer: fuel_core_producer::Config, pub block_executor: fuel_core_executor::Config, + pub block_importer: fuel_core_importer::Config, #[cfg(feature = "relayer")] pub relayer: fuel_core_relayer::Config, #[cfg(feature = "p2p")] @@ -54,6 +59,7 @@ impl Config { database_type: DbType::InMemory, chain_conf: chain_conf.clone(), manual_blocks_enabled: false, + block_production: Trigger::Instant, vm: Default::default(), utxo_validation, txpool: fuel_core_txpool::Config::new( @@ -63,6 +69,7 @@ impl Config { ), block_producer: Default::default(), block_executor: Default::default(), + block_importer: Default::default(), #[cfg(feature = "relayer")] relayer: Default::default(), #[cfg(feature = "p2p")] @@ -72,12 +79,25 @@ impl Config { } } +impl From<&Config> for fuel_core_poa::Config { + fn from(config: &Config) -> Self { + fuel_core_poa::Config { + trigger: config.block_production, + block_gas_limit: config.chain_conf.block_gas_limit, + signing_key: config.consensus_key.clone(), + metrics: false, + } + } +} + #[derive(Clone, Debug, Default)] pub struct VMConfig { pub backtrace: bool, } -#[derive(Clone, Debug, Display, Eq, PartialEq, EnumString, EnumVariantNames)] +#[derive( + Clone, Debug, Display, Eq, PartialEq, EnumString, EnumVariantNames, ValueEnum, +)] #[strum(serialize_all = "kebab_case")] pub enum DbType { InMemory, diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index b46bb35c740..c94428854b0 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -1,7 +1,6 @@ #![allow(clippy::let_unit_value)] use super::adapters::P2PAdapter; use crate::{ - chain_config::BlockProduction, database::Database, fuel_core_graphql_api::Config as GraphQLConfig, schema::{ @@ -103,23 +102,16 @@ pub fn init_sub_services( dry_run_semaphore: Semaphore::new(max_dry_run_concurrency), }); - let poa = match &config.chain_conf.block_production { - BlockProduction::ProofOfAuthority { trigger } => fuel_core_poa::new_service( - fuel_core_poa::Config { - trigger: *trigger, - block_gas_limit: config.chain_conf.block_gas_limit, - signing_key: config.consensus_key.clone(), - metrics: false, - }, - TxPoolAdapter::new(txpool.shared.clone()), - // TODO: Pass Importer - importer_adapter.tx, - BlockProducerAdapter { - block_producer: block_producer.clone(), - }, - database.clone(), - ), - }; + let poa = fuel_core_poa::new_service( + config.into(), + TxPoolAdapter::new(txpool.shared.clone()), + // TODO: Pass Importer + importer_adapter.tx, + BlockProducerAdapter { + block_producer: block_producer.clone(), + }, + database.clone(), + ); // TODO: Figure out on how to move it into `fuel-core-graphql-api`. let schema = dap::init( diff --git a/crates/services/consensus_module/Cargo.toml b/crates/services/consensus_module/Cargo.toml new file mode 100644 index 00000000000..4e1a15fba81 --- /dev/null +++ b/crates/services/consensus_module/Cargo.toml @@ -0,0 +1,20 @@ +[package] +authors = ["Fuel Labs "] +description = "The common code for fuel core consensuses." +edition = "2021" +homepage = "https://fuel.network/" +keywords = ["blockchain", "consensus", "fuel"] +license = "BUSL-1.1" +name = "fuel-core-consensus-module" +repository = "https://github.com/FuelLabs/fuel-core" +version = "0.15.1" + +[dependencies] +anyhow = "1.0" +fuel-core-chain-config = { version = "0.15.1", path = "../../chain-config" } +fuel-core-poa = { version = "0.15.1", path = "poa" } +fuel-core-types = { version = "0.15.1", path = "../../types" } + +[dev-dependencies] +fuel-core-types = { path = "../../types", features = ["test-helpers"] } +test-case = "2.2" diff --git a/crates/services/consensus_module/poa/Cargo.toml b/crates/services/consensus_module/poa/Cargo.toml index cc6e49bc030..9d131db1676 100644 --- a/crates/services/consensus_module/poa/Cargo.toml +++ b/crates/services/consensus_module/poa/Cargo.toml @@ -12,17 +12,17 @@ version = "0.15.1" [dependencies] anyhow = "1.0" async-trait = "0.1" -fuel-core-services = { path = "../.." } +fuel-core-services = { path = "../..", version = "0.15.1" } fuel-core-storage = { path = "../../../storage", version = "0.15.1" } fuel-core-types = { path = "../../../types", version = "0.15.1" } -humantime-serde = "1.1.1" -serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.21", features = ["full"] } tokio-stream = "0.1" tracing = "0.1" [dev-dependencies] +fuel-core-storage = { path = "../../../storage", features = ["test-helpers"] } fuel-core-types = { path = "../../../types", features = ["test-helpers"] } mockall = "0.11" rand = "0.8" +test-case = "2.2" tokio = { version = "1.21", features = ["full", "test-util"] } diff --git a/crates/services/consensus_module/poa/src/config.rs b/crates/services/consensus_module/poa/src/config.rs index 974838d86c5..231e6c2f70d 100644 --- a/crates/services/consensus_module/poa/src/config.rs +++ b/crates/services/consensus_module/poa/src/config.rs @@ -3,10 +3,6 @@ use fuel_core_types::{ fuel_asm::Word, secrecy::Secret, }; -use serde::{ - Deserialize, - Serialize, -}; use tokio::time::Duration; #[derive(Default, Debug, Clone)] @@ -18,9 +14,7 @@ pub struct Config { } /// Block production trigger for PoA operation -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -#[serde(tag = "trigger")] +#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)] pub enum Trigger { /// A new block is produced instantly when transactions are available. /// This is useful for some test cases. @@ -29,10 +23,7 @@ pub enum Trigger { /// This node doesn't produce new blocks. Used for passive listener nodes. Never, /// A new block is produced periodically. Used to simulate consensus block delay. - Interval { - #[serde(with = "humantime_serde")] - block_time: Duration, - }, + Interval { block_time: Duration }, /// A new block will be produced when the timer runs out. /// Set to `max_block_time` when the txpool is empty, otherwise /// `min(max_block_time, max_tx_idle_time)`. If it expires, @@ -42,14 +33,11 @@ pub enum Trigger { /// Requires `min_block_time` <= `max_tx_idle_time` <= `max_block_time`. Hybrid { /// Minimum time between two blocks, even if there are more txs available - #[serde(with = "humantime_serde")] min_block_time: Duration, /// If there are txs available, but not enough for a full block, /// this is how long the block is waiting for more txs - #[serde(with = "humantime_serde")] max_tx_idle_time: Duration, /// Time after which a new block is produced, even if it's empty - #[serde(with = "humantime_serde")] max_block_time: Duration, }, } diff --git a/crates/services/consensus_module/poa/src/lib.rs b/crates/services/consensus_module/poa/src/lib.rs index 29d21640b03..af98e73bb45 100644 --- a/crates/services/consensus_module/poa/src/lib.rs +++ b/crates/services/consensus_module/poa/src/lib.rs @@ -9,6 +9,7 @@ mod service_test; pub mod config; pub mod ports; pub mod service; +pub mod verifier; pub use config::{ Config, diff --git a/crates/services/consensus_module/poa/src/service.rs b/crates/services/consensus_module/poa/src/service.rs index e4b9215c3fe..44b7c11cb17 100644 --- a/crates/services/consensus_module/poa/src/service.rs +++ b/crates/services/consensus_module/poa/src/service.rs @@ -75,6 +75,9 @@ pub struct Task { /// a bit, but doesn't cause any other issues. pub(crate) last_block_created: Instant, pub(crate) trigger: Trigger, + // TODO: Consider that the creation of the block takes some time, and maybe we need to + // patch the timer to generate the block earlier. + // https://github.com/FuelLabs/fuel-core/issues/918 /// Deadline clock, used by the triggers pub(crate) timer: DeadlineClock, } diff --git a/crates/services/consensus_module/poa/src/verifier.rs b/crates/services/consensus_module/poa/src/verifier.rs new file mode 100644 index 00000000000..54c0e31f985 --- /dev/null +++ b/crates/services/consensus_module/poa/src/verifier.rs @@ -0,0 +1,78 @@ +use anyhow::ensure; +use fuel_core_storage::Result as StorageResult; +use fuel_core_types::{ + blockchain::{ + block::Block, + header::BlockHeader, + primitives::BlockHeight, + }, + fuel_types::Bytes32, +}; + +#[cfg(test)] +mod tests; + +/// The config of the block verifier. +pub struct Config { + /// If the manual block is enabled, skip verification of some fields. + pub enabled_manual_blocks: bool, +} + +#[cfg_attr(test, mockall::automock)] +/// The port for the database. +pub trait Database { + /// Gets the block header at `height`. + fn block_header(&self, height: &BlockHeight) -> StorageResult; + + /// Gets the block header BMT MMR root at `height`. + fn block_header_merkle_root(&self, height: &BlockHeight) -> StorageResult; +} + +pub fn verify_poa_block_fields( + config: &Config, + database: &D, + block: &Block, +) -> anyhow::Result<()> { + let height = *block.header().height(); + ensure!( + height != 0u32.into(), + "The PoA block can't have the zero height" + ); + + let prev_height = height - 1u32.into(); + let prev_root = database.block_header_merkle_root(&prev_height)?; + let header = block.header(); + ensure!( + header.prev_root() == &prev_root, + "Previous root of the next block should match the previous block root" + ); + + let prev_header = database.block_header(&prev_height)?; + + ensure!( + header.da_height >= prev_header.da_height, + "The `da_height` of the next block can't be lower" + ); + + // Skip the verification of the time if it is possible to produce blocks manually. + if !config.enabled_manual_blocks { + ensure!( + header.time() >= prev_header.time(), + "The `time` of the next block can't be lower" + ); + } + + ensure!( + header.consensus.application_hash == header.application.hash(), + "The application hash mismatch." + ); + + // TODO: We can check the root of the transactions and the root of the messages here. + // But we do the same in the executor right now during validation mode. I will not check + // it for now. But after merge of the https://github.com/FuelLabs/fuel-core/pull/889 it + // is should be easy to do with the `validate_transactions` method. And maybe we want + // to remove this check from the executor and replace it with check that transaction + // id is not modified during the execution. + + Ok(()) +} diff --git a/crates/services/consensus_module/poa/src/verifier/tests.rs b/crates/services/consensus_module/poa/src/verifier/tests.rs new file mode 100644 index 00000000000..3071a432784 --- /dev/null +++ b/crates/services/consensus_module/poa/src/verifier/tests.rs @@ -0,0 +1,111 @@ +use super::*; +use fuel_core_types::{ + blockchain::header::{ + ApplicationHeader, + ConsensusHeader, + GeneratedApplicationFields, + GeneratedConsensusFields, + }, + tai64::Tai64, +}; +use test_case::test_case; + +struct Input { + c: Config, + block_header_merkle_root: [u8; 32], + prev_header_time: Tai64, + prev_header_da_height: u64, + ch: ConsensusHeader, + ah: ApplicationHeader, +} + +fn app_hash(da_height: u64) -> Bytes32 { + ApplicationHeader { + da_height: da_height.into(), + ..Default::default() + } + .hash() +} + +fn correct() -> Input { + Input { + c: Config { + enabled_manual_blocks: false, + }, + block_header_merkle_root: [2u8; 32], + prev_header_time: Tai64(2), + prev_header_da_height: 2, + ch: ConsensusHeader { + prev_root: [2u8; 32].into(), + height: 2u32.into(), + time: Tai64(2), + generated: GeneratedConsensusFields { + application_hash: app_hash(2), + }, + }, + ah: ApplicationHeader { + da_height: 2u64.into(), + ..Default::default() + }, + } +} + +#[test_case(correct() => matches Ok(_) ; "Correct block")] +#[test_case( + { + let mut i = correct(); + i.ch.height = 0u32.into(); + i + } => matches Err(_) ; "Height 0" +)] +#[test_case( + { + let mut i = correct(); + i.ch.prev_root = [3u8; 32].into(); + i + } => matches Err(_) ; "Prev root mis-match" +)] +#[test_case( + { + let mut i = correct(); + i.ah.da_height = 1u64.into(); + i + } => matches Err(_) ; "da height lower then prev header" +)] +#[test_case( + { + let mut i = correct(); + i.ch.generated.application_hash = [0u8; 32].into(); + i + } => matches Err(_) ; "application hash mis-match" +)] +#[test_case( + { + let mut i = correct(); + i.ch.time = Tai64(1); + i + } => matches Err(_) ; "time before prev header" +)] +fn test_verify_genesis_block_fields(input: Input) -> anyhow::Result<()> { + let Input { + c, + block_header_merkle_root, + prev_header_time, + prev_header_da_height, + ch, + ah, + } = input; + let mut d = MockDatabase::default(); + d.expect_block_header_merkle_root() + .returning(move |_| Ok(block_header_merkle_root.into())); + d.expect_block_header().returning(move |_| { + let mut h = BlockHeader::default(); + h.consensus.time = prev_header_time; + h.application.da_height = prev_header_da_height.into(); + Ok(h) + }); + let mut b = Block::default(); + b.header_mut().consensus = ch; + b.header_mut().application = ah; + verify_poa_block_fields(&c, &d, &b) +} diff --git a/crates/services/consensus_module/src/block_verifier.rs b/crates/services/consensus_module/src/block_verifier.rs new file mode 100644 index 00000000000..090b4148a47 --- /dev/null +++ b/crates/services/consensus_module/src/block_verifier.rs @@ -0,0 +1,98 @@ +//! The module provides the functionality that verifies the blocks and headers based +//! on the used consensus. + +pub mod config; + +#[cfg(test)] +mod tests; + +use crate::block_verifier::config::Config; +use anyhow::ensure; +use fuel_core_poa::verifier::{ + verify_poa_block_fields, + Database as PoAVerifierDatabase, +}; +use fuel_core_types::{ + blockchain::{ + block::Block, + consensus::Consensus, + header::BlockHeader, + primitives::BlockHeight, + }, + fuel_types::Bytes32, + tai64::Tai64, +}; + +/// Verifier is responsible for validation of the blocks and headers. +pub struct Verifier { + config: Config, + database: D, + _relayer: R, +} + +impl Verifier { + /// Creates a new instance of the verifier. + pub fn new(config: Config, database: D, _relayer: R) -> Self { + Self { + config, + database, + _relayer, + } + } +} + +impl Verifier +where + D: PoAVerifierDatabase, +{ + /// Verifies **all** fields of the block based on used consensus to produce a block. + /// + /// Return an error if the verification failed, otherwise `Ok(())`. + pub fn verify_block_fields( + &self, + consensus: &Consensus, + block: &Block, + ) -> anyhow::Result<()> { + match consensus { + Consensus::Genesis(_) => { + let expected_genesis_height = self + .config + .chain_config + .initial_state + .as_ref() + .map(|config| config.height.unwrap_or_else(|| 0u32.into())) + .unwrap_or_else(|| 0u32.into()); + verify_genesis_block_fields(expected_genesis_height, block.header()) + } + Consensus::PoA(_) => { + verify_poa_block_fields(&self.config.poa, &self.database, block) + } + } + } +} + +fn verify_genesis_block_fields( + expected_genesis_height: BlockHeight, + header: &BlockHeader, +) -> anyhow::Result<()> { + let actual_genesis_height = *header.height(); + + ensure!( + header.prev_root() == &Bytes32::zeroed(), + "The genesis previous root should be zeroed" + ); + ensure!( + header.time() == Tai64::UNIX_EPOCH, + "The genesis time should be unix epoch time" + ); + ensure!( + // TODO: Set `da_height` based on the chain config. + header.da_height == Default::default(), + "The genesis `da_height` is not as expected" + ); + ensure!( + expected_genesis_height == actual_genesis_height, + "The genesis height is not as expected" + ); + Ok(()) +} diff --git a/crates/services/consensus_module/src/block_verifier/config.rs b/crates/services/consensus_module/src/block_verifier/config.rs new file mode 100644 index 00000000000..ed7321d1ae2 --- /dev/null +++ b/crates/services/consensus_module/src/block_verifier/config.rs @@ -0,0 +1,24 @@ +//! The config of the block verifier. + +use fuel_core_chain_config::ChainConfig; +use fuel_core_poa::verifier::Config as PoAVerifierConfig; + +/// The config of the block verifier. +pub struct Config { + /// The chain configuration. + pub chain_config: ChainConfig, + /// The config of verifier for the PoA. + pub poa: PoAVerifierConfig, +} + +impl Config { + /// Creates the verifier config for all possible consensuses. + pub fn new(chain_config: ChainConfig, enabled_manual_blocks: bool) -> Self { + Self { + chain_config, + poa: PoAVerifierConfig { + enabled_manual_blocks, + }, + } + } +} diff --git a/crates/services/consensus_module/src/block_verifier/tests.rs b/crates/services/consensus_module/src/block_verifier/tests.rs new file mode 100644 index 00000000000..2c7bc090597 --- /dev/null +++ b/crates/services/consensus_module/src/block_verifier/tests.rs @@ -0,0 +1,69 @@ +use super::*; +use test_case::test_case; + +#[test_case( + { + let mut h = BlockHeader::default(); + h.consensus.prev_root = Bytes32::zeroed(); + h.consensus.time = Tai64::UNIX_EPOCH; + h.consensus.height = 0u32.into(); + h + }, + 0 => matches Ok(_) ; "Correct header at `0`" +)] +#[test_case( + { + let mut h = BlockHeader::default(); + h.consensus.prev_root = Bytes32::zeroed(); + h.consensus.time = Tai64::UNIX_EPOCH; + h.consensus.height = 113u32.into(); + h + }, + 113 => matches Ok(_) ; "Correct header at `113`" +)] +#[test_case( + { + let mut h = BlockHeader::default(); + h.consensus.prev_root = Bytes32::zeroed(); + h.consensus.time = Tai64::UNIX_EPOCH; + h.consensus.height = 0u32.into(); + h + }, + 10 => matches Err(_) ; "wrong expected height" +)] +#[test_case( + { + let mut h = BlockHeader::default(); + h.consensus.prev_root = Bytes32::zeroed(); + h.consensus.time = Tai64::UNIX_EPOCH; + h.consensus.height = 5u32.into(); + h + }, + 0 => matches Err(_) ; "wrong header height" +)] +#[test_case( + { + let mut h = BlockHeader::default(); + h.consensus.prev_root = Bytes32::zeroed(); + h.consensus.time = Tai64(0); + h.consensus.height = 0u32.into(); + h + }, + 0 => matches Err(_) ; "wrong time" +)] +#[test_case( + { + let mut h = BlockHeader::default(); + h.consensus.prev_root = Bytes32::from([1u8; 32]); + h.consensus.time = Tai64::UNIX_EPOCH; + h.consensus.height = 0u32.into(); + h + }, + 0 => matches Err(_) ; "wrong root" +)] +fn test_verify_genesis_block_fields( + header: BlockHeader, + expected_genesis_height: u32, +) -> anyhow::Result<()> { + verify_genesis_block_fields(expected_genesis_height.into(), &header) +} diff --git a/crates/services/consensus_module/src/lib.rs b/crates/services/consensus_module/src/lib.rs new file mode 100644 index 00000000000..6a8523a10df --- /dev/null +++ b/crates/services/consensus_module/src/lib.rs @@ -0,0 +1,7 @@ +//! Common traits and logic for managing the lifecycle of services +#![deny(unused_crate_dependencies)] +#![deny(missing_docs)] + +extern crate core; + +pub mod block_verifier; diff --git a/tests/tests/trigger_integration/hybrid.rs b/tests/tests/trigger_integration/hybrid.rs index 8261aed6e34..3fcf0c92255 100644 --- a/tests/tests/trigger_integration/hybrid.rs +++ b/tests/tests/trigger_integration/hybrid.rs @@ -1,5 +1,4 @@ use fuel_core::{ - chain_config::BlockProduction, database::Database, service::{ Config, @@ -11,6 +10,7 @@ use fuel_core_client::client::{ PageDirection, PaginationRequest, }; +use fuel_core_poa::Trigger; use fuel_core_types::{ fuel_asm::*, fuel_crypto::SecretKey, @@ -34,12 +34,10 @@ async fn poa_hybrid_produces_empty_blocks_at_correct_rate() { let db = Database::default(); let mut config = Config::local_node(); config.consensus_key = Some(Secret::new(SecretKey::random(&mut rng).into())); - config.chain_conf.block_production = BlockProduction::ProofOfAuthority { - trigger: fuel_core_poa::Trigger::Hybrid { - max_block_time: Duration::new(round_time_seconds, 0), - max_tx_idle_time: Duration::new(5, 0), - min_block_time: Duration::new(2, 0), - }, + config.block_production = Trigger::Hybrid { + max_block_time: Duration::new(round_time_seconds, 0), + max_tx_idle_time: Duration::new(5, 0), + min_block_time: Duration::new(2, 0), }; let srv = FuelService::from_database(db.clone(), config) @@ -103,12 +101,10 @@ async fn poa_hybrid_produces_nonempty_blocks_at_correct_rate() { let db = Database::default(); let mut config = Config::local_node(); config.consensus_key = Some(Secret::new(SecretKey::random(&mut rng).into())); - config.chain_conf.block_production = BlockProduction::ProofOfAuthority { - trigger: fuel_core_poa::Trigger::Hybrid { - max_block_time: Duration::new(30, 0), - max_tx_idle_time: Duration::new(5, 0), - min_block_time: Duration::new(round_time_seconds, 0), - }, + config.block_production = Trigger::Hybrid { + max_block_time: Duration::new(30, 0), + max_tx_idle_time: Duration::new(5, 0), + min_block_time: Duration::new(round_time_seconds, 0), }; let srv = FuelService::from_database(db.clone(), config) diff --git a/tests/tests/trigger_integration/instant.rs b/tests/tests/trigger_integration/instant.rs index 6cb7702b1ae..a7cb6dc14fe 100644 --- a/tests/tests/trigger_integration/instant.rs +++ b/tests/tests/trigger_integration/instant.rs @@ -1,5 +1,4 @@ use fuel_core::{ - chain_config::BlockProduction, database::Database, service::{ Config, @@ -11,6 +10,7 @@ use fuel_core_client::client::{ PageDirection, PaginationRequest, }; +use fuel_core_poa::Trigger; use fuel_core_types::{ fuel_asm::*, fuel_crypto::SecretKey, @@ -29,9 +29,7 @@ async fn poa_instant_trigger_is_produces_instantly() { let db = Database::default(); let mut config = Config::local_node(); config.consensus_key = Some(Secret::new(SecretKey::random(&mut rng).into())); - config.chain_conf.block_production = BlockProduction::ProofOfAuthority { - trigger: fuel_core_poa::Trigger::Instant, - }; + config.block_production = Trigger::Instant; let srv = FuelService::from_database(db.clone(), config) .await diff --git a/tests/tests/trigger_integration/interval.rs b/tests/tests/trigger_integration/interval.rs index 1f00abd432c..79a3eedf70a 100644 --- a/tests/tests/trigger_integration/interval.rs +++ b/tests/tests/trigger_integration/interval.rs @@ -1,5 +1,4 @@ use fuel_core::{ - chain_config::BlockProduction, database::Database, service::{ Config, @@ -11,6 +10,7 @@ use fuel_core_client::client::{ PageDirection, PaginationRequest, }; +use fuel_core_poa::Trigger; use fuel_core_types::{ fuel_asm::*, fuel_crypto::SecretKey, @@ -34,10 +34,8 @@ async fn poa_interval_produces_empty_blocks_at_correct_rate() { let db = Database::default(); let mut config = Config::local_node(); config.consensus_key = Some(Secret::new(SecretKey::random(&mut rng).into())); - config.chain_conf.block_production = BlockProduction::ProofOfAuthority { - trigger: fuel_core_poa::Trigger::Interval { - block_time: Duration::new(round_time_seconds, 0), - }, + config.block_production = Trigger::Interval { + block_time: Duration::new(round_time_seconds, 0), }; let srv = FuelService::from_database(db.clone(), config) @@ -98,10 +96,8 @@ async fn poa_interval_produces_nonempty_blocks_at_correct_rate() { let db = Database::default(); let mut config = Config::local_node(); config.consensus_key = Some(Secret::new(SecretKey::random(&mut rng).into())); - config.chain_conf.block_production = BlockProduction::ProofOfAuthority { - trigger: fuel_core_poa::Trigger::Interval { - block_time: Duration::new(round_time_seconds, 0), - }, + config.block_production = Trigger::Interval { + block_time: Duration::new(round_time_seconds, 0), }; let srv = FuelService::from_database(db.clone(), config) diff --git a/tests/tests/trigger_integration/never.rs b/tests/tests/trigger_integration/never.rs index f0c85347fb5..08a875d7843 100644 --- a/tests/tests/trigger_integration/never.rs +++ b/tests/tests/trigger_integration/never.rs @@ -1,5 +1,4 @@ use fuel_core::{ - chain_config::BlockProduction, database::Database, service::{ Config, @@ -11,6 +10,7 @@ use fuel_core_client::client::{ PageDirection, PaginationRequest, }; +use fuel_core_poa::Trigger; use fuel_core_types::{ fuel_asm::Opcode, fuel_crypto::SecretKey, @@ -27,9 +27,7 @@ async fn poa_never_trigger_doesnt_produce_blocks() { let mut rng = StdRng::seed_from_u64(10); let db = Database::default(); let mut config = Config::local_node(); - config.chain_conf.block_production = BlockProduction::ProofOfAuthority { - trigger: fuel_core_poa::Trigger::Never, - }; + config.block_production = Trigger::Never; config.consensus_key = Some(Secret::new(SecretKey::random(&mut rng).into())); let srv = FuelService::from_database(db.clone(), config) .await diff --git a/tests/tests/tx/txn_status_subscription.rs b/tests/tests/tx/txn_status_subscription.rs index f6f3871b813..1b15377f767 100644 --- a/tests/tests/tx/txn_status_subscription.rs +++ b/tests/tests/tx/txn_status_subscription.rs @@ -14,14 +14,9 @@ use futures::StreamExt; #[tokio::test] async fn subscribe_txn_status() { - use fuel_core_poa::Trigger; let mut config = Config::local_node(); - match &mut config.chain_conf.block_production { - fuel_core::chain_config::BlockProduction::ProofOfAuthority { trigger } => { - *trigger = Trigger::Interval { - block_time: Duration::from_secs(2), - } - } + config.block_production = fuel_core::service::config::Trigger::Interval { + block_time: Duration::from_secs(2), }; let srv = FuelService::new_node(config).await.unwrap(); let client = FuelClient::from(srv.bound_address);