Skip to content

Commit 61fd108

Browse files
committed
Bugfix: the iter_all_by_prefix was not working for all tables (#1814)
We used `RocksDB` filtering by prefix during `iter_all_by_prefix`. But if the prefix is not set for the column, the `RocksDB` ignores it. We don't set the prefix for all tables because of performance purposes. But it means that iteration sometimes can be wrong. The change adds a `Rust` level filtering since not all tables really need a prefix. The issue found by @segfault-magnet in FuelLabs/fuel-core#1799 (comment) ### Before requesting review - [x] I have reviewed the code myself
1 parent 4f25b01 commit 61fd108

File tree

6 files changed

+68
-86
lines changed

6 files changed

+68
-86
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77
## [Unreleased]
88

99
Description of the upcoming release here.
10+
### Fixed
1011

12+
- [#1814](https://github.com/FuelLabs/fuel-core/pull/1814): Bugfix: the `iter_all_by_prefix` was not working for all tables. The change adds a `Rust` level filtering.
13+
=======
1114
### Added
1215

1316
- [#1799](https://github.com/FuelLabs/fuel-core/pull/1799) Snapshot creation is now concurrent.

crates/fuel-core/src/combined_database.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,13 @@ impl CombinedDatabase {
151151
use fuel_core_chain_config::AddTable;
152152
use itertools::Itertools;
153153
let mut builder = StateConfigBuilder::default();
154-
use crate::database::IncludeAll;
155154

156155
macro_rules! add_tables {
157156
($($table: ty),*) => {
158157
$(
159158
let table = self
160159
.on_chain()
161-
.entries::<$table>(IncludeAll, fuel_core_storage::iter::IterDirection::Forward)
160+
.entries::<$table>(None, fuel_core_storage::iter::IterDirection::Forward)
162161
.try_collect()?;
163162
builder.add(table);
164163
)*

crates/fuel-core/src/database.rs

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -104,42 +104,20 @@ impl Database<OnChain> {
104104
}
105105
}
106106

107-
pub trait KeyFilter<K> {
108-
fn start_at_prefix(&self) -> Option<&[u8]>;
109-
fn should_stop(&self, key: &K) -> bool;
110-
}
111-
112-
pub struct IncludeAll;
113-
impl<K> KeyFilter<K> for IncludeAll {
114-
fn start_at_prefix(&self) -> Option<&[u8]> {
115-
None
116-
}
117-
118-
fn should_stop(&self, _: &K) -> bool {
119-
false
120-
}
121-
}
122-
123107
impl<DbDesc> Database<DbDesc>
124108
where
125109
DbDesc: DatabaseDescription,
126110
{
127111
pub fn entries<'a, T>(
128112
&'a self,
129-
filter: impl KeyFilter<T::OwnedKey> + 'a,
113+
prefix: Option<Vec<u8>>,
130114
direction: IterDirection,
131115
) -> impl Iterator<Item = StorageResult<TableEntry<T>>> + 'a
132116
where
133117
T: TableWithBlueprint<Column = <DbDesc as DatabaseDescription>::Column> + 'a,
134118
T::Blueprint: BlueprintInspect<T, Self>,
135119
{
136-
self.iter_all_filtered::<T, _>(filter.start_at_prefix(), None, Some(direction))
137-
.take_while(move |result| {
138-
let Ok((key, _)) = result.as_ref() else {
139-
return true;
140-
};
141-
!filter.should_stop(key)
142-
})
120+
self.iter_all_filtered::<T, _>(prefix, None, Some(direction))
143121
.map_ok(|(key, value)| TableEntry { key, value })
144122
}
145123
}
@@ -1009,4 +987,48 @@ mod tests {
1009987
);
1010988
}
1011989
}
990+
991+
#[cfg(feature = "rocksdb")]
992+
#[test]
993+
fn database_iter_all_by_prefix_works() {
994+
use fuel_core_storage::tables::ContractsRawCode;
995+
use fuel_core_types::fuel_types::ContractId;
996+
use std::str::FromStr;
997+
998+
let test = |mut db: Database<OnChain>| {
999+
let contract_id_1 = ContractId::from_str(
1000+
"5962be5ebddc516cb4ed7d7e76365f59e0d231ac25b53f262119edf76564aab4",
1001+
)
1002+
.unwrap();
1003+
1004+
let mut insert_empty_code = |id| {
1005+
StorageMutate::<ContractsRawCode>::insert(&mut db, &id, &[]).unwrap()
1006+
};
1007+
insert_empty_code(contract_id_1);
1008+
1009+
let contract_id_2 = ContractId::from_str(
1010+
"5baf0dcae7c114f647f6e71f1723f59bcfc14ecb28071e74895d97b14873c5dc",
1011+
)
1012+
.unwrap();
1013+
insert_empty_code(contract_id_2);
1014+
1015+
let matched_keys: Vec<_> = db
1016+
.iter_all_by_prefix::<ContractsRawCode, _>(Some(contract_id_1))
1017+
.map_ok(|(k, _)| k)
1018+
.try_collect()
1019+
.unwrap();
1020+
1021+
assert_eq!(matched_keys, vec![contract_id_1]);
1022+
};
1023+
1024+
let temp_dir = tempfile::tempdir().unwrap();
1025+
let db = Database::<OnChain>::in_memory();
1026+
// in memory passes
1027+
test(db);
1028+
1029+
let db = Database::<OnChain>::open_rocksdb(temp_dir.path(), 1024 * 1024 * 1024)
1030+
.unwrap();
1031+
// rocks db fails
1032+
test(db);
1033+
}
10121034
}

crates/fuel-core/src/service/genesis/exporter.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ use crate::{
33
database::{
44
database_description::DatabaseDescription,
55
Database,
6-
IncludeAll,
7-
KeyFilter,
86
},
97
fuel_core_graphql_api::storage::transactions::{
108
OwnedTransactions,
@@ -39,10 +37,7 @@ use itertools::Itertools;
3937

4038
use tokio_util::sync::CancellationToken;
4139

42-
use self::filter::ByContractId;
43-
4440
use super::task_manager::TaskManager;
45-
mod filter;
4641

4742
pub struct Exporter<Fun> {
4843
db: CombinedDatabase,
@@ -74,7 +69,7 @@ where
7469
pub async fn write_full_snapshot(mut self) -> Result<(), anyhow::Error> {
7570
macro_rules! export {
7671
($db: expr, $($table: ty),*) => {
77-
$(self.spawn_task::<$table, _>(IncludeAll, $db)?;)*
72+
$(self.spawn_task::<$table, _>(None, $db)?;)*
7873
};
7974
}
8075

@@ -106,8 +101,7 @@ where
106101
) -> Result<(), anyhow::Error> {
107102
macro_rules! export {
108103
($($table: ty),*) => {
109-
let filter = ByContractId::new(contract_id);
110-
$(self.spawn_task::<$table, _>(filter, |ctx: &Self| ctx.db.on_chain())?;)*
104+
$(self.spawn_task::<$table, _>(Some(contract_id.as_ref()), |ctx: &Self| ctx.db.on_chain())?;)*
111105
};
112106
}
113107
export!(
@@ -145,7 +139,7 @@ where
145139

146140
fn spawn_task<T, DbDesc>(
147141
&mut self,
148-
filter: impl KeyFilter<T::OwnedKey> + Send + 'static,
142+
prefix: Option<&[u8]>,
149143
db_picker: impl FnOnce(&Self) -> &Database<DbDesc>,
150144
) -> anyhow::Result<()>
151145
where
@@ -160,9 +154,10 @@ where
160154
let group_size = self.group_size;
161155

162156
let db = db_picker(self).clone();
157+
let prefix = prefix.map(|p| p.to_vec());
163158
self.task_manager.spawn(move |cancel| {
164159
tokio_rayon::spawn(move || {
165-
db.entries::<T>(filter, IterDirection::Forward)
160+
db.entries::<T>(prefix, IterDirection::Forward)
166161
.chunks(group_size)
167162
.into_iter()
168163
.take_while(|_| !cancel.is_cancelled())

crates/fuel-core/src/service/genesis/exporter/filter.rs

Lines changed: 0 additions & 49 deletions
This file was deleted.

crates/fuel-core/src/state/rocks_db.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,10 +470,22 @@ where
470470
prefix,
471471
convert_to_rocksdb_direction(direction),
472472
);
473+
474+
// Setting prefix on the RocksDB level to optimize iteration.
473475
let mut opts = ReadOptions::default();
474476
opts.set_prefix_same_as_start(true);
475477

476-
self._iter_all(column, opts, iter_mode).into_boxed()
478+
let prefix = prefix.to_vec();
479+
self._iter_all(column, opts, iter_mode)
480+
// Not all tables has a prefix set, so we need to filter out the keys.
481+
.take_while(move |item| {
482+
if let Ok((key, _)) = item {
483+
key.starts_with(prefix.as_slice())
484+
} else {
485+
true
486+
}
487+
})
488+
.into_boxed()
477489
}
478490
}
479491
(None, Some(start)) => {

0 commit comments

Comments
 (0)