Skip to content

Commit f0e78c4

Browse files
committed
refactor: make iter_k2 more sensible
1 parent 26722da commit f0e78c4

File tree

4 files changed

+66
-68
lines changed

4 files changed

+66
-68
lines changed

TODOS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Append optimization for tables keyed by blocknumbers

crates/hot/src/db/inconsistent.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,8 @@ pub trait UnsafeHistoryWrite: UnsafeDbWrite + HistoryRead {
148148
fn write_wipe(&self, block_number: u64, address: &Address) -> Result<(), Self::Error> {
149149
let mut cursor = self.traverse_dual::<tables::PlainStorageState>()?;
150150

151-
for entry in cursor.iter_k2(address, &U256::ZERO)? {
152-
let (_addr, slot, value) = entry?;
151+
for entry in cursor.iter_k2(address)? {
152+
let (slot, value) = entry?;
153153
self.write_storage_prestate(block_number, *address, &slot, &value)?;
154154
}
155155
Ok(())

crates/hot/src/model/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ pub use traits::{HotKv, HotKvRead, HotKvWrite};
4646
mod traverse;
4747
pub use traverse::{
4848
DualKeyTraverse, DualKeyTraverseMut, DualTableCursor, DualTableTraverse, DualTableTraverseMut,
49-
KvTraverse, KvTraverseMut, TableCursor, TableTraverse, TableTraverseMut,
49+
K2Value, KvTraverse, KvTraverseMut, RawK2Value, TableCursor, TableTraverse, TableTraverseMut,
5050
};
5151

5252
use crate::tables::{DualKey, Table};

crates/hot/src/model/traverse.rs

Lines changed: 62 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,18 @@ use crate::{
66
tables::{DualKey, SingleKey},
77
};
88
use core::marker::PhantomData;
9+
use std::borrow::Cow;
910
use std::ops::{Range, RangeInclusive};
1011

12+
/// Raw k2-value pair: (key2, value).
13+
///
14+
/// This is returned by `iter_k2()` on dual-keyed tables. The caller already
15+
/// knows k1 (they passed it in), so we don't return it redundantly.
16+
pub type RawK2Value<'a> = (Cow<'a, [u8]>, RawValue<'a>);
17+
18+
/// Typed k2-value pair for a dual-keyed table.
19+
pub type K2Value<T> = (<T as DualKey>::Key2, <T as crate::tables::Table>::Value);
20+
1121
/// Trait for traversing key-value pairs in the database.
1222
pub trait KvTraverse<E: HotKvReadError> {
1323
/// Set position to the first key-value pair in the database, and return
@@ -212,30 +222,29 @@ pub trait DualKeyTraverse<E: HotKvReadError> {
212222
Ok(RawDualKeyIter { cursor: self, done: false, _marker: PhantomData })
213223
}
214224

215-
/// Iterate k2 entries within a single k1.
225+
/// Iterate all k2 entries within a single k1.
216226
///
217-
/// The iterator starts at the first entry with (k1, k2) >= the specified
218-
/// keys and stops when k1 changes or the table is exhausted.
227+
/// The iterator yields `(k2, value)` pairs for the specified k1, starting
228+
/// from the first k2 value, and stops when k1 changes or the table is
229+
/// exhausted.
230+
///
231+
/// Note: k1 is not included in the output since the caller already knows
232+
/// it (they passed it in). This avoids redundant allocations.
219233
fn iter_k2<'a>(
220234
&'a mut self,
221235
k1: &[u8],
222-
start_k2: &[u8],
223-
) -> Result<impl Iterator<Item = Result<RawDualKeyValue<'a>, E>> + 'a, E>
236+
) -> Result<impl Iterator<Item = Result<RawK2Value<'a>, E>> + 'a, E>
224237
where
225238
Self: Sized,
226239
{
227-
let entry = self.next_dual_above(k1, start_k2)?;
240+
// Position at first entry for this k1 (using empty slice as minimum k2)
241+
let entry = self.next_dual_above(k1, &[])?;
228242
let Some((found_k1, _, _)) = entry else {
229-
return Ok(RawDualKeyK2Iter {
230-
cursor: self,
231-
k1: Vec::new(),
232-
done: true,
233-
_marker: PhantomData,
234-
});
243+
return Ok(RawDualKeyK2Iter { cursor: self, done: true, _marker: PhantomData });
235244
};
236245
// If the found k1 doesn't match, we're done
237246
let done = found_k1.as_ref() != k1;
238-
Ok(RawDualKeyK2Iter { cursor: self, k1: k1.to_vec(), done, _marker: PhantomData })
247+
Ok(RawDualKeyK2Iter { cursor: self, done, _marker: PhantomData })
239248
}
240249
}
241250

@@ -520,15 +529,18 @@ pub trait DualTableTraverse<T: DualKey, E: HotKvReadError>: DualKeyTraverse<E> {
520529
where
521530
T::Key: PartialEq;
522531

523-
/// Iterate k2 entries within a single k1.
532+
/// Iterate all k2 entries within a single k1.
524533
///
525-
/// The iterator starts at the first entry with (k1, k2) >= the specified
526-
/// keys and stops when k1 changes or the table is exhausted.
534+
/// The iterator yields `(k2, value)` pairs for the specified k1, starting
535+
/// from the first k2 value, and stops when k1 changes or the table is
536+
/// exhausted.
537+
///
538+
/// Note: k1 is not included in the output since the caller already knows
539+
/// it (they passed it in). This avoids redundant allocations.
527540
fn iter_k2(
528541
&mut self,
529542
k1: &T::Key,
530-
start_k2: &T::Key2,
531-
) -> Result<impl Iterator<Item = Result<DualKeyValue<T>, E>> + '_, E>
543+
) -> Result<impl Iterator<Item = Result<K2Value<T>, E>> + '_, E>
532544
where
533545
T::Key: PartialEq;
534546
}
@@ -607,29 +619,26 @@ where
607619
fn iter_k2(
608620
&mut self,
609621
k1: &T::Key,
610-
start_k2: &T::Key2,
611-
) -> Result<impl Iterator<Item = Result<DualKeyValue<T>, E>> + '_, E>
622+
) -> Result<impl Iterator<Item = Result<K2Value<T>, E>> + '_, E>
612623
where
613624
T::Key: PartialEq,
614625
{
615-
// Position cursor and get the target k1 for comparison
616-
let entry = DualTableTraverse::<T, E>::next_dual_above(self, k1, start_k2)?;
626+
// Position cursor at first entry for this k1 using raw interface with empty k2
627+
let mut key1_buf = [0u8; MAX_KEY_SIZE];
628+
let key1_bytes = k1.encode_key(&mut key1_buf);
629+
let entry = DualKeyTraverse::next_dual_above(self, key1_bytes, &[])?;
617630
let Some((found_k1, _, _)) = entry else {
618631
return Ok(DualTableK2Iter::<'_, C, T, E> {
619632
cursor: self,
620-
k1: k1.clone(),
621633
done: true,
622634
_marker: PhantomData,
623635
});
624636
};
637+
// Decode the found k1 to check if it matches
638+
let decoded_k1 = T::decode_key(found_k1)?;
625639
// If the found k1 doesn't match, we're done
626-
let done = found_k1 != *k1;
627-
Ok(DualTableK2Iter::<'_, C, T, E> {
628-
cursor: self,
629-
k1: found_k1,
630-
done,
631-
_marker: PhantomData,
632-
})
640+
let done = decoded_k1 != *k1;
641+
Ok(DualTableK2Iter::<'_, C, T, E> { cursor: self, done, _marker: PhantomData })
633642
}
634643
}
635644

