Skip to main content

reth_db_api/models/
bal.rs

1//! Block access list table models.
2
3use crate::{
4    table::{Compress, Decode, Decompress, Encode},
5    DatabaseError,
6};
7use alloy_primitives::{keccak256, BlockNumber, Bytes, B256};
8use bytes::BufMut;
9use core::cmp::Ordering;
10use reth_codecs::DecompressError;
11use serde::{Deserialize, Serialize};
12
13/// Encoded [`StoredBlockAccessListKey`] length.
14const BLOCK_ACCESS_LIST_KEY_BYTES: usize = 8 + 32;
15
16/// Hash prefix length in [`StoredBlockAccessList`] values.
17const STORED_BLOCK_ACCESS_LIST_HASH_BYTES: usize = 32;
18
19/// Block access list table key.
20///
21/// Encoded as block number followed by block hash so pruning can scan by block number.
22#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
23pub struct StoredBlockAccessListKey {
24    block_number: BlockNumber,
25    block_hash: B256,
26}
27
28impl StoredBlockAccessListKey {
29    /// Creates a key from a block number/hash pair.
30    pub const fn new(block_number: BlockNumber, block_hash: B256) -> Self {
31        Self { block_number, block_hash }
32    }
33
34    /// Returns the smallest key for the given block number.
35    pub const fn first_at_number(block_number: BlockNumber) -> Self {
36        Self::new(block_number, B256::ZERO)
37    }
38
39    /// Returns the block number.
40    pub const fn number(&self) -> BlockNumber {
41        self.block_number
42    }
43
44    /// Returns the block hash.
45    pub const fn hash(&self) -> B256 {
46        self.block_hash
47    }
48}
49
50impl Ord for StoredBlockAccessListKey {
51    fn cmp(&self, other: &Self) -> Ordering {
52        self.block_number
53            .cmp(&other.block_number)
54            .then_with(|| self.block_hash.as_slice().cmp(other.block_hash.as_slice()))
55    }
56}
57
58impl PartialOrd for StoredBlockAccessListKey {
59    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
60        Some(self.cmp(other))
61    }
62}
63
64impl Encode for StoredBlockAccessListKey {
65    type Encoded = [u8; BLOCK_ACCESS_LIST_KEY_BYTES];
66
67    fn encode(self) -> Self::Encoded {
68        let mut buf = [0u8; BLOCK_ACCESS_LIST_KEY_BYTES];
69        buf[..8].copy_from_slice(&self.block_number.to_be_bytes());
70        buf[8..].copy_from_slice(self.block_hash.as_slice());
71        buf
72    }
73}
74
75impl Decode for StoredBlockAccessListKey {
76    fn decode(value: &[u8]) -> Result<Self, DatabaseError> {
77        if value.len() != BLOCK_ACCESS_LIST_KEY_BYTES {
78            return Err(DatabaseError::Decode)
79        }
80
81        let block_number =
82            u64::from_be_bytes(value[..8].try_into().map_err(|_| DatabaseError::Decode)?);
83        let block_hash = B256::decode(&value[8..])?;
84
85        Ok(Self::new(block_number, block_hash))
86    }
87}
88
89/// Stored block access list value.
90#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
91pub struct StoredBlockAccessList {
92    /// Keccak hash carried by the source BAL, trusted without verification on decode.
93    hash: B256,
94    /// Raw BAL RLP bytes.
95    raw: Bytes,
96}
97
98impl StoredBlockAccessList {
99    /// Creates a stored BAL from raw bytes.
100    pub fn new(raw: Bytes) -> Self {
101        let hash = keccak256(&raw);
102        Self::new_unchecked(hash, raw)
103    }
104
105    /// Creates a stored BAL from its hash and raw bytes without verifying that they match.
106    pub const fn new_unchecked(hash: B256, raw: Bytes) -> Self {
107        Self { hash, raw }
108    }
109
110    /// Returns the stored hash without verifying it against the raw bytes.
111    pub const fn hash(&self) -> B256 {
112        self.hash
113    }
114
115    /// Consumes the stored BAL and returns its raw bytes.
116    pub fn into_raw(self) -> Bytes {
117        self.raw
118    }
119}
120
121impl Compress for StoredBlockAccessList {
122    type Compressed = Vec<u8>;
123
124    fn compress(self) -> Self::Compressed {
125        let mut out = Vec::with_capacity(STORED_BLOCK_ACCESS_LIST_HASH_BYTES + self.raw.len());
126        out.extend_from_slice(self.hash.as_slice());
127        out.extend_from_slice(&self.raw);
128        out
129    }
130
131    fn compress_to_buf<B: BufMut + AsMut<[u8]>>(&self, buf: &mut B) {
132        buf.put_slice(self.hash.as_slice());
133        buf.put_slice(&self.raw);
134    }
135}
136
137impl Decompress for StoredBlockAccessList {
138    fn decompress(value: &[u8]) -> Result<Self, DecompressError> {
139        if value.len() < STORED_BLOCK_ACCESS_LIST_HASH_BYTES {
140            return Err(DecompressError::new(StoredBlockAccessListDecodeError))
141        }
142
143        let hash = B256::from_slice(&value[..STORED_BLOCK_ACCESS_LIST_HASH_BYTES]);
144        let raw = Bytes::copy_from_slice(&value[STORED_BLOCK_ACCESS_LIST_HASH_BYTES..]);
145
146        Ok(Self::new_unchecked(hash, raw))
147    }
148}
149
150/// Error returned when a stored BAL value is too short to contain its hash prefix.
151#[derive(Debug, derive_more::Display, derive_more::Error)]
152#[display("stored block access list value is missing its hash prefix")]
153struct StoredBlockAccessListDecodeError;
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158    use crate::table::{Compress, Decompress};
159
160    #[test]
161    fn key_encodes_number_first() {
162        let low_hash = B256::with_last_byte(0xff);
163        let high_hash = B256::ZERO;
164        let low_number = StoredBlockAccessListKey::new(1, low_hash).encode();
165        let high_number = StoredBlockAccessListKey::new(2, high_hash).encode();
166
167        assert!(low_number < high_number);
168    }
169
170    #[test]
171    fn key_roundtrip() {
172        let key = StoredBlockAccessListKey::new(42, B256::with_last_byte(7));
173        let encoded = key.encode();
174
175        assert_eq!(StoredBlockAccessListKey::decode(&encoded).unwrap(), key);
176    }
177
178    #[test]
179    fn stored_bal_roundtrip() {
180        let raw = Bytes::from_static(&[0xc0]);
181        let stored = StoredBlockAccessList::new(raw.clone());
182        let encoded = stored.clone().compress();
183        let decoded = StoredBlockAccessList::decompress(&encoded).unwrap();
184
185        assert_eq!(decoded, stored);
186        assert_eq!(decoded.hash(), keccak256(&raw));
187        assert_eq!(decoded.into_raw(), raw);
188    }
189
190    #[test]
191    fn stored_bal_unchecked_preserves_hash_and_raw_bytes() {
192        let hash = B256::with_last_byte(1);
193        let raw = Bytes::from_static(&[0xc0]);
194        let stored = StoredBlockAccessList::new_unchecked(hash, raw.clone());
195        let encoded = stored.compress();
196
197        assert_eq!(&encoded[..STORED_BLOCK_ACCESS_LIST_HASH_BYTES], hash.as_slice());
198        assert_eq!(&encoded[STORED_BLOCK_ACCESS_LIST_HASH_BYTES..], raw.as_ref());
199    }
200}