referrerpolicy=no-referrer-when-downgrade

polkadot_node_core_dispute_coordinator/
backend.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//! An abstraction over storage used by the chain selection subsystem.
18//!
19//! This provides both a [`Backend`] trait and an [`OverlayedBackend`]
20//! struct which allows in-memory changes to be applied on top of a
21//! [`Backend`], maintaining consistency between queries and temporary writes,
22//! before any commit to the underlying storage is made.
23
24use polkadot_primitives::{CandidateHash, SessionIndex};
25
26use std::collections::HashMap;
27
28use super::db::v1::{CandidateVotes, RecentDisputes};
29use crate::error::FatalResult;
30
31#[derive(Debug)]
32pub enum BackendWriteOp {
33	WriteEarliestSession(SessionIndex),
34	WriteRecentDisputes(RecentDisputes),
35	WriteCandidateVotes(SessionIndex, CandidateHash, CandidateVotes),
36	DeleteCandidateVotes(SessionIndex, CandidateHash),
37}
38
39/// An abstraction over backend storage for the logic of this subsystem.
40pub trait Backend {
41	/// Load the earliest session, if any.
42	fn load_earliest_session(&self) -> FatalResult<Option<SessionIndex>>;
43
44	/// Load the recent disputes, if any.
45	fn load_recent_disputes(&self) -> FatalResult<Option<RecentDisputes>>;
46
47	/// Load the candidate votes for the specific session-candidate pair, if any.
48	fn load_candidate_votes(
49		&self,
50		session: SessionIndex,
51		candidate_hash: &CandidateHash,
52	) -> FatalResult<Option<CandidateVotes>>;
53
54	/// Atomically writes the list of operations, with later operations taking precedence over
55	/// prior.
56	fn write<I>(&mut self, ops: I) -> FatalResult<()>
57	where
58		I: IntoIterator<Item = BackendWriteOp>;
59}
60
61/// An in-memory overlay for the backend.
62///
63/// This maintains read-only access to the underlying backend, but can be converted into a set of
64/// write operations which will, when written to the underlying backend, give the same view as the
65/// state of the overlay.
66pub struct OverlayedBackend<'a, B: 'a> {
67	inner: &'a B,
68
69	// `None` means unchanged.
70	earliest_session: Option<SessionIndex>,
71	// `None` means unchanged.
72	recent_disputes: Option<RecentDisputes>,
73	// `None` means deleted, missing means query inner.
74	candidate_votes: HashMap<(SessionIndex, CandidateHash), Option<CandidateVotes>>,
75}
76
77impl<'a, B: 'a + Backend> OverlayedBackend<'a, B> {
78	pub fn new(backend: &'a B) -> Self {
79		Self {
80			inner: backend,
81			earliest_session: None,
82			recent_disputes: None,
83			candidate_votes: HashMap::new(),
84		}
85	}
86
87	/// Returns true if the are no write operations to perform.
88	pub fn is_empty(&self) -> bool {
89		self.earliest_session.is_none() &&
90			self.recent_disputes.is_none() &&
91			self.candidate_votes.is_empty()
92	}
93
94	/// Load the earliest session, if any.
95	pub fn load_earliest_session(&self) -> FatalResult<Option<SessionIndex>> {
96		if let Some(val) = self.earliest_session {
97			return Ok(Some(val))
98		}
99
100		self.inner.load_earliest_session()
101	}
102
103	/// Load the recent disputes, if any.
104	pub fn load_recent_disputes(&self) -> FatalResult<Option<RecentDisputes>> {
105		if let Some(val) = &self.recent_disputes {
106			return Ok(Some(val.clone()))
107		}
108
109		self.inner.load_recent_disputes()
110	}
111
112	/// Load the candidate votes for the specific session-candidate pair, if any.
113	pub fn load_candidate_votes(
114		&self,
115		session: SessionIndex,
116		candidate_hash: &CandidateHash,
117	) -> FatalResult<Option<CandidateVotes>> {
118		if let Some(val) = self.candidate_votes.get(&(session, *candidate_hash)) {
119			return Ok(val.clone())
120		}
121
122		self.inner.load_candidate_votes(session, candidate_hash)
123	}
124
125	/// Prepare a write to the "earliest session" field of the DB.
126	///
127	/// Later calls to this function will override earlier ones.
128	pub fn write_earliest_session(&mut self, session: SessionIndex) {
129		self.earliest_session = Some(session);
130	}
131
132	/// Prepare a write to the recent disputes stored in the DB.
133	///
134	/// Later calls to this function will override earlier ones.
135	pub fn write_recent_disputes(&mut self, recent_disputes: RecentDisputes) {
136		self.recent_disputes = Some(recent_disputes)
137	}
138
139	/// Prepare a write of the candidate votes under the indicated candidate.
140	///
141	/// Later calls to this function for the same candidate will override earlier ones.
142	pub fn write_candidate_votes(
143		&mut self,
144		session: SessionIndex,
145		candidate_hash: CandidateHash,
146		votes: CandidateVotes,
147	) {
148		self.candidate_votes.insert((session, candidate_hash), Some(votes));
149	}
150
151	/// Transform this backend into a set of write-ops to be written to the inner backend.
152	pub fn into_write_ops(self) -> impl Iterator<Item = BackendWriteOp> {
153		let earliest_session_ops = self
154			.earliest_session
155			.map(|s| BackendWriteOp::WriteEarliestSession(s))
156			.into_iter();
157
158		let recent_dispute_ops =
159			self.recent_disputes.map(|d| BackendWriteOp::WriteRecentDisputes(d)).into_iter();
160
161		let candidate_vote_ops =
162			self.candidate_votes
163				.into_iter()
164				.map(|((session, candidate), votes)| match votes {
165					Some(votes) => BackendWriteOp::WriteCandidateVotes(session, candidate, votes),
166					None => BackendWriteOp::DeleteCandidateVotes(session, candidate),
167				});
168
169		earliest_session_ops.chain(recent_dispute_ops).chain(candidate_vote_ops)
170	}
171}