kaspa_consensus/model/stores/
virtual_state.rs

1use std::ops::Deref;
2use std::sync::Arc;
3
4use arc_swap::ArcSwap;
5use kaspa_consensus_core::api::stats::VirtualStateStats;
6use kaspa_consensus_core::{
7    block::VirtualStateApproxId, coinbase::BlockRewardData, config::genesis::GenesisBlock, tx::TransactionId,
8    utxo::utxo_diff::UtxoDiff, BlockHashMap, BlockHashSet, HashMapCustomHasher,
9};
10use kaspa_database::prelude::{BatchDbWriter, CachedDbItem, DirectDbWriter, StoreResultExtensions};
11use kaspa_database::prelude::{CachePolicy, StoreResult};
12use kaspa_database::prelude::{StoreError, DB};
13use kaspa_database::registry::DatabaseStorePrefixes;
14use kaspa_hashes::Hash;
15use kaspa_muhash::MuHash;
16use rocksdb::WriteBatch;
17use serde::{Deserialize, Serialize};
18
19use super::ghostdag::GhostdagData;
20use super::utxo_set::DbUtxoSetStore;
21
22#[derive(Clone, Serialize, Deserialize, Default)]
23pub struct VirtualState {
24    pub parents: Vec<Hash>,
25    pub ghostdag_data: GhostdagData,
26    pub daa_score: u64,
27    pub bits: u32,
28    pub past_median_time: u64,
29    pub multiset: MuHash,
30    pub utxo_diff: UtxoDiff, // This is the UTXO diff from the selected tip to the virtual. i.e., if this diff is applied on the past UTXO of the selected tip, we'll get the virtual UTXO set.
31    pub accepted_tx_ids: Vec<TransactionId>, // TODO: consider saving `accepted_id_merkle_root` directly
32    pub mergeset_rewards: BlockHashMap<BlockRewardData>,
33    pub mergeset_non_daa: BlockHashSet,
34}
35
36impl VirtualState {
37    pub fn new(
38        parents: Vec<Hash>,
39        daa_score: u64,
40        bits: u32,
41        past_median_time: u64,
42        multiset: MuHash,
43        utxo_diff: UtxoDiff,
44        accepted_tx_ids: Vec<TransactionId>,
45        mergeset_rewards: BlockHashMap<BlockRewardData>,
46        mergeset_non_daa: BlockHashSet,
47        ghostdag_data: GhostdagData,
48    ) -> Self {
49        Self {
50            parents,
51            ghostdag_data,
52            daa_score,
53            bits,
54            past_median_time,
55            multiset,
56            utxo_diff,
57            accepted_tx_ids,
58            mergeset_rewards,
59            mergeset_non_daa,
60        }
61    }
62
63    pub fn from_genesis(genesis: &GenesisBlock, ghostdag_data: GhostdagData) -> Self {
64        Self {
65            parents: vec![genesis.hash],
66            ghostdag_data,
67            daa_score: genesis.daa_score,
68            bits: genesis.bits,
69            past_median_time: genesis.timestamp,
70            multiset: MuHash::new(),
71            utxo_diff: UtxoDiff::default(), // Virtual diff is initially empty since genesis receives no reward
72            accepted_tx_ids: genesis.build_genesis_transactions().into_iter().map(|tx| tx.id()).collect(),
73            mergeset_rewards: BlockHashMap::new(),
74            mergeset_non_daa: BlockHashSet::from_iter(std::iter::once(genesis.hash)),
75        }
76    }
77
78    pub fn to_virtual_state_approx_id(&self) -> VirtualStateApproxId {
79        VirtualStateApproxId::new(self.daa_score, self.ghostdag_data.blue_work, self.ghostdag_data.selected_parent)
80    }
81}
82
83impl From<&VirtualState> for VirtualStateStats {
84    fn from(state: &VirtualState) -> Self {
85        Self {
86            num_parents: state.parents.len() as u32,
87            daa_score: state.daa_score,
88            bits: state.bits,
89            past_median_time: state.past_median_time,
90        }
91    }
92}
93
94/// Represents the "last known good" virtual state. To be used by any logic which does not want to wait
95/// for a possible virtual state write to complete but can rather settle with the last known state
96#[derive(Clone, Default)]
97pub struct LkgVirtualState {
98    inner: Arc<ArcSwap<VirtualState>>,
99}
100
101/// Guard for accessing the last known good virtual state (lock-free)
102/// It's a simple newtype over arc_swap::Guard just to avoid explicit dependency
103pub struct LkgVirtualStateGuard(arc_swap::Guard<Arc<VirtualState>>);
104
105impl Deref for LkgVirtualStateGuard {
106    type Target = Arc<VirtualState>;
107
108    fn deref(&self) -> &Self::Target {
109        &self.0
110    }
111}
112
113impl LkgVirtualState {
114    /// Provides a temporary borrow to the last known good virtual state.
115    pub fn load(&self) -> LkgVirtualStateGuard {
116        LkgVirtualStateGuard(self.inner.load())
117    }
118
119    /// Loads the last known good virtual state.
120    pub fn load_full(&self) -> Arc<VirtualState> {
121        self.inner.load_full()
122    }
123
124    // Kept private in order to make sure it is only updated by DbVirtualStateStore
125    fn store(&self, virtual_state: Arc<VirtualState>) {
126        self.inner.store(virtual_state)
127    }
128}
129
130/// Used in order to group virtual related stores under a single lock
131pub struct VirtualStores {
132    pub state: DbVirtualStateStore,
133    pub utxo_set: DbUtxoSetStore,
134}
135
136impl VirtualStores {
137    pub fn new(db: Arc<DB>, lkg_virtual_state: LkgVirtualState, utxoset_cache_policy: CachePolicy) -> Self {
138        Self {
139            state: DbVirtualStateStore::new(db.clone(), lkg_virtual_state),
140            utxo_set: DbUtxoSetStore::new(db, utxoset_cache_policy, DatabaseStorePrefixes::VirtualUtxoset.into()),
141        }
142    }
143}
144
145/// Reader API for `VirtualStateStore`.
146pub trait VirtualStateStoreReader {
147    fn get(&self) -> StoreResult<Arc<VirtualState>>;
148}
149
150pub trait VirtualStateStore: VirtualStateStoreReader {
151    fn set(&mut self, state: Arc<VirtualState>) -> StoreResult<()>;
152}
153
154/// A DB + cache implementation of `VirtualStateStore` trait
155#[derive(Clone)]
156pub struct DbVirtualStateStore {
157    db: Arc<DB>,
158    access: CachedDbItem<Arc<VirtualState>>,
159    /// The "last known good" virtual state
160    lkg_virtual_state: LkgVirtualState,
161}
162
163impl DbVirtualStateStore {
164    pub fn new(db: Arc<DB>, lkg_virtual_state: LkgVirtualState) -> Self {
165        let access = CachedDbItem::new(db.clone(), DatabaseStorePrefixes::VirtualState.into());
166        // Init the LKG cache from DB store data
167        lkg_virtual_state.store(access.read().unwrap_option().unwrap_or_default());
168        Self { db, access, lkg_virtual_state }
169    }
170
171    pub fn clone_with_new_cache(&self) -> Self {
172        Self::new(self.db.clone(), self.lkg_virtual_state.clone())
173    }
174
175    pub fn is_initialized(&self) -> StoreResult<bool> {
176        match self.access.read() {
177            Ok(_) => Ok(true),
178            Err(StoreError::KeyNotFound(_)) => Ok(false),
179            Err(e) => Err(e),
180        }
181    }
182
183    pub fn set_batch(&mut self, batch: &mut WriteBatch, state: Arc<VirtualState>) -> StoreResult<()> {
184        self.lkg_virtual_state.store(state.clone()); // Keep the LKG cache up-to-date
185        self.access.write(BatchDbWriter::new(batch), &state)
186    }
187}
188
189impl VirtualStateStoreReader for DbVirtualStateStore {
190    fn get(&self) -> StoreResult<Arc<VirtualState>> {
191        self.access.read()
192    }
193}
194
195impl VirtualStateStore for DbVirtualStateStore {
196    fn set(&mut self, state: Arc<VirtualState>) -> StoreResult<()> {
197        self.lkg_virtual_state.store(state.clone()); // Keep the LKG cache up-to-date
198        self.access.write(DirectDbWriter::new(&self.db), &state)
199    }
200}