1#![recursion_limit = "256"]
20#![warn(missing_docs)]
21
22use std::{
23 collections::{BTreeSet, HashMap, HashSet},
24 io,
25 sync::Arc,
26 time::Duration,
27};
28
29use codec::{Decode, Encode, Error as CodecError, Input};
30use futures::{
31 channel::{
32 mpsc::{channel, Receiver as MpscReceiver, Sender as MpscSender},
33 oneshot,
34 },
35 future, select, FutureExt, SinkExt, StreamExt,
36};
37use futures_timer::Delay;
38use polkadot_node_clock::Clock;
39use polkadot_node_subsystem_util::database::{DBTransaction, Database};
40use sp_consensus::SyncOracle;
41
42use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec};
43use polkadot_node_primitives::{AvailableData, ErasureChunk};
44use polkadot_node_subsystem::{
45 errors::{ChainApiError, RuntimeApiError},
46 messages::{AvailabilityStoreMessage, ChainApiMessage, StoreAvailableDataError},
47 overseer, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, SpawnedSubsystem, SubsystemError,
48};
49use polkadot_node_subsystem_util as util;
50use polkadot_primitives::{
51 BlockNumber, CandidateEvent, CandidateHash, CandidateReceiptV2 as CandidateReceipt, ChunkIndex,
52 CoreIndex, Hash, Header, NodeFeatures, ValidatorIndex,
53};
54use util::availability_chunks::availability_chunk_indices;
55
56mod metrics;
57pub use self::metrics::*;
58
59#[cfg(test)]
60mod tests;
61
62const LOG_TARGET: &str = "parachain::availability-store";
63
64const AVAILABLE_PREFIX: &[u8; 9] = b"available";
67const CHUNK_PREFIX: &[u8; 5] = b"chunk";
68const META_PREFIX: &[u8; 4] = b"meta";
69const UNFINALIZED_PREFIX: &[u8; 11] = b"unfinalized";
70const PRUNE_BY_TIME_PREFIX: &[u8; 13] = b"prune_by_time";
71
72const TOMBSTONE_VALUE: &[u8] = b" ";
75
76const KEEP_UNAVAILABLE_FOR: Duration = Duration::from_secs(60 * 60);
78
79const PRUNING_INTERVAL: Duration = Duration::from_secs(60 * 5);
81
82#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
84struct BETimestamp(u64);
85
86impl Encode for BETimestamp {
87 fn size_hint(&self) -> usize {
88 std::mem::size_of::<u64>()
89 }
90
91 fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
92 f(&self.0.to_be_bytes())
93 }
94}
95
96impl Decode for BETimestamp {
97 fn decode<I: Input>(value: &mut I) -> Result<Self, CodecError> {
98 <[u8; 8]>::decode(value).map(u64::from_be_bytes).map(Self)
99 }
100}
101
102impl From<Duration> for BETimestamp {
103 fn from(d: Duration) -> Self {
104 BETimestamp(d.as_secs())
105 }
106}
107
108impl Into<Duration> for BETimestamp {
109 fn into(self) -> Duration {
110 Duration::from_secs(self.0)
111 }
112}
113
114#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
116struct BEBlockNumber(BlockNumber);
117
118impl Encode for BEBlockNumber {
119 fn size_hint(&self) -> usize {
120 std::mem::size_of::<BlockNumber>()
121 }
122
123 fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
124 f(&self.0.to_be_bytes())
125 }
126}
127
128impl Decode for BEBlockNumber {
129 fn decode<I: Input>(value: &mut I) -> Result<Self, CodecError> {
130 <[u8; std::mem::size_of::<BlockNumber>()]>::decode(value)
131 .map(BlockNumber::from_be_bytes)
132 .map(Self)
133 }
134}
135
136#[derive(Debug, Encode, Decode)]
137enum State {
138 #[codec(index = 0)]
140 Unavailable(BETimestamp),
141 #[codec(index = 1)]
147 Unfinalized(BETimestamp, Vec<(BEBlockNumber, Hash)>),
148 #[codec(index = 2)]
150 Finalized(BETimestamp),
151}
152
153#[derive(Debug, Encode, Decode)]
155struct CandidateMeta {
156 state: State,
157 data_available: bool,
158 chunks_stored: BitVec<u8, BitOrderLsb0>,
159}
160
161fn query_inner<D: Decode>(
162 db: &Arc<dyn Database>,
163 column: u32,
164 key: &[u8],
165) -> Result<Option<D>, Error> {
166 match db.get(column, key) {
167 Ok(Some(raw)) => {
168 let res = D::decode(&mut &raw[..])?;
169 Ok(Some(res))
170 },
171 Ok(None) => Ok(None),
172 Err(err) => {
173 gum::warn!(target: LOG_TARGET, ?err, "Error reading from the availability store");
174 Err(err.into())
175 },
176 }
177}
178
179fn write_available_data(
180 tx: &mut DBTransaction,
181 config: &Config,
182 hash: &CandidateHash,
183 available_data: &AvailableData,
184) {
185 let key = (AVAILABLE_PREFIX, hash).encode();
186
187 tx.put_vec(config.col_data, &key[..], available_data.encode());
188}
189
190fn load_available_data(
191 db: &Arc<dyn Database>,
192 config: &Config,
193 hash: &CandidateHash,
194) -> Result<Option<AvailableData>, Error> {
195 let key = (AVAILABLE_PREFIX, hash).encode();
196
197 query_inner(db, config.col_data, &key)
198}
199
200fn delete_available_data(tx: &mut DBTransaction, config: &Config, hash: &CandidateHash) {
201 let key = (AVAILABLE_PREFIX, hash).encode();
202
203 tx.delete(config.col_data, &key[..])
204}
205
206fn load_chunk(
207 db: &Arc<dyn Database>,
208 config: &Config,
209 candidate_hash: &CandidateHash,
210 validator_index: ValidatorIndex,
211) -> Result<Option<ErasureChunk>, Error> {
212 let key = (CHUNK_PREFIX, candidate_hash, validator_index).encode();
213
214 query_inner(db, config.col_data, &key)
215}
216
217fn write_chunk(
218 tx: &mut DBTransaction,
219 config: &Config,
220 candidate_hash: &CandidateHash,
221 validator_index: ValidatorIndex,
222 erasure_chunk: &ErasureChunk,
223) {
224 let key = (CHUNK_PREFIX, candidate_hash, validator_index).encode();
225
226 tx.put_vec(config.col_data, &key, erasure_chunk.encode());
227}
228
229fn delete_chunk(
230 tx: &mut DBTransaction,
231 config: &Config,
232 candidate_hash: &CandidateHash,
233 validator_index: ValidatorIndex,
234) {
235 let key = (CHUNK_PREFIX, candidate_hash, validator_index).encode();
236
237 tx.delete(config.col_data, &key[..]);
238}
239
240fn load_meta(
241 db: &Arc<dyn Database>,
242 config: &Config,
243 hash: &CandidateHash,
244) -> Result<Option<CandidateMeta>, Error> {
245 let key = (META_PREFIX, hash).encode();
246
247 query_inner(db, config.col_meta, &key)
248}
249
250fn write_meta(tx: &mut DBTransaction, config: &Config, hash: &CandidateHash, meta: &CandidateMeta) {
251 let key = (META_PREFIX, hash).encode();
252
253 tx.put_vec(config.col_meta, &key, meta.encode());
254}
255
256fn delete_meta(tx: &mut DBTransaction, config: &Config, hash: &CandidateHash) {
257 let key = (META_PREFIX, hash).encode();
258 tx.delete(config.col_meta, &key[..])
259}
260
261fn delete_unfinalized_height(tx: &mut DBTransaction, config: &Config, block_number: BlockNumber) {
262 let prefix = (UNFINALIZED_PREFIX, BEBlockNumber(block_number)).encode();
263 tx.delete_prefix(config.col_meta, &prefix);
264}
265
266fn delete_unfinalized_inclusion(
267 tx: &mut DBTransaction,
268 config: &Config,
269 block_number: BlockNumber,
270 block_hash: &Hash,
271 candidate_hash: &CandidateHash,
272) {
273 let key =
274 (UNFINALIZED_PREFIX, BEBlockNumber(block_number), block_hash, candidate_hash).encode();
275
276 tx.delete(config.col_meta, &key[..]);
277}
278
279fn delete_pruning_key(
280 tx: &mut DBTransaction,
281 config: &Config,
282 t: impl Into<BETimestamp>,
283 h: &CandidateHash,
284) {
285 let key = (PRUNE_BY_TIME_PREFIX, t.into(), h).encode();
286 tx.delete(config.col_meta, &key);
287}
288
289fn write_pruning_key(
290 tx: &mut DBTransaction,
291 config: &Config,
292 t: impl Into<BETimestamp>,
293 h: &CandidateHash,
294) {
295 let t = t.into();
296 let key = (PRUNE_BY_TIME_PREFIX, t, h).encode();
297 tx.put(config.col_meta, &key, TOMBSTONE_VALUE);
298}
299
300fn finalized_block_range(finalized: BlockNumber) -> (Vec<u8>, Vec<u8>) {
301 let start = UNFINALIZED_PREFIX.encode();
303 let end = (UNFINALIZED_PREFIX, BEBlockNumber(finalized + 1)).encode();
304
305 (start, end)
306}
307
308fn write_unfinalized_block_contains(
309 tx: &mut DBTransaction,
310 config: &Config,
311 n: BlockNumber,
312 h: &Hash,
313 ch: &CandidateHash,
314) {
315 let key = (UNFINALIZED_PREFIX, BEBlockNumber(n), h, ch).encode();
316 tx.put(config.col_meta, &key, TOMBSTONE_VALUE);
317}
318
319fn pruning_range(now: impl Into<BETimestamp>) -> (Vec<u8>, Vec<u8>) {
320 let start = PRUNE_BY_TIME_PREFIX.encode();
321 let end = (PRUNE_BY_TIME_PREFIX, BETimestamp(now.into().0 + 1)).encode();
322
323 (start, end)
324}
325
326fn decode_unfinalized_key(s: &[u8]) -> Result<(BlockNumber, Hash, CandidateHash), CodecError> {
327 if !s.starts_with(UNFINALIZED_PREFIX) {
328 return Err("missing magic string".into());
329 }
330
331 <(BEBlockNumber, Hash, CandidateHash)>::decode(&mut &s[UNFINALIZED_PREFIX.len()..])
332 .map(|(b, h, ch)| (b.0, h, ch))
333}
334
335fn decode_pruning_key(s: &[u8]) -> Result<(Duration, CandidateHash), CodecError> {
336 if !s.starts_with(PRUNE_BY_TIME_PREFIX) {
337 return Err("missing magic string".into());
338 }
339
340 <(BETimestamp, CandidateHash)>::decode(&mut &s[PRUNE_BY_TIME_PREFIX.len()..])
341 .map(|(t, ch)| (t.into(), ch))
342}
343
344#[derive(Debug, thiserror::Error)]
345#[allow(missing_docs)]
346pub enum Error {
347 #[error(transparent)]
348 RuntimeApi(#[from] RuntimeApiError),
349
350 #[error(transparent)]
351 ChainApi(#[from] ChainApiError),
352
353 #[error(transparent)]
354 Erasure(#[from] polkadot_erasure_coding::Error),
355
356 #[error(transparent)]
357 Io(#[from] io::Error),
358
359 #[error(transparent)]
360 Oneshot(#[from] oneshot::Canceled),
361
362 #[error(transparent)]
363 Subsystem(#[from] SubsystemError),
364
365 #[error("Context signal channel closed")]
366 ContextChannelClosed,
367
368 #[error(transparent)]
369 Codec(#[from] CodecError),
370
371 #[error("Custom databases are not supported")]
372 CustomDatabase,
373
374 #[error("Erasure root does not match expected one")]
375 InvalidErasureRoot,
376}
377
378impl Error {
379 fn is_fatal(&self) -> bool {
383 match self {
384 Self::Io(_) => true,
385 Self::Oneshot(_) => true,
386 Self::CustomDatabase => true,
387 Self::ContextChannelClosed => true,
388 _ => false,
389 }
390 }
391}
392
393impl Error {
394 fn trace(&self) {
395 match self {
396 Self::RuntimeApi(_) | Self::Oneshot(_) => {
398 gum::debug!(target: LOG_TARGET, err = ?self)
399 },
400 _ => gum::warn!(target: LOG_TARGET, err = ?self),
402 }
403 }
404}
405
406#[derive(Clone)]
410struct PruningConfig {
411 keep_unavailable_for: Duration,
413
414 keep_finalized_for: Duration,
416
417 pruning_interval: Duration,
419}
420
421#[derive(Debug, Clone, Copy)]
423pub struct Config {
424 pub col_data: u32,
426 pub col_meta: u32,
428 pub keep_finalized_for: u32,
430}
431
432pub struct AvailabilityStoreSubsystem {
434 pruning_config: PruningConfig,
435 config: Config,
436 db: Arc<dyn Database>,
437 known_blocks: KnownUnfinalizedBlocks,
438 finalized_number: Option<BlockNumber>,
439 metrics: Metrics,
440 clock: Arc<dyn Clock>,
441 sync_oracle: Box<dyn SyncOracle + Send + Sync>,
442}
443
444impl AvailabilityStoreSubsystem {
445 pub fn new(
447 db: Arc<dyn Database>,
448 config: Config,
449 sync_oracle: Box<dyn SyncOracle + Send + Sync>,
450 metrics: Metrics,
451 ) -> Self {
452 let pruning_config = PruningConfig {
453 keep_unavailable_for: KEEP_UNAVAILABLE_FOR,
454 keep_finalized_for: Duration::from_secs(config.keep_finalized_for as u64 * 3600),
455 pruning_interval: PRUNING_INTERVAL,
456 };
457
458 Self::with_pruning_config_and_clock(
459 db,
460 config,
461 pruning_config,
462 polkadot_node_clock::system_clock(),
463 sync_oracle,
464 metrics,
465 )
466 }
467
468 fn with_pruning_config_and_clock(
470 db: Arc<dyn Database>,
471 config: Config,
472 pruning_config: PruningConfig,
473 clock: Arc<dyn Clock>,
474 sync_oracle: Box<dyn SyncOracle + Send + Sync>,
475 metrics: Metrics,
476 ) -> Self {
477 Self {
478 pruning_config,
479 config,
480 db,
481 metrics,
482 clock,
483 known_blocks: KnownUnfinalizedBlocks::default(),
484 sync_oracle,
485 finalized_number: None,
486 }
487 }
488}
489
490#[derive(Default, Debug)]
493struct KnownUnfinalizedBlocks {
494 by_hash: HashSet<Hash>,
495 by_number: BTreeSet<(BlockNumber, Hash)>,
496}
497
498impl KnownUnfinalizedBlocks {
499 fn is_known(&self, hash: &Hash) -> bool {
501 self.by_hash.contains(hash)
502 }
503
504 fn insert(&mut self, hash: Hash, number: BlockNumber) {
506 self.by_hash.insert(hash);
507 self.by_number.insert((number, hash));
508 }
509
510 fn prune_finalized(&mut self, finalized: BlockNumber) {
512 let split_point = finalized.saturating_add(1);
514 let mut finalized = self.by_number.split_off(&(split_point, Hash::zero()));
515 std::mem::swap(&mut self.by_number, &mut finalized);
517 for (_, block) in finalized {
518 self.by_hash.remove(&block);
519 }
520 }
521}
522
523#[overseer::subsystem(AvailabilityStore, error=SubsystemError, prefix=self::overseer)]
524impl<Context> AvailabilityStoreSubsystem {
525 fn start(self, ctx: Context) -> SpawnedSubsystem {
526 let future = run::<Context>(self, ctx).map(|_| Ok(())).boxed();
527
528 SpawnedSubsystem { name: "availability-store-subsystem", future }
529 }
530}
531
532#[overseer::contextbounds(AvailabilityStore, prefix = self::overseer)]
533async fn run<Context>(mut subsystem: AvailabilityStoreSubsystem, mut ctx: Context) {
534 let mut next_pruning = Delay::new(subsystem.pruning_config.pruning_interval).fuse();
535 let (mut pruning_result_tx, mut pruning_result_rx) = channel(10);
538 loop {
539 let res = run_iteration(
540 &mut ctx,
541 &mut subsystem,
542 &mut next_pruning,
543 (&mut pruning_result_tx, &mut pruning_result_rx),
544 )
545 .await;
546 match res {
547 Err(e) => {
548 e.trace();
549 if e.is_fatal() {
550 break;
551 }
552 },
553 Ok(true) => {
554 gum::info!(target: LOG_TARGET, "received `Conclude` signal, exiting");
555 break;
556 },
557 Ok(false) => continue,
558 }
559 }
560}
561
562#[overseer::contextbounds(AvailabilityStore, prefix = self::overseer)]
563async fn run_iteration<Context>(
564 ctx: &mut Context,
565 subsystem: &mut AvailabilityStoreSubsystem,
566 mut next_pruning: &mut future::Fuse<Delay>,
567 (pruning_result_tx, pruning_result_rx): (
568 &mut MpscSender<Result<(), Error>>,
569 &mut MpscReceiver<Result<(), Error>>,
570 ),
571) -> Result<bool, Error> {
572 select! {
573 incoming = ctx.recv().fuse() => {
574 match incoming.map_err(|_| Error::ContextChannelClosed)? {
575 FromOrchestra::Signal(OverseerSignal::Conclude) => return Ok(true),
576 FromOrchestra::Signal(OverseerSignal::ActiveLeaves(
577 ActiveLeavesUpdate { activated, .. })
578 ) => {
579 for activated in activated.into_iter() {
580 let _timer = subsystem.metrics.time_block_activated();
581 process_block_activated(ctx, subsystem, activated.hash).await?;
582 }
583 }
584 FromOrchestra::Signal(OverseerSignal::BlockFinalized(hash, number)) => {
585 let _timer = subsystem.metrics.time_process_block_finalized();
586
587 if !subsystem.known_blocks.is_known(&hash) {
588 if !subsystem.sync_oracle.is_major_syncing() {
594 process_block_activated(ctx, subsystem, hash).await?;
598 }
599 }
600 subsystem.finalized_number = Some(number);
601 subsystem.known_blocks.prune_finalized(number);
602 process_block_finalized(
603 ctx,
604 &subsystem,
605 hash,
606 number,
607 ).await?;
608 }
609 FromOrchestra::Communication { msg } => {
610 let _timer = subsystem.metrics.time_process_message();
611 process_message(subsystem, msg)?;
612 }
613 }
614 }
615 _ = next_pruning => {
616 *next_pruning = Delay::new(subsystem.pruning_config.pruning_interval).fuse();
619 start_prune_all(ctx, subsystem, pruning_result_tx.clone()).await?;
620 },
621 result = pruning_result_rx.next() => {
624 if let Some(result) = result {
625 result?;
626 }
627 },
628 }
629
630 Ok(false)
631}
632
633#[overseer::contextbounds(AvailabilityStore, prefix = self::overseer)]
637async fn start_prune_all<Context>(
638 ctx: &mut Context,
639 subsystem: &mut AvailabilityStoreSubsystem,
640 mut pruning_result_tx: MpscSender<Result<(), Error>>,
641) -> Result<(), Error> {
642 let metrics = subsystem.metrics.clone();
643 let db = subsystem.db.clone();
644 let config = subsystem.config;
645 let time_now = subsystem.clock.duration_since_epoch();
646
647 ctx.spawn_blocking(
648 "av-store-prunning",
649 Box::pin(async move {
650 let _timer = metrics.time_pruning();
651
652 gum::debug!(target: LOG_TARGET, "Prunning started");
653 let result = prune_all(&db, &config, time_now);
654
655 if let Err(err) = pruning_result_tx.send(result).await {
656 gum::debug!(target: LOG_TARGET, ?err, "Failed to send prune_all result",);
658 }
659 }),
660 )?;
661 Ok(())
662}
663
664#[overseer::contextbounds(AvailabilityStore, prefix = self::overseer)]
665async fn process_block_activated<Context>(
666 ctx: &mut Context,
667 subsystem: &mut AvailabilityStoreSubsystem,
668 activated: Hash,
669) -> Result<(), Error> {
670 let now = subsystem.clock.duration_since_epoch();
671
672 let block_header = {
673 let (tx, rx) = oneshot::channel();
674
675 ctx.send_message(ChainApiMessage::BlockHeader(activated, tx)).await;
676
677 match rx.await?? {
678 None => return Ok(()),
679 Some(n) => n,
680 }
681 };
682 let block_number = block_header.number;
683
684 let new_blocks = util::determine_new_blocks(
685 ctx.sender(),
686 |hash| -> Result<bool, Error> { Ok(subsystem.known_blocks.is_known(hash)) },
687 activated,
688 &block_header,
689 subsystem.finalized_number.unwrap_or(block_number.saturating_sub(1)),
690 )
691 .await?;
692
693 for (hash, header) in new_blocks.into_iter().rev() {
695 let mut tx = DBTransaction::new();
698 process_new_head(
699 ctx,
700 &subsystem.db,
701 &mut tx,
702 &subsystem.config,
703 &subsystem.pruning_config,
704 now,
705 hash,
706 header,
707 )
708 .await?;
709 subsystem.known_blocks.insert(hash, block_number);
710 subsystem.db.write(tx)?;
711 }
712
713 Ok(())
714}
715
716#[overseer::contextbounds(AvailabilityStore, prefix = self::overseer)]
717async fn process_new_head<Context>(
718 ctx: &mut Context,
719 db: &Arc<dyn Database>,
720 db_transaction: &mut DBTransaction,
721 config: &Config,
722 pruning_config: &PruningConfig,
723 now: Duration,
724 hash: Hash,
725 header: Header,
726) -> Result<(), Error> {
727 let candidate_events = util::request_candidate_events(hash, ctx.sender()).await.await??;
728
729 let n_validators =
732 util::request_validators(header.parent_hash, ctx.sender()).await.await??.len();
733
734 for event in candidate_events {
735 match event {
736 CandidateEvent::CandidateBacked(receipt, _head, _core_index, _group_index) => {
737 note_block_backed(
738 db,
739 db_transaction,
740 config,
741 pruning_config,
742 now,
743 n_validators,
744 receipt,
745 )?;
746 },
747 CandidateEvent::CandidateIncluded(receipt, _head, _core_index, _group_index) => {
748 note_block_included(
749 db,
750 db_transaction,
751 config,
752 pruning_config,
753 (header.number, hash),
754 receipt,
755 )?;
756 },
757 _ => {},
758 }
759 }
760
761 Ok(())
762}
763
764fn note_block_backed(
765 db: &Arc<dyn Database>,
766 db_transaction: &mut DBTransaction,
767 config: &Config,
768 pruning_config: &PruningConfig,
769 now: Duration,
770 n_validators: usize,
771 candidate: CandidateReceipt,
772) -> Result<(), Error> {
773 let candidate_hash = candidate.hash();
774
775 gum::debug!(target: LOG_TARGET, ?candidate_hash, "Candidate backed");
776
777 if load_meta(db, config, &candidate_hash)?.is_none() {
778 let meta = CandidateMeta {
779 state: State::Unavailable(now.into()),
780 data_available: false,
781 chunks_stored: bitvec::bitvec![u8, BitOrderLsb0; 0; n_validators],
782 };
783
784 let prune_at = now + pruning_config.keep_unavailable_for;
785
786 write_pruning_key(db_transaction, config, prune_at, &candidate_hash);
787 write_meta(db_transaction, config, &candidate_hash, &meta);
788 }
789
790 Ok(())
791}
792
793fn note_block_included(
794 db: &Arc<dyn Database>,
795 db_transaction: &mut DBTransaction,
796 config: &Config,
797 pruning_config: &PruningConfig,
798 block: (BlockNumber, Hash),
799 candidate: CandidateReceipt,
800) -> Result<(), Error> {
801 let candidate_hash = candidate.hash();
802
803 match load_meta(db, config, &candidate_hash)? {
804 None => {
805 gum::warn!(
808 target: LOG_TARGET,
809 ?candidate_hash,
810 "Candidate included without being backed?",
811 );
812 },
813 Some(mut meta) => {
814 let be_block = (BEBlockNumber(block.0), block.1);
815
816 gum::debug!(target: LOG_TARGET, ?candidate_hash, "Candidate included");
817
818 meta.state = match meta.state {
819 State::Unavailable(at) => {
820 let at_d: Duration = at.into();
821 let prune_at = at_d + pruning_config.keep_unavailable_for;
822 delete_pruning_key(db_transaction, config, prune_at, &candidate_hash);
823
824 State::Unfinalized(at, vec![be_block])
825 },
826 State::Unfinalized(at, mut within) => {
827 if let Err(i) = within.binary_search(&be_block) {
828 within.insert(i, be_block);
829 State::Unfinalized(at, within)
830 } else {
831 return Ok(());
832 }
833 },
834 State::Finalized(_at) => {
835 return Ok(());
838 },
839 };
840
841 write_unfinalized_block_contains(
842 db_transaction,
843 config,
844 block.0,
845 &block.1,
846 &candidate_hash,
847 );
848 write_meta(db_transaction, config, &candidate_hash, &meta);
849 },
850 }
851
852 Ok(())
853}
854
855macro_rules! peek_num {
856 ($iter:ident) => {
857 match $iter.peek() {
858 Some(Ok((k, _))) => Ok(decode_unfinalized_key(&k[..]).ok().map(|(b, _, _)| b)),
859 Some(Err(_)) => Err($iter.next().expect("peek returned Some(Err); qed").unwrap_err()),
860 None => Ok(None),
861 }
862 };
863}
864
865#[overseer::contextbounds(AvailabilityStore, prefix = self::overseer)]
866async fn process_block_finalized<Context>(
867 ctx: &mut Context,
868 subsystem: &AvailabilityStoreSubsystem,
869 finalized_hash: Hash,
870 finalized_number: BlockNumber,
871) -> Result<(), Error> {
872 let now = subsystem.clock.duration_since_epoch();
873
874 let mut next_possible_batch = 0;
875 loop {
876 let mut db_transaction = DBTransaction::new();
877 let (start_prefix, end_prefix) = finalized_block_range(finalized_number);
878
879 let batch_num = {
883 let mut iter = subsystem
884 .db
885 .iter_with_prefix(subsystem.config.col_meta, &start_prefix)
886 .take_while(|r| r.as_ref().map_or(true, |(k, _v)| &k[..] < &end_prefix[..]))
887 .peekable();
888
889 match peek_num!(iter)? {
890 None => break, Some(n) => n,
892 }
893 };
894
895 if batch_num < next_possible_batch {
896 continue;
897 } next_possible_batch = batch_num + 1;
899
900 let batch_finalized_hash = if batch_num == finalized_number {
901 finalized_hash
902 } else {
903 let (tx, rx) = oneshot::channel();
904 ctx.send_message(ChainApiMessage::FinalizedBlockHash(batch_num, tx)).await;
905
906 match rx.await? {
907 Err(err) => {
908 gum::warn!(
909 target: LOG_TARGET,
910 batch_num,
911 ?err,
912 "Failed to retrieve finalized block number.",
913 );
914
915 break;
916 },
917 Ok(None) => {
918 gum::warn!(
919 target: LOG_TARGET,
920 "Availability store was informed that block #{} is finalized, \
921 but chain API has no finalized hash.",
922 batch_num,
923 );
924
925 break;
926 },
927 Ok(Some(h)) => h,
928 }
929 };
930
931 let iter = subsystem
932 .db
933 .iter_with_prefix(subsystem.config.col_meta, &start_prefix)
934 .take_while(|r| r.as_ref().map_or(true, |(k, _v)| &k[..] < &end_prefix[..]))
935 .peekable();
936
937 let batch = load_all_at_finalized_height(iter, batch_num, batch_finalized_hash)?;
938
939 delete_unfinalized_height(&mut db_transaction, &subsystem.config, batch_num);
943
944 update_blocks_at_finalized_height(&subsystem, &mut db_transaction, batch, batch_num, now)?;
945
946 subsystem.db.write(db_transaction)?;
950 }
951
952 Ok(())
953}
954
955fn load_all_at_finalized_height(
958 mut iter: std::iter::Peekable<impl Iterator<Item = io::Result<util::database::DBKeyValue>>>,
959 block_number: BlockNumber,
960 finalized_hash: Hash,
961) -> io::Result<impl IntoIterator<Item = (CandidateHash, bool)>> {
962 let mut candidates = HashMap::new();
964
965 loop {
967 match peek_num!(iter)? {
968 None => break, Some(n) if n != block_number => break, _ => {},
971 }
972
973 let (k, _v) = iter.next().expect("`peek` used to check non-empty; qed")?;
974 let (_, block_hash, candidate_hash) =
975 decode_unfinalized_key(&k[..]).expect("`peek_num` checks validity of key; qed");
976
977 if block_hash == finalized_hash {
978 candidates.insert(candidate_hash, true);
979 } else {
980 candidates.entry(candidate_hash).or_insert(false);
981 }
982 }
983
984 Ok(candidates)
985}
986
987fn update_blocks_at_finalized_height(
988 subsystem: &AvailabilityStoreSubsystem,
989 db_transaction: &mut DBTransaction,
990 candidates: impl IntoIterator<Item = (CandidateHash, bool)>,
991 block_number: BlockNumber,
992 now: Duration,
993) -> Result<(), Error> {
994 for (candidate_hash, is_finalized) in candidates {
995 let mut meta = match load_meta(&subsystem.db, &subsystem.config, &candidate_hash)? {
996 None => {
997 gum::warn!(
998 target: LOG_TARGET,
999 "Dangling candidate metadata for {}",
1000 candidate_hash,
1001 );
1002
1003 continue;
1004 },
1005 Some(c) => c,
1006 };
1007
1008 if is_finalized {
1009 match meta.state {
1011 State::Finalized(_) => continue, State::Unavailable(at) => {
1013 delete_pruning_key(db_transaction, &subsystem.config, at, &candidate_hash);
1017 },
1018 State::Unfinalized(_, blocks) => {
1019 for (block_num, block_hash) in blocks.iter().cloned() {
1020 if block_num.0 != block_number {
1022 delete_unfinalized_inclusion(
1023 db_transaction,
1024 &subsystem.config,
1025 block_num.0,
1026 &block_hash,
1027 &candidate_hash,
1028 );
1029 }
1030 }
1031 },
1032 }
1033
1034 meta.state = State::Finalized(now.into());
1035
1036 write_meta(db_transaction, &subsystem.config, &candidate_hash, &meta);
1038 write_pruning_key(
1039 db_transaction,
1040 &subsystem.config,
1041 now + subsystem.pruning_config.keep_finalized_for,
1042 &candidate_hash,
1043 );
1044 } else {
1045 meta.state = match meta.state {
1046 State::Finalized(_) => continue, State::Unavailable(_) => continue, State::Unfinalized(at, mut blocks) => {
1049 blocks.retain(|(n, _)| n.0 != block_number);
1051
1052 if blocks.is_empty() {
1055 let at_d: Duration = at.into();
1056 let prune_at = at_d + subsystem.pruning_config.keep_unavailable_for;
1057 write_pruning_key(
1058 db_transaction,
1059 &subsystem.config,
1060 prune_at,
1061 &candidate_hash,
1062 );
1063 State::Unavailable(at)
1064 } else {
1065 State::Unfinalized(at, blocks)
1066 }
1067 },
1068 };
1069
1070 write_meta(db_transaction, &subsystem.config, &candidate_hash, &meta)
1072 }
1073 }
1074
1075 Ok(())
1076}
1077
1078fn process_message(
1079 subsystem: &mut AvailabilityStoreSubsystem,
1080 msg: AvailabilityStoreMessage,
1081) -> Result<(), Error> {
1082 match msg {
1083 AvailabilityStoreMessage::QueryAvailableData(candidate, tx) => {
1084 let _ = tx.send(load_available_data(&subsystem.db, &subsystem.config, &candidate)?);
1085 },
1086 AvailabilityStoreMessage::QueryDataAvailability(candidate, tx) => {
1087 let a = load_meta(&subsystem.db, &subsystem.config, &candidate)?
1088 .map_or(false, |m| m.data_available);
1089 let _ = tx.send(a);
1090 },
1091 AvailabilityStoreMessage::QueryChunk(candidate, validator_index, tx) => {
1092 let _timer = subsystem.metrics.time_get_chunk();
1093 let _ =
1094 tx.send(load_chunk(&subsystem.db, &subsystem.config, &candidate, validator_index)?);
1095 },
1096 AvailabilityStoreMessage::QueryChunkSize(candidate, tx) => {
1097 let meta = load_meta(&subsystem.db, &subsystem.config, &candidate)?;
1098
1099 let validator_index = meta.map_or(None, |meta| meta.chunks_stored.first_one());
1100
1101 let maybe_chunk_size = if let Some(validator_index) = validator_index {
1102 load_chunk(
1103 &subsystem.db,
1104 &subsystem.config,
1105 &candidate,
1106 ValidatorIndex(validator_index as u32),
1107 )?
1108 .map(|erasure_chunk| erasure_chunk.chunk.len())
1109 } else {
1110 None
1111 };
1112
1113 let _ = tx.send(maybe_chunk_size);
1114 },
1115 AvailabilityStoreMessage::QueryAllChunks(candidate, tx) => {
1116 match load_meta(&subsystem.db, &subsystem.config, &candidate)? {
1117 None => {
1118 let _ = tx.send(Vec::new());
1119 },
1120 Some(meta) => {
1121 let mut chunks = Vec::new();
1122
1123 for (validator_index, _) in
1124 meta.chunks_stored.iter().enumerate().filter(|(_, b)| **b)
1125 {
1126 let validator_index = ValidatorIndex(validator_index as _);
1127 let _timer = subsystem.metrics.time_get_chunk();
1128 match load_chunk(
1129 &subsystem.db,
1130 &subsystem.config,
1131 &candidate,
1132 validator_index,
1133 )? {
1134 Some(c) => chunks.push((validator_index, c)),
1135 None => {
1136 gum::warn!(
1137 target: LOG_TARGET,
1138 ?candidate,
1139 ?validator_index,
1140 "No chunk found for set bit in meta"
1141 );
1142 },
1143 }
1144 }
1145
1146 let _ = tx.send(chunks);
1147 },
1148 }
1149 },
1150 AvailabilityStoreMessage::QueryChunkAvailability(candidate, validator_index, tx) => {
1151 let a = load_meta(&subsystem.db, &subsystem.config, &candidate)?.map_or(false, |m| {
1152 *m.chunks_stored.get(validator_index.0 as usize).as_deref().unwrap_or(&false)
1153 });
1154 let _ = tx.send(a);
1155 },
1156 AvailabilityStoreMessage::StoreChunk { candidate_hash, validator_index, chunk, tx } => {
1157 subsystem.metrics.on_chunks_received(1);
1158 let _timer = subsystem.metrics.time_store_chunk();
1159
1160 match store_chunk(
1161 &subsystem.db,
1162 &subsystem.config,
1163 candidate_hash,
1164 validator_index,
1165 chunk,
1166 ) {
1167 Ok(true) => {
1168 let _ = tx.send(Ok(()));
1169 },
1170 Ok(false) => {
1171 let _ = tx.send(Err(()));
1172 },
1173 Err(e) => {
1174 let _ = tx.send(Err(()));
1175 return Err(e);
1176 },
1177 }
1178 },
1179 AvailabilityStoreMessage::StoreAvailableData {
1180 candidate_hash,
1181 n_validators,
1182 available_data,
1183 expected_erasure_root,
1184 core_index,
1185 node_features,
1186 tx,
1187 } => {
1188 subsystem.metrics.on_chunks_received(n_validators as _);
1189
1190 let _timer = subsystem.metrics.time_store_available_data();
1191
1192 let res = store_available_data(
1193 &subsystem,
1194 candidate_hash,
1195 n_validators as _,
1196 available_data,
1197 expected_erasure_root,
1198 core_index,
1199 node_features,
1200 );
1201
1202 match res {
1203 Ok(()) => {
1204 let _ = tx.send(Ok(()));
1205 },
1206 Err(Error::InvalidErasureRoot) => {
1207 let _ = tx.send(Err(StoreAvailableDataError::InvalidErasureRoot));
1208 return Err(Error::InvalidErasureRoot);
1209 },
1210 Err(e) => {
1211 return Err(e.into());
1217 },
1218 }
1219 },
1220 }
1221
1222 Ok(())
1223}
1224
1225fn store_chunk(
1227 db: &Arc<dyn Database>,
1228 config: &Config,
1229 candidate_hash: CandidateHash,
1230 validator_index: ValidatorIndex,
1231 chunk: ErasureChunk,
1232) -> Result<bool, Error> {
1233 let mut tx = DBTransaction::new();
1234
1235 let mut meta = match load_meta(db, config, &candidate_hash)? {
1236 Some(m) => m,
1237 None => return Ok(false), };
1239
1240 match meta.chunks_stored.get(validator_index.0 as usize).map(|b| *b) {
1241 Some(true) => return Ok(true), Some(false) => {
1243 meta.chunks_stored.set(validator_index.0 as usize, true);
1244
1245 write_chunk(&mut tx, config, &candidate_hash, validator_index, &chunk);
1246 write_meta(&mut tx, config, &candidate_hash, &meta);
1247 },
1248 None => return Ok(false), }
1250
1251 gum::debug!(
1252 target: LOG_TARGET,
1253 ?candidate_hash,
1254 chunk_index = %chunk.index.0,
1255 validator_index = %validator_index.0,
1256 "Stored chunk index for candidate.",
1257 );
1258
1259 db.write(tx)?;
1260 Ok(true)
1261}
1262
1263fn store_available_data(
1264 subsystem: &AvailabilityStoreSubsystem,
1265 candidate_hash: CandidateHash,
1266 n_validators: usize,
1267 available_data: AvailableData,
1268 expected_erasure_root: Hash,
1269 core_index: CoreIndex,
1270 node_features: NodeFeatures,
1271) -> Result<(), Error> {
1272 let mut tx = DBTransaction::new();
1273
1274 let mut meta = match load_meta(&subsystem.db, &subsystem.config, &candidate_hash)? {
1275 Some(m) => {
1276 if m.data_available {
1277 return Ok(()); }
1279
1280 m
1281 },
1282 None => {
1283 let now = subsystem.clock.duration_since_epoch();
1284
1285 let prune_at = now + subsystem.pruning_config.keep_unavailable_for;
1287 write_pruning_key(&mut tx, &subsystem.config, prune_at, &candidate_hash);
1288
1289 CandidateMeta {
1290 state: State::Unavailable(now.into()),
1291 data_available: false,
1292 chunks_stored: BitVec::new(),
1293 }
1294 },
1295 };
1296
1297 let chunks = polkadot_erasure_coding::obtain_chunks_v1(n_validators, &available_data)?;
1300 let branches = polkadot_erasure_coding::branches(chunks.as_ref());
1301
1302 if branches.root() != expected_erasure_root {
1303 return Err(Error::InvalidErasureRoot);
1304 }
1305
1306 let erasure_chunks: Vec<_> = chunks
1307 .iter()
1308 .zip(branches.map(|(proof, _)| proof))
1309 .enumerate()
1310 .map(|(index, (chunk, proof))| ErasureChunk {
1311 chunk: chunk.clone(),
1312 proof,
1313 index: ChunkIndex(index as u32),
1314 })
1315 .collect();
1316
1317 let chunk_indices = availability_chunk_indices(&node_features, n_validators, core_index)?;
1318 for (validator_index, chunk_index) in chunk_indices.into_iter().enumerate() {
1319 write_chunk(
1320 &mut tx,
1321 &subsystem.config,
1322 &candidate_hash,
1323 ValidatorIndex(validator_index as u32),
1324 &erasure_chunks[chunk_index.0 as usize],
1325 );
1326 }
1327
1328 meta.data_available = true;
1329 meta.chunks_stored = bitvec::bitvec![u8, BitOrderLsb0; 1; n_validators];
1330
1331 write_meta(&mut tx, &subsystem.config, &candidate_hash, &meta);
1332 write_available_data(&mut tx, &subsystem.config, &candidate_hash, &available_data);
1333
1334 subsystem.db.write(tx)?;
1335
1336 gum::debug!(target: LOG_TARGET, ?candidate_hash, "Stored data and chunks");
1337
1338 Ok(())
1339}
1340
1341fn prune_all(db: &Arc<dyn Database>, config: &Config, now: Duration) -> Result<(), Error> {
1342 let (range_start, range_end) = pruning_range(now);
1343
1344 let mut tx = DBTransaction::new();
1345 let iter = db
1346 .iter_with_prefix(config.col_meta, &range_start[..])
1347 .take_while(|r| r.as_ref().map_or(true, |(k, _v)| &k[..] < &range_end[..]));
1348
1349 for r in iter {
1350 let (k, _v) = r?;
1351 tx.delete(config.col_meta, &k[..]);
1352
1353 let (_, candidate_hash) = match decode_pruning_key(&k[..]) {
1354 Ok(m) => m,
1355 Err(_) => continue, };
1357
1358 delete_meta(&mut tx, config, &candidate_hash);
1359
1360 if let Some(meta) = load_meta(db, config, &candidate_hash)? {
1362 if meta.data_available {
1364 delete_available_data(&mut tx, config, &candidate_hash)
1365 }
1366
1367 for (i, b) in meta.chunks_stored.iter().enumerate() {
1369 if *b {
1370 delete_chunk(&mut tx, config, &candidate_hash, ValidatorIndex(i as _));
1371 }
1372 }
1373
1374 if let State::Unfinalized(_, blocks) = meta.state {
1377 for (block_number, block_hash) in blocks {
1378 delete_unfinalized_inclusion(
1379 &mut tx,
1380 config,
1381 block_number.0,
1382 &block_hash,
1383 &candidate_hash,
1384 );
1385 }
1386 }
1387 }
1388 }
1389
1390 db.write(tx)?;
1391 Ok(())
1392}