@@ -811,13 +820,13 @@ where
811820
}
812821
}
813822

814-
/// Default forward iterator over raw dual-keyed entries within a single k1.
823+
/// Default forward iterator over raw k2-value entries within a single k1.
815824
///
816825
/// This iterator wraps a cursor implementing `DualKeyTraverse` and yields
817-
/// entries while k1 remains unchanged.
826+
/// `(k2, value)` pairs while k1 remains unchanged. The iterator stops when k1
827+
/// changes or the table is exhausted.
818828
pub struct RawDualKeyK2Iter<'a, C, E> {
819829
cursor: &'a mut C,
820-
k1: Vec<u8>,
821830
done: bool,
822831
_marker: PhantomData<fn() -> E>,
823832
}
@@ -827,7 +836,7 @@ where
827836
C: DualKeyTraverse<E>,
828837
E: HotKvReadError,
829838
{
830-
type Item = Result<RawDualKeyValue<'a>, E>;
839+
type Item = Result<RawK2Value<'a>, E>;
831840

832841
fn next(&mut self) -> Option<Self::Item> {
833842
if self.done {
@@ -838,17 +847,10 @@ where
838847
std::mem::transmute::<
839848
Result<Option<RawDualKeyValue<'_>>, E>,
840849
Result<Option<RawDualKeyValue<'a>>, E>,
841-
>(self.cursor.read_next())
850+
>(self.cursor.next_k2())
842851
};
843852
match result {
844-
Ok(Some((k1, k2, v))) => {
845-
if k1.as_ref() != self.k1.as_slice() {
846-
self.done = true;
847-
None
848-
} else {
849-
Some(Ok((k1, k2, v)))
850-
}
851-
}
853+
Ok(Some((_k1, k2, v))) => Some(Ok((k2, v))),
852854
Ok(None) => {
853855
self.done = true;
854856
None
@@ -865,42 +867,34 @@ where
865867
// Typed Iterator Structs (for extension traits)
866868
// ============================================================================
867869

868-
/// Forward iterator over k2 entries within a single k1.
870+
/// Forward iterator over k2-value pairs within a single k1.
869871
///
870-
/// This iterator wraps a cursor and yields entries while k1 remains equal
871-
/// to the initial k1 value.
872+
/// This iterator wraps a cursor and yields `(k2, value)` pairs by calling
873+
/// `next_k2()` on each iteration. The iterator stops when there are no more
874+
/// k2 entries for the current k1 or when an error occurs.
872875
pub struct DualTableK2Iter<'a, C, T, E>
873876
where
874877
T: DualKey,
875878
{
876879
cursor: &'a mut C,
877-
k1: T::Key,
878880
done: bool,
879-
_marker: PhantomData<fn() -> E>,
881+
_marker: PhantomData<fn() -> (T, E)>,
880882
}
881883

882884
impl<'a, C, T, E> Iterator for DualTableK2Iter<'a, C, T, E>
883885
where
884886
C: DualKeyTraverse<E>,
885887
T: DualKey,
886-
T::Key: PartialEq,
887888
E: HotKvReadError,
888889
{
889-
type Item = Result<DualKeyValue<T>, E>;
890+
type Item = Result<K2Value<T>, E>;
890891

891892
fn next(&mut self) -> Option<Self::Item> {
892893
if self.done {
893894
return None;
894895
}
895-
match DualTableTraverse::<T, E>::read_next(self.cursor) {
896-
Ok(Some((k1, k2, v))) => {
897-
if k1 != self.k1 {
898-
self.done = true;
899-
None
900-
} else {
901-
Some(Ok((k1, k2, v)))
902-
}
903-
}
896+
match DualTableTraverse::<T, E>::next_k2(self.cursor) {
897+
Ok(Some((_k1, k2, v))) => Some(Ok((k2, v))),
904898
Ok(None) => {
905899
self.done = true;
906900
None
@@ -1161,16 +1155,19 @@ where
11611155
DualTableTraverse::<T, E>::iter(&mut self.inner)
11621156
}
11631157

1164-
/// Iterate k2 entries within a single k1.
1158+
/// Iterate all k2 entries within a single k1.
11651159
///
1166-
/// The iterator starts at the first entry with (k1, k2) >= the specified
1167-
/// keys and stops when k1 changes or the table is exhausted.
1160+
/// The iterator yields `(k2, value)` pairs for the specified k1, starting
1161+
/// from the first k2 value, and stops when k1 changes or the table is
1162+
/// exhausted.
1163+
///
1164+
/// Note: k1 is not included in the output since the caller already knows
1165+
/// it (they passed it in). This avoids redundant allocations.
11681166
pub fn iter_k2(
11691167
&mut self,
11701168
k1: &T::Key,
1171-
start_k2: &T::Key2,
1172-
) -> Result<impl Iterator<Item = Result<DualKeyValue<T>, E>> + '_, E> {
1173-
DualTableTraverse::<T, E>::iter_k2(&mut self.inner, k1, start_k2)
1169+
) -> Result<impl Iterator<Item = Result<K2Value<T>, E>> + '_, E> {
1170+
DualTableTraverse::<T, E>::iter_k2(&mut self.inner, k1)
11741171
}
11751172
}
11761173

0 commit comments

Comments
 (0)