Skip to content
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
f6d903a
feat(fsck): add checker template
qusuyan Jun 5, 2025
8df1b8a
Batch dependabot opentelemetry updates (#913)
aaronbuchwald Jun 4, 2025
0e0c525
refactor(ffi): Cleanup unused and duplicate code (#926)
alarso16 Jun 4, 2025
0b8078f
feat(fsck): checker traverse trie
qusuyan Jun 4, 2025
bc77ca4
Cleanup ffi unit tests (#932)
aaronbuchwald Jun 4, 2025
89b3405
ci: Require conventional commit format (#933)
rkuris Jun 4, 2025
314bc24
Merge branch 'main' into qusuyan/fsck
qusuyan Jun 5, 2025
4e2c553
fix(fsck): fix clippy and add licenses
qusuyan Jun 5, 2025
3d558cd
feat(fsck): add bound checks and area overlapping checks
qusuyan Jun 5, 2025
2c8afe6
Merge branch 'main' into qusuyan/fsck
qusuyan Jun 5, 2025
a155004
test(fsck): add more tests for LinearAddressRangeSet
qusuyan Jun 5, 2025
2dfc795
test(fsck): fix clippy
qusuyan Jun 5, 2025
08ae8b7
docs(fsck): remove comments by cursor
qusuyan Jun 5, 2025
97644ca
refactor(fsck): avoid changing the visibility of NodeStoreHeader
qusuyan Jun 5, 2025
b73d777
refactor(fsck): move checker function into nodestore
qusuyan Jun 5, 2025
7d0b904
feat(fsck): range set return complement
qusuyan Jun 6, 2025
83ae327
refactor(fsck): fix clippy
qusuyan Jun 6, 2025
e15234b
fix: Use saturating subtraction for metrics counter (#937)
rkuris Jun 5, 2025
a3e6f86
refactor(fsck): simplify range set complement
qusuyan Jun 6, 2025
8826b0b
refactor(fsck): test check error content in assert
qusuyan Jun 6, 2025
d9fb9c0
build(fsck): add custom range set implementation
qusuyan Jun 10, 2025
a135abd
test(fsck): add test for range sets
qusuyan Jun 11, 2025
f8b26d3
feat(fsck): add insert_disjoint_area interface that returns the inter…
qusuyan Jun 11, 2025
353247f
refactor(fsck): print test seed on e2e testing and refactor code
qusuyan Jun 11, 2025
32a21c8
test(fsck): add more test cases
qusuyan Jun 12, 2025
6584e86
feat: Improve error handling and add sync iterator (#941)
qusuyan Jun 12, 2025
4ec72e1
build(deps): update fastrace-opentelemetry requirement from 0.11.0 to…
dependabot[bot] Jun 9, 2025
b8b8b33
fix(attach-static-libs): push commit/branch to remote on tag events (…
aaronbuchwald Jun 10, 2025
da278b9
style(attach-static-libs): use go mod edit instead of sed to update m…
aaronbuchwald Jun 10, 2025
3c0d6d4
feat(metrics): Add read_node counters (#947)
rkuris Jun 10, 2025
737a571
Create two pkgs for firewood differential testing against eth and mer…
aaronbuchwald Jun 11, 2025
6666dda
ci: add push to main to attach static libs triggers (#952)
aaronbuchwald Jun 11, 2025
507dd69
ci: Check the PR title for conventional commits (#953)
rkuris Jun 11, 2025
476845b
chore: add Brandon to CODEOWNERS (#954)
rkuris Jun 11, 2025
9f23bec
test(ethhash): convert ethhash test to fuzz test for ethhash compatib…
aaronbuchwald Jun 12, 2025
5e3b7f8
Merge branch 'main' into qusuyan/fsck
qusuyan Jun 12, 2025
0955b03
fix(fsck): fix checker test
qusuyan Jun 13, 2025
1806d22
refactor(fsck): fix clippy
qusuyan Jun 13, 2025
a6e8050
refactor(fsck): refactor code for better readability
qusuyan Jun 13, 2025
1de8e38
refactor(checker): refactor checker code
qusuyan Jun 16, 2025
129f41a
test(checker): add more tests for range sets
qusuyan Jun 17, 2025
985c1fd
Merge branch 'main' into qusuyan/fsck
qusuyan Jun 18, 2025
04b3055
refactor(checker): add helper branch iterator functions
qusuyan Jun 18, 2025
05fc365
refactor(checker): make checker run sync
qusuyan Jun 18, 2025
dc2edb6
refactor(checker): use std::collections::BTreeMap in RangeSet
qusuyan Jun 20, 2025
156ee71
docs(checker): add more comments
qusuyan Jun 23, 2025
2967de6
Merge branch 'main' into qusuyan/fsck
qusuyan Jun 23, 2025
3a1afe6
refactor(checker): update range set logic
qusuyan Jun 23, 2025
38dde3c
refactor(checker): refactor interface
qusuyan Jun 23, 2025
ae66d20
Merge branch 'main' into qusuyan/fsck
qusuyan Jun 24, 2025
4a3212e
Merge branch 'main' into qusuyan/fsck
qusuyan Jun 25, 2025
cbe2f8c
docs(checker): add lisence
qusuyan Jun 26, 2025
6077b92
Merge branch 'main' into qusuyan/fsck
qusuyan Jun 26, 2025
4f34ac2
Merge branch 'main' into qusuyan/fsck
qusuyan Jun 26, 2025
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
2 changes: 1 addition & 1 deletion firewood/src/merkle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ impl<T: TrieReader> Merkle<T> {
if let Some(branch) = root.as_branch() {
// TODO danlaine: can we avoid indexing?
#[expect(clippy::indexing_slicing)]
for (i, hash) in branch.children_iter() {
for (i, hash) in branch.children_hashes() {
child_hashes[i] = Some(hash.clone());
}
}
Expand Down
2 changes: 1 addition & 1 deletion firewood/src/proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ impl From<PathIterItem> for ProofNode {
if let Some(branch) = item.node.as_branch() {
// TODO danlaine: can we avoid indexing?
#[expect(clippy::indexing_slicing)]
for (i, hash) in branch.children_iter() {
for (i, hash) in branch.children_hashes() {
child_hashes[i] = Some(hash.clone());
}
}
Expand Down
6 changes: 5 additions & 1 deletion firewood/src/v2/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::manager::RevisionManagerError;
use crate::proof::{Proof, ProofError, ProofNode};
pub use crate::range_proof::RangeProof;
use async_trait::async_trait;
use firewood_storage::{FileIoError, TrieHash};
use firewood_storage::{CheckerError, FileIoError, TrieHash};
use futures::Stream;
use std::fmt::Debug;
use std::sync::Arc;
Expand Down Expand Up @@ -159,6 +159,10 @@ pub enum Error {
/// Revision not found
#[error("revision not found")]
RevisionNotFound,

/// Checker error
#[error("checker error")]
CheckerError(#[from] CheckerError),
}

impl From<RevisionManagerError> for Error {
Expand Down
3 changes: 3 additions & 0 deletions fwdctl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,22 @@ path = "src/main.rs"

[dependencies]
firewood = { version = "0.0.6", path = "../firewood" }
firewood-storage = { version = "0.0.6", path = "../storage" }
clap = { version = "4.5.0", features = ["cargo", "derive"] }
env_logger = "0.11.2"
log = "0.4.20"
tokio = { version = "1.36.0", features = ["full"] }
futures-util = "0.3.30"
hex = "0.4.3"
csv = "1.3.1"
nonzero_ext = "0.3.0"

[dev-dependencies]
anyhow = "1.0.79"
assert_cmd = "2.0.13"
predicates = "3.1.0"
serial_test = "3.0.0"
rand = "0.9.1"

[lints]
workspace = true
41 changes: 41 additions & 0 deletions fwdctl/src/check.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (C) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE.md for licensing terms.

use std::path::PathBuf;
use std::sync::Arc;

use clap::Args;
use firewood::v2::api;
use firewood_storage::{CacheReadStrategy, FileBacked, NodeStore};
use nonzero_ext::nonzero;

// TODO: (optionally) add a fix option
#[derive(Args)]
pub struct Options {
/// The database path (if no path is provided, return an error). Defaults to firewood.
#[arg(
long,
required = false,
value_name = "DB_NAME",
default_value_t = String::from("firewood"),
help = "Name of the database"
)]
pub db: String,
}

#[allow(clippy::unused_async)]
pub(super) async fn run(opts: &Options) -> Result<(), api::Error> {
let db_path = PathBuf::from(&opts.db);
let node_cache_size = nonzero!(1usize);
let free_list_cache_size = nonzero!(1usize);

let storage = Arc::new(FileBacked::new(
db_path,
node_cache_size,
free_list_cache_size,
false,
CacheReadStrategy::WritesOnly, // we scan the database once - no need to cache anything
)?);

NodeStore::open(storage)?.check().map_err(Into::into)
}
4 changes: 4 additions & 0 deletions fwdctl/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use clap::{Parser, Subcommand};
use firewood::v2::api;

pub mod check;
pub mod create;
pub mod delete;
pub mod dump;
Expand Down Expand Up @@ -49,6 +50,8 @@ enum Commands {
Dump(dump::Options),
/// Produce a dot file of the database
Graph(graph::Options),
/// Runs the checker on the database
Check(check::Options),
}

#[tokio::main]
Expand All @@ -68,5 +71,6 @@ async fn main() -> Result<(), api::Error> {
Commands::Root(opts) => root::run(opts).await,
Commands::Dump(opts) => dump::run(opts).await,
Commands::Graph(opts) => graph::run(opts).await,
Commands::Check(opts) => check::run(opts).await,
}
}
104 changes: 75 additions & 29 deletions fwdctl/tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ fn fwdctl_creates_database() -> Result<()> {
.assert()
.success();

fwdctl_delete_db().map_err(|e| anyhow!(e))?;

Ok(())
fwdctl_delete_db()
}

#[test]
Expand All @@ -71,9 +69,7 @@ fn fwdctl_insert_successful() -> Result<()> {
.success()
.stdout(predicate::str::contains("year"));

fwdctl_delete_db().map_err(|e| anyhow!(e))?;

Ok(())
fwdctl_delete_db()
}

#[test]
Expand Down Expand Up @@ -106,9 +102,7 @@ fn fwdctl_get_successful() -> Result<()> {
.success()
.stdout(predicate::str::contains("2023"));

fwdctl_delete_db().map_err(|e| anyhow!(e))?;

Ok(())
fwdctl_delete_db()
}

#[test]
Expand Down Expand Up @@ -140,9 +134,7 @@ fn fwdctl_delete_successful() -> Result<()> {
.success()
.stdout(predicate::str::contains("key year deleted successfully"));

fwdctl_delete_db().map_err(|e| anyhow!(e))?;

Ok(())
fwdctl_delete_db()
}

#[test]
Expand Down Expand Up @@ -173,9 +165,7 @@ fn fwdctl_root_hash() -> Result<()> {
.success()
.stdout(predicate::str::is_empty().not());

fwdctl_delete_db().map_err(|e| anyhow!(e))?;

Ok(())
fwdctl_delete_db()
}

#[test]
Expand Down Expand Up @@ -205,9 +195,7 @@ fn fwdctl_dump() -> Result<()> {
.success()
.stdout(predicate::str::contains("2023"));

fwdctl_delete_db().map_err(|e| anyhow!(e))?;

Ok(())
fwdctl_delete_db()
}

#[test]
Expand Down Expand Up @@ -313,9 +301,7 @@ fn fwdctl_dump_with_start_stop_and_max() -> Result<()> {
"Next key is c, resume with \"--start-key=c\"",
));

fwdctl_delete_db().map_err(|e| anyhow!(e))?;

Ok(())
fwdctl_delete_db()
}

#[test]
Expand Down Expand Up @@ -388,9 +374,7 @@ fn fwdctl_dump_with_csv_and_json() -> Result<()> {
);
fs::remove_file("dump.json").expect("Should remove dump.json file");

fwdctl_delete_db().map_err(|e| anyhow!(e))?;

Ok(())
fwdctl_delete_db()
}

#[test]
Expand Down Expand Up @@ -454,9 +438,7 @@ fn fwdctl_dump_with_file_name() -> Result<()> {
assert_eq!(contents, "{\n \"a\": \"1\"\n}\n");
fs::remove_file("test.json").expect("Should remove test.json file");

fwdctl_delete_db().map_err(|e| anyhow!(e))?;

Ok(())
fwdctl_delete_db()
}

#[test]
Expand Down Expand Up @@ -534,9 +516,73 @@ fn fwdctl_dump_with_hex() -> Result<()> {
.stdout(predicate::str::contains("--start-key=c"))
.stdout(predicate::str::contains("--start-key-hex=63"));

fwdctl_delete_db().map_err(|e| anyhow!(e))?;
fwdctl_delete_db()
}

Ok(())
#[test]
#[serial]
fn fwdctl_check_empty_db() -> Result<()> {
Command::cargo_bin(PRG)?
.arg("create")
.arg(tmpdb::path())
.assert()
.success();

Command::cargo_bin(PRG)?
.arg("check")
.arg("--db")
.arg(tmpdb::path())
.assert()
.success();

fwdctl_delete_db()
}

#[test]
#[serial]
fn fwdctl_check_db_with_data() -> Result<()> {
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng, rng};

let seed = std::env::var("FIREWOOD_TEST_SEED")
.ok()
.map_or_else(
|| None,
|s| Some(str::parse(&s).expect("couldn't parse FIREWOOD_TEST_SEED; must be a u64")),
)
.unwrap_or_else(|| rng().random());

eprintln!("Seed {seed}: to rerun with this data, export FIREWOOD_TEST_SEED={seed}");
let mut rng = StdRng::seed_from_u64(seed);

Command::cargo_bin(PRG)?
.arg("create")
.arg(tmpdb::path())
.assert()
.success();

// TODO: bulk loading data instead of inserting one by one
for _ in 0..4 {
let key = format!("key_{}", rng.random::<u64>());
let value = format!("value_{}", rng.random::<u64>());
Command::cargo_bin(PRG)?
.arg("insert")
.args([key])
.args([value])
.args(["--db"])
.args([tmpdb::path()])
.assert()
.success();
}

Command::cargo_bin(PRG)?
.arg("check")
.arg("--db")
.arg(tmpdb::path())
.assert()
.success();

fwdctl_delete_db()
}

// A module to create a temporary database name for use in
Expand Down
2 changes: 2 additions & 0 deletions storage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ coarsetime = "0.1.35"
rlp = { version = "0.6.1", optional = true }
sha3 = { version = "0.10.8", optional = true }
bytes = { version = "1.10.1", optional = true }
thiserror = "2.0.3"
semver = "1.0.26"

[dev-dependencies]
Expand All @@ -46,6 +47,7 @@ test-case = "3.3.1"
criterion = { version = "0.6.0", features = ["async_tokio", "html_reports"] }
pprof = { version = "0.15.0", features = ["flamegraph"] }
tempfile = "3.12.0"
nonzero_ext = "0.3.0"

[features]
logger = ["log"]
Expand Down
Loading
Loading