1use std::collections::HashSet;
18use std::sync::atomic::Ordering;
19use std::sync::Arc;
20use std::time::Duration;
21
22use chia_protocol::Bytes32;
23use dig_block::{BlockStatus, L2Block};
24use rocksdb::{ColumnFamily, Direction, IteratorMode, ReadOptions, WriteBatch};
25use tokio::sync::{mpsc, oneshot};
26
27use crate::constants::{CF_BLOCKS, CF_CANONICAL, CF_HEADERS};
28use crate::encoding::{decode_height_key, hash_key, height_key};
29use crate::error::{BlockStoreError, ERR_MUTATION_READ_ONLY};
30use crate::store::{BlockStore, BlockStoreInner};
31use crate::types::BlockRecord;
32
33pub(crate) type PipelineJob = (
39 L2Block,
40 bool,
41 oneshot::Sender<Result<bool, BlockStoreError>>,
42);
43
44impl BlockStore {
45 fn blocks_stream_read_options(&self) -> ReadOptions {
47 let mut o = ReadOptions::default();
48 o.set_readahead_size(self.readahead_size);
49 o
50 }
51
52 pub fn stream_blocks_in_range(
72 &self,
73 start: u64,
74 end: u64,
75 ) -> Result<StreamBlocksInRange<'_>, BlockStoreError> {
76 let cf_blocks = self.cf(CF_BLOCKS)?;
77 if start > end {
78 return Ok(StreamBlocksInRange {
79 store: self,
80 pairs: Vec::new(),
81 idx: 0,
82 read_opts: self.blocks_stream_read_options(),
83 cf_blocks,
84 });
85 }
86 let cf_canon = self.cf(CF_CANONICAL)?;
87 let mut ro_canon = ReadOptions::default();
88 ro_canon.set_readahead_size(self.readahead_size);
89 ro_canon.set_iterate_lower_bound(height_key(start).to_vec());
90 if end < u64::MAX {
91 ro_canon.set_iterate_upper_bound(height_key(end.saturating_add(1)).to_vec());
92 }
93 let iter = self.db.iterator_cf_opt(
94 cf_canon,
95 ro_canon,
96 IteratorMode::From(height_key(start).as_slice(), Direction::Forward),
97 );
98 let mut pairs = Vec::new();
99 for item in iter {
100 let (k, v) = item?;
101 let karr: [u8; 8] = k.as_ref().try_into().map_err(|_| {
102 BlockStoreError::Serialization(
103 "stream_blocks_in_range: CF_CANONICAL key must be exactly 8 bytes".into(),
104 )
105 })?;
106 let height = decode_height_key(&karr);
107 if height > end {
108 break;
109 }
110 if height < start {
111 continue;
112 }
113 let varr: [u8; 32] = v.as_ref().try_into().map_err(|_| {
114 BlockStoreError::Serialization(
115 "stream_blocks_in_range: CF_CANONICAL value must be exactly 32 bytes".into(),
116 )
117 })?;
118 pairs.push((height, Bytes32::new(varr)));
119 }
120 Ok(StreamBlocksInRange {
121 store: self,
122 pairs,
123 idx: 0,
124 read_opts: self.blocks_stream_read_options(),
125 cf_blocks,
126 })
127 }
128
129 pub async fn put_pipelined(
142 &self,
143 block: L2Block,
144 canonical: bool,
145 ) -> Result<oneshot::Receiver<Result<bool, BlockStoreError>>, BlockStoreError> {
146 if self.read_only {
147 return Err(BlockStoreError::Serialization(
148 ERR_MUTATION_READ_ONLY.into(),
149 ));
150 }
151 let tx = self.pipeline_sender().await?;
152 let (ack_tx, ack_rx) = oneshot::channel();
153 tx.send((block, canonical, ack_tx))
154 .await
155 .map_err(|_| BlockStoreError::PipelineClosed)?;
156 Ok(ack_rx)
157 }
158
159 #[must_use]
163 pub fn pipeline_write_batch_count(&self) -> u64 {
164 self.pipeline_write_batches.load(Ordering::Relaxed) as u64
165 }
166
167 async fn pipeline_sender(&self) -> Result<mpsc::Sender<PipelineJob>, BlockStoreError> {
169 let mut guard = self.pipeline_tx.lock().await;
170 if let Some(tx) = guard.as_ref() {
171 return Ok(tx.clone());
172 }
173 let _handle = tokio::runtime::Handle::try_current().map_err(|_| {
174 BlockStoreError::Serialization(
175 "put_pipelined requires an active Tokio runtime (use #[tokio::test] or Runtime::block_on)"
176 .into(),
177 )
178 })?;
179 let cap = self.pipeline_channel_capacity;
180 let (tx, rx) = mpsc::channel::<PipelineJob>(cap);
181 let inner = self.inner.clone();
182 let batch = self.pipeline_batch_size;
183 let flush_ms = self.pipeline_flush_ms;
184 tokio::spawn(run_write_pipeline(
185 inner,
186 Arc::new(tokio::sync::Mutex::new(None)),
187 rx,
188 batch,
189 flush_ms,
190 ));
191 *guard = Some(tx.clone());
192 Ok(tx)
193 }
194}
195
196pub(crate) async fn run_write_pipeline(
202 inner: Arc<BlockStoreInner>,
203 _worker_unused_pipeline_tx: Arc<tokio::sync::Mutex<Option<mpsc::Sender<PipelineJob>>>>,
204 mut rx: mpsc::Receiver<PipelineJob>,
205 batch_size: usize,
206 flush_ms: u64,
207) {
208 let store = BlockStore {
209 inner,
210 pipeline_tx: _worker_unused_pipeline_tx,
211 };
212 let mut buf: Vec<PipelineJob> = Vec::with_capacity(batch_size);
213 let tick = Duration::from_millis(flush_ms);
214
215 loop {
216 match rx.recv().await {
217 None => {
218 return;
219 }
220 Some(job) => buf.push(job),
221 }
222 if buf.len() >= batch_size {
223 let _ = flush_pipeline_batch(&store, &mut buf);
224 buf.clear();
225 continue;
226 }
227
228 let mut sleep = Box::pin(tokio::time::sleep(tick));
229 'collect: loop {
230 tokio::select! {
231 biased;
232 maybe = rx.recv() => {
233 match maybe {
234 None => {
235 let _ = flush_pipeline_batch(&store, &mut buf);
236 return;
237 }
238 Some(job) => {
239 buf.push(job);
240 if buf.len() >= batch_size {
241 break 'collect;
242 }
243 }
244 }
245 }
246 _ = &mut sleep, if !buf.is_empty() => {
247 break 'collect;
248 }
249 }
250 }
251
252 let _ = flush_pipeline_batch(&store, &mut buf);
253 buf.clear();
254 }
255}
256
257fn flush_pipeline_batch(
266 store: &BlockStore,
267 jobs: &mut Vec<PipelineJob>,
268) -> Result<(), BlockStoreError> {
269 if store.read_only {
270 let pending: Vec<PipelineJob> = std::mem::take(jobs);
271 let msg = ERR_MUTATION_READ_ONLY.to_string();
272 for (_, _, ack) in pending {
273 let _ = ack.send(Err(BlockStoreError::Serialization(msg.clone())));
274 }
275 return Ok(());
276 }
277 let pending: Vec<PipelineJob> = std::mem::take(jobs);
278 let cf_b = match store.cf(CF_BLOCKS) {
279 Ok(c) => c,
280 Err(e) => {
281 let msg = e.to_string();
282 for (_, _, ack) in pending {
283 let _ = ack.send(Err(BlockStoreError::Serialization(format!(
284 "write pipeline: {msg}"
285 ))));
286 }
287 return Ok(());
288 }
289 };
290 let cf_h = match store.cf(CF_HEADERS) {
291 Ok(c) => c,
292 Err(e) => {
293 let msg = e.to_string();
294 for (_, _, ack) in pending {
295 let _ = ack.send(Err(BlockStoreError::Serialization(format!(
296 "write pipeline: {msg}"
297 ))));
298 }
299 return Ok(());
300 }
301 };
302 let cf_c = match store.cf(CF_CANONICAL) {
303 Ok(c) => c,
304 Err(e) => {
305 let msg = e.to_string();
306 for (_, _, ack) in pending {
307 let _ = ack.send(Err(BlockStoreError::Serialization(format!(
308 "write pipeline: {msg}"
309 ))));
310 }
311 return Ok(());
312 }
313 };
314
315 let mut seen: HashSet<Bytes32> = HashSet::new();
316 struct StagedRow {
317 hash: Bytes32,
318 block: L2Block,
319 compressed: Vec<u8>,
320 header_bytes: Vec<u8>,
321 canonical: bool,
322 ack: oneshot::Sender<Result<bool, BlockStoreError>>,
323 }
324 let mut staged: Vec<StagedRow> = Vec::new();
325
326 for (block, canonical, ack) in pending {
327 let hash = block.hash();
328 if !seen.insert(hash) {
329 let _ = ack.send(Ok(false));
330 continue;
331 }
332 let exists = match store.db.get_cf(cf_b, hash_key(&hash).as_slice()) {
333 Ok(o) => o.is_some(),
334 Err(e) => {
335 let _ = ack.send(Err(BlockStoreError::RocksDb(e)));
336 continue;
337 }
338 };
339 if exists {
340 let _ = ack.send(Ok(false));
341 continue;
342 }
343 let compressed = match store.serialize_block(&block) {
344 Ok(b) => b,
345 Err(e) => {
346 let _ = ack.send(Err(e));
347 continue;
348 }
349 };
350 let header_bytes = match BlockStore::serialize_header(&block.header) {
351 Ok(b) => b,
352 Err(e) => {
353 let _ = ack.send(Err(e));
354 continue;
355 }
356 };
357 staged.push(StagedRow {
358 hash,
359 block,
360 compressed,
361 header_bytes,
362 canonical,
363 ack,
364 });
365 }
366
367 let mut wb = WriteBatch::default();
368 for row in &staged {
369 wb.put_cf(
370 cf_b,
371 hash_key(&row.hash).as_slice(),
372 row.compressed.as_slice(),
373 );
374 wb.put_cf(
375 cf_h,
376 hash_key(&row.hash).as_slice(),
377 row.header_bytes.as_slice(),
378 );
379 if row.canonical {
380 wb.put_cf(
381 cf_c,
382 height_key(row.block.height()),
383 hash_key(&row.hash).as_slice(),
384 );
385 }
386 }
387
388 if wb.is_empty() {
389 return Ok(());
390 }
391
392 if let Err(e) = store.db.write(wb) {
393 let msg = format!("write pipeline: rocksdb write failed: {e}");
394 for row in staged {
395 let _ = row
396 .ack
397 .send(Err(BlockStoreError::Serialization(msg.clone())));
398 }
399 return Ok(());
400 }
401
402 for row in &staged {
403 if row.canonical {
404 if let Err(e) = store
405 .canonical_bin
406 .write()
407 .extend_write(row.block.height(), &row.hash)
408 {
409 let msg = format!("write pipeline: canonical.bin mmap update failed: {e}");
410 for row in staged {
411 let _ = row
412 .ack
413 .send(Err(BlockStoreError::Serialization(msg.clone())));
414 }
415 return Ok(());
416 }
417 }
418 }
419
420 store.pipeline_write_batches.fetch_add(1, Ordering::Relaxed);
421
422 for row in staged {
423 let record = BlockRecord::from_header(&row.block.header, BlockStatus::Validated);
424 store.record_cache.lock().insert(row.hash, record);
425 store.block_cache.insert(row.hash, row.block.clone());
426 store
427 .header_cache
428 .insert(row.hash, row.block.header.clone());
429 let ack_res = match store.maybe_train_dictionary() {
430 Ok(()) => Ok(true),
431 Err(e) => Err(e),
432 };
433 let _ = row.ack.send(ack_res);
434 }
435 Ok(())
436}
437
438pub struct StreamBlocksInRange<'a> {
448 store: &'a BlockStore,
449 pairs: Vec<(u64, Bytes32)>,
450 idx: usize,
451 read_opts: ReadOptions,
452 cf_blocks: &'a ColumnFamily,
453}
454
455impl<'a> Iterator for StreamBlocksInRange<'a> {
456 type Item = Result<L2Block, BlockStoreError>;
457
458 fn next(&mut self) -> Option<Self::Item> {
459 if self.idx >= self.pairs.len() {
460 return None;
461 }
462 let (_expected_height, hash) = self.pairs[self.idx];
463 self.idx += 1;
464 if let Some(block) = self.store.block_cache.get_clone(&hash) {
465 return Some(Ok(block));
466 }
467 self.store
468 .cf_blocks_stream_physical_gets
469 .fetch_add(1, Ordering::Relaxed);
470 let raw_opt = match self.store.db.get_cf_opt(
471 self.cf_blocks,
472 hash_key(&hash).as_slice(),
473 &self.read_opts,
474 ) {
475 Ok(o) => o,
476 Err(e) => return Some(Err(e.into())),
477 };
478 let Some(raw) = raw_opt else {
479 return Some(Err(BlockStoreError::BlockNotFound(hash)));
480 };
481 match self.store.deserialize_block(&raw) {
482 Ok(block) => {
483 self.store.block_cache.insert(hash, block.clone());
484 self.store.header_cache.insert(hash, block.header.clone());
485 Some(Ok(block))
486 }
487 Err(e) => Some(Err(e)),
488 }
489 }
490}