1use std::{
5 collections::{BTreeMap, VecDeque},
6 ops::Bound::{Excluded, Included},
7 time::Duration,
8};
9
10use bytes::Bytes;
11use consensus_config::AuthorityIndex;
12use consensus_types::block::{BlockDigest, BlockRef, Round, TransactionIndex};
13use mysten_common::ZipDebugEqIteratorExt;
14use sui_macros::fail_point;
15#[cfg(not(tidehunter))]
16use typed_store::rocks::{DBMapTableConfigMap, default_db_options};
17use typed_store::{
18 DBMapUtils, Map as _,
19 metrics::SamplingInterval,
20 rocks::{DBMap, MetricConf},
21};
22
23use super::{CommitInfo, Store, WriteBatch};
24use crate::{
25 block::{BlockAPI as _, SignedBlock, VerifiedBlock},
26 commit::{CommitAPI as _, CommitDigest, CommitIndex, CommitRange, CommitRef, TrustedCommit},
27 error::{ConsensusError, ConsensusResult},
28};
29
30#[derive(DBMapUtils)]
32#[cfg_attr(tidehunter, tidehunter)]
33pub struct RocksDBStore {
34 blocks: DBMap<(Round, AuthorityIndex, BlockDigest), Bytes>,
36 #[rename = "digests"]
38 digests_by_authorities: DBMap<(AuthorityIndex, Round, BlockDigest), ()>,
39 commits: DBMap<(CommitIndex, CommitDigest), Bytes>,
41 commit_votes: DBMap<(CommitIndex, CommitDigest, BlockRef), ()>,
44 commit_info: DBMap<(CommitIndex, CommitDigest), CommitInfo>,
46 finalized_commits:
48 DBMap<(CommitIndex, CommitDigest), BTreeMap<BlockRef, Vec<TransactionIndex>>>,
49}
50
51impl RocksDBStore {
52 const BLOCKS_CF: &'static str = "blocks";
53 const DIGESTS_BY_AUTHORITIES_CF: &'static str = "digests";
54 const COMMITS_CF: &'static str = "commits";
55 const COMMIT_VOTES_CF: &'static str = "commit_votes";
56 const COMMIT_INFO_CF: &'static str = "commit_info";
57 const FINALIZED_COMMITS_CF: &'static str = "finalized_commits";
58
59 #[cfg(not(tidehunter))]
61 pub fn new(path: &str) -> Self {
62 let db_options =
65 default_db_options().optimize_db_for_write_throughput(2, true);
66 let mut metrics_conf = MetricConf::new("consensus");
67 metrics_conf.read_sample_interval = SamplingInterval::new(Duration::from_secs(60), 0);
68 let cf_options = default_db_options().optimize_for_no_deletion();
69 let column_family_options = DBMapTableConfigMap::new(BTreeMap::from([
70 (
71 Self::BLOCKS_CF.to_string(),
72 cf_options
73 .clone()
74 .set_block_options(512, 128 << 10),
76 ),
77 (
78 Self::DIGESTS_BY_AUTHORITIES_CF.to_string(),
79 cf_options.clone(),
80 ),
81 (Self::COMMITS_CF.to_string(), cf_options.clone()),
82 (Self::COMMIT_VOTES_CF.to_string(), cf_options.clone()),
83 (Self::COMMIT_INFO_CF.to_string(), cf_options.clone()),
84 (Self::FINALIZED_COMMITS_CF.to_string(), cf_options.clone()),
85 ]));
86 Self::open_tables_read_write(
87 path.into(),
88 metrics_conf,
89 Some(db_options.options),
90 Some(column_family_options),
91 )
92 }
93
94 #[cfg(tidehunter)]
95 pub fn new(path: &str) -> Self {
96 tracing::warn!("Consensus store using tidehunter");
97 use typed_store::tidehunter_util::{
98 KeyIndexing, KeySpaceConfig, KeyType, ThConfig, default_mutex_count,
99 };
100 let mutexes = default_mutex_count();
101 let index_digest_key = KeyIndexing::key_reduction(36, 0..12);
102 let index_index_digest_key = KeyIndexing::key_reduction(40, 0..24);
103 let commit_vote_key = KeyIndexing::key_reduction(76, 0..60);
104 let u32_prefix = KeyType::from_prefix_bits(3 * 8);
105 let u64_prefix = KeyType::from_prefix_bits(6 * 8);
106 let configs = vec![
107 (
108 Self::BLOCKS_CF.to_string(),
109 ThConfig::new_with_config_indexing(
110 index_index_digest_key.clone(),
111 mutexes,
112 u32_prefix,
113 KeySpaceConfig::new(),
114 ),
115 ),
116 (
117 Self::DIGESTS_BY_AUTHORITIES_CF.to_string(),
118 ThConfig::new_with_config_indexing(
119 index_index_digest_key.clone(),
120 mutexes,
121 u64_prefix,
122 KeySpaceConfig::new(),
123 ),
124 ),
125 (
126 Self::COMMITS_CF.to_string(),
127 ThConfig::new_with_indexing(index_digest_key.clone(), mutexes, u32_prefix),
128 ),
129 (
130 Self::COMMIT_VOTES_CF.to_string(),
131 ThConfig::new_with_config_indexing(
132 commit_vote_key,
133 mutexes,
134 u32_prefix,
135 KeySpaceConfig::new(),
136 ),
137 ),
138 (
139 Self::COMMIT_INFO_CF.to_string(),
140 ThConfig::new_with_indexing(index_digest_key.clone(), mutexes, u32_prefix),
141 ),
142 (
143 Self::FINALIZED_COMMITS_CF.to_string(),
144 ThConfig::new_with_indexing(index_digest_key.clone(), mutexes, u32_prefix),
145 ),
146 ];
147 Self::open_tables_read_write(
148 path.into(),
149 MetricConf::new("consensus")
150 .with_sampling(SamplingInterval::new(Duration::from_secs(60), 0))
151 .with_th_batch_compression(),
152 configs.into_iter().collect(),
153 )
154 }
155}
156
157impl Store for RocksDBStore {
158 fn write(&self, write_batch: WriteBatch) -> ConsensusResult<()> {
159 fail_point!("consensus-store-before-write");
160
161 let mut batch = self.blocks.batch();
162 for block in write_batch.blocks {
163 let block_ref = block.reference();
164 batch
165 .insert_batch(
166 &self.blocks,
167 [(
168 (block_ref.round, block_ref.author, block_ref.digest),
169 block.serialized(),
170 )],
171 )
172 .map_err(ConsensusError::RocksDBFailure)?;
173 batch
174 .insert_batch(
175 &self.digests_by_authorities,
176 [((block_ref.author, block_ref.round, block_ref.digest), ())],
177 )
178 .map_err(ConsensusError::RocksDBFailure)?;
179 for vote in block.commit_votes() {
180 batch
181 .insert_batch(
182 &self.commit_votes,
183 [((vote.index, vote.digest, block_ref), ())],
184 )
185 .map_err(ConsensusError::RocksDBFailure)?;
186 }
187 }
188
189 for commit in write_batch.commits {
190 batch
191 .insert_batch(
192 &self.commits,
193 [((commit.index(), commit.digest()), commit.serialized())],
194 )
195 .map_err(ConsensusError::RocksDBFailure)?;
196 }
197
198 for (commit_ref, commit_info) in write_batch.commit_info {
199 batch
200 .insert_batch(
201 &self.commit_info,
202 [((commit_ref.index, commit_ref.digest), commit_info)],
203 )
204 .map_err(ConsensusError::RocksDBFailure)?;
205 }
206
207 for (commit_ref, rejected_transactions) in write_batch.finalized_commits {
208 batch
209 .insert_batch(
210 &self.finalized_commits,
211 [((commit_ref.index, commit_ref.digest), rejected_transactions)],
212 )
213 .map_err(ConsensusError::RocksDBFailure)?;
214 }
215
216 batch.write()?;
217 fail_point!("consensus-store-after-write");
218 Ok(())
219 }
220
221 fn read_blocks(&self, refs: &[BlockRef]) -> ConsensusResult<Vec<Option<VerifiedBlock>>> {
222 let keys = refs
223 .iter()
224 .map(|r| (r.round, r.author, r.digest))
225 .collect::<Vec<_>>();
226 let serialized = self.blocks.multi_get(keys)?;
227 let mut blocks = vec![];
228 for (key, serialized) in refs.iter().zip_debug_eq(serialized) {
229 if let Some(serialized) = serialized {
230 let signed_block: SignedBlock =
231 bcs::from_bytes(&serialized).map_err(ConsensusError::MalformedBlock)?;
232 let block = VerifiedBlock::new_verified(signed_block, serialized);
234 assert_eq!(*key, block.reference());
236 blocks.push(Some(block));
237 } else {
238 blocks.push(None);
239 }
240 }
241 Ok(blocks)
242 }
243
244 fn contains_blocks(&self, refs: &[BlockRef]) -> ConsensusResult<Vec<bool>> {
245 let refs = refs
246 .iter()
247 .map(|r| (r.round, r.author, r.digest))
248 .collect::<Vec<_>>();
249 let exist = self.blocks.multi_contains_keys(refs)?;
250 Ok(exist)
251 }
252
253 fn scan_blocks_by_author(
254 &self,
255 author: AuthorityIndex,
256 start_round: Round,
257 ) -> ConsensusResult<Vec<VerifiedBlock>> {
258 self.scan_blocks_by_author_in_range(author, start_round, Round::MAX, usize::MAX)
259 }
260
261 fn scan_blocks_by_author_in_range(
262 &self,
263 author: AuthorityIndex,
264 start_round: Round,
265 end_round: Round,
266 limit: usize,
267 ) -> ConsensusResult<Vec<VerifiedBlock>> {
268 let mut refs = vec![];
269 for kv in self.digests_by_authorities.safe_range_iter((
270 Included((author, start_round, BlockDigest::MIN)),
271 Excluded((author, end_round, BlockDigest::MIN)),
272 )) {
273 let ((author, round, digest), _) = kv?;
274 refs.push(BlockRef::new(round, author, digest));
275 if refs.len() >= limit {
276 break;
277 }
278 }
279 let results = self.read_blocks(refs.as_slice())?;
280 let mut blocks = Vec::with_capacity(refs.len());
281 for (r, block) in refs.into_iter().zip_debug_eq(results) {
282 blocks.push(
283 block.unwrap_or_else(|| panic!("Storage inconsistency: block {:?} not found!", r)),
284 );
285 }
286 Ok(blocks)
287 }
288
289 fn scan_last_blocks_by_author(
293 &self,
294 author: AuthorityIndex,
295 num_of_rounds: u64,
296 before_round: Option<Round>,
297 ) -> ConsensusResult<Vec<VerifiedBlock>> {
298 let before_round = before_round.unwrap_or(Round::MAX);
299 let mut refs = VecDeque::new();
300 for kv in self
301 .digests_by_authorities
302 .reversed_safe_iter_with_bounds(
303 Some((author, Round::MIN, BlockDigest::MIN)),
304 Some((author, before_round, BlockDigest::MAX)),
305 )?
306 .take(num_of_rounds as usize)
307 {
308 let ((author, round, digest), _) = kv?;
309 refs.push_front(BlockRef::new(round, author, digest));
310 }
311 let refs_slice = refs.make_contiguous();
312 let results = self.read_blocks(refs_slice)?;
313 let mut blocks = vec![];
314 for (r, block) in refs.into_iter().zip_debug_eq(results) {
315 blocks.push(
316 block.unwrap_or_else(|| panic!("Storage inconsistency: block {:?} not found!", r)),
317 );
318 }
319 Ok(blocks)
320 }
321
322 fn read_last_commit(&self) -> ConsensusResult<Option<TrustedCommit>> {
323 let Some(result) = self
324 .commits
325 .reversed_safe_iter_with_bounds(None, None)?
326 .next()
327 else {
328 return Ok(None);
329 };
330 let ((_index, digest), serialized) = result?;
331 let commit = TrustedCommit::new_trusted(
332 bcs::from_bytes(&serialized).map_err(ConsensusError::MalformedCommit)?,
333 serialized,
334 );
335 assert_eq!(commit.digest(), digest);
336 Ok(Some(commit))
337 }
338
339 fn scan_commits(&self, range: CommitRange) -> ConsensusResult<Vec<TrustedCommit>> {
340 let mut commits = vec![];
341 for result in self.commits.safe_range_iter((
342 Included((range.start(), CommitDigest::MIN)),
343 Included((range.end(), CommitDigest::MAX)),
344 )) {
345 let ((_index, digest), serialized) = result?;
346 let commit = TrustedCommit::new_trusted(
347 bcs::from_bytes(&serialized).map_err(ConsensusError::MalformedCommit)?,
348 serialized,
349 );
350 assert_eq!(commit.digest(), digest);
351 commits.push(commit);
352 }
353 Ok(commits)
354 }
355
356 fn read_commit_votes(&self, commit_index: CommitIndex) -> ConsensusResult<Vec<BlockRef>> {
357 let mut votes = Vec::new();
358 for vote in self.commit_votes.safe_range_iter((
359 Included((commit_index, CommitDigest::MIN, BlockRef::MIN)),
360 Included((commit_index, CommitDigest::MAX, BlockRef::MAX)),
361 )) {
362 let ((_, _, block_ref), _) = vote?;
363 votes.push(block_ref);
364 }
365 Ok(votes)
366 }
367
368 fn read_last_commit_info(&self) -> ConsensusResult<Option<(CommitRef, CommitInfo)>> {
369 let Some(result) = self
370 .commit_info
371 .reversed_safe_iter_with_bounds(None, None)?
372 .next()
373 else {
374 return Ok(None);
375 };
376 let (key, commit_info) = result.map_err(ConsensusError::RocksDBFailure)?;
377 Ok(Some((CommitRef::new(key.0, key.1), commit_info)))
378 }
379
380 fn read_last_finalized_commit(&self) -> ConsensusResult<Option<CommitRef>> {
381 let Some(result) = self
382 .finalized_commits
383 .reversed_safe_iter_with_bounds(None, None)?
384 .next()
385 else {
386 return Ok(None);
387 };
388 let ((index, digest), _) = result.map_err(ConsensusError::RocksDBFailure)?;
389 Ok(Some(CommitRef::new(index, digest)))
390 }
391
392 fn read_rejected_transactions(
393 &self,
394 commit_ref: CommitRef,
395 ) -> ConsensusResult<Option<BTreeMap<BlockRef, Vec<TransactionIndex>>>> {
396 let result = self
397 .finalized_commits
398 .get(&(commit_ref.index, commit_ref.digest))?;
399 Ok(result)
400 }
401}