referrerpolicy=no-referrer-when-downgrade

polkadot_node_core_av_store/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Implements a `AvailabilityStoreSubsystem`.
18
19#![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
64/// The following constants are used under normal conditions:
65
66const 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
72// We have some keys we want to map to empty values because existence of the key is enough. We use
73// this because rocksdb doesn't support empty values.
74const TOMBSTONE_VALUE: &[u8] = b" ";
75
76/// Unavailable blocks are kept for 1 hour.
77const KEEP_UNAVAILABLE_FOR: Duration = Duration::from_secs(60 * 60);
78
79/// The pruning interval.
80const PRUNING_INTERVAL: Duration = Duration::from_secs(60 * 5);
81
82/// Unix time wrapper with big-endian encoding.
83#[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/// [`BlockNumber`] wrapper with big-endian encoding.
115#[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	/// Candidate data was first observed at the given time but is not available in any block.
139	#[codec(index = 0)]
140	Unavailable(BETimestamp),
141	/// The candidate was first observed at the given time and was included in the given list of
142	/// unfinalized blocks, which may be empty. The timestamp here is not used for pruning. Either
143	/// one of these blocks will be finalized or the state will regress to `State::Unavailable`, in
144	/// which case the same timestamp will be reused. Blocks are sorted ascending first by block
145	/// number and then hash.
146	#[codec(index = 1)]
147	Unfinalized(BETimestamp, Vec<(BEBlockNumber, Hash)>),
148	/// Candidate data has appeared in a finalized block and did so at the given time.
149	#[codec(index = 2)]
150	Finalized(BETimestamp),
151}
152
153// Meta information about a candidate.
154#[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	// We use big-endian encoding to iterate in ascending order.
302	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	/// Determine if the error is irrecoverable
380	/// or notifying the user via means of logging
381	/// is sufficient.
382	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			// don't spam the log with spurious errors
397			Self::RuntimeApi(_) | Self::Oneshot(_) => {
398				gum::debug!(target: LOG_TARGET, err = ?self)
399			},
400			// it's worth reporting otherwise
401			_ => gum::warn!(target: LOG_TARGET, err = ?self),
402		}
403	}
404}
405
406/// Struct holding pruning timing configuration.
407/// The only purpose of this structure is to use different timing
408/// configurations in production and in testing.
409#[derive(Clone)]
410struct PruningConfig {
411	/// How long unavailable data should be kept.
412	keep_unavailable_for: Duration,
413
414	/// How long finalized data should be kept.
415	keep_finalized_for: Duration,
416
417	/// How often to perform data pruning.
418	pruning_interval: Duration,
419}
420
421/// Configuration for the availability store.
422#[derive(Debug, Clone, Copy)]
423pub struct Config {
424	/// The column family for availability data and chunks.
425	pub col_data: u32,
426	/// The column family for availability store meta information.
427	pub col_meta: u32,
428	/// How long finalized data should be kept (in hours).
429	pub keep_finalized_for: u32,
430}
431
432/// An implementation of the Availability Store subsystem.
433pub 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	/// Create a new `AvailabilityStoreSubsystem` with a given config on disk.
446	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	/// Create a new `AvailabilityStoreSubsystem` with a given config on disk.
469	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/// We keep the hashes and numbers of all unfinalized
491/// processed blocks in memory.
492#[derive(Default, Debug)]
493struct KnownUnfinalizedBlocks {
494	by_hash: HashSet<Hash>,
495	by_number: BTreeSet<(BlockNumber, Hash)>,
496}
497
498impl KnownUnfinalizedBlocks {
499	/// Check whether the block has been already processed.
500	fn is_known(&self, hash: &Hash) -> bool {
501		self.by_hash.contains(hash)
502	}
503
504	/// Insert a new block into the known set.
505	fn insert(&mut self, hash: Hash, number: BlockNumber) {
506		self.by_hash.insert(hash);
507		self.by_number.insert((number, hash));
508	}
509
510	/// Prune all finalized blocks.
511	fn prune_finalized(&mut self, finalized: BlockNumber) {
512		// split_off returns everything after the given key, including the key
513		let split_point = finalized.saturating_add(1);
514		let mut finalized = self.by_number.split_off(&(split_point, Hash::zero()));
515		// after split_off `finalized` actually contains unfinalized blocks, we need to swap
516		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	// Pruning interval is in the order of minutes so we shouldn't have more than one task running
536	// at one moment in time, so 10 should be more than enough.
537	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 we haven't processed this block yet,
589						// make sure we write the metadata about the
590						// candidates backed in this finalized block.
591						// Otherwise, we won't be able to store our chunk
592						// for these candidates.
593						if !subsystem.sync_oracle.is_major_syncing() {
594							// If we're major syncing, processing finalized
595							// blocks might take quite a very long time
596							// and make the subsystem unresponsive.
597							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			// It's important to set the delay before calling `prune_all` because an error in `prune_all`
617			// could lead to the delay not being set again. Then we would never prune anything anymore.
618			*next_pruning = Delay::new(subsystem.pruning_config.pruning_interval).fuse();
619			start_prune_all(ctx, subsystem, pruning_result_tx.clone()).await?;
620		},
621		// Received the prune result and propagate the errors, so that in case of a fatal error
622		// the main loop of the subsystem can exit graciously.
623		result = pruning_result_rx.next() => {
624			if let Some(result) = result {
625				result?;
626			}
627		},
628	}
629
630	Ok(false)
631}
632
633// Start prune-all on a separate thread, so that in the case when the operation takes
634// longer than expected we don't keep the whole subsystem blocked.
635// See: https://github.com/paritytech/polkadot/issues/7237 for more details.
636#[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				// This usually means that the node is closing down, log it just in case
657				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	// determine_new_blocks is descending in block height
694	for (hash, header) in new_blocks.into_iter().rev() {
695		// it's important to commit the db transactions for a head before the next one is processed
696		// alternatively, we could utilize the OverlayBackend from approval-voting
697		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	// We need to request the number of validators based on the parent state,
730	// as that is the number of validators used to create this block.
731	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			// This is alarming. We've observed a block being included without ever seeing it
806			// backed. Warn and ignore.
807			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					// This should never happen as a candidate would have to be included after
836					// finality.
837					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		// We have to do some juggling here of the `iter` to make sure it doesn't cross the `.await`
880		// boundary as it is not `Send`. That is why we create the iterator once within this loop,
881		// drop it, do an asynchronous request, and then instantiate the exact same iterator again.
882		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, // end of iterator.
891				Some(n) => n,
892			}
893		};
894
895		if batch_num < next_possible_batch {
896			continue;
897		} // sanity.
898		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		// Now that we've iterated over the entire batch at this finalized height,
940		// update the meta.
941
942		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		// We need to write at the end of the loop so the prefix iterator doesn't pick up the same
947		// values again in the next iteration. Another unfortunate effect of having to re-initialize
948		// the iterator.
949		subsystem.db.write(db_transaction)?;
950	}
951
952	Ok(())
953}
954
955// loads all candidates at the finalized height and maps them to `true` if finalized
956// and `false` if unfinalized.
957fn 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	// maps candidate hashes to true if finalized, false otherwise.
963	let mut candidates = HashMap::new();
964
965	// Load all candidates that were included at this height.
966	loop {
967		match peek_num!(iter)? {
968			None => break,                         // end of iterator.
969			Some(n) if n != block_number => break, // end of batch.
970			_ => {},
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			// Clear everything else related to this block. We're finalized now!
1010			match meta.state {
1011				State::Finalized(_) => continue, // sanity
1012				State::Unavailable(at) => {
1013					// This is also not going to happen; the very fact that we are
1014					// iterating over the candidate here indicates that `State` should
1015					// be `Unfinalized`.
1016					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						// this exact height is all getting cleared out anyway.
1021						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 the meta and a pruning record.
1037			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,   // sanity.
1047				State::Unavailable(_) => continue, // sanity.
1048				State::Unfinalized(at, mut blocks) => {
1049					// Clear out everything at this height.
1050					blocks.retain(|(n, _)| n.0 != block_number);
1051
1052					// If empty, we need to go back to being unavailable as we aren't
1053					// aware of any blocks this is included in.
1054					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			// Update the meta entry.
1071			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					// We do not bubble up internal errors to caller subsystems, instead the
1212					// tx channel is dropped and that error is caught by the caller subsystem.
1213					//
1214					// We bubble up the specific error here so `av-store` logs still tell what
1215					// happened.
1216					return Err(e.into());
1217				},
1218			}
1219		},
1220	}
1221
1222	Ok(())
1223}
1224
1225// Ok(true) on success, Ok(false) on failure, and Err on internal error.
1226fn 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), // we weren't informed of this candidate by import events.
1238	};
1239
1240	match meta.chunks_stored.get(validator_index.0 as usize).map(|b| *b) {
1241		Some(true) => return Ok(true), // already stored.
1242		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), // out of bounds.
1249	}
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(()); // already stored.
1278			}
1279
1280			m
1281		},
1282		None => {
1283			let now = subsystem.clock.duration_since_epoch();
1284
1285			// Write a pruning record.
1286			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	// Important note: This check below is critical for consensus and the `backing` subsystem relies
1298	// on it to ensure candidate validity.
1299	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, // sanity
1356		};
1357
1358		delete_meta(&mut tx, config, &candidate_hash);
1359
1360		// Clean up all attached data of the candidate.
1361		if let Some(meta) = load_meta(db, config, &candidate_hash)? {
1362			// delete available data.
1363			if meta.data_available {
1364				delete_available_data(&mut tx, config, &candidate_hash)
1365			}
1366
1367			// delete chunks.
1368			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			// delete unfinalized block references. Pruning references don't need to be
1375			// manually taken care of as we are deleting them as we go in the outer loop.
1376			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}