1use alloy_primitives::{
3 map::{AddressMap, HashMap},
4 Address, BlockNumber, TxNumber, B256,
5};
6use reth_config::config::EtlConfig;
7use reth_db_api::{
8 cursor::{DbCursorRO, DbCursorRW},
9 models::{
10 sharded_key::NUM_OF_INDICES_IN_SHARD, storage_sharded_key::StorageShardedKey,
11 AccountBeforeTx, AddressStorageKey, BlockNumberAddress, ShardedKey,
12 },
13 table::{Decode, Decompress, Table},
14 transaction::DbTx,
15 BlockNumberList,
16};
17use reth_etl::Collector;
18use reth_primitives_traits::NodePrimitives;
19use reth_provider::{
20 providers::StaticFileProvider, to_range, BlockReader, DBProvider, EitherWriter, ProviderError,
21 StaticFileProviderFactory,
22};
23use reth_stages_api::StageError;
24use reth_static_file_types::StaticFileSegment;
25use reth_storage_api::{ChangeSetReader, StorageChangeSetReader};
26use std::{hash::Hash, ops::RangeBounds};
27use tracing::info;
28
29const DEFAULT_CACHE_THRESHOLD: u64 = 100_000;
31
32pub(crate) fn collect_history_indices<Provider, CS, H, P>(
50 provider: &Provider,
51 range: impl RangeBounds<CS::Key>,
52 sharded_key_factory: impl Fn(P, BlockNumber) -> H::Key,
53 partial_key_factory: impl Fn((CS::Key, CS::Value)) -> (u64, P),
54 etl_config: &EtlConfig,
55) -> Result<Collector<H::Key, H::Value>, StageError>
56where
57 Provider: DBProvider,
58 CS: Table,
59 H: Table<Value = BlockNumberList>,
60 P: Copy + Eq + Hash,
61{
62 let mut changeset_cursor = provider.tx_ref().cursor_read::<CS>()?;
63
64 let mut collector = Collector::new(etl_config.file_size, etl_config.dir.clone());
65 let mut cache: HashMap<P, Vec<u64>> = HashMap::default();
66
67 let mut collect = |cache: &mut HashMap<P, Vec<u64>>| {
68 for (key, indices) in cache.drain() {
69 let last = *indices.last().expect("qed");
70 collector
71 .insert(sharded_key_factory(key, last), BlockNumberList::new_pre_sorted(indices))?;
72 }
73 Ok::<(), StageError>(())
74 };
75
76 let total_changesets = provider.tx_ref().entries::<CS>()?;
78 let interval = (total_changesets / 1000).max(1);
79
80 let mut flush_counter = 0;
81 let mut current_block_number = u64::MAX;
82 for (idx, entry) in changeset_cursor.walk_range(range)?.enumerate() {
83 let (block_number, key) = partial_key_factory(entry?);
84 cache.entry(key).or_default().push(block_number);
85
86 if idx > 0 && idx.is_multiple_of(interval) && total_changesets > 1000 {
87 info!(target: "sync::stages::index_history", progress = %format_args!("{:.4}%", (idx as f64 / total_changesets as f64) * 100.0), "Collecting indices");
88 }
89
90 if current_block_number != block_number {
92 current_block_number = block_number;
93 flush_counter += 1;
94 if flush_counter > DEFAULT_CACHE_THRESHOLD {
95 collect(&mut cache)?;
96 flush_counter = 0;
97 }
98 }
99 }
100 collect(&mut cache)?;
101
102 Ok(collector)
103}
104
105fn collect_indices<K, F>(
107 cache: impl Iterator<Item = (K, Vec<u64>)>,
108 mut insert_fn: F,
109) -> Result<(), StageError>
110where
111 F: FnMut(K, Vec<u64>) -> Result<(), StageError>,
112{
113 for (key, indices) in cache {
114 insert_fn(key, indices)?
115 }
116 Ok(())
117}
118
119pub(crate) fn collect_account_history_indices<Provider>(
121 provider: &Provider,
122 range: impl RangeBounds<BlockNumber>,
123 etl_config: &EtlConfig,
124) -> Result<Collector<ShardedKey<Address>, BlockNumberList>, StageError>
125where
126 Provider: DBProvider + ChangeSetReader + StaticFileProviderFactory,
127{
128 let mut collector = Collector::new(etl_config.file_size, etl_config.dir.clone());
129 let mut cache: AddressMap<Vec<u64>> = AddressMap::default();
130
131 let mut insert_fn = |address: Address, indices: Vec<u64>| {
132 let last = indices.last().expect("indices is non-empty");
133 collector
134 .insert(ShardedKey::new(address, *last), BlockNumberList::new_pre_sorted(indices))?;
135 Ok(())
136 };
137
138 let range = to_range(range);
140 let start_block = range.start;
141
142 let static_file_provider = provider.static_file_provider();
144
145 let walker = static_file_provider.walk_account_changeset_range(range);
146
147 let mut flush_counter = 0;
148 let mut current_block_number = u64::MAX;
149
150 for changeset_result in walker {
151 let (block_number, AccountBeforeTx { address, .. }) = changeset_result?;
152 cache.entry(address).or_default().push(block_number);
153
154 if block_number != current_block_number {
155 current_block_number = block_number;
156 flush_counter += 1;
157 }
158
159 if flush_counter > DEFAULT_CACHE_THRESHOLD {
160 info!(
161 target: "sync::stages::index_history",
162 processed_blocks = current_block_number.saturating_sub(start_block) + 1,
163 current_block = current_block_number,
164 "Collecting indices"
165 );
166 collect_indices(cache.drain(), &mut insert_fn)?;
167 flush_counter = 0;
168 }
169 }
170 collect_indices(cache.into_iter(), insert_fn)?;
171
172 Ok(collector)
173}
174
175pub(crate) fn collect_storage_history_indices<Provider>(
177 provider: &Provider,
178 range: impl RangeBounds<BlockNumber>,
179 etl_config: &EtlConfig,
180) -> Result<Collector<StorageShardedKey, BlockNumberList>, StageError>
181where
182 Provider: DBProvider + StorageChangeSetReader + StaticFileProviderFactory,
183{
184 let mut collector = Collector::new(etl_config.file_size, etl_config.dir.clone());
185 let mut cache: HashMap<AddressStorageKey, Vec<u64>> = HashMap::default();
186
187 let mut insert_fn = |key: AddressStorageKey, indices: Vec<u64>| {
188 let last = indices.last().expect("qed");
189 collector.insert(
190 StorageShardedKey::new(key.0 .0, key.0 .1, *last),
191 BlockNumberList::new_pre_sorted(indices),
192 )?;
193 Ok::<(), StageError>(())
194 };
195
196 let range = to_range(range);
197 let start_block = range.start;
198 let static_file_provider = provider.static_file_provider();
199
200 let walker = static_file_provider.walk_storage_changeset_range(range);
201
202 let mut flush_counter = 0;
203 let mut current_block_number = u64::MAX;
204
205 for changeset_result in walker {
206 let (BlockNumberAddress((block_number, address)), storage) = changeset_result?;
207 cache.entry(AddressStorageKey((address, storage.key))).or_default().push(block_number);
208
209 if block_number != current_block_number {
210 current_block_number = block_number;
211 flush_counter += 1;
212 }
213
214 if flush_counter > DEFAULT_CACHE_THRESHOLD {
215 info!(
216 target: "sync::stages::index_history",
217 processed_blocks = current_block_number.saturating_sub(start_block) + 1,
218 current_block = current_block_number,
219 "Collecting indices"
220 );
221 collect_indices(cache.drain(), &mut insert_fn)?;
222 flush_counter = 0;
223 }
224 }
225
226 collect_indices(cache.into_iter(), insert_fn)?;
227
228 Ok(collector)
229}
230
231pub(crate) fn load_account_history<N, CURSOR>(
243 mut collector: Collector<ShardedKey<Address>, BlockNumberList>,
244 append_only: bool,
245 writer: &mut EitherWriter<'_, CURSOR, N>,
246) -> Result<(), StageError>
247where
248 N: NodePrimitives,
249 CURSOR: DbCursorRW<reth_db_api::tables::AccountsHistory>
250 + DbCursorRO<reth_db_api::tables::AccountsHistory>,
251{
252 let mut current_address: Option<Address> = None;
253 let mut current_list = Vec::<u64>::new();
255
256 let total_entries = collector.len();
257 let interval = (total_entries / 10).max(1);
258
259 for (index, element) in collector.iter()?.enumerate() {
260 let (k, v) = element?;
261 let sharded_key = ShardedKey::<Address>::decode_owned(k)?;
262 let new_list = BlockNumberList::decompress_owned(v)?;
263
264 if index > 0 && index.is_multiple_of(interval) && total_entries > 10 {
265 info!(target: "sync::stages::index_history", progress = %format_args!("{:.2}%", (index as f64 / total_entries as f64) * 100.0), "Writing indices");
266 }
267
268 let address = sharded_key.key;
269
270 if current_address != Some(address) {
272 if let Some(prev_addr) = current_address {
274 flush_account_history_shards(prev_addr, &mut current_list, append_only, writer)?;
275 }
276
277 current_address = Some(address);
278 current_list.clear();
279
280 if !append_only &&
283 let Some(last_shard) = writer.get_last_account_history_shard(address)?
284 {
285 current_list.extend(last_shard.iter());
286 }
287 }
288
289 current_list.extend(new_list.iter());
291
292 flush_account_history_shards_partial(address, &mut current_list, append_only, writer)?;
294 }
295
296 if let Some(addr) = current_address {
298 flush_account_history_shards(addr, &mut current_list, append_only, writer)?;
299 }
300
301 Ok(())
302}
303
304fn flush_account_history_shards_partial<N, CURSOR>(
310 address: Address,
311 list: &mut Vec<u64>,
312 append_only: bool,
313 writer: &mut EitherWriter<'_, CURSOR, N>,
314) -> Result<(), StageError>
315where
316 N: NodePrimitives,
317 CURSOR: DbCursorRW<reth_db_api::tables::AccountsHistory>
318 + DbCursorRO<reth_db_api::tables::AccountsHistory>,
319{
320 if list.len() <= NUM_OF_INDICES_IN_SHARD {
322 return Ok(());
323 }
324
325 let num_full_shards = list.len() / NUM_OF_INDICES_IN_SHARD;
326
327 let shards_to_flush = if list.len().is_multiple_of(NUM_OF_INDICES_IN_SHARD) {
330 num_full_shards - 1
331 } else {
332 num_full_shards
333 };
334
335 if shards_to_flush == 0 {
336 return Ok(());
337 }
338
339 let flush_len = shards_to_flush * NUM_OF_INDICES_IN_SHARD;
341 let remainder = list.split_off(flush_len);
342
343 for chunk in list.chunks(NUM_OF_INDICES_IN_SHARD) {
345 let highest = *chunk.last().expect("chunk is non-empty");
346 let key = ShardedKey::new(address, highest);
347 let value = BlockNumberList::new_pre_sorted(chunk.iter().copied());
348
349 if append_only {
350 writer.append_account_history(key, &value)?;
351 } else {
352 writer.upsert_account_history(key, &value)?;
353 }
354 }
355
356 *list = remainder;
358 Ok(())
359}
360
361fn flush_account_history_shards<N, CURSOR>(
366 address: Address,
367 list: &mut Vec<u64>,
368 append_only: bool,
369 writer: &mut EitherWriter<'_, CURSOR, N>,
370) -> Result<(), StageError>
371where
372 N: NodePrimitives,
373 CURSOR: DbCursorRW<reth_db_api::tables::AccountsHistory>
374 + DbCursorRO<reth_db_api::tables::AccountsHistory>,
375{
376 if list.is_empty() {
377 return Ok(());
378 }
379
380 let num_chunks = list.len().div_ceil(NUM_OF_INDICES_IN_SHARD);
381
382 for (i, chunk) in list.chunks(NUM_OF_INDICES_IN_SHARD).enumerate() {
383 let is_last = i == num_chunks - 1;
384
385 let highest = if is_last { u64::MAX } else { *chunk.last().expect("chunk is non-empty") };
388
389 let key = ShardedKey::new(address, highest);
390 let value = BlockNumberList::new_pre_sorted(chunk.iter().copied());
391
392 if append_only {
393 writer.append_account_history(key, &value)?;
394 } else {
395 writer.upsert_account_history(key, &value)?;
396 }
397 }
398
399 list.clear();
400 Ok(())
401}
402
403pub(crate) fn missing_static_data_error<Provider>(
406 last_tx_num: TxNumber,
407 static_file_provider: &StaticFileProvider<Provider::Primitives>,
408 provider: &Provider,
409 segment: StaticFileSegment,
410) -> Result<StageError, ProviderError>
411where
412 Provider: BlockReader + StaticFileProviderFactory,
413{
414 let mut last_block =
415 static_file_provider.get_highest_static_file_block(segment).unwrap_or_default();
416
417 loop {
420 if let Some(indices) = provider.block_body_indices(last_block)? &&
421 indices.last_tx_num() <= last_tx_num
422 {
423 break
424 }
425 if last_block == 0 {
426 break
427 }
428 last_block -= 1;
429 }
430
431 let missing_block = Box::new(provider.sealed_header(last_block + 1)?.unwrap_or_default());
432
433 Ok(StageError::MissingStaticFileData {
434 block: Box::new(missing_block.block_with_parent()),
435 segment,
436 })
437}
438
439pub(crate) fn load_storage_history<N, CURSOR>(
451 mut collector: Collector<StorageShardedKey, BlockNumberList>,
452 append_only: bool,
453 writer: &mut EitherWriter<'_, CURSOR, N>,
454) -> Result<(), StageError>
455where
456 N: NodePrimitives,
457 CURSOR: DbCursorRW<reth_db_api::tables::StoragesHistory>
458 + DbCursorRO<reth_db_api::tables::StoragesHistory>,
459{
460 let mut current_key: Option<(Address, B256)> = None;
461 let mut current_list = Vec::<u64>::new();
463
464 let total_entries = collector.len();
465 let interval = (total_entries / 10).max(1);
466
467 for (index, element) in collector.iter()?.enumerate() {
468 let (k, v) = element?;
469 let sharded_key = StorageShardedKey::decode_owned(k)?;
470 let new_list = BlockNumberList::decompress_owned(v)?;
471
472 if index > 0 && index.is_multiple_of(interval) && total_entries > 10 {
473 info!(target: "sync::stages::index_history", progress = %format_args!("{:.2}%", (index as f64 / total_entries as f64) * 100.0), "Writing indices");
474 }
475
476 let partial_key = (sharded_key.address, sharded_key.sharded_key.key);
477
478 if current_key != Some(partial_key) {
480 if let Some((prev_addr, prev_storage_key)) = current_key {
482 flush_storage_history_shards(
483 prev_addr,
484 prev_storage_key,
485 &mut current_list,
486 append_only,
487 writer,
488 )?;
489 }
490
491 current_key = Some(partial_key);
492 current_list.clear();
493
494 if !append_only &&
497 let Some(last_shard) =
498 writer.get_last_storage_history_shard(partial_key.0, partial_key.1)?
499 {
500 current_list.extend(last_shard.iter());
501 }
502 }
503
504 current_list.extend(new_list.iter());
506
507 flush_storage_history_shards_partial(
509 partial_key.0,
510 partial_key.1,
511 &mut current_list,
512 append_only,
513 writer,
514 )?;
515 }
516
517 if let Some((addr, storage_key)) = current_key {
519 flush_storage_history_shards(addr, storage_key, &mut current_list, append_only, writer)?;
520 }
521
522 Ok(())
523}
524
525fn flush_storage_history_shards_partial<N, CURSOR>(
531 address: Address,
532 storage_key: B256,
533 list: &mut Vec<u64>,
534 append_only: bool,
535 writer: &mut EitherWriter<'_, CURSOR, N>,
536) -> Result<(), StageError>
537where
538 N: NodePrimitives,
539 CURSOR: DbCursorRW<reth_db_api::tables::StoragesHistory>
540 + DbCursorRO<reth_db_api::tables::StoragesHistory>,
541{
542 if list.len() <= NUM_OF_INDICES_IN_SHARD {
544 return Ok(());
545 }
546
547 let num_full_shards = list.len() / NUM_OF_INDICES_IN_SHARD;
548
549 let shards_to_flush = if list.len().is_multiple_of(NUM_OF_INDICES_IN_SHARD) {
552 num_full_shards - 1
553 } else {
554 num_full_shards
555 };
556
557 if shards_to_flush == 0 {
558 return Ok(());
559 }
560
561 let flush_len = shards_to_flush * NUM_OF_INDICES_IN_SHARD;
563 let remainder = list.split_off(flush_len);
564
565 for chunk in list.chunks(NUM_OF_INDICES_IN_SHARD) {
567 let highest = *chunk.last().expect("chunk is non-empty");
568 let key = StorageShardedKey::new(address, storage_key, highest);
569 let value = BlockNumberList::new_pre_sorted(chunk.iter().copied());
570
571 if append_only {
572 writer.append_storage_history(key, &value)?;
573 } else {
574 writer.upsert_storage_history(key, &value)?;
575 }
576 }
577
578 *list = remainder;
580 Ok(())
581}
582
583fn flush_storage_history_shards<N, CURSOR>(
589 address: Address,
590 storage_key: B256,
591 list: &mut Vec<u64>,
592 append_only: bool,
593 writer: &mut EitherWriter<'_, CURSOR, N>,
594) -> Result<(), StageError>
595where
596 N: NodePrimitives,
597 CURSOR: DbCursorRW<reth_db_api::tables::StoragesHistory>
598 + DbCursorRO<reth_db_api::tables::StoragesHistory>,
599{
600 if list.is_empty() {
601 return Ok(());
602 }
603
604 let num_chunks = list.len().div_ceil(NUM_OF_INDICES_IN_SHARD);
605
606 for (i, chunk) in list.chunks(NUM_OF_INDICES_IN_SHARD).enumerate() {
607 let is_last = i == num_chunks - 1;
608
609 let highest = if is_last { u64::MAX } else { *chunk.last().expect("chunk is non-empty") };
612
613 let key = StorageShardedKey::new(address, storage_key, highest);
614 let value = BlockNumberList::new_pre_sorted(chunk.iter().copied());
615
616 if append_only {
617 writer.append_storage_history(key, &value)?;
618 } else {
619 writer.upsert_storage_history(key, &value)?;
620 }
621 }
622
623 list.clear();
624 Ok(())
625}