diff --git a/.changelog/proud-crabs-bark.md b/.changelog/proud-crabs-bark.md new file mode 100644 index 0000000000..a3457e7585 --- /dev/null +++ b/.changelog/proud-crabs-bark.md @@ -0,0 +1,5 @@ +--- +reth-db-api: patch +--- + +Changed `StoredNibblesSubKey` encoding to use a stack-allocated `[u8; 65]` array instead of a heap-allocated `Vec`, avoiding unnecessary heap allocation. diff --git a/crates/storage/db-api/src/models/mod.rs b/crates/storage/db-api/src/models/mod.rs index 12e6c46c46..36a340eb32 100644 --- a/crates/storage/db-api/src/models/mod.rs +++ b/crates/storage/db-api/src/models/mod.rs @@ -138,13 +138,10 @@ impl Decode for StoredNibbles { } impl Encode for StoredNibblesSubKey { - type Encoded = Vec; + type Encoded = [u8; 65]; - // Delegate to the Compact implementation fn encode(self) -> Self::Encoded { - let mut buf = Vec::with_capacity(65); - self.to_compact(&mut buf); - buf + self.to_compact_array() } } diff --git a/crates/trie/common/src/nibbles.rs b/crates/trie/common/src/nibbles.rs index de1e9a321a..456482163f 100644 --- a/crates/trie/common/src/nibbles.rs +++ b/crates/trie/common/src/nibbles.rs @@ -90,6 +90,22 @@ impl From for Nibbles { } } +impl StoredNibblesSubKey { + /// Encodes the nibbles into a fixed-size `[u8; 65]` array. + /// + /// The first 64 bytes contain the nibble values (right-padded with zeros), + /// and the 65th byte stores the actual nibble count. + pub fn to_compact_array(&self) -> [u8; 65] { + assert!(self.0.len() <= 64); + let mut buf = [0u8; 65]; + for (i, nibble) in self.0.iter().enumerate() { + buf[i] = nibble; + } + buf[64] = self.0.len() as u8; + buf + } +} + #[cfg(any(test, feature = "reth-codec"))] impl reth_codecs::Compact for StoredNibblesSubKey { fn to_compact(&self, buf: &mut B) -> usize @@ -187,4 +203,51 @@ mod tests { let deserialized: StoredNibblesSubKey = serde_json::from_str(&serialized).unwrap(); assert_eq!(subkey, deserialized); } + + #[test] + fn test_stored_nibbles_subkey_compact_array_empty() { + let subkey = StoredNibblesSubKey::from(vec![]); + let arr = subkey.to_compact_array(); + assert_eq!(arr, [0u8; 65]); + } + + #[test] + fn test_stored_nibbles_subkey_compact_array_full() { + let nibbles: Vec = (0..64).map(|i| i % 16).collect(); + let subkey = StoredNibblesSubKey::from(nibbles.clone()); + let arr = subkey.to_compact_array(); + assert_eq!(&arr[..64], &nibbles[..]); + assert_eq!(arr[64], 64); + } + + #[test] + fn test_stored_nibbles_subkey_compact_array_roundtrip() { + let subkey = StoredNibblesSubKey::from(vec![0x0A, 0x0B, 0x0C]); + let arr = subkey.to_compact_array(); + let (recovered, rest) = StoredNibblesSubKey::from_compact(&arr, 65); + assert_eq!(recovered, subkey); + assert!(rest.is_empty()); + } + + #[test] + fn test_stored_nibbles_subkey_compact_array_matches_to_compact() { + for len in [0, 1, 2, 31, 32, 33, 63, 64] { + let nibbles: Vec = (0..len).map(|i| (i % 16) as u8).collect(); + let subkey = StoredNibblesSubKey::from(nibbles); + + let arr = subkey.to_compact_array(); + + let mut compact_buf = BytesMut::with_capacity(65); + subkey.to_compact(&mut compact_buf); + + assert_eq!(&arr[..], &compact_buf[..], "mismatch at nibble length {len}"); + } + } + + #[test] + fn test_stored_nibbles_subkey_compact_array_padding_is_zero() { + let subkey = StoredNibblesSubKey::from(vec![0x0F; 10]); + let arr = subkey.to_compact_array(); + assert!(arr[10..64].iter().all(|&b| b == 0), "padding bytes must be zero"); + } }