Skip to content
Merged
Changes from all commits
Commits
Show all changes
111 commits
Select commit Hold shift + click to select a range
e38dad3
Moving merkle.rs to merkle directory
bernard-avalabs Aug 14, 2025
03d5ad1
chore: Moved merkle.rs to mod.rs in merkle directory
bernard-avalabs Aug 14, 2025
a0aeb19
Merge branch 'bernard/insert-worker-pool-rayon' of github.com:ava-lab…
bernard-avalabs Aug 14, 2025
afae01e
Simple version where all operations are offloaded to a single
bernard-avalabs Aug 19, 2025
c9e7651
Basic handoff to offload thread appears to be working.
bernard-avalabs Aug 19, 2025
bd877e6
More testing
bernard-avalabs Aug 19, 2025
7a22038
Working on prepare phase. For the case where the trie is empty,
bernard-avalabs Aug 20, 2025
c3d4100
Working on case for non-empty trie and an non-empty partial path in t…
bernard-avalabs Aug 20, 2025
c2445bc
Implemented all of the prepare phase cases. Appears to be working.
bernard-avalabs Aug 20, 2025
894033c
Added comments detailing steps to fix a temporarily malformed
bernard-avalabs Aug 20, 2025
8fb6da5
Initial code for undoing transform
bernard-avalabs Aug 28, 2025
bff76c1
Minor cleanup
bernard-avalabs Aug 29, 2025
7ef3fe2
Additional comments.
bernard-avalabs Aug 29, 2025
31129e6
Added some nodestore related changes. Fix possible error with the use of
bernard-avalabs Aug 29, 2025
efce980
Extra debug
bernard-avalabs Aug 29, 2025
6628617
Appears to work with an added insert path (needs to be refactored).
bernard-avalabs Sep 1, 2025
954a02d
Added Delete and DeleteRange
bernard-avalabs Sep 1, 2025
bd1e7c2
Cleanup
bernard-avalabs Sep 1, 2025
fcda015
Added code to handle most of the cases where the key is empty.
bernard-avalabs Sep 2, 2025
364f8d7
Improved comments around handling empty key.
bernard-avalabs Sep 2, 2025
b12ac8b
Added deleted from child nodestores to the proposal.
bernard-avalabs Sep 2, 2025
e2fde1a
Moved threadpool to DB.
bernard-avalabs Sep 2, 2025
2cbf542
Support case where trie only has a single leaf with an empty key.
bernard-avalabs Sep 2, 2025
9c4ac97
Added some error handling
bernard-avalabs Sep 2, 2025
660f0f8
Reorganizing code
bernard-avalabs Sep 2, 2025
eb2d0dd
Code cleanup
bernard-avalabs Sep 2, 2025
231c264
More error handling.
bernard-avalabs Sep 3, 2025
5fff34d
More cleanly handled the remove empty prefix case
bernard-avalabs Sep 3, 2025
ee9a488
Small fix to also delete the root value on a remove all.
bernard-avalabs Sep 3, 2025
e5bf520
Clean up. Fixed test.
bernard-avalabs Sep 3, 2025
e5a5757
Removed OneLock since it is not necessary
bernard-avalabs Sep 3, 2025
b670403
Cleanup with additional comments.
bernard-avalabs Sep 3, 2025
e816dad
Added test cases
bernard-avalabs Sep 3, 2025
e6d953e
More cleanup
bernard-avalabs Sep 3, 2025
b3b8c3d
Merge remote-tracking branch 'origin/main' into bernard/insert-worker…
bernard-avalabs Sep 4, 2025
2bfaf34
Cleanup for PR
bernard-avalabs Sep 4, 2025
2c2f3b5
Replaced Option<ThreadPool> with OnceLock<ThreadPool> so propose_para…
bernard-avalabs Sep 4, 2025
57717d9
Addressing most of the PR comments.
bernard-avalabs Sep 10, 2025
320c178
Moved threadpool to RevisionManager.
bernard-avalabs Sep 10, 2025
cfe983e
Made the initial change to the insert/remove/remove_prefix interface …
bernard-avalabs Sep 11, 2025
301243c
Moved take child code from parallel.rs to branch.rs
bernard-avalabs Sep 11, 2025
5effd6e
Added a TODO to describe a possible optimization using scoped threads.
bernard-avalabs Sep 11, 2025
7dd784c
Merge remote-tracking branch 'origin/main' into bernard/insert-worker…
bernard-avalabs Sep 11, 2025
7e12f7b
Initial commit for parallel hashing.
bernard-avalabs Sep 24, 2025
bfae618
Fixed hash computation
bernard-avalabs Sep 24, 2025
d57f8aa
Added error handling.
bernard-avalabs Sep 25, 2025
c8422b0
Cleanup
bernard-avalabs Sep 25, 2025
7c8ff58
More cleanup. Sends vector of deleted nodes instead of child's nodest…
bernard-avalabs Sep 25, 2025
6b07fb3
Cleanup.
bernard-avalabs Sep 25, 2025
42e6c1c
Modify fuzz_checker to randomly use either propose or propose_parallel
bernard-avalabs Sep 25, 2025
acbaca7
Comment fix
bernard-avalabs Sep 25, 2025
89aa167
Comment fix
bernard-avalabs Sep 25, 2025
4e21f71
Removed printlns
bernard-avalabs Sep 25, 2025
885f384
Comment fix
bernard-avalabs Sep 25, 2025
9c0c139
Comment fix
bernard-avalabs Sep 25, 2025
3a090b6
Changes for Eth hash
bernard-avalabs Sep 25, 2025
fbaf3eb
Removed hash_subtrie_with_index and modified hash_helper to take a path.
bernard-avalabs Sep 25, 2025
82d2c59
Updated typed-builder
bernard-avalabs Sep 25, 2025
1850186
Merge branch 'main' into bernard/insert-worker-pool-rayon-part2-merged
bernard-avalabs Sep 25, 2025
6d6c61c
Merge branch 'bernard/insert-worker-pool-rayon-part2-merged' into ber…
bernard-avalabs Sep 25, 2025
24be026
Updated rayon
bernard-avalabs Sep 25, 2025
0c60b09
Merge branch 'bernard/insert-worker-pool-rayon-part2-merged' into ber…
bernard-avalabs Sep 25, 2025
f2c464b
Updating CI
bernard-avalabs Sep 25, 2025
8f7cb00
Merge branch 'bernard/insert-worker-pool-rayon-part2-merged' into ber…
bernard-avalabs Sep 25, 2025
f11bd53
Merge branch 'main' into bernard/insert-worker-pool-rayon-part2-merged
bernard-avalabs Sep 26, 2025
21dec41
Removed extra "with" entries in ci.yaml
bernard-avalabs Sep 26, 2025
82b76aa
Merge branch 'bernard/insert-worker-pool-rayon-part2-merged' into ber…
bernard-avalabs Sep 26, 2025
4cc16b1
Merge branch 'main' into bernard/insert-worker-pool-rayon-part2-merged
bernard-avalabs Sep 26, 2025
35e12a7
Merge branch 'bernard/insert-worker-pool-rayon-part2-merged' into ber…
bernard-avalabs Sep 26, 2025
e85d0b0
Fix after merge
bernard-avalabs Sep 26, 2025
4291ed6
Formatting
bernard-avalabs Sep 26, 2025
426868a
Ethhash fix after merge
bernard-avalabs Sep 26, 2025
6645609
Merge branch 'main' into bernard/insert-worker-pool-rayon-part2-merged
bernard-avalabs Sep 26, 2025
5d17d5a
Merge branch 'bernard/insert-worker-pool-rayon-part2-merged' into ber…
bernard-avalabs Sep 26, 2025
30093de
Separate response channel into sender/receiver
bernard-avalabs Sep 29, 2025
9b70357
Merge branch 'bernard/insert-worker-pool-rayon-part2-merged' into ber…
bernard-avalabs Sep 29, 2025
a6970e7
Addressed PR feedback
bernard-avalabs Oct 6, 2025
2176b9a
Fix for ethhash
bernard-avalabs Oct 6, 2025
0356df3
Comment update
bernard-avalabs Oct 6, 2025
5516f9d
Additional change for feedback
bernard-avalabs Oct 6, 2025
1e98c9a
Merge branch 'main' into bernard/insert-worker-pool-rayon-part2-merged
bernard-avalabs Oct 6, 2025
d01ca7b
Updating cargo.lock
bernard-avalabs Oct 6, 2025
8a80806
Merge branch 'bernard/insert-worker-pool-rayon-part2-merged' into ber…
bernard-avalabs Oct 6, 2025
fccb73e
Added config option to turn on or off parallel insert and hashing.
bernard-avalabs Oct 6, 2025
9a59216
Comment fix
bernard-avalabs Oct 6, 2025
99049dd
FFI-related changes
bernard-avalabs Oct 6, 2025
b7474c6
Added testcase for FFI changes
bernard-avalabs Oct 6, 2025
40cd643
Combine Db.propose with Proposal.create_proposal
bernard-avalabs Oct 7, 2025
e6c1015
chore: Create propose_helper to reduce code duplication between Db.pr…
bernard-avalabs Oct 7, 2025
b37394c
Merge branch 'bernard/combine-propose' of github.com:ava-labs/firewoo…
bernard-avalabs Oct 7, 2025
2f0d051
Changes to address review feedback.
bernard-avalabs Oct 15, 2025
4e4ae26
Merge branch 'main' into bernard/combine-propose
bernard-avalabs Oct 20, 2025
781943b
Undo some changes
bernard-avalabs Oct 21, 2025
6875144
Formatting
bernard-avalabs Oct 21, 2025
283aa64
Removed duplicate function
bernard-avalabs Oct 21, 2025
85ca38e
Removed unused field
bernard-avalabs Oct 21, 2025
bd15c4d
Added some comments
bernard-avalabs Oct 21, 2025
f89909e
Removed unneeded field
bernard-avalabs Oct 21, 2025
a5e16e0
Merge branch 'main' into bernard/combine-propose
bernard-avalabs Oct 21, 2025
2a6f9d5
Expanded comments
bernard-avalabs Oct 21, 2025
44d36dc
Merge remote-tracking branch 'refs/remotes/origin/bernard/combine-pro…
bernard-avalabs Oct 21, 2025
3aaed82
Fixed clippy
bernard-avalabs Oct 21, 2025
337da8d
Comment change
bernard-avalabs Oct 21, 2025
0115b91
Added support to force parallel for testing
bernard-avalabs Oct 22, 2025
ba51043
Addressing feedback.
bernard-avalabs Oct 28, 2025
2af39e1
Added UseParallel enum
bernard-avalabs Oct 28, 2025
e02f14c
Merge remote-tracking branch 'origin/main' into bernard/combine-propose
bernard-avalabs Oct 28, 2025
ea3ed51
Comment update
bernard-avalabs Oct 28, 2025
100a15c
Merge branch 'main' into bernard/combine-propose
bernard-avalabs Oct 30, 2025
35fee03
Addressing review feedback
bernard-avalabs Nov 5, 2025
9c85631
Merge branch 'main' into bernard/combine-propose
bernard-avalabs Nov 5, 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
194 changes: 109 additions & 85 deletions firewood/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use crate::merkle::{Merkle, Value};
use crate::root_store::{NoOpStore, RootStore};
pub use crate::v2::api::BatchOp;
use crate::v2::api::{
self, ArcDynDbView, FrozenProof, FrozenRangeProof, HashKey, KeyType, KeyValuePair,
KeyValuePairIter, OptionalHashKeyExt,
self, ArcDynDbView, FrozenProof, FrozenRangeProof, HashKey, KeyType, KeyValuePairIter,
OptionalHashKeyExt,
};

