diff --git a/.config/forest.dic b/.config/forest.dic index 8e31de43101..5333d1098a4 100644 --- a/.config/forest.dic +++ b/.config/forest.dic @@ -45,6 +45,8 @@ Enum EOF Ethereum exa +F3 +FFI FIL Filecoin/M Filops diff --git a/f3-sidecar/ffi_impl.go b/f3-sidecar/ffi_impl.go index 104b1db2968..852ab99bae7 100644 --- a/f3-sidecar/ffi_impl.go +++ b/f3-sidecar/ffi_impl.go @@ -9,10 +9,14 @@ import ( func init() { setGoDebugEnv() - logging.SetAllLoggers(logging.LevelWarn) - err := logging.SetLogLevel("f3/sidecar", "info") + logging.SetAllLoggers(logging.LevelInfo) + err := logging.SetLogLevel("dht", "error") checkError(err) - err = logging.SetLogLevel("f3", "info") + err = logging.SetLogLevel("dht/RtRefreshManager", "warn") + checkError(err) + err = logging.SetLogLevel("net/identify", "error") + checkError(err) + err = logging.SetLogLevel("f3/sidecar", "debug") checkError(err) GoF3NodeImpl = &f3Impl{ctx: context.Background()} } diff --git a/f3-sidecar/main.go b/f3-sidecar/main.go index 1fe2463bf01..dfe361404ea 100644 --- a/f3-sidecar/main.go +++ b/f3-sidecar/main.go @@ -10,11 +10,17 @@ import ( var logger = logging.Logger("f3/sidecar") func main() { - logging.SetAllLoggers(logging.LevelError) - if err := logging.SetLogLevel("f3/sidecar", "debug"); err != nil { + logging.SetAllLoggers(logging.LevelInfo) + if err := logging.SetLogLevel("dht", "error"); err != nil { + panic(err) + } + if err := logging.SetLogLevel("dht/RtRefreshManager", "warn"); err != nil { panic(err) } - if err := logging.SetLogLevel("f3", "debug"); err != nil { + if err := logging.SetLogLevel("net/identify", "error"); err != nil { + panic(err) + } + if err := logging.SetLogLevel("f3/sidecar", "debug"); err != nil { panic(err) } diff --git a/f3-sidecar/run.go b/f3-sidecar/run.go index 3de34bb4de1..9c7f28b0e5b 100644 --- a/f3-sidecar/run.go +++ b/f3-sidecar/run.go @@ -89,8 +89,7 @@ func run(ctx context.Context, rpcEndpoint string, f3RpcEndpoint string, initialP } else { m.BootstrapEpoch = bootstrapEpoch } - m.CommitteeLookback = 5 - // m.Pause = true + m.CommitteeLookback = manifest.DefaultCommitteeLookback var manifestProvider manifest.ManifestProvider switch manifestServerID, err := peer.Decode(manifestServer); { diff --git a/src/blocks/header.rs b/src/blocks/header.rs index 0ac48cafa96..a25403a25f4 100644 --- a/src/blocks/header.rs +++ b/src/blocks/header.rs @@ -228,7 +228,8 @@ pub struct CachingBlockHeader { impl PartialEq for CachingBlockHeader { fn eq(&self, other: &Self) -> bool { - self.cid() == other.cid() + // Epoch check is redundant but cheap. + self.uncached.epoch == other.uncached.epoch && self.cid() == other.cid() } } diff --git a/src/chain_sync/chain_muxer.rs b/src/chain_sync/chain_muxer.rs index 53383b7c233..182c0f2343a 100644 --- a/src/chain_sync/chain_muxer.rs +++ b/src/chain_sync/chain_muxer.rs @@ -201,6 +201,11 @@ where }) } + /// Returns a clone of the inner [`SyncNetworkContext`] + pub fn sync_network_context(&self) -> SyncNetworkContext { + self.network.clone() + } + /// Returns a clone of the bad blocks cache to be used outside of chain /// sync. pub fn bad_blocks_cloned(&self) -> Arc { diff --git a/src/chain_sync/mod.rs b/src/chain_sync/mod.rs index de79dadaafa..a649f29de8a 100644 --- a/src/chain_sync/mod.rs +++ b/src/chain_sync/mod.rs @@ -5,7 +5,7 @@ mod bad_block_cache; mod chain_muxer; pub mod consensus; mod metrics; -mod network_context; +pub mod network_context; mod sync_state; mod tipset_syncer; mod validation; diff --git a/src/chain_sync/network_context.rs b/src/chain_sync/network_context.rs index c39599ef682..d39c9e84279 100644 --- a/src/chain_sync/network_context.rs +++ b/src/chain_sync/network_context.rs @@ -52,10 +52,9 @@ const MAX_CONCURRENT_CHAIN_EXCHANGE_REQUESTS: usize = 2; /// Context used in chain sync to handle network requests. /// This contains the peer manager, P2P service interface, and [`Blockstore`] /// required to make network requests. -pub(in crate::chain_sync) struct SyncNetworkContext { +pub struct SyncNetworkContext { /// Channel to send network messages through P2P service network_send: flume::Sender, - /// Manages peers to send requests to and updates request stats for the /// respective peers. peer_manager: Arc, @@ -141,6 +140,11 @@ where self.peer_manager.as_ref() } + /// Returns a reference to the channel for sending network messages through P2P service. + pub fn network_send(&self) -> &flume::Sender { + &self.network_send + } + /// Send a `chain_exchange` request for only block headers (ignore /// messages). If `peer_id` is `None`, requests will be sent to a set of /// shuffled peers. diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index 32c115c1eb8..b74777dc6a2 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -363,6 +363,7 @@ pub(super) async fn start( )?; let bad_blocks = chain_muxer.bad_blocks_cloned(); let sync_state = chain_muxer.sync_state_cloned(); + let sync_network_context = chain_muxer.sync_network_context(); services.spawn(async { Err(anyhow::anyhow!("{}", chain_muxer.await)) }); if config.client.enable_health_check { @@ -402,7 +403,7 @@ pub(super) async fn start( bad_blocks, sync_state, eth_event_handler: Arc::new(EthEventHandler::new()), - network_send, + sync_network_context, network_name, start_time, shutdown: shutdown_send, @@ -414,6 +415,7 @@ pub(super) async fn start( }); services.spawn_blocking({ + let chain_config = chain_config.clone(); let default_f3_root = config.client.data_dir.join(format!("f3/{}", config.chain)); let crate::f3::F3Options { chain_finality, @@ -423,6 +425,7 @@ pub(super) async fn start( } = crate::f3::get_f3_sidecar_params(&chain_config); move || { crate::f3::run_f3_sidecar_if_enabled( + &chain_config, format!("http://{rpc_address}/rpc/v1"), crate::rpc::f3::get_f3_rpc_endpoint().to_string(), initial_power_table.to_string(), diff --git a/src/f3/mod.rs b/src/f3/mod.rs index d0ad55a9736..35c16977bb5 100644 --- a/src/f3/mod.rs +++ b/src/f3/mod.rs @@ -1,6 +1,8 @@ // Copyright 2019-2024 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT +#![allow(clippy::too_many_arguments)] + #[cfg(all(f3sidecar, not(feature = "no-f3-sidecar")))] mod go_ffi; #[cfg(all(f3sidecar, not(feature = "no-f3-sidecar")))] @@ -9,7 +11,7 @@ use go_ffi::*; use cid::Cid; use libp2p::PeerId; -use crate::{networks::ChainConfig, utils::misc::env::is_env_truthy}; +use crate::{networks::ChainConfig, utils::misc::env::is_env_set_and_truthy}; #[derive(Debug, Clone, Eq, PartialEq)] pub struct F3Options { @@ -85,6 +87,7 @@ pub fn get_f3_sidecar_params(chain_config: &ChainConfig) -> F3Options { } pub fn run_f3_sidecar_if_enabled( + chain_config: &ChainConfig, _rpc_endpoint: String, _f3_rpc_endpoint: String, _initial_power_table: String, @@ -93,7 +96,7 @@ pub fn run_f3_sidecar_if_enabled( _f3_root: String, _manifest_server: String, ) { - if is_sidecar_ffi_enabled() { + if is_sidecar_ffi_enabled(chain_config) { #[cfg(all(f3sidecar, not(feature = "no-f3-sidecar")))] { GoF3NodeImpl::run( @@ -109,10 +112,11 @@ pub fn run_f3_sidecar_if_enabled( } } -// Use opt-in mode for now. Consider switching to opt-out mode once F3 is shipped. -fn is_sidecar_ffi_enabled() -> bool { - // Opt-out building the F3 sidecar staticlib - let enabled = is_env_truthy("FOREST_F3_SIDECAR_FFI_ENABLED"); +/// Whether F3 sidecar via FFI is enabled. +fn is_sidecar_ffi_enabled(chain_config: &ChainConfig) -> bool { + // Respect the environment variable when set, and fallback to chain config when not set. + let enabled = + is_env_set_and_truthy("FOREST_F3_SIDECAR_FFI_ENABLED").unwrap_or(chain_config.f3_enabled); cfg_if::cfg_if! { if #[cfg(all(f3sidecar, not(feature = "no-f3-sidecar")))] { enabled diff --git a/src/networks/mod.rs b/src/networks/mod.rs index c1c28492350..93f8eea3748 100644 --- a/src/networks/mod.rs +++ b/src/networks/mod.rs @@ -228,6 +228,9 @@ pub struct ChainConfig { pub breeze_gas_tamping_duration: i64, // FIP0081 gradually comes into effect over this many epochs. pub fip0081_ramp_duration_epochs: u64, + pub f3_enabled: bool, + // F3Consensus set whether F3 should checkpoint tipsets finalized by F3. This flag has no effect if F3 is not enabled. + pub f3_consensus: bool, pub f3_bootstrap_epoch: i64, pub f3_initial_power_table: Cid, // This will likely be deprecated once F3 is fully bootstrapped to avoid single point network dependencies. @@ -254,6 +257,8 @@ impl ChainConfig { breeze_gas_tamping_duration: BREEZE_GAS_TAMPING_DURATION, // 1 year on mainnet fip0081_ramp_duration_epochs: 365 * EPOCHS_IN_DAY as u64, + f3_enabled: false, + f3_consensus: false, f3_bootstrap_epoch: -1, f3_initial_power_table: Default::default(), f3_manifest_server: Some( @@ -282,6 +287,10 @@ impl ChainConfig { breeze_gas_tamping_duration: BREEZE_GAS_TAMPING_DURATION, // 3 days on calibnet fip0081_ramp_duration_epochs: 3 * EPOCHS_IN_DAY as u64, + // Enable after `f3_initial_power_table` is determined and set to avoid GC hell + // (state tree of epoch 2_081_674 - 900 has to be present in the database if `f3_initial_power_table` is not set) + f3_enabled: false, + f3_consensus: true, // 2024-10-24T13:30:00Z f3_bootstrap_epoch: 2_081_674, f3_initial_power_table: Default::default(), @@ -308,6 +317,8 @@ impl ChainConfig { breeze_gas_tamping_duration: BREEZE_GAS_TAMPING_DURATION, // Devnet ramp is 200 epochs in Lotus (subject to change). fip0081_ramp_duration_epochs: env_or_default(ENV_PLEDGE_RULE_RAMP, 200), + f3_enabled: false, + f3_consensus: false, f3_bootstrap_epoch: -1, f3_initial_power_table: Default::default(), f3_manifest_server: None, @@ -335,7 +346,9 @@ impl ChainConfig { ENV_PLEDGE_RULE_RAMP, 365 * EPOCHS_IN_DAY as u64, ), - f3_bootstrap_epoch: -1, + f3_enabled: true, + f3_consensus: true, + f3_bootstrap_epoch: 2760, f3_initial_power_table: Default::default(), f3_manifest_server: Some( "12D3KooWJr9jy4ngtJNR7JC1xgLFra3DjEtyxskRYWvBK9TC3Yn6" diff --git a/src/rpc/error.rs b/src/rpc/error.rs index 416f4dfb21e..bc20343ccf6 100644 --- a/src/rpc/error.rs +++ b/src/rpc/error.rs @@ -111,6 +111,7 @@ macro_rules! from2internal { // TODO(forest): https://github.com/ChainSafe/forest/issues/3965 // Just mapping everything to an internal error is not appropriate from2internal! { + String, anyhow::Error, base64::DecodeError, cid::multibase::Error, diff --git a/src/rpc/methods/f3.rs b/src/rpc/methods/f3.rs index 0cb0b05c5cd..855afe8c386 100644 --- a/src/rpc/methods/f3.rs +++ b/src/rpc/methods/f3.rs @@ -22,8 +22,10 @@ use crate::{ clock::ChainEpoch, crypto::Signature, }, + utils::misc::env::is_env_set_and_truthy, }; use ahash::{HashMap, HashSet}; +use anyhow::Context as _; use fil_actor_interface::{ convert::{ from_policy_v13_to_v10, from_policy_v13_to_v11, from_policy_v13_to_v12, @@ -37,7 +39,7 @@ use libp2p::PeerId; use num::Signed as _; use once_cell::sync::Lazy; use parking_lot::RwLock; -use std::{borrow::Cow, fmt::Display, str::FromStr as _, sync::Arc}; +use std::{borrow::Cow, fmt::Display, num::NonZeroU64, str::FromStr as _, sync::Arc}; static F3_LEASE_MANAGER: Lazy = Lazy::new(Default::default); @@ -423,7 +425,7 @@ impl RpcMethod<1> for ProtectPeer { ) -> Result { let peer_id = PeerId::from_str(&peer_id)?; let (tx, rx) = flume::bounded(1); - ctx.network_send + ctx.network_send() .send_async(NetworkMessage::JSONRPCRequest { method: NetRPCMethods::ProtectPeer(tx, std::iter::once(peer_id).collect()), }) @@ -463,10 +465,56 @@ impl RpcMethod<1> for Finalize { type Ok = (); async fn handle( - _: Ctx, - (_tsk,): Self::Params, + ctx: Ctx, + (f3_tsk,): Self::Params, ) -> Result { - // TODO(hanabi1224): https://github.com/ChainSafe/forest/issues/4775 + // Respect the environment variable when set, and fallback to chain config when not set. + let enabled = is_env_set_and_truthy("FOREST_F3_CONSENSUS_ENABLED") + .unwrap_or(ctx.chain_config().f3_consensus); + if !enabled { + return Ok(()); + } + + let tsk = f3_tsk.try_into()?; + let finalized_ts = match ctx.chain_index().load_tipset(&tsk)? { + Some(ts) => ts, + None => { + let ts = ctx + .sync_network_context + .chain_exchange_headers(None, &tsk, NonZeroU64::new(1).expect("Infallible")) + .await? + .first() + .cloned() + .with_context(|| { + format!("failed to get tipset via chain exchange. tsk: {tsk}") + })?; + ctx.chain_store().put_tipset(&ts)?; + ts + } + }; + tracing::info!( + "F3 finalized tsk {} at epoch {}", + finalized_ts.key(), + finalized_ts.epoch() + ); + let head = ctx.chain_store().heaviest_tipset(); + // When finalized_ts is not part of the current chain, + // reset the current head to finalized_ts. + // Note that when finalized_ts is newer than head, we don't reset the head + // to allow the chain to catch up. + if head.epoch() >= finalized_ts.epoch() + && !head + .chain_arc(ctx.store()) + .take_while(|ts| ts.epoch() >= finalized_ts.epoch()) + .any(|ts| ts == finalized_ts) + { + tracing::info!( + "F3 reset chain head to tsk {} at epoch {}", + finalized_ts.key(), + finalized_ts.epoch() + ); + ctx.chain_store().set_heaviest_tipset(finalized_ts)?; + } Ok(()) } } diff --git a/src/rpc/methods/net.rs b/src/rpc/methods/net.rs index 99e3d7c6d74..5c368e50b1e 100644 --- a/src/rpc/methods/net.rs +++ b/src/rpc/methods/net.rs @@ -30,7 +30,7 @@ impl RpcMethod<0> for NetAddrsListen { method: NetRPCMethods::AddrsListen(tx), }; - ctx.network_send.send_async(req).await?; + ctx.network_send().send_async(req).await?; let (id, addrs) = rx.recv_async().await?; Ok(AddrInfo::new(id, addrs)) @@ -53,7 +53,7 @@ impl RpcMethod<0> for NetPeers { method: NetRPCMethods::Peers(tx), }; - ctx.network_send.send_async(req).await?; + ctx.network_send().send_async(req).await?; let peer_addresses = rx.recv_async().await?; let connections = peer_addresses @@ -81,7 +81,7 @@ impl RpcMethod<1> for NetFindPeer { ) -> Result { let peer_id = PeerId::from_str(&peer_id)?; let (tx, rx) = flume::bounded(1); - ctx.network_send + ctx.network_send() .send_async(NetworkMessage::JSONRPCRequest { method: NetRPCMethods::Peer(tx, peer_id), }) @@ -125,7 +125,7 @@ impl RpcMethod<0> for NetInfo { method: NetRPCMethods::Info(tx), }; - ctx.network_send.send_async(req).await?; + ctx.network_send().send_async(req).await?; Ok(rx.recv_async().await?) } } @@ -152,7 +152,7 @@ impl RpcMethod<1> for NetConnect { method: NetRPCMethods::Connect(tx, peer_id, addrs), }; - ctx.network_send.send_async(req).await?; + ctx.network_send().send_async(req).await?; let success = rx.recv_async().await?; if success { @@ -184,7 +184,7 @@ impl RpcMethod<1> for NetDisconnect { method: NetRPCMethods::Disconnect(tx, peer_id), }; - ctx.network_send.send_async(req).await?; + ctx.network_send().send_async(req).await?; rx.recv_async().await?; Ok(()) @@ -207,7 +207,7 @@ impl RpcMethod<1> for NetAgentVersion { ) -> Result { let peer_id = PeerId::from_str(&id)?; let (tx, rx) = flume::bounded(1); - ctx.network_send + ctx.network_send() .send_async(NetworkMessage::JSONRPCRequest { method: NetRPCMethods::AgentVersion(tx, peer_id), }) @@ -231,7 +231,7 @@ impl RpcMethod<0> for NetAutoNatStatus { let req = NetworkMessage::JSONRPCRequest { method: NetRPCMethods::AutoNATStatus(tx), }; - ctx.network_send.send_async(req).await?; + ctx.network_send().send_async(req).await?; let nat_status = rx.recv_async().await?; Ok(nat_status.into()) } @@ -276,7 +276,7 @@ impl RpcMethod<1> for NetProtectAdd { .map(PeerId::from_str) .try_collect()?; let (tx, rx) = flume::bounded(1); - ctx.network_send + ctx.network_send() .send_async(NetworkMessage::JSONRPCRequest { method: NetRPCMethods::ProtectPeer(tx, peer_ids), }) @@ -296,7 +296,7 @@ impl RpcMethod<0> for NetProtectList { type Ok = Vec; async fn handle(ctx: Ctx, (): Self::Params) -> Result { let (tx, rx) = flume::bounded(1); - ctx.network_send + ctx.network_send() .send_async(NetworkMessage::JSONRPCRequest { method: NetRPCMethods::ListProtectedPeers(tx), }) @@ -327,7 +327,7 @@ impl RpcMethod<1> for NetProtectRemove { .map(PeerId::from_str) .try_collect()?; let (tx, rx) = flume::bounded(1); - ctx.network_send + ctx.network_send() .send_async(NetworkMessage::JSONRPCRequest { method: NetRPCMethods::UnprotectPeer(tx, peer_ids), }) diff --git a/src/rpc/methods/state.rs b/src/rpc/methods/state.rs index 2865fe16930..a09b8f92a5c 100644 --- a/src/rpc/methods/state.rs +++ b/src/rpc/methods/state.rs @@ -1176,7 +1176,7 @@ impl RpcMethod<2> for StateFetchRoot { ctx: Ctx, (root_cid, save_to_file): Self::Params, ) -> Result { - let network_send = ctx.network_send.clone(); + let network_send = ctx.network_send().clone(); let db = ctx.store_owned(); let (car_tx, car_handle) = if let Some(save_to_file) = save_to_file { diff --git a/src/rpc/methods/sync.rs b/src/rpc/methods/sync.rs index 9261f407056..0ced1077b14 100644 --- a/src/rpc/methods/sync.rs +++ b/src/rpc/methods/sync.rs @@ -115,7 +115,7 @@ impl RpcMethod<1> for SyncSubmitBlock { .try_send(Arc::new(ts.into_tipset())) .context("tipset queue is full")?; - ctx.network_send.send(NetworkMessage::PubsubMessage { + ctx.network_send().send(NetworkMessage::PubsubMessage { topic: IdentTopic::new(pubsub_block_str), message: encoded_message, })?; @@ -140,10 +140,11 @@ mod tests { use crate::blocks::RawBlockHeader; use crate::blocks::{CachingBlockHeader, Tipset}; use crate::chain::ChainStore; + use crate::chain_sync::network_context::SyncNetworkContext; use crate::chain_sync::{SyncConfig, SyncStage}; use crate::db::MemoryDB; use crate::key_management::{KeyStore, KeyStoreConfig}; - use crate::libp2p::NetworkMessage; + use crate::libp2p::{NetworkMessage, PeerManager}; use crate::message_pool::{MessagePool, MpoolRpcProvider}; use crate::networks::ChainConfig; use crate::rpc::eth::filter::EthEventHandler; @@ -215,6 +216,9 @@ mod tests { }; let start_time = chrono::Utc::now(); + let peer_manager = Arc::new(PeerManager::default()); + let sync_network_context = + SyncNetworkContext::new(network_send, peer_manager, state_manager.blockstore_owned()); let state = Arc::new(RPCState { state_manager, keystore: Arc::new(RwLock::new(KeyStore::new(KeyStoreConfig::Memory).unwrap())), @@ -222,7 +226,7 @@ mod tests { bad_blocks: Default::default(), sync_state: Arc::new(parking_lot::RwLock::new(Default::default())), eth_event_handler: Arc::new(EthEventHandler::new()), - network_send, + sync_network_context, network_name: TEST_NET_NAME.to_owned(), start_time, shutdown: mpsc::channel(1).0, // dummy for tests diff --git a/src/rpc/mod.rs b/src/rpc/mod.rs index c389709159c..6b43a10cfac 100644 --- a/src/rpc/mod.rs +++ b/src/rpc/mod.rs @@ -340,11 +340,11 @@ mod methods { pub mod wallet; } -use crate::key_management::KeyStore; use crate::rpc::auth_layer::AuthLayer; use crate::rpc::channel::RpcModule as FilRpcModule; pub use crate::rpc::channel::CANCEL_METHOD_NAME; use crate::rpc::metrics_layer::MetricsLayer; +use crate::{chain_sync::network_context::SyncNetworkContext, key_management::KeyStore}; use crate::blocks::Tipset; use fvm_ipld_blockstore::Blockstore; @@ -384,7 +384,7 @@ pub struct RPCState { pub bad_blocks: Arc, pub sync_state: Arc>, pub eth_event_handler: Arc, - pub network_send: flume::Sender, + pub sync_network_context: SyncNetworkContext, pub network_name: String, pub tipset_send: flume::Sender>, pub start_time: chrono::DateTime, @@ -415,6 +415,10 @@ impl RPCState { pub fn store_owned(&self) -> Arc { self.state_manager.blockstore_owned() } + + pub fn network_send(&self) -> &flume::Sender { + self.sync_network_context.network_send() + } } #[derive(Clone)] diff --git a/src/tool/offline_server/server.rs b/src/tool/offline_server/server.rs index ac55816fd6f..f9a94b10a29 100644 --- a/src/tool/offline_server/server.rs +++ b/src/tool/offline_server/server.rs @@ -3,12 +3,14 @@ use crate::auth::generate_priv_key; use crate::chain::ChainStore; +use crate::chain_sync::network_context::SyncNetworkContext; use crate::chain_sync::{SyncConfig, SyncStage}; use crate::cli_shared::snapshot::TrustedVendor; use crate::daemon::db_util::{download_to, populate_eth_mappings}; use crate::db::{car::ManyCar, MemoryDB}; use crate::genesis::{get_network_name_from_genesis, read_genesis_header}; use crate::key_management::{KeyStore, KeyStoreConfig}; +use crate::libp2p::PeerManager; use crate::message_pool::{MessagePool, MpoolRpcProvider}; use crate::networks::{ChainConfig, NetworkChain}; use crate::rpc::eth::filter::EthEventHandler; @@ -125,6 +127,9 @@ pub async fn start_offline_server( std::fs::write(path, token)?; } + let peer_manager = Arc::new(PeerManager::default()); + let sync_network_context = + SyncNetworkContext::new(network_send, peer_manager, state_manager.blockstore_owned()); let rpc_state = RPCState { state_manager, keystore: Arc::new(RwLock::new(keystore)), @@ -132,7 +137,7 @@ pub async fn start_offline_server( bad_blocks: Default::default(), sync_state: Arc::new(parking_lot::RwLock::new(Default::default())), eth_event_handler: Arc::new(EthEventHandler::new()), - network_send, + sync_network_context, network_name, start_time: chrono::Utc::now(), shutdown, diff --git a/src/utils/misc/env.rs b/src/utils/misc/env.rs index d4021094913..b318ec6f59b 100644 --- a/src/utils/misc/env.rs +++ b/src/utils/misc/env.rs @@ -13,11 +13,17 @@ pub fn env_or_default(key: &str, default: T) -> T { } /// Check if the given environment variable is set to truthy value. +/// Returns false if not set. pub fn is_env_truthy(env: &str) -> bool { - match std::env::var(env) { - Ok(var) => matches!(var.to_lowercase().as_str(), "1" | "true"), - _ => false, - } + is_env_set_and_truthy(env).unwrap_or_default() +} + +/// Check if the given environment variable is set to truthy value. +/// Returns None if not set. +pub fn is_env_set_and_truthy(env: &str) -> Option { + std::env::var(env) + .ok() + .map(|var| matches!(var.to_lowercase().as_str(), "1" | "true")) } #[cfg(test)]