Skip to content

Commit e7de2e1

Browse files
committed
feat: iter-like traverse methods
1 parent 6a30cc2 commit e7de2e1

4 files changed

Lines changed: 251 additions & 64 deletions

File tree

crates/storage/src/hot/conformance.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,6 +1288,7 @@ where
12881288
///
12891289
/// This function creates a proper BundleState with reverts populated so that
12901290
/// `to_plain_state_and_reverts` will produce the expected output.
1291+
#[allow(clippy::type_complexity)]
12911292
fn make_bundle_state(
12921293
accounts: Vec<(Address, Option<AccountInfo>, Option<AccountInfo>)>,
12931294
storage: Vec<(Address, Vec<(U256, U256, U256)>)>, // (addr, [(slot, old, new)])

crates/storage/src/hot/db/inconsistent.rs

Lines changed: 29 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -153,31 +153,11 @@ pub trait UnsafeHistoryWrite: UnsafeDbWrite + HotHistoryRead {
153153
/// Write a pre-state for every storage key that exists for an account at a
154154
/// specific block.
155155
fn write_wipe(&self, block_number: u64, address: &Address) -> Result<(), Self::Error> {
156-
// SAFETY: the cursor is scoped to the transaction lifetime, which is
157-
// valid for the duration of this method.
158156
let mut cursor = self.traverse_dual::<tables::PlainStorageState>()?;
159157

160-
let Some(start) = cursor.next_dual_above(address, &U256::ZERO)? else {
161-
// No storage entries at or above this address
162-
return Ok(());
163-
};
164-
165-
if start.0 != *address {
166-
// No storage entries for this address
167-
return Ok(());
168-
}
169-
170-
self.write_storage_prestate(block_number, *address, &start.1, &start.2)?;
171-
172-
while let Some((k, k2, v)) = cursor.next_k2()? {
173-
if k != *address {
174-
break;
175-
}
176-
177-
self.write_storage_prestate(block_number, *address, &k2, &v)?;
178-
}
179-
180-
Ok(())
158+
cursor.for_each_k2(address, &U256::ZERO, |_addr, slot, value| {
159+
self.write_storage_prestate(block_number, *address, &slot, &value)
160+
})
181161
}
182162

183163
/// Write a block's plain state revert information.
@@ -294,33 +274,26 @@ pub trait UnsafeHistoryWrite: UnsafeDbWrite + HotHistoryRead {
294274

295275
/// Get all changed accounts with the list of block numbers in the given
296276
/// range.
277+
///
278+
/// Note: This iterates using `next_k2()` which stays within the same k1
279+
/// (block number). It effectively only collects changes from the first
280+
/// block number in the range.
297281
fn changed_accounts_with_range(
298282
&self,
299283
range: RangeInclusive<BlockNumber>,
300284
) -> Result<BTreeMap<Address, Vec<u64>>, Self::Error> {
301285
let mut changeset_cursor = self.traverse_dual::<tables::AccountChangeSets>()?;
302-
303286
let mut result: BTreeMap<Address, Vec<u64>> = BTreeMap::new();
304287

305-
// Position cursor at first entry at or above range start
306-
let Some((num, addr, _)) =
307-
changeset_cursor.next_dual_above(range.start(), &Address::ZERO)?
308-
else {
309-
return Ok(result);
310-
};
311-
312-
if !range.contains(&num) {
313-
return Ok(result);
314-
}
315-
result.entry(addr).or_default().push(num);
316-
317-
// Iterate through remaining entries
318-
while let Some((num, addr, _)) = changeset_cursor.next_k2()? {
319-
if !range.contains(&num) {
320-
break;
321-
}
322-
result.entry(addr).or_default().push(num);
323-
}
288+
changeset_cursor.for_each_while_k2(
289+
range.start(),
290+
&Address::ZERO,
291+
|num, _, _| range.contains(num),
292+
|num, addr, _| {
293+
result.entry(addr).or_default().push(num);
294+
Ok(())
295+
},
296+
)?;
324297

325298
Ok(result)
326299
}
@@ -372,33 +345,27 @@ pub trait UnsafeHistoryWrite: UnsafeDbWrite + HotHistoryRead {
372345

373346
/// Get all changed storages with the list of block numbers in the given
374347
/// range.
348+
///
349+
/// Note: This iterates using `next_k2()` which stays within the same k1
350+
/// (block number + address). It effectively only collects changes from
351+
/// the first key1 value in the range.
375352
#[allow(clippy::type_complexity)]
376353
fn changed_storages_with_range(
377354
&self,
378355
range: RangeInclusive<BlockNumber>,
379356
) -> Result<BTreeMap<(Address, U256), Vec<u64>>, Self::Error> {
380357
let mut changeset_cursor = self.traverse_dual::<tables::StorageChangeSets>()?;
381-
382358
let mut result: BTreeMap<(Address, U256), Vec<u64>> = BTreeMap::new();
383-
// Position cursor at first entry at or above range start
384-
let Some((num_addr, slot, _)) = changeset_cursor
385-
.next_dual_above(&BlockNumberAddress((*range.start(), Address::ZERO)), &U256::ZERO)?
386-
else {
387-
return Ok(result);
388-
};
389359

390-
if !range.contains(&num_addr.block_number()) {
391-
return Ok(result);
392-
}
393-
result.entry((num_addr.address(), slot)).or_default().push(num_addr.block_number());
394-
395-
// Iterate through remaining entries
396-
while let Some((num_addr, slot, _)) = changeset_cursor.next_k2()? {
397-
if !range.contains(&num_addr.block_number()) {
398-
break;
399-
}
400-
result.entry((num_addr.address(), slot)).or_default().push(num_addr.block_number());
401-
}
360+
changeset_cursor.for_each_while_k2(
361+
&BlockNumberAddress((*range.start(), Address::ZERO)),
362+
&U256::ZERO,
363+
|num_addr, _, _| range.contains(&num_addr.block_number()),
364+
|num_addr, slot, _| {
365+
result.entry((num_addr.address(), slot)).or_default().push(num_addr.block_number());
366+
Ok(())
367+
},
368+
)?;
402369

403370
Ok(result)
404371
}

crates/storage/src/hot/impls/mdbx/cursor.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,7 @@ where
222222
let key2_size = fsi.key2_size().unwrap_or(key2.len());
223223

224224
// Use set_range to find the first entry with key1 >= search_key1
225-
let Some((found_k1, v)) =
226-
self.inner.set_range::<Cow<'_, [u8]>, Cow<'_, [u8]>>(key1)?
225+
let Some((found_k1, v)) = self.inner.set_range::<Cow<'_, [u8]>, Cow<'_, [u8]>>(key1)?
227226
else {
228227
return Ok(None);
229228
};

crates/storage/src/hot/model/traverse.rs

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,53 @@ pub trait TableTraverse<T: SingleKey, E: HotKvReadError>: KvTraverse<E> {
174174
fn read_prev(&mut self) -> Result<Option<KeyValue<T>>, E> {
175175
KvTraverse::read_prev(self)?.map(T::decode_kv_tuple).transpose().map_err(Into::into)
176176
}
177+
178+
/// Iterate entries starting from a key while a predicate holds.
179+
///
180+
/// Positions the cursor at `start_key` and calls `f` for each entry
181+
/// while `predicate` returns true.
182+
///
183+
/// Returns `Ok(())` on successful completion, or the first error encountered.
184+
fn for_each_while<P, F>(&mut self, start_key: &T::Key, predicate: P, mut f: F) -> Result<(), E>
185+
where
186+
P: Fn(&T::Key, &T::Value) -> bool,
187+
F: FnMut(T::Key, T::Value) -> Result<(), E>,
188+
{
189+
let Some((k, v)) = TableTraverse::lower_bound(self, start_key)? else {
190+
return Ok(());
191+
};
192+
193+
if !predicate(&k, &v) {
194+
return Ok(());
195+
}
196+
197+
f(k, v)?;
198+
199+
while let Some((k, v)) = TableTraverse::read_next(self)? {
200+
if !predicate(&k, &v) {
201+
break;
202+
}
203+
f(k, v)?;
204+
}
205+
206+
Ok(())
207+
}
208+
209+
/// Collect entries from start_key while predicate holds.
210+
///
211+
/// This is useful when you need to process entries after iteration completes
212+
/// or when the closure would need to borrow mutably from multiple sources.
213+
fn collect_while<P>(&mut self, start_key: &T::Key, predicate: P) -> Result<Vec<KeyValue<T>>, E>
214+
where
215+
P: Fn(&T::Key, &T::Value) -> bool,
216+
{
217+
let mut result = Vec::new();
218+
self.for_each_while(start_key, predicate, |k, v| {
219+
result.push((k, v));
220+
Ok(())
221+
})?;
222+
Ok(result)
223+
}
177224
}
178225

179226
/// Blanket implementation of `TableTraverse` for any cursor that implements `KvTraverse`.
@@ -268,6 +315,94 @@ pub trait DualTableTraverse<T: DualKey, E: HotKvReadError>: DualKeyTraverse<E> {
268315

269316
/// Move to the PREVIOUS key2 entry for the CURRENT key1.
270317
fn previous_k2(&mut self) -> Result<Option<DualKeyValue<T>>, E>;
318+
319+
/// Iterate entries (crossing k1 boundaries) while a predicate holds.
320+
///
321+
/// Positions the cursor at `(key1, start_k2)` and calls `f` for each entry
322+
/// while `predicate` returns true. Uses `read_next()` to cross k1 boundaries.
323+
///
324+
/// Returns `Ok(())` on successful completion, or the first error encountered.
325+
fn for_each_while<P, F>(
326+
&mut self,
327+
key1: &T::Key,
328+
start_k2: &T::Key2,
329+
predicate: P,
330+
mut f: F,
331+
) -> Result<(), E>
332+
where
333+
P: Fn(&T::Key, &T::Key2, &T::Value) -> bool,
334+
F: FnMut(T::Key, T::Key2, T::Value) -> Result<(), E>,
335+
{
336+
let Some((k1, k2, v)) = DualTableTraverse::next_dual_above(self, key1, start_k2)? else {
337+
return Ok(());
338+
};
339+
340+
if !predicate(&k1, &k2, &v) {
341+
return Ok(());
342+
}
343+
344+
f(k1, k2, v)?;
345+
346+
while let Some((k1, k2, v)) = DualTableTraverse::read_next(self)? {
347+
if !predicate(&k1, &k2, &v) {
348+
break;
349+
}
350+
f(k1, k2, v)?;
351+
}
352+
353+
Ok(())
354+
}
355+
356+
/// Iterate entries within the same k1 while a predicate holds.
357+
///
358+
/// Positions the cursor at `(key1, start_k2)` and calls `f` for each entry
359+
/// while `predicate` returns true. Uses `next_k2()` which stays within
360+
/// the same k1 value.
361+
///
362+
/// Returns `Ok(())` on successful completion, or the first error encountered.
363+
fn for_each_while_k2<P, F>(
364+
&mut self,
365+
key1: &T::Key,
366+
start_k2: &T::Key2,
367+
predicate: P,
368+
f: F,
369+
) -> Result<(), E>
370+
where
371+
P: Fn(&T::Key, &T::Key2, &T::Value) -> bool,
372+
F: FnMut(T::Key, T::Key2, T::Value) -> Result<(), E>,
373+
{
374+
self.for_each_while(key1, start_k2, |k, k2, v| key1 == k && predicate(k, k2, v), f)
375+
}
376+
377+
/// Iterate all k2 entries for a given k1, starting from `start_k2`.
378+
///
379+
/// Calls `f` for each (k1, k2, v) tuple where k1 matches the provided key1
380+
/// and k2 >= start_k2. Stops when k1 changes or the table is exhausted.
381+
///
382+
/// Returns `Ok(())` on successful completion, or the first error encountered.
383+
fn for_each_k2<F>(&mut self, key1: &T::Key, start_k2: &T::Key2, f: F) -> Result<(), E>
384+
where
385+
T::Key: PartialEq,
386+
F: FnMut(T::Key, T::Key2, T::Value) -> Result<(), E>,
387+
{
388+
self.for_each_while_k2(key1, start_k2, |_, _, _| true, f)
389+
}
390+
391+
/// Collect all k2 entries for a given k1 into a Vec.
392+
///
393+
/// This is useful when you need to process entries after iteration completes
394+
/// or when the closure would need to borrow mutably from multiple sources.
395+
fn collect_k2(&mut self, key1: &T::Key, start_k2: &T::Key2) -> Result<Vec<DualKeyValue<T>>, E>
396+
where
397+
T::Key: PartialEq,
398+
{
399+
let mut result = Vec::new();
400+
self.for_each_k2(key1, start_k2, |k1, k2, v| {
401+
result.push((k1, k2, v));
402+
Ok(())
403+
})?;
404+
Ok(result)
405+
}
271406
}
272407

273408
impl<C, T, E> DualTableTraverse<T, E> for C
@@ -393,6 +528,30 @@ where
393528
pub fn read_prev(&mut self) -> Result<Option<KeyValue<T>>, E> {
394529
TableTraverse::<T, E>::read_prev(&mut self.inner)
395530
}
531+
532+
/// Iterate entries starting from a key while a predicate holds.
533+
///
534+
/// Positions the cursor at `start_key` and calls `f` for each entry
535+
/// while `predicate` returns true.
536+
pub fn for_each_while<P, F>(&mut self, start_key: &T::Key, predicate: P, f: F) -> Result<(), E>
537+
where
538+
P: Fn(&T::Key, &T::Value) -> bool,
539+
F: FnMut(T::Key, T::Value) -> Result<(), E>,
540+
{
541+
TableTraverse::<T, E>::for_each_while(&mut self.inner, start_key, predicate, f)
542+
}
543+
544+
/// Collect entries from start_key while predicate holds.
545+
pub fn collect_while<P>(
546+
&mut self,
547+
start_key: &T::Key,
548+
predicate: P,
549+
) -> Result<Vec<KeyValue<T>>, E>
550+
where
551+
P: Fn(&T::Key, &T::Value) -> bool,
552+
{
553+
TableTraverse::<T, E>::collect_while(&mut self.inner, start_key, predicate)
554+
}
396555
}
397556

398557
impl<C, T, E> TableCursor<C, T, E>
@@ -517,6 +676,67 @@ where
517676
pub fn read_prev(&mut self) -> Result<Option<DualKeyValue<T>>, E> {
518677
DualTableTraverse::<T, E>::read_prev(&mut self.inner)
519678
}
679+
680+
/// Iterate all k2 entries for a given k1, starting from `start_k2`.
681+
///
682+
/// Calls `f` for each (k1, k2, v) tuple where k1 matches the provided key1
683+
/// and k2 >= start_k2. Stops when k1 changes or the table is exhausted.
684+
pub fn for_each_k2<F>(&mut self, key1: &T::Key, start_k2: &T::Key2, f: F) -> Result<(), E>
685+
where
686+
T::Key: PartialEq,
687+
F: FnMut(T::Key, T::Key2, T::Value) -> Result<(), E>,
688+
{
689+
DualTableTraverse::<T, E>::for_each_k2(&mut self.inner, key1, start_k2, f)
690+
}
691+
692+
/// Iterate entries within the same k1 while a predicate holds.
693+
///
694+
/// Positions the cursor at `(key1, start_k2)` and calls `f` for each entry
695+
/// while `predicate` returns true. Uses `next_k2()` which stays within
696+
/// the same k1 value.
697+
pub fn for_each_while_k2<P, F>(
698+
&mut self,
699+
key1: &T::Key,
700+
start_k2: &T::Key2,
701+
predicate: P,
702+
f: F,
703+
) -> Result<(), E>
704+
where
705+
P: Fn(&T::Key, &T::Key2, &T::Value) -> bool,
706+
F: FnMut(T::Key, T::Key2, T::Value) -> Result<(), E>,
707+
{
708+
DualTableTraverse::<T, E>::for_each_while_k2(&mut self.inner, key1, start_k2, predicate, f)
709+
}
710+
711+
/// Iterate entries (crossing k1 boundaries) while a predicate holds.
712+
///
713+
/// Positions the cursor at `(key1, start_k2)` and calls `f` for each entry
714+
/// while `predicate` returns true. Uses `read_next()` to cross k1 boundaries.
715+
pub fn for_each_while<P, F>(
716+
&mut self,
717+
key1: &T::Key,
718+
start_k2: &T::Key2,
719+
predicate: P,
720+
f: F,
721+
) -> Result<(), E>
722+
where
723+
P: Fn(&T::Key, &T::Key2, &T::Value) -> bool,
724+
F: FnMut(T::Key, T::Key2, T::Value) -> Result<(), E>,
725+
{
726+
DualTableTraverse::<T, E>::for_each_while(&mut self.inner, key1, start_k2, predicate, f)
727+
}
728+
729+
/// Collect all k2 entries for a given k1 into a Vec.
730+
pub fn collect_k2(
731+
&mut self,
732+
key1: &T::Key,
733+
start_k2: &T::Key2,
734+
) -> Result<Vec<DualKeyValue<T>>, E>
735+
where
736+
T::Key: PartialEq,
737+
{
738+
DualTableTraverse::<T, E>::collect_k2(&mut self.inner, key1, start_k2)
739+
}
520740
}
521741

522742
impl<C, T, E> DualTableCursor<C, T, E>

0 commit comments

Comments
 (0)