use crate::manager::{ConfigManager, RevisionManager, RevisionManagerConfig};
Expand Down Expand Up @@ -95,6 +95,14 @@ where
}
}

#[allow(dead_code)]
#[derive(Clone, Debug)]
pub enum UseParallel {
Never,
BatchSize(usize),
Always,
}

/// Database configuration.
#[derive(Clone, TypedBuilder, Debug)]
#[non_exhaustive]
Expand All @@ -109,13 +117,19 @@ pub struct DbConfig {
/// Revision manager configuration.
#[builder(default = RevisionManagerConfig::builder().build())]
pub manager: RevisionManagerConfig,
// Whether to perform parallel proposal creation. If set to BatchSize, then firewood
// performs parallel proposal creation if the batch is >= to the BatchSize value.
// TODO: Experimentally determine the right value for BatchSize.
#[builder(default = UseParallel::BatchSize(8))]
pub use_parallel: UseParallel,
}

#[derive(Debug)]
/// A database instance.
pub struct Db {
metrics: Arc<DbMetrics>,
manager: RevisionManager,
use_parallel: UseParallel,
}

impl api::Db for Db {
Expand All @@ -139,45 +153,11 @@ impl api::Db for Db {
Ok(self.manager.all_hashes())
}

#[fastrace::trace(short_name = true)]
fn propose(
&self,
batch: impl IntoIterator<IntoIter: KeyValuePairIter>,
) -> Result<Self::Proposal<'_>, api::Error> {
let parent = self.manager.current_revision();
let proposal = NodeStore::new(&parent)?;
let mut merkle = Merkle::from(proposal);
let span = fastrace::Span::enter_with_local_parent("merkleops");
for op in batch.into_iter().map_into_batch() {
match op {
BatchOp::Put { key, value } => {
merkle.insert(key.as_ref(), value.as_ref().into())?;
}
BatchOp::Delete { key } => {
merkle.remove(key.as_ref())?;
}
BatchOp::DeleteRange { prefix } => {
merkle.remove_prefix(prefix.as_ref())?;
}
}
}

drop(span);
let span = fastrace::Span::enter_with_local_parent("freeze");

let nodestore = merkle.into_inner();
let immutable: Arc<NodeStore<Arc<ImmutableProposal>, FileBacked>> =
Arc::new(nodestore.try_into()?);

drop(span);
self.manager.add_proposal(immutable.clone());

self.metrics.proposals.increment(1);

Ok(Self::Proposal {
nodestore: immutable,
db: self,
})
self.propose_with_parent(batch, &self.manager.current_revision())
}
}

