Skip to content

Commit a0aeec3

Browse files
authored
Optimize window cache building for ibd (#576)
* show changes. * optimize window caches for ibd. * do lints and checks etc.. * bench and compare. * clean-up * rework lock time check a bit. * // bool: todo!(), * fmt * address some reveiw points. * address reveiw comments. * update comments. * pass tests. * fix blue work assumption, update error message. * Update window.rs slight comment update. * simplify a bit more. * remove some unneeded things. rearrange access to cmpct gdd. * fix conflicts. * lints.. * address reveiw points from m. sutton. * uncomplicate check_block_transactions_in_context * commit in lazy * fix lints. * query compact data as much as possible. * Use DefefMut to unify push_mergeset logic for all cases (addresses @tiram's review) * comment on cache_sink_windows * add comment to new_sink != prev_sink * return out of push_mergeset, if we cannot push any more. * remove unused diff cache and do non-daa as option.
1 parent 1d3b9a9 commit a0aeec3

File tree

9 files changed

+220
-117
lines changed

9 files changed

+220
-117
lines changed

consensus/core/src/config/constants.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ pub mod perf {
121121

122122
const BASELINE_HEADER_DATA_CACHE_SIZE: usize = 10_000;
123123
const BASELINE_BLOCK_DATA_CACHE_SIZE: usize = 200;
124-
const BASELINE_BLOCK_WINDOW_CACHE_SIZE: usize = 2000;
124+
const BASELINE_BLOCK_WINDOW_CACHE_SIZE: usize = 2_000;
125125
const BASELINE_UTXOSET_CACHE_SIZE: usize = 10_000;
126126

127127
#[derive(Clone, Debug)]

consensus/core/src/errors/block.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ pub enum RuleError {
6464
#[error("expected header blue work {0} but got {1}")]
6565
UnexpectedHeaderBlueWork(BlueWorkType, BlueWorkType),
6666

67-
#[error("block difficulty of {0} is not the expected value of {1}")]
68-
UnexpectedDifficulty(u32, u32),
67+
#[error("block {0} difficulty of {1} is not the expected value of {2}")]
68+
UnexpectedDifficulty(Hash, u32, u32),
6969

7070
#[error("block timestamp of {0} is not after expected {1}")]
7171
TimeTooOld(u64, u64),

consensus/src/consensus/mod.rs

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -241,23 +241,13 @@ impl Consensus {
241241
body_receiver,
242242
virtual_sender,
243243
block_processors_pool,
244+
params,
244245
db.clone(),
245-
storage.statuses_store.clone(),
246-
storage.ghostdag_store.clone(),
247-
storage.headers_store.clone(),
248-
storage.block_transactions_store.clone(),
249-
storage.body_tips_store.clone(),
250-
services.reachability_service.clone(),
251-
services.coinbase_manager.clone(),
252-
services.mass_calculator.clone(),
253-
services.transaction_validator.clone(),
254-
services.window_manager.clone(),
255-
params.max_block_mass,
256-
params.genesis.clone(),
246+
&storage,
247+
&services,
257248
pruning_lock.clone(),
258249
notification_root.clone(),
259250
counters.clone(),
260-
params.storage_mass_activation,
261251
));
262252

263253
let virtual_processor = Arc::new(VirtualStateProcessor::new(

consensus/src/model/stores/ghostdag.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ impl MemSizeEstimator for GhostdagData {
4848
impl MemSizeEstimator for CompactGhostdagData {}
4949

5050
impl From<&GhostdagData> for CompactGhostdagData {
51+
#[inline(always)]
5152
fn from(value: &GhostdagData) -> Self {
5253
Self { blue_score: value.blue_score, blue_work: value.blue_work, selected_parent: value.selected_parent }
5354
}

consensus/src/pipeline/body_processor/body_validation_in_context.rs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use kaspa_consensus_core::block::Block;
88
use kaspa_database::prelude::StoreResultExtensions;
99
use kaspa_hashes::Hash;
1010
use kaspa_utils::option::OptionExtensions;
11+
use once_cell::unsync::Lazy;
1112
use std::sync::Arc;
1213

1314
impl BlockBodyProcessor {
@@ -18,13 +19,31 @@ impl BlockBodyProcessor {
1819
}
1920

2021
fn check_block_transactions_in_context(self: &Arc<Self>, block: &Block) -> BlockProcessResult<()> {
21-
let (pmt, _) = self.window_manager.calc_past_median_time(&self.ghostdag_store.get_data(block.hash()).unwrap())?;
22+
// Note: This is somewhat expensive during ibd, as it incurs cache misses.
23+
24+
// Use lazy evaluation to avoid unnecessary work, as most of the time we expect the txs not to have lock time.
25+
let lazy_pmt_res =
26+
Lazy::new(|| match self.window_manager.calc_past_median_time(&self.ghostdag_store.get_data(block.hash()).unwrap()) {
27+
Ok((pmt, pmt_window)) => {
28+
if !self.block_window_cache_for_past_median_time.contains_key(&block.hash()) {
29+
self.block_window_cache_for_past_median_time.insert(block.hash(), pmt_window);
30+
};
31+
Ok(pmt)
32+
}
33+
Err(e) => Err(e),
34+
});
35+
2236
for tx in block.transactions.iter() {
23-
if let Err(e) = self.transaction_validator.utxo_free_tx_validation(tx, block.header.daa_score, pmt) {
24-
return Err(RuleError::TxInContextFailed(tx.id(), e));
25-
}
37+
// Quick check to avoid the expensive Lazy eval during ibd (in most cases).
38+
// TODO: refactor this and avoid classifying the tx lock outside of the transaction validator.
39+
if tx.lock_time != 0 {
40+
if let Err(e) =
41+
self.transaction_validator.utxo_free_tx_validation(tx, block.header.daa_score, (*lazy_pmt_res).clone()?)
42+
{
43+
return Err(RuleError::TxInContextFailed(tx.id(), e));
44+
};
45+
};
2646
}
27-
2847
Ok(())
2948
}
3049

consensus/src/pipeline/body_processor/processor.rs

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
use crate::{
2-
consensus::services::DbWindowManager,
2+
consensus::{
3+
services::{ConsensusServices, DbWindowManager},
4+
storage::ConsensusStorage,
5+
},
36
errors::{BlockProcessResult, RuleError},
47
model::{
58
services::reachability::MTReachabilityService,
69
stores::{
710
block_transactions::DbBlockTransactionsStore,
11+
block_window_cache::BlockWindowCacheStore,
812
ghostdag::DbGhostdagStore,
913
headers::DbHeadersStore,
1014
reachability::DbReachabilityStore,
@@ -23,7 +27,10 @@ use crossbeam_channel::{Receiver, Sender};
2327
use kaspa_consensus_core::{
2428
block::Block,
2529
blockstatus::BlockStatus::{self, StatusHeaderOnly, StatusInvalid},
26-
config::{genesis::GenesisBlock, params::ForkActivation},
30+
config::{
31+
genesis::GenesisBlock,
32+
params::{ForkActivation, Params},
33+
},
2734
mass::MassCalculator,
2835
tx::Transaction,
2936
};
@@ -60,6 +67,7 @@ pub struct BlockBodyProcessor {
6067
pub(super) headers_store: Arc<DbHeadersStore>,
6168
pub(super) block_transactions_store: Arc<DbBlockTransactionsStore>,
6269
pub(super) body_tips_store: Arc<RwLock<DbTipsStore>>,
70+
pub(super) block_window_cache_for_past_median_time: Arc<BlockWindowCacheStore>,
6371

6472
// Managers and services
6573
pub(super) reachability_service: MTReachabilityService<DbReachabilityStore>,
@@ -91,47 +99,42 @@ impl BlockBodyProcessor {
9199
sender: Sender<VirtualStateProcessingMessage>,
92100
thread_pool: Arc<ThreadPool>,
93101

102+
params: &Params,
94103
db: Arc<DB>,
95-
statuses_store: Arc<RwLock<DbStatusesStore>>,
96-
ghostdag_store: Arc<DbGhostdagStore>,
97-
headers_store: Arc<DbHeadersStore>,
98-
block_transactions_store: Arc<DbBlockTransactionsStore>,
99-
body_tips_store: Arc<RwLock<DbTipsStore>>,
100-
101-
reachability_service: MTReachabilityService<DbReachabilityStore>,
102-
coinbase_manager: CoinbaseManager,
103-
mass_calculator: MassCalculator,
104-
transaction_validator: TransactionValidator,
105-
window_manager: DbWindowManager,
106-
max_block_mass: u64,
107-
genesis: GenesisBlock,
104+
storage: &Arc<ConsensusStorage>,
105+
services: &Arc<ConsensusServices>,
106+
108107
pruning_lock: SessionLock,
109108
notification_root: Arc<ConsensusNotificationRoot>,
110109
counters: Arc<ProcessingCounters>,
111-
storage_mass_activation: ForkActivation,
112110
) -> Self {
113111
Self {
114112
receiver,
115113
sender,
116114
thread_pool,
117115
db,
118-
statuses_store,
119-
reachability_service,
120-
ghostdag_store,
121-
headers_store,
122-
block_transactions_store,
123-
body_tips_store,
124-
coinbase_manager,
125-
mass_calculator,
126-
transaction_validator,
127-
window_manager,
128-
max_block_mass,
129-
genesis,
116+
117+
max_block_mass: params.max_block_mass,
118+
genesis: params.genesis.clone(),
119+
120+
statuses_store: storage.statuses_store.clone(),
121+
ghostdag_store: storage.ghostdag_store.clone(),
122+
headers_store: storage.headers_store.clone(),
123+
block_transactions_store: storage.block_transactions_store.clone(),
124+
body_tips_store: storage.body_tips_store.clone(),
125+
block_window_cache_for_past_median_time: storage.block_window_cache_for_past_median_time.clone(),
126+
127+
reachability_service: services.reachability_service.clone(),
128+
coinbase_manager: services.coinbase_manager.clone(),
129+
mass_calculator: services.mass_calculator.clone(),
130+
transaction_validator: services.transaction_validator.clone(),
131+
window_manager: services.window_manager.clone(),
132+
130133
pruning_lock,
131134
task_manager: BlockTaskDependencyManager::new(),
132135
notification_root,
133136
counters,
134-
storage_mass_activation,
137+
storage_mass_activation: params.storage_mass_activation,
135138
}
136139
}
137140

consensus/src/pipeline/header_processor/pre_pow_validation.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ impl HeaderProcessor {
3535
ctx.mergeset_non_daa = Some(daa_window.mergeset_non_daa);
3636

3737
if header.bits != expected_bits {
38-
return Err(RuleError::UnexpectedDifficulty(header.bits, expected_bits));
38+
return Err(RuleError::UnexpectedDifficulty(header.hash, header.bits, expected_bits));
3939
}
4040

4141
ctx.block_window_for_difficulty = Some(daa_window.window);

consensus/src/pipeline/virtual_processor/processor.rs

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use crate::{
1616
stores::{
1717
acceptance_data::{AcceptanceDataStoreReader, DbAcceptanceDataStore},
1818
block_transactions::{BlockTransactionsStoreReader, DbBlockTransactionsStore},
19+
block_window_cache::BlockWindowCacheStore,
1920
daa::DbDaaStore,
2021
depth::{DbDepthStore, DepthStoreReader},
2122
ghostdag::{DbGhostdagStore, GhostdagData, GhostdagStoreReader},
@@ -76,6 +77,7 @@ use kaspa_database::prelude::{StoreError, StoreResultEmptyTuple, StoreResultExte
7677
use kaspa_hashes::Hash;
7778
use kaspa_muhash::MuHash;
7879
use kaspa_notify::{events::EventType, notifier::Notify};
80+
use once_cell::unsync::Lazy;
7981

8082
use super::errors::{PruningImportError, PruningImportResult};
8183
use crossbeam_channel::{Receiver as CrossbeamReceiver, Sender as CrossbeamSender};
@@ -149,6 +151,10 @@ pub struct VirtualStateProcessor {
149151
pub(super) parents_manager: DbParentsManager,
150152
pub(super) depth_manager: DbBlockDepthManager,
151153

154+
// block window caches
155+
pub(super) block_window_cache_for_difficulty: Arc<BlockWindowCacheStore>,
156+
pub(super) block_window_cache_for_past_median_time: Arc<BlockWindowCacheStore>,
157+
152158
// Pruning lock
153159
pruning_lock: SessionLock,
154160

@@ -206,6 +212,9 @@ impl VirtualStateProcessor {
206212
pruning_utxoset_stores: storage.pruning_utxoset_stores.clone(),
207213
lkg_virtual_state: storage.lkg_virtual_state.clone(),
208214

215+
block_window_cache_for_difficulty: storage.block_window_cache_for_difficulty.clone(),
216+
block_window_cache_for_past_median_time: storage.block_window_cache_for_past_median_time.clone(),
217+
209218
ghostdag_manager: services.ghostdag_manager.clone(),
210219
reachability_service: services.reachability_service.clone(),
211220
relations_service: services.relations_service.clone(),
@@ -291,6 +300,10 @@ impl VirtualStateProcessor {
291300

292301
let sink_multiset = self.utxo_multisets_store.get(new_sink).unwrap();
293302
let chain_path = self.dag_traversal_manager.calculate_chain_path(prev_sink, new_sink, None);
303+
let sink_ghostdag_data = Lazy::new(|| self.ghostdag_store.get_data(new_sink).unwrap());
304+
// Cache the DAA and Median time windows of the sink for future use, as well as prepare for virtual's window calculations
305+
self.cache_sink_windows(new_sink, prev_sink, &sink_ghostdag_data);
306+
294307
let new_virtual_state = self
295308
.calculate_and_commit_virtual_state(
296309
virtual_read,
@@ -302,12 +315,19 @@ impl VirtualStateProcessor {
302315
)
303316
.expect("all possible rule errors are unexpected here");
304317

318+
let compact_sink_ghostdag_data = if let Some(sink_ghostdag_data) = Lazy::get(&sink_ghostdag_data) {
319+
// If we had to retrieve the full data, we convert it to compact
320+
sink_ghostdag_data.to_compact()
321+
} else {
322+
// Else we query the compact data directly.
323+
self.ghostdag_store.get_compact_data(new_sink).unwrap()
324+
};
325+
305326
// Update the pruning processor about the virtual state change
306-
let sink_ghostdag_data = self.ghostdag_store.get_compact_data(new_sink).unwrap();
307327
// Empty the channel before sending the new message. If pruning processor is busy, this step makes sure
308328
// the internal channel does not grow with no need (since we only care about the most recent message)
309329
let _consume = self.pruning_receiver.try_iter().count();
310-
self.pruning_sender.send(PruningProcessingMessage::Process { sink_ghostdag_data }).unwrap();
330+
self.pruning_sender.send(PruningProcessingMessage::Process { sink_ghostdag_data: compact_sink_ghostdag_data }).unwrap();
311331

312332
// Emit notifications
313333
let accumulated_diff = Arc::new(accumulated_diff);
@@ -319,7 +339,7 @@ impl VirtualStateProcessor {
319339
.notify(Notification::UtxosChanged(UtxosChangedNotification::new(accumulated_diff, virtual_parents)))
320340
.expect("expecting an open unbounded channel");
321341
self.notification_root
322-
.notify(Notification::SinkBlueScoreChanged(SinkBlueScoreChangedNotification::new(sink_ghostdag_data.blue_score)))
342+
.notify(Notification::SinkBlueScoreChanged(SinkBlueScoreChangedNotification::new(compact_sink_ghostdag_data.blue_score)))
323343
.expect("expecting an open unbounded channel");
324344
self.notification_root
325345
.notify(Notification::VirtualDaaScoreChanged(VirtualDaaScoreChangedNotification::new(new_virtual_state.daa_score)))
@@ -540,6 +560,26 @@ impl VirtualStateProcessor {
540560
drop(selected_chain_write);
541561
}
542562

563+
/// Caches the DAA and Median time windows of the sink block (if needed). Following, virtual's window calculations will
564+
/// naturally hit the cache finding the sink's windows and building upon them.
565+
fn cache_sink_windows(&self, new_sink: Hash, prev_sink: Hash, sink_ghostdag_data: &impl Deref<Target = Arc<GhostdagData>>) {
566+
// We expect that the `new_sink` is cached (or some close-enough ancestor thereof) if it is equal to the `prev_sink`,
567+
// Hence we short-circuit the check of the keys in such cases, thereby reducing the access of the read-lock
568+
if new_sink != prev_sink {
569+
// this is only important for ibd performance, as we incur expensive cache misses otherwise.
570+
// this occurs because we cannot rely on header processing to pre-cache in this scenario.
571+
if !self.block_window_cache_for_difficulty.contains_key(&new_sink) {
572+
self.block_window_cache_for_difficulty
573+
.insert(new_sink, self.window_manager.block_daa_window(sink_ghostdag_data.deref()).unwrap().window);
574+
};
575+
576+
if !self.block_window_cache_for_past_median_time.contains_key(&new_sink) {
577+
self.block_window_cache_for_past_median_time
578+
.insert(new_sink, self.window_manager.calc_past_median_time(sink_ghostdag_data.deref()).unwrap().1);
579+
};
580+
}
581+
}
582+
543583
/// Returns the max number of tips to consider as virtual parents in a single virtual resolve operation.
544584
///
545585
/// Guaranteed to be `>= self.max_block_parents`

0 commit comments

Comments
 (0)