Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ Have questions? Feel free to post them in [Forest Q&A]!

## Run with Docker

## Optional Chainstore Fallback

Forest supports an experimental fallback blockstore to fetch missing blocks from the network via Bitswap when they are not available locally. This behavior is disabled by default.

To enable fallback, set the environment variable:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Typo: double space before "enable".

-To  enable fallback, set the environment variable:
+To enable fallback, set the environment variable:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
To enable fallback, set the environment variable:
To enable fallback, set the environment variable:
🤖 Prompt for AI Agents
In @README.md at line 36, The sentence "To  enable fallback, set the environment
variable:" contains a double space after "To"; fix it by replacing "To  enable
fallback, set the environment variable:" with "To enable fallback, set the
environment variable:" in the README (the line containing the string "To  enable
fallback, set the environment variable:").


FOREST_ENABLE_CHAINSTORE_FALLBACK=1

When enabled, Forest will attempt to retrieve missing blocks via Bitswap during chain sync. If the block is successfully retrieved, it will be stored locally and chain sync will continue. If the block cannot be fetched from the network, chain sync will return an error as usual.

Note: This feature is inspired by Lotus's `LOTUS_ENABLE_CHAINSTORE_FALLBACK` and is considered experimental. Use with caution in production.


No need to install Rust toolchain or other dependencies, you will need only
Docker - works on Linux, macOS and Windows.

Expand Down
76 changes: 76 additions & 0 deletions src/chain/store/blockstore_fallback.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//! Fallback blockstore for Forest
//!
//! This module provides a wrapper around an existing blockstore that adds a
//! fallback mechanism: if a requested block is missing locally and the
//! environment variable `FOREST_ENABLE_CHAINSTORE_FALLBACK` is set to `1`,
//! the block will be fetched from the network via bitswap and then
//! re-attempted from the local store.
use std::env;
use std::sync::Arc;

use cid::Cid;
use anyhow::Result;

/// A simple trait representing the ability to fetch blocks by CID from a
/// local store. In Forest this corresponds to the `Blockstore` trait.
pub trait BlockGetter {
type Block;
fn get(&self, cid: &Cid) -> Result<Option<Self::Block>>;
}

/// Trait representing a minimal bitswap request manager. Implementors
/// should fetch the given CID from the network and insert it into the
/// local blockstore on success.
#[async_trait::async_trait]
pub trait BitswapFetcher {
async fn fetch_block(&self, cid: &Cid) -> Result<()>;
}

/// A wrapper that adds fallback behaviour to a blockstore.
///
/// When `FOREST_ENABLE_CHAINSTORE_FALLBACK=1` and a block is missing
/// locally, it will be fetched from the network via `bitswap` and then
/// reloaded from the underlying store.
pub struct FallbackBlockstore<S, B> {
store: S,
bitswap: Arc<B>,
}

impl<S, B> FallbackBlockstore<S, B>
where
S: BlockGetter + Send + Sync,
B: BitswapFetcher + Send + Sync,
{
/// Create a new fallback wrapper around an existing blockstore and
/// bitswap request manager.
pub fn new(store: S, bitswap: Arc<B>) -> Self {
Self { store, bitswap }
}

/// Retrieve a block from the local store, falling back to bitswap when
/// enabled and necessary. If the block is still missing after the
/// network fetch, `None` is returned.
pub async fn get_with_fallback(&self, cid: &Cid) -> Result<Option<S::Block>> {
// Attempt to read from the underlying store first.
if let Some(block) = self.store.get(cid)? {
return Ok(Some(block));
}

// Check environment variable to decide if fallback is enabled.
let enable = env::var("FOREST_ENABLE_CHAINSTORE_FALLBACK")
.map(|v| v == "1")
.unwrap_or(false);
if !enable {
return Ok(None);
}
Comment on lines +61 to +66
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid repeated env::var() calls on hot path.

Reading the environment variable on every get_with_fallback() call incurs syscall overhead and string allocation. During chain sync, this could be called thousands of times. Cache the value at construction time or use std::sync::LazyLock (stable in Rust 1.80+).

🔧 Suggested fix using LazyLock
 use std::env;
 use std::sync::Arc;
+use std::sync::LazyLock;
 
 use cid::Cid;
 use anyhow::Result;
+
+static FALLBACK_ENABLED: LazyLock<bool> = LazyLock::new(|| {
+    env::var("FOREST_ENABLE_CHAINSTORE_FALLBACK")
+        .map(|v| v == "1")
+        .unwrap_or(false)
+});

Then in get_with_fallback:

-        // Check environment variable to decide if fallback is enabled.
-        let enable = env::var("FOREST_ENABLE_CHAINSTORE_FALLBACK")
-            .map(|v| v == "1")
-            .unwrap_or(false);
-        if !enable {
+        if !*FALLBACK_ENABLED {
             return Ok(None);
         }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @src/chain/store/blockstore_fallback.rs around lines 61 - 66, The code reads
FOREST_ENABLE_CHAINSTORE_FALLBACK via env::var inside get_with_fallback on the
hot path; move that env var read out of the hot path by caching the boolean at
construction or in a static LazyLock so get_with_fallback checks a stored flag
instead of calling env::var each time. Concretely, add a field like
enable_fallback to the BlockstoreFallback (or initialize a static
std::sync::LazyLock<bool> with env::var("FOREST_ENABLE_CHAINSTORE_FALLBACK") ==
"1"), set it once at creation, and replace the inline env::var check in
get_with_fallback with a read of that cached boolean.


// Fetch the block from the network via bitswap.
// Errors from bitswap are propagated so callers can decide how to
// handle network failures.
self.bitswap.fetch_block(cid).await?;

// Try again from the local store after network fetch.
self.store.get(cid)
}
}
4 changes: 2 additions & 2 deletions src/chain/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ mod chain_store;
mod errors;
pub mod index;
mod tipset_tracker;

pub use self::{base_fee::*, chain_store::*, errors::*};
mod blockstore_fallback;
pub use self::{base_fee::*, chain_store::*, errors::*, blockstore_fallback::*};