Expand All @@ -204,7 +184,11 @@ impl Db {

let manager =
RevisionManager::new(db_path.as_ref().to_path_buf(), config_manager, root_store)?;
let db = Self { metrics, manager };
let db = Self {
metrics,
manager,
use_parallel: cfg.use_parallel,
};
Ok(db)
}

Expand All @@ -231,20 +215,57 @@ impl Db {
latest_rev_nodestore.check(opt)
}

/// Propose a new batch that is processed in parallel.
/// Create a proposal with a specified parent. A proposal is created in parallel if `use_parallel`
/// is `Always` or if `use_parallel` is `BatchSize` and the batch is >= to the `BatchSize` value.
///
/// # Panics
///
/// Panics if the revision manager cannot create a thread pool.
pub fn propose_parallel(
#[fastrace::trace(name = "propose")]
fn propose_with_parent<F: Parentable>(
&self,
batch: impl IntoIterator<IntoIter: KeyValuePairIter>,
parent: &NodeStore<F, FileBacked>,
) -> Result<Proposal<'_>, api::Error> {
let parent = self.manager.current_revision();
let mut parallel_merkle = ParallelMerkle::default();
let immutable =
parallel_merkle.create_proposal(&parent, batch, self.manager.threadpool())?;
// If use_parallel is BatchSize, then perform parallel proposal creation if the batch
// size is >= BatchSize.
let batch = batch.into_iter();
let use_parallel = match self.use_parallel {
UseParallel::Never => false,
UseParallel::Always => true,
UseParallel::BatchSize(required_size) => batch.size_hint().0 >= required_size,
};
let immutable = if use_parallel {
let mut parallel_merkle = ParallelMerkle::default();
let _span = fastrace::Span::enter_with_local_parent("parallel_merkle");
parallel_merkle.create_proposal(parent, batch, self.manager.threadpool())?
} else {
let proposal = NodeStore::new(parent)?;
let mut merkle = Merkle::from(proposal);
let span = fastrace::Span::enter_with_local_parent("merkleops");
for op in batch.into_iter().map_into_batch() {
match op {
BatchOp::Put { key, value } => {
merkle.insert(key.as_ref(), value.as_ref().into())?;
}
BatchOp::Delete { key } => {
merkle.remove(key.as_ref())?;
}
BatchOp::DeleteRange { prefix } => {
merkle.remove_prefix(prefix.as_ref())?;
}
}
}

drop(span);
let _span = fastrace::Span::enter_with_local_parent("freeze");
let nodestore = merkle.into_inner();
Arc::new(nodestore.try_into()?)
};
self.manager.add_proposal(immutable.clone());

self.metrics.proposals.increment(1);

Ok(Proposal {
nodestore: immutable,
db: self,
Expand Down Expand Up @@ -313,31 +334,7 @@ impl Proposal<'_> {
&self,
batch: impl IntoIterator<IntoIter: KeyValuePairIter>,
) -> Result<Self, api::Error> {
let parent = self.nodestore.clone();
let proposal = NodeStore::new(&parent)?;
let mut merkle = Merkle::from(proposal);
for op in batch {
match op.into_batch() {
BatchOp::Put { key, value } => {
merkle.insert(key.as_ref(), value.as_ref().into())?;
}
BatchOp::Delete { key } => {
merkle.remove(key.as_ref())?;
}
BatchOp::DeleteRange { prefix } => {
merkle.remove_prefix(prefix.as_ref())?;
}
}
}
let nodestore = merkle.into_inner();
let immutable: Arc<NodeStore<Arc<ImmutableProposal>, FileBacked>> =
Arc::new(nodestore.try_into()?);
self.db.manager.add_proposal(immutable.clone());

Ok(Self {
nodestore: immutable,
db: self.db,
})
self.db.propose_with_parent(batch, &self.nodestore)
}
}

Expand All @@ -355,7 +352,7 @@ mod test {
CheckOpt, CheckerError, HashedNodeReader, IntoHashType, NodeStore, TrieHash,
};

use crate::db::{Db, Proposal};
use crate::db::{Db, Proposal, UseParallel};
use crate::root_store::{MockStore, RootStore};
use crate::v2::api::{Db as _, DbView, KeyValuePairIter, Proposal as _};

Expand Down Expand Up @@ -606,14 +603,23 @@ mod test {
let keys: Vec<[u8; 1]> = vec![[kv; 1]];
let vals: Vec<Box<[u8]>> = vec![Box::new([kv; 1])];
let kviter = keys.iter().zip(vals.iter()).map_into_batch();
let proposal = db.propose_parallel(kviter).unwrap();
let proposal = db.propose(kviter).unwrap();
proposal.commit().unwrap();
}

// Create, insert, close, open, insert
let db = TestDb::new();
let db = TestDb::new_with_config(
DbConfig::builder()
.use_parallel(UseParallel::Always)
.build(),
);
insert_commit(&db, 1);
let db = db.reopen();
let db = db.reopen_with_config(
DbConfig::builder()
.truncate(false)
.use_parallel(UseParallel::Always)
.build(),
);
insert_commit(&db, 2);
// Check that the keys are still there after the commits
let committed = db.revision(db.root_hash().unwrap().unwrap()).unwrap();
Expand All @@ -626,9 +632,17 @@ mod test {
drop(db);

// Open-db1, insert, open-db2, insert
let db1 = TestDb::new();
let db1 = TestDb::new_with_config(
DbConfig::builder()
.use_parallel(UseParallel::Always)
.build(),
);
insert_commit(&db1, 1);
let db2 = TestDb::new();
let db2 = TestDb::new_with_config(
DbConfig::builder()
.use_parallel(UseParallel::Always)
.build(),
);
insert_commit(&db2, 2);
let committed1 = db1.revision(db1.root_hash().unwrap().unwrap()).unwrap();
let committed2 = db2.revision(db2.root_hash().unwrap().unwrap()).unwrap();
Expand All @@ -644,14 +658,18 @@ mod test {
#[test]
fn test_propose_parallel() {
const N: usize = 100;
let db = TestDb::new();
let db = TestDb::new_with_config(
DbConfig::builder()
.use_parallel(UseParallel::Always)
.build(),
);

// Test an empty proposal
let keys: Vec<[u8; 0]> = Vec::new();
let vals: Vec<Box<[u8]>> = Vec::new();

let kviter = keys.iter().zip(vals.iter()).map_into_batch();
let proposal = db.propose_parallel(kviter).unwrap();
let proposal = db.propose(kviter).unwrap();
proposal.commit().unwrap();

// Create a proposal consisting of a single entry and an empty key.
Expand All @@ -662,7 +680,7 @@ mod test {
let vals: Vec<Box<[u8]>> = vec![Box::new([0; 1])];

let kviter = keys.iter().zip(vals.iter()).map_into_batch();
let proposal = db.propose_parallel(kviter).unwrap();
let proposal = db.propose(kviter).unwrap();

let kviter = keys.iter().zip(vals.iter());
for (k, v) in kviter {
Expand All @@ -680,7 +698,7 @@ mod test {
// Create a proposal that deletes the previous entry
let vals: Vec<Box<[u8]>> = vec![Box::new([0; 0])];
let kviter = keys.iter().zip(vals.iter()).map_into_batch();
let proposal = db.propose_parallel(kviter).unwrap();
let proposal = db.propose(kviter).unwrap();

let kviter = keys.iter().zip(vals.iter());
for (k, _v) in kviter {
Expand All @@ -699,7 +717,7 @@ mod test {
.unzip();

let kviter = keys.iter().zip(vals.iter()).map_into_batch();
let proposal = db.propose_parallel(kviter).unwrap();
let proposal = db.propose(kviter).unwrap();
let kviter = keys.iter().zip(vals.iter());
for (k, v) in kviter {
assert_eq!(&proposal.val(k).unwrap().unwrap(), v);
Expand All @@ -718,7 +736,7 @@ mod test {
.unzip();

let kviter = keys.iter().zip(vals.iter()).map_into_batch();
let proposal = db.propose_parallel(kviter).unwrap();
let proposal = db.propose(kviter).unwrap();
let kviter = keys.iter().zip(vals.iter());
for (k, _v) in kviter {
assert_eq!(proposal.val(k).unwrap(), None);
Expand All @@ -729,7 +747,7 @@ mod test {
let keys: Vec<[u8; 0]> = vec![[0; 0]];
let vals: Vec<Box<[u8]>> = vec![Box::new([0; 0])];
let kviter = keys.iter().zip(vals.iter()).map_into_batch();
let proposal = db.propose_parallel(kviter).unwrap();
let proposal = db.propose(kviter).unwrap();
proposal.commit().unwrap();

// Create N keys and values like (key0, value0)..(keyN, valueN)
Expand All @@ -746,7 +764,7 @@ mod test {
// Looping twice to test that we are reusing the thread pool.
for _ in 0..2 {
let kviter = keys.iter().zip(vals.iter()).map_into_batch();
let proposal = db.propose_parallel(kviter).unwrap();
let proposal = db.propose(kviter).unwrap();

// iterate over the keys and values again, checking that the values are in the correct proposal
let kviter = keys.iter().zip(vals.iter());
Expand Down Expand Up @@ -1090,11 +1108,14 @@ mod test {

impl TestDb {
fn new() -> Self {
TestDb::new_with_config(DbConfig::builder().build())
}

fn new_with_config(dbconfig: DbConfig) -> Self {
let tmpdir = tempfile::tempdir().unwrap();
let dbpath: PathBuf = [tmpdir.path().to_path_buf(), PathBuf::from("testdb")]
.iter()
.collect();
let dbconfig = DbConfig::builder().build();
let db = Db::new(dbpath, dbconfig).unwrap();
TestDb { db, tmpdir }
}
Expand All @@ -1110,12 +1131,15 @@ mod test {
}

fn reopen(self) -> Self {
self.reopen_with_config(DbConfig::builder().truncate(false).build())
}

fn reopen_with_config(self, dbconfig: DbConfig) -> Self {
let path = self.path();
let TestDb { db, tmpdir } = self;

let root_store = db.into_root_store();

let dbconfig = DbConfig::builder().truncate(false).build();
let db = Db::with_root_store(path, dbconfig, root_store).unwrap();
TestDb { db, tmpdir }
}
Expand Down