forked from paradigmxyz/reth
-
Notifications
You must be signed in to change notification settings - Fork 35
Expand file tree
/
Copy pathblock_store.rs
More file actions
147 lines (129 loc) · 5.08 KB
/
Copy pathblock_store.rs
File metadata and controls
147 lines (129 loc) · 5.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use super::{sources::BlockSourceBoxed, utils::LruBiMap};
use crate::node::types::BlockAndReceipts;
use alloy_primitives::B256;
use futures::future::BoxFuture;
use parking_lot::RwLock;
use reth_network::cache::LruMap;
use std::{
collections::HashMap,
sync::Arc,
time::Duration,
};
/// Function that resolves a block hash to its number via the node's database.
/// Returns `None` if the hash is not yet in the database (e.g. headers not synced yet).
/// Note: This queries the HeaderNumbers table which covers both database and static files.
pub type DbBlockNumberFn = Arc<dyn Fn(B256) -> Option<u64> + Send + Sync>;
const BLOCK_CACHE_LIMIT: u32 = 100_000;
const HASH_INDEX_LIMIT: u32 = 1_000_000;
/// Unified block store that combines block content caching, hash↔number indexing,
/// and database fallback into a single abstraction.
///
/// Every block that passes through the store has its hash (and parent hash)
/// automatically indexed, eliminating scattered cache population.
pub struct BlockStore {
/// Block content cache: number → block
blocks: RwLock<LruMap<u64, BlockAndReceipts>>,
/// Hash index: hash ↔ number (bidirectional)
hash_index: RwLock<LruBiMap<B256, u64>>,
/// DB fallback for hash→number (HeaderNumbers table)
db_block_number: Option<DbBlockNumberFn>,
/// Underlying fetch source (S3, RPC, etc.)
source: BlockSourceBoxed,
}
impl std::fmt::Debug for BlockStore {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BlockStore").finish_non_exhaustive()
}
}
impl BlockStore {
pub fn new(source: BlockSourceBoxed, db_block_number: Option<DbBlockNumberFn>) -> Self {
Self {
blocks: RwLock::new(LruMap::new(BLOCK_CACHE_LIMIT)),
hash_index: RwLock::new(LruBiMap::new(HASH_INDEX_LIMIT)),
db_block_number,
source,
}
}
/// Index a block's hash and parent hash in the hash↔number map.
pub fn index_block(&self, block: &BlockAndReceipts) {
let mut idx = self.hash_index.write();
Self::index_block_inner(&mut idx, block);
}
/// Index a block into an already-held write guard. Avoids repeated lock acquisition.
fn index_block_inner(idx: &mut LruBiMap<B256, u64>, block: &BlockAndReceipts) {
let number = block.number();
idx.insert(block.hash(), number);
if number > 0 {
idx.insert(block.parent_hash(), number - 1);
}
}
/// Fetch a single block by number. Auto-indexes and caches.
pub async fn get_by_number(&self, n: u64) -> eyre::Result<BlockAndReceipts> {
if let Some(block) = self.blocks.write().get(&n) {
return Ok(block.clone());
}
let block = self.source.collect_block(n).await?;
self.blocks.write().insert(n, block.clone());
self.index_block(&block);
Ok(block)
}
/// Fetch multiple blocks by number. Auto-indexes and caches.
pub async fn get_by_numbers(
&self,
heights: Vec<u64>,
) -> eyre::Result<Vec<BlockAndReceipts>> {
let mut cached: HashMap<u64, BlockAndReceipts> = HashMap::new();
let mut uncached_heights = Vec::new();
{
let mut c = self.blocks.write();
for &h in &heights {
if let Some(block) = c.get(&h) {
cached.insert(h, block.clone());
} else {
uncached_heights.push(h);
}
}
}
if !uncached_heights.is_empty() {
let fetched = self.source.collect_blocks(uncached_heights).await?;
let mut c = self.blocks.write();
for block in fetched {
let h = block.number();
c.insert(h, block.clone());
cached.insert(h, block);
}
}
// Batch-index all blocks under a single write lock
{
let mut idx = self.hash_index.write();
for block in cached.values() {
Self::index_block_inner(&mut idx, block);
}
}
heights
.iter()
.map(|h| cached.remove(h).ok_or_else(|| eyre::eyre!("Block {h} not found")))
.collect()
}
/// Resolve a block hash to a block number.
/// Checks the in-memory index first, then falls back to the database.
pub fn hash_to_number(&self, hash: B256) -> eyre::Result<u64> {
// Fast path: in-memory index
if let Some(n) = self.hash_index.read().get_by_left(&hash).copied() {
return Ok(n);
}
// Fallback: database lookup (MDBX is mmap'd, no need to re-cache)
if let Some(ref db_fn) = self.db_block_number
&& let Some(n) = db_fn(hash) {
return Ok(n);
}
Err(eyre::eyre!("Hash not found in index or database: {hash:?}"))
}
// --- Delegated block source methods ---
pub fn find_latest_block_number(&self) -> BoxFuture<'static, Option<u64>> {
self.source.find_latest_block_number()
}
pub fn polling_interval(&self) -> Duration {
self.source.polling_interval()
}
}