kaspa_consensusmanager/
session.rs

1//! Consensus and Session management structures.
2//!
3//! We use newtypes in order to simplify changing the underlying lock in the future
4
5use kaspa_consensus_core::{
6    acceptance_data::AcceptanceData,
7    api::{BlockCount, BlockValidationFutures, ConsensusApi, ConsensusStats, DynConsensus},
8    block::Block,
9    blockstatus::BlockStatus,
10    daa_score_timestamp::DaaScoreTimestamp,
11    errors::consensus::ConsensusResult,
12    header::Header,
13    pruning::{PruningPointProof, PruningPointTrustedData, PruningPointsList},
14    trusted::{ExternalGhostdagData, TrustedBlock},
15    tx::{MutableTransaction, Transaction, TransactionOutpoint, UtxoEntry},
16    BlockHashSet, BlueWorkType, ChainPath, Hash,
17};
18use kaspa_utils::sync::rwlock::*;
19use std::{ops::Deref, sync::Arc};
20
21pub use tokio::task::spawn_blocking;
22
23use crate::BlockProcessingBatch;
24
25#[allow(dead_code)]
26#[derive(Clone)]
27pub struct SessionOwnedReadGuard(Arc<RfRwLockOwnedReadGuard>);
28
29#[allow(dead_code)]
30pub struct SessionReadGuard<'a>(RfRwLockReadGuard<'a>);
31
32pub struct SessionWriteGuard<'a>(RfRwLockWriteGuard<'a>);
33
34impl SessionWriteGuard<'_> {
35    /// Releases and recaptures the write lock. Makes sure that other pending readers/writers get a
36    /// chance to capture the lock before this thread does so.
37    pub fn blocking_yield(&mut self) {
38        self.0.blocking_yield();
39    }
40}
41
42#[derive(Clone)]
43pub struct SessionLock(Arc<RfRwLock>);
44
45impl Default for SessionLock {
46    fn default() -> Self {
47        Self::new()
48    }
49}
50
51impl SessionLock {
52    pub fn new() -> SessionLock {
53        SessionLock(Arc::new(RfRwLock::new()))
54    }
55
56    pub async fn read_owned(&self) -> SessionOwnedReadGuard {
57        SessionOwnedReadGuard(Arc::new(self.0.clone().read_owned().await))
58    }
59
60    pub async fn read(&self) -> SessionReadGuard {
61        SessionReadGuard(self.0.read().await)
62    }
63
64    pub fn blocking_read(&self) -> SessionReadGuard {
65        SessionReadGuard(self.0.blocking_read())
66    }
67
68    pub fn blocking_write(&self) -> SessionWriteGuard<'_> {
69        SessionWriteGuard(self.0.blocking_write())
70    }
71}
72
73#[derive(Clone)]
74pub struct ConsensusInstance {
75    session_lock: SessionLock,
76    consensus: DynConsensus,
77}
78
79impl ConsensusInstance {
80    pub fn new(session_lock: SessionLock, consensus: DynConsensus) -> Self {
81        Self { session_lock, consensus }
82    }
83
84    /// Returns a blocking session to be used in **non async** environments.
85    /// Users would usually need to call something like `futures::executor::block_on` in order
86    /// to acquire the session, but we prefer leaving this decision to the caller
87    pub async fn session_blocking(&self) -> ConsensusSessionBlocking {
88        let g = self.session_lock.read().await;
89        ConsensusSessionBlocking::new(g, self.consensus.clone())
90    }
91
92    /// Returns an unguarded *blocking* consensus session. There's no guarantee that data will not be pruned between
93    /// two sequential consensus calls. This session doesn't hold the consensus pruning lock, so it should
94    /// be preferred upon [`session_blocking()`](Self::session_blocking) when data consistency is not important.
95    pub fn unguarded_session_blocking(&self) -> ConsensusSessionBlocking<'static> {
96        ConsensusSessionBlocking::new_without_session_guard(self.consensus.clone())
97    }
98
99    /// Returns a consensus session for accessing consensus operations in a bulk. The user can safely assume
100    /// that consensus state is consistent between operations, that is, no pruning was performed between the calls.
101    /// The returned object is an *owned* consensus session type which can be cloned and shared across threads.
102    /// The sharing ability is useful for spawning blocking operations on a different thread using the same
103    /// session object, see [`ConsensusSessionOwned::spawn_blocking()`](ConsensusSessionOwned::spawn_blocking). The caller is responsible to make sure
104    /// that the overall lifetime of this session is not too long (~2 seconds max)
105    pub async fn session(&self) -> ConsensusSessionOwned {
106        let g = self.session_lock.read_owned().await;
107        ConsensusSessionOwned::new(g, self.consensus.clone())
108    }
109
110    /// Returns an unguarded consensus session. There's no guarantee that data will not be pruned between
111    /// two sequential consensus calls. This session doesn't hold the consensus pruning lock, so it should
112    /// be preferred upon [`session()`](Self::session) when data consistency is not important.
113    pub fn unguarded_session(&self) -> ConsensusSessionOwned {
114        ConsensusSessionOwned::new_without_session_guard(self.consensus.clone())
115    }
116}
117
118pub struct ConsensusSessionBlocking<'a> {
119    _session_guard: Option<SessionReadGuard<'a>>,
120    consensus: DynConsensus,
121}
122
123impl<'a> ConsensusSessionBlocking<'a> {
124    pub fn new(session_guard: SessionReadGuard<'a>, consensus: DynConsensus) -> Self {
125        Self { _session_guard: Some(session_guard), consensus }
126    }
127
128    pub fn new_without_session_guard(consensus: DynConsensus) -> Self {
129        Self { _session_guard: None, consensus }
130    }
131}
132
133impl Deref for ConsensusSessionBlocking<'_> {
134    type Target = dyn ConsensusApi; // We avoid exposing the Arc itself by ref since it can be easily cloned and misused
135
136    fn deref(&self) -> &Self::Target {
137        self.consensus.as_ref()
138    }
139}
140
141/// An *owned* consensus session type which can be cloned and shared across threads.
142/// See method `spawn_blocking` within for context on the usefulness of this type.
143/// Please note - you must use [`ConsensusProxy`] type alias instead of this struct.
144#[derive(Clone)]
145pub struct ConsensusSessionOwned {
146    _session_guard: Option<SessionOwnedReadGuard>,
147    consensus: DynConsensus,
148}
149
150impl ConsensusSessionOwned {
151    pub fn new(session_guard: SessionOwnedReadGuard, consensus: DynConsensus) -> Self {
152        Self { _session_guard: Some(session_guard), consensus }
153    }
154
155    pub fn new_without_session_guard(consensus: DynConsensus) -> Self {
156        Self { _session_guard: None, consensus }
157    }
158
159    /// Uses [`tokio::task::spawn_blocking`] to run the provided consensus closure on a thread where blocking is acceptable.
160    /// Note that this function is only available on the *owned* session, and requires cloning the session. In fact this
161    /// function is the main motivation for a separate session type.
162    pub async fn spawn_blocking<F, R>(self, f: F) -> R
163    where
164        F: FnOnce(&dyn ConsensusApi) -> R + Send + 'static,
165        R: Send + 'static,
166    {
167        spawn_blocking(move || f(self.consensus.as_ref())).await.unwrap()
168    }
169}
170
171impl ConsensusSessionOwned {
172    pub fn validate_and_insert_block(&self, block: Block) -> BlockValidationFutures {
173        self.consensus.validate_and_insert_block(block)
174    }
175
176    pub fn validate_and_insert_block_batch(&self, mut batch: Vec<Block>) -> BlockProcessingBatch {
177        // Sort by blue work in order to ensure topological order
178        batch.sort_by(|a, b| a.header.blue_work.partial_cmp(&b.header.blue_work).unwrap());
179        let (block_tasks, virtual_state_tasks) = batch
180            .iter()
181            .map(|b| {
182                let BlockValidationFutures { block_task, virtual_state_task } = self.consensus.validate_and_insert_block(b.clone());
183                (block_task, virtual_state_task)
184            })
185            .unzip();
186        BlockProcessingBatch::new(batch, block_tasks, virtual_state_tasks)
187    }
188
189    pub fn validate_and_insert_trusted_block(&self, tb: TrustedBlock) -> BlockValidationFutures {
190        self.consensus.validate_and_insert_trusted_block(tb)
191    }
192
193    pub fn calculate_transaction_compute_mass(&self, transaction: &Transaction) -> u64 {
194        // This method performs pure calculations so no need for an async wrapper
195        self.consensus.calculate_transaction_compute_mass(transaction)
196    }
197
198    pub fn calculate_transaction_storage_mass(&self, transaction: &MutableTransaction) -> Option<u64> {
199        // This method performs pure calculations so no need for an async wrapper
200        self.consensus.calculate_transaction_storage_mass(transaction)
201    }
202
203    pub fn get_virtual_daa_score(&self) -> u64 {
204        // Accessing cached virtual fields is lock-free and does not require spawn_blocking
205        self.consensus.get_virtual_daa_score()
206    }
207
208    pub fn get_virtual_bits(&self) -> u32 {
209        // Accessing cached virtual fields is lock-free and does not require spawn_blocking
210        self.consensus.get_virtual_bits()
211    }
212
213    pub fn get_virtual_past_median_time(&self) -> u64 {
214        // Accessing cached virtual fields is lock-free and does not require spawn_blocking
215        self.consensus.get_virtual_past_median_time()
216    }
217
218    pub fn get_virtual_parents(&self) -> BlockHashSet {
219        // Accessing cached virtual fields is lock-free and does not require spawn_blocking
220        self.consensus.get_virtual_parents()
221    }
222
223    pub fn get_virtual_parents_len(&self) -> usize {
224        // Accessing cached virtual fields is lock-free and does not require spawn_blocking
225        self.consensus.get_virtual_parents_len()
226    }
227
228    pub async fn async_get_stats(&self) -> ConsensusStats {
229        self.clone().spawn_blocking(|c| c.get_stats()).await
230    }
231
232    pub async fn async_get_virtual_merge_depth_root(&self) -> Option<Hash> {
233        self.clone().spawn_blocking(|c| c.get_virtual_merge_depth_root()).await
234    }
235
236    /// Returns the `BlueWork` threshold at which blocks with lower or equal blue work are considered
237    /// to be un-mergeable by current virtual state.
238    /// (Note: in some rare cases when the node is unsynced the function might return zero as the threshold)
239    pub async fn async_get_virtual_merge_depth_blue_work_threshold(&self) -> BlueWorkType {
240        self.clone().spawn_blocking(|c| c.get_virtual_merge_depth_blue_work_threshold()).await
241    }
242
243    pub async fn async_get_sink(&self) -> Hash {
244        self.clone().spawn_blocking(|c| c.get_sink()).await
245    }
246
247    pub async fn async_get_sink_timestamp(&self) -> u64 {
248        self.clone().spawn_blocking(|c| c.get_sink_timestamp()).await
249    }
250
251    pub async fn async_get_current_block_color(&self, hash: Hash) -> Option<bool> {
252        self.clone().spawn_blocking(move |c| c.get_current_block_color(hash)).await
253    }
254
255    /// source refers to the earliest block from which the current node has full header & block data  
256    pub async fn async_get_source(&self) -> Hash {
257        self.clone().spawn_blocking(|c| c.get_source()).await
258    }
259
260    pub async fn async_estimate_block_count(&self) -> BlockCount {
261        self.clone().spawn_blocking(|c| c.estimate_block_count()).await
262    }
263
264    /// Returns whether this consensus is considered synced or close to being synced.
265    ///
266    /// This info is used to determine if it's ok to use a block template from this node for mining purposes.
267    pub async fn async_is_nearly_synced(&self) -> bool {
268        self.clone().spawn_blocking(|c| c.is_nearly_synced()).await
269    }
270
271    pub async fn async_get_virtual_chain_from_block(
272        &self,
273        low: Hash,
274        chain_path_added_limit: Option<usize>,
275    ) -> ConsensusResult<ChainPath> {
276        self.clone().spawn_blocking(move |c| c.get_virtual_chain_from_block(low, chain_path_added_limit)).await
277    }
278
279    pub async fn async_get_virtual_utxos(
280        &self,
281        from_outpoint: Option<TransactionOutpoint>,
282        chunk_size: usize,
283        skip_first: bool,
284    ) -> Vec<(TransactionOutpoint, UtxoEntry)> {
285        self.clone().spawn_blocking(move |c| c.get_virtual_utxos(from_outpoint, chunk_size, skip_first)).await
286    }
287
288    pub async fn async_get_tips(&self) -> Vec<Hash> {
289        self.clone().spawn_blocking(|c| c.get_tips()).await
290    }
291
292    pub async fn async_get_tips_len(&self) -> usize {
293        self.clone().spawn_blocking(|c| c.get_tips_len()).await
294    }
295
296    pub async fn async_is_chain_ancestor_of(&self, low: Hash, high: Hash) -> ConsensusResult<bool> {
297        self.clone().spawn_blocking(move |c| c.is_chain_ancestor_of(low, high)).await
298    }
299
300    pub async fn async_get_hashes_between(&self, low: Hash, high: Hash, max_blocks: usize) -> ConsensusResult<(Vec<Hash>, Hash)> {
301        self.clone().spawn_blocking(move |c| c.get_hashes_between(low, high, max_blocks)).await
302    }
303
304    pub async fn async_get_header(&self, hash: Hash) -> ConsensusResult<Arc<Header>> {
305        self.clone().spawn_blocking(move |c| c.get_header(hash)).await
306    }
307
308    pub async fn async_get_headers_selected_tip(&self) -> Hash {
309        self.clone().spawn_blocking(|c| c.get_headers_selected_tip()).await
310    }
311
312    pub async fn async_get_chain_block_samples(&self) -> Vec<DaaScoreTimestamp> {
313        self.clone().spawn_blocking(|c| c.get_chain_block_samples()).await
314    }
315
316    /// Returns the antipast of block `hash` from the POV of `context`, i.e. `antipast(hash) ∩ past(context)`.
317    /// Since this might be an expensive operation for deep blocks, we allow the caller to specify a limit
318    /// `max_traversal_allowed` on the maximum amount of blocks to traverse for obtaining the answer
319    pub async fn async_get_antipast_from_pov(
320        &self,
321        hash: Hash,
322        context: Hash,
323        max_traversal_allowed: Option<u64>,
324    ) -> ConsensusResult<Vec<Hash>> {
325        self.clone().spawn_blocking(move |c| c.get_antipast_from_pov(hash, context, max_traversal_allowed)).await
326    }
327
328    /// Returns the anticone of block `hash` from the POV of `virtual`
329    pub async fn async_get_anticone(&self, hash: Hash) -> ConsensusResult<Vec<Hash>> {
330        self.clone().spawn_blocking(move |c| c.get_anticone(hash)).await
331    }
332
333    pub async fn async_get_pruning_point_proof(&self) -> Arc<PruningPointProof> {
334        self.clone().spawn_blocking(|c| c.get_pruning_point_proof()).await
335    }
336
337    pub async fn async_create_virtual_selected_chain_block_locator(
338        &self,
339        low: Option<Hash>,
340        high: Option<Hash>,
341    ) -> ConsensusResult<Vec<Hash>> {
342        self.clone().spawn_blocking(move |c| c.create_virtual_selected_chain_block_locator(low, high)).await
343    }
344
345    pub async fn async_create_block_locator_from_pruning_point(&self, high: Hash, limit: usize) -> ConsensusResult<Vec<Hash>> {
346        self.clone().spawn_blocking(move |c| c.create_block_locator_from_pruning_point(high, limit)).await
347    }
348
349    pub async fn async_pruning_point_headers(&self) -> Vec<Arc<Header>> {
350        self.clone().spawn_blocking(|c| c.pruning_point_headers()).await
351    }
352
353    pub async fn async_get_pruning_point_anticone_and_trusted_data(&self) -> ConsensusResult<Arc<PruningPointTrustedData>> {
354        self.clone().spawn_blocking(|c| c.get_pruning_point_anticone_and_trusted_data()).await
355    }
356
357    pub async fn async_get_block(&self, hash: Hash) -> ConsensusResult<Block> {
358        self.clone().spawn_blocking(move |c| c.get_block(hash)).await
359    }
360
361    pub async fn async_get_block_even_if_header_only(&self, hash: Hash) -> ConsensusResult<Block> {
362        self.clone().spawn_blocking(move |c| c.get_block_even_if_header_only(hash)).await
363    }
364
365    pub async fn async_get_ghostdag_data(&self, hash: Hash) -> ConsensusResult<ExternalGhostdagData> {
366        self.clone().spawn_blocking(move |c| c.get_ghostdag_data(hash)).await
367    }
368
369    pub async fn async_get_block_children(&self, hash: Hash) -> Option<Vec<Hash>> {
370        self.clone().spawn_blocking(move |c| c.get_block_children(hash)).await
371    }
372
373    pub async fn async_get_block_parents(&self, hash: Hash) -> Option<Arc<Vec<Hash>>> {
374        self.clone().spawn_blocking(move |c| c.get_block_parents(hash)).await
375    }
376
377    pub async fn async_get_block_status(&self, hash: Hash) -> Option<BlockStatus> {
378        self.clone().spawn_blocking(move |c| c.get_block_status(hash)).await
379    }
380
381    pub async fn async_get_block_acceptance_data(&self, hash: Hash) -> ConsensusResult<Arc<AcceptanceData>> {
382        self.clone().spawn_blocking(move |c| c.get_block_acceptance_data(hash)).await
383    }
384
385    /// Returns acceptance data for a set of blocks belonging to the selected parent chain.
386    ///
387    /// See `self::get_virtual_chain`
388    pub async fn async_get_blocks_acceptance_data(
389        &self,
390        hashes: Vec<Hash>,
391        merged_blocks_limit: Option<usize>,
392    ) -> ConsensusResult<Vec<Arc<AcceptanceData>>> {
393        self.clone().spawn_blocking(move |c| c.get_blocks_acceptance_data(&hashes, merged_blocks_limit)).await
394    }
395
396    pub async fn async_is_chain_block(&self, hash: Hash) -> ConsensusResult<bool> {
397        self.clone().spawn_blocking(move |c| c.is_chain_block(hash)).await
398    }
399
400    pub async fn async_get_pruning_point_utxos(
401        &self,
402        expected_pruning_point: Hash,
403        from_outpoint: Option<TransactionOutpoint>,
404        chunk_size: usize,
405        skip_first: bool,
406    ) -> ConsensusResult<Vec<(TransactionOutpoint, UtxoEntry)>> {
407        self.clone()
408            .spawn_blocking(move |c| c.get_pruning_point_utxos(expected_pruning_point, from_outpoint, chunk_size, skip_first))
409            .await
410    }
411
412    pub async fn async_get_missing_block_body_hashes(&self, high: Hash) -> ConsensusResult<Vec<Hash>> {
413        self.clone().spawn_blocking(move |c| c.get_missing_block_body_hashes(high)).await
414    }
415
416    pub async fn async_pruning_point(&self) -> Hash {
417        self.clone().spawn_blocking(|c| c.pruning_point()).await
418    }
419
420    pub async fn async_get_daa_window(&self, hash: Hash) -> ConsensusResult<Vec<Hash>> {
421        self.clone().spawn_blocking(move |c| c.get_daa_window(hash)).await
422    }
423
424    pub async fn async_get_trusted_block_associated_ghostdag_data_block_hashes(&self, hash: Hash) -> ConsensusResult<Vec<Hash>> {
425        self.clone().spawn_blocking(move |c| c.get_trusted_block_associated_ghostdag_data_block_hashes(hash)).await
426    }
427
428    pub async fn async_estimate_network_hashes_per_second(
429        &self,
430        start_hash: Option<Hash>,
431        window_size: usize,
432    ) -> ConsensusResult<u64> {
433        self.clone().spawn_blocking(move |c| c.estimate_network_hashes_per_second(start_hash, window_size)).await
434    }
435
436    pub async fn async_validate_pruning_points(&self) -> ConsensusResult<()> {
437        self.clone().spawn_blocking(move |c| c.validate_pruning_points()).await
438    }
439
440    pub async fn async_are_pruning_points_violating_finality(&self, pp_list: PruningPointsList) -> bool {
441        self.clone().spawn_blocking(move |c| c.are_pruning_points_violating_finality(pp_list)).await
442    }
443
444    pub async fn async_creation_timestamp(&self) -> u64 {
445        self.clone().spawn_blocking(move |c| c.creation_timestamp()).await
446    }
447
448    pub async fn async_finality_point(&self) -> Hash {
449        self.clone().spawn_blocking(move |c| c.finality_point()).await
450    }
451}
452
453pub type ConsensusProxy = ConsensusSessionOwned;