Skip to main content

reth_db_api/models/
sharded_key.rs

1//! Sharded key
2use crate::{
3    table::{Decode, Encode},
4    DatabaseError,
5};
6use alloy_primitives::{Address, BlockNumber};
7use serde::{Deserialize, Serialize};
8use std::hash::Hash;
9
10/// Number of indices in one shard.
11pub const NUM_OF_INDICES_IN_SHARD: usize = 2_000;
12
13/// Size of `BlockNumber` in bytes (u64 = 8 bytes).
14const BLOCK_NUMBER_SIZE: usize = std::mem::size_of::<BlockNumber>();
15
16/// Sometimes data can be too big to be saved for a single key. This helps out by dividing the data
17/// into different shards. Example:
18///
19/// `Address | 200` -> data is from block 0 to 200.
20///
21/// `Address | 300` -> data is from block 201 to 300.
22#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)]
23pub struct ShardedKey<T> {
24    /// The key for this type.
25    pub key: T,
26    /// Highest block number to which `value` is related to.
27    pub highest_block_number: BlockNumber,
28}
29
30impl<T> AsRef<Self> for ShardedKey<T> {
31    fn as_ref(&self) -> &Self {
32        self
33    }
34}
35
36impl<T> ShardedKey<T> {
37    /// Creates a new `ShardedKey<T>`.
38    pub const fn new(key: T, highest_block_number: BlockNumber) -> Self {
39        Self { key, highest_block_number }
40    }
41
42    /// Creates a new key with the highest block number set to maximum.
43    /// This is useful when we want to search the last value for a given key.
44    pub const fn last(key: T) -> Self {
45        Self { key, highest_block_number: u64::MAX }
46    }
47}
48
49/// Stack-allocated encoded key for `ShardedKey<Address>`.
50///
51/// This avoids heap allocation in hot database paths. The key layout is:
52/// - 20 bytes: `Address`
53/// - 8 bytes: `BlockNumber` (big-endian)
54pub type ShardedKeyAddressEncoded = [u8; 20 + BLOCK_NUMBER_SIZE];
55
56impl Encode for ShardedKey<Address> {
57    type Encoded = ShardedKeyAddressEncoded;
58
59    #[inline]
60    fn encode(self) -> Self::Encoded {
61        let mut buf = [0u8; 20 + BLOCK_NUMBER_SIZE];
62        buf[..20].copy_from_slice(self.key.as_slice());
63        buf[20..].copy_from_slice(&self.highest_block_number.to_be_bytes());
64        buf
65    }
66}
67
68impl Decode for ShardedKey<Address> {
69    fn decode(value: &[u8]) -> Result<Self, DatabaseError> {
70        if value.len() != 20 + BLOCK_NUMBER_SIZE {
71            return Err(DatabaseError::Decode);
72        }
73        let key = Address::from_slice(&value[..20]);
74        let highest_block_number =
75            u64::from_be_bytes(value[20..].try_into().map_err(|_| DatabaseError::Decode)?);
76        Ok(Self::new(key, highest_block_number))
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use alloy_primitives::address;
84
85    #[test]
86    fn sharded_key_address_encode_decode_roundtrip() {
87        let addr = address!("0102030405060708091011121314151617181920");
88        let block_num = 0x123456789ABCDEF0u64;
89        let key = ShardedKey::new(addr, block_num);
90
91        let encoded = key.encode();
92
93        // Verify it's stack-allocated (28 bytes)
94        assert_eq!(encoded.len(), 28);
95        assert_eq!(std::mem::size_of_val(&encoded), 28);
96
97        // Verify roundtrip (check against expected values since key was consumed)
98        let decoded = ShardedKey::<Address>::decode(&encoded).unwrap();
99        assert_eq!(decoded.key, address!("0102030405060708091011121314151617181920"));
100        assert_eq!(decoded.highest_block_number, 0x123456789ABCDEF0u64);
101    }
102
103    #[test]
104    fn sharded_key_last_works() {
105        let addr = address!("0102030405060708091011121314151617181920");
106        let key = ShardedKey::<Address>::last(addr);
107        assert_eq!(key.highest_block_number, u64::MAX);
108
109        let encoded = key.encode();
110        let decoded = ShardedKey::<Address>::decode(&encoded).unwrap();
111        assert_eq!(decoded.highest_block_number, u64::MAX);
112    }
113}