Skip to content

Commit 29bfea8

Browse files
committed
[DK] WIP inc
1 parent 211a258 commit 29bfea8

10 files changed

Lines changed: 858 additions & 123 deletions

File tree

‎consensus/src/consensus/services.rs‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ use crate::{
1313
},
1414
processes::{
1515
block_depth::BlockDepthManager, coinbase::CoinbaseManager, dagknight::DagknightCounters,
16-
dagknight::protocol::DagknightExecutor, ghostdag::protocol::GhostdagManager,
17-
parents_builder::ParentsManager, pruning::PruningPointManager, pruning_proof::PruningProofManager,
18-
sync::SyncManager, transaction_validator::TransactionValidator, traversal_manager::DagTraversalManager,
19-
window::SampledWindowManager,
16+
dagknight::protocol::DagknightExecutor, dagknight::umc_cascade_persistence::DbUmcCascadeStore,
17+
ghostdag::protocol::GhostdagManager, parents_builder::ParentsManager, pruning::PruningPointManager,
18+
pruning_proof::PruningProofManager, sync::SyncManager, transaction_validator::TransactionValidator,
19+
traversal_manager::DagTraversalManager, window::SampledWindowManager,
2020
},
2121
};
2222
use kaspa_consensus_core::mass::MassCalculator;
@@ -52,7 +52,7 @@ pub type DbPruningPointManager = PruningPointManager<
5252
pub type DbBlockDepthManager = BlockDepthManager<DbDepthStore, DbReachabilityStore, DbGhostdagStore, DbHeadersStore>;
5353
pub type DbParentsManager = ParentsManager<DbHeadersStore, DbReachabilityStore, MTRelationsService<DbRelationsStore>>;
5454
pub type DbDagknightExecutor =
55-
DagknightExecutor<DbDagknightStore, DbHeadersStore, MTRelationsService<DbRelationsStore>, DbReachabilityStore>;
55+
DagknightExecutor<DbDagknightStore, DbHeadersStore, MTRelationsService<DbRelationsStore>, DbUmcCascadeStore, DbReachabilityStore>;
5656

5757
pub struct ConsensusServices {
5858
// Underlying storage
@@ -149,6 +149,7 @@ impl ConsensusServices {
149149
relations_store: Arc::new(RwLock::new(relations_service.clone())),
150150
reachability_service: reachability_service.clone(),
151151
counters: dagknight_counters.clone(),
152+
umc_persistence_store: storage.umc_persistence_store.clone(),
152153
});
153154

154155
let coinbase_manager = CoinbaseManager::new(

‎consensus/src/consensus/storage.rs‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,15 @@ use crate::{
2424
utxo_multisets::DbUtxoMultisetsStore,
2525
virtual_state::{LkgVirtualState, VirtualStores},
2626
},
27-
processes::{ghostdag::ordering::SortableBlock, reachability::inquirer as reachability, relations},
27+
processes::{
28+
dagknight::umc_cascade_persistence::DbUmcCascadeStore, ghostdag::ordering::SortableBlock,
29+
reachability::inquirer as reachability, relations,
30+
},
2831
};
2932

3033
use super::cache_policy_builder::CachePolicyBuilder as PolicyBuilder;
3134
use kaspa_consensus_core::{BlockHashSet, blockstatus::BlockStatus};
32-
use kaspa_database::registry::DatabaseStorePrefixes;
35+
use kaspa_database::{prelude::CachePolicy, registry::DatabaseStorePrefixes};
3336
use kaspa_hashes::Hash;
3437
use parking_lot::RwLock;
3538
use std::{ops::DerefMut, sync::Arc};
@@ -50,6 +53,7 @@ pub struct ConsensusStorage {
5053
pub virtual_stores: Arc<RwLock<VirtualStores>>,
5154
pub selected_chain_store: Arc<RwLock<DbSelectedChainStore>>,
5255
pub dagknight_store: Option<Arc<DbDagknightStore>>,
56+
pub umc_persistence_store: Arc<DbUmcCascadeStore>,
5357

5458
// Append-only stores
5559
pub topology_ghostdag_store: Arc<DbGhostdagStore>,
@@ -249,6 +253,7 @@ impl ConsensusStorage {
249253
let dagknight_builder = PolicyBuilder::new().bytes_budget(scaled(5_000_000)).tracked_bytes();
250254
// TODO[DK]: Use a config or ForkActivation to gate this
251255
let dagknight_store = Some(Arc::new(DbDagknightStore::new(db.clone(), dagknight_builder.build())));
256+
let umc_persistence_store = Arc::new(DbUmcCascadeStore::new(db.clone(), CachePolicy::Count(256)));
252257

253258
Arc::new(Self {
254259
_db: db,
@@ -277,6 +282,7 @@ impl ConsensusStorage {
277282
block_window_cache_for_past_median_time,
278283
lkg_virtual_state,
279284
dagknight_store,
285+
umc_persistence_store,
280286
})
281287
}
282288
}

‎consensus/src/processes/dagknight/appendable_segment_tree_api.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,7 @@ where
6262
fn flip_to_positive(&mut self, leaf: T);
6363

6464
fn score(&mut self, leaf: T) -> S;
65+
66+
/// Returns all leaves in left-to-right (position) order.
67+
fn leaves(&self) -> Vec<T>;
6568
}

‎consensus/src/processes/dagknight/appendable_segment_tree_impl.rs‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,20 @@ where
227227
self.point_score(ROOT_NODE, self.full_leaf_range(), target_position)
228228
}
229229

230+
/// Returns all leaves in left-to-right (position) order.
231+
pub fn leaves(&self) -> Vec<T> {
232+
(0..self.len)
233+
.map(|pos| {
234+
let leaf = &self.nodes[self.leaf_node(pos)];
235+
if let Some(candidate) = leaf.min_positive {
236+
candidate.leaf
237+
} else {
238+
leaf.max_negative.expect("occupied leaf must have a candidate").leaf
239+
}
240+
})
241+
.collect()
242+
}
243+
230244
// ---------------------------------------------------------------------
231245
// Internal queries and bucket transitions
232246
// ---------------------------------------------------------------------
@@ -479,6 +493,10 @@ where
479493
fn score(&mut self, leaf: T) -> S {
480494
AppendableSegmentTree::score(self, leaf)
481495
}
496+
497+
fn leaves(&self) -> Vec<T> {
498+
AppendableSegmentTree::leaves(self)
499+
}
482500
}
483501

484502
#[cfg(test)]
@@ -648,4 +666,42 @@ mod tests {
648666
assert_eq!(tree.score(1), -5);
649667
assert_eq!(tree.score(2), 0);
650668
}
669+
670+
#[test]
671+
fn test_leaves_returns_in_position_order() {
672+
let mut tree = AppendableSegmentTree::<u64>::new();
673+
tree.append_leaf(10, 1);
674+
tree.append_leaf(20, 2);
675+
tree.append_leaf(30, 3);
676+
tree.append_leaf(40, 4);
677+
tree.append_leaf(50, 5);
678+
679+
let leaves = tree.leaves();
680+
assert_eq!(leaves, vec![10, 20, 30, 40, 50]);
681+
}
682+
683+
#[test]
684+
fn test_leaves_after_prefix_add() {
685+
let mut tree = AppendableSegmentTree::<u64>::new();
686+
tree.append_leaf(1, 10);
687+
tree.append_leaf(2, 20);
688+
tree.append_leaf(3, 30);
689+
690+
tree.prefix_add(2, -5);
691+
692+
// Leaves should still be in position order regardless of score changes
693+
let leaves = tree.leaves();
694+
assert_eq!(leaves, vec![1, 2, 3]);
695+
696+
// Verify scores are correct after prefix add
697+
assert_eq!(tree.score(1), 5);
698+
assert_eq!(tree.score(2), 15);
699+
assert_eq!(tree.score(3), 30);
700+
}
701+
702+
#[test]
703+
fn test_leaves_empty_tree() {
704+
let tree = AppendableSegmentTree::<u64>::new();
705+
assert!(tree.leaves().is_empty());
706+
}
651707
}

‎consensus/src/processes/dagknight/mod.rs‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub mod protocol;
1616
pub mod rank_search;
1717
pub mod tie_breaking;
1818
pub mod umc_cascade;
19+
pub mod umc_cascade_persistence;
1920

2021
pub struct GroupMetadata {
2122
conflict_genesis: Hash,
@@ -34,6 +35,16 @@ pub struct DagknightCounters {
3435
total_cascade_flips: AtomicU64,
3536
/// Maximum flips in a single cascade run
3637
max_cascade_flips: AtomicU64,
38+
/// Number of UMC cascade calls that started from a persisted checkpoint
39+
checkpoint_from_checkpoint: AtomicU64,
40+
/// Number of UMC cascade calls that started from scratch
41+
checkpoint_from_scratch: AtomicU64,
42+
/// Total estimated effort saved across all checkpoint loads,
43+
/// representing estimated blue blocks skipped by checkpointing
44+
checkpoint_estimated_effort_saved: AtomicU64,
45+
/// Total estimated effort across all UMC calls (sum of virtual_gd.blue_score).
46+
/// Used as denominator for effort_saved percentage.
47+
checkpoint_estimated_effort_total: AtomicU64,
3748
/// Baseline true, cascade false
3849
#[cfg(feature = "baseline-debugging")]
3950
baseline_true_cascade_false: AtomicU64,
@@ -55,6 +66,10 @@ impl DagknightCounters {
5566
total_voting_blocks: AtomicU64::new(0),
5667
total_cascade_flips: AtomicU64::new(0),
5768
max_cascade_flips: AtomicU64::new(0),
69+
checkpoint_from_checkpoint: AtomicU64::new(0),
70+
checkpoint_from_scratch: AtomicU64::new(0),
71+
checkpoint_estimated_effort_saved: AtomicU64::new(0),
72+
checkpoint_estimated_effort_total: AtomicU64::new(0),
5873
#[cfg(feature = "baseline-debugging")]
5974
baseline_true_cascade_false: AtomicU64::new(0),
6075
#[cfg(feature = "baseline-debugging")]
@@ -90,13 +105,28 @@ impl DagknightCounters {
90105
}
91106
}
92107

108+
/// Record checkpoint persistence statistics from a single run_cascade call.
109+
pub fn record_checkpoint_stats(&self, from_checkpoint: bool, estimated_effort_saved: u64, estimated_effort_total: u64) {
110+
if from_checkpoint {
111+
self.checkpoint_from_checkpoint.fetch_add(1, Ordering::Relaxed);
112+
} else {
113+
self.checkpoint_from_scratch.fetch_add(1, Ordering::Relaxed);
114+
}
115+
self.checkpoint_estimated_effort_saved.fetch_add(estimated_effort_saved, Ordering::Relaxed);
116+
self.checkpoint_estimated_effort_total.fetch_add(estimated_effort_total, Ordering::Relaxed);
117+
}
118+
93119
/// Take a point-in-time snapshot of all counters.
94120
pub fn snapshot(&self) -> DagknightCountersSnapshot {
95121
DagknightCountersSnapshot {
96122
total_calls: self.total_calls.load(Ordering::Relaxed),
97123
total_voting_blocks: self.total_voting_blocks.load(Ordering::Relaxed),
98124
total_cascade_flips: self.total_cascade_flips.load(Ordering::Relaxed),
99125
max_cascade_flips: self.max_cascade_flips.load(Ordering::Relaxed),
126+
checkpoint_from_checkpoint: self.checkpoint_from_checkpoint.load(Ordering::Relaxed),
127+
checkpoint_from_scratch: self.checkpoint_from_scratch.load(Ordering::Relaxed),
128+
checkpoint_estimated_effort_saved: self.checkpoint_estimated_effort_saved.load(Ordering::Relaxed),
129+
checkpoint_estimated_effort_total: self.checkpoint_estimated_effort_total.load(Ordering::Relaxed),
100130
#[cfg(feature = "baseline-debugging")]
101131
baseline_true_cascade_false: self.baseline_true_cascade_false.load(Ordering::Relaxed),
102132
#[cfg(feature = "baseline-debugging")]
@@ -112,6 +142,14 @@ pub struct DagknightCountersSnapshot {
112142
pub total_voting_blocks: u64,
113143
pub total_cascade_flips: u64,
114144
pub max_cascade_flips: u64,
145+
/// Number of UMC cascade calls that started from a persisted checkpoint
146+
pub checkpoint_from_checkpoint: u64,
147+
/// Number of UMC cascade calls that started from scratch
148+
pub checkpoint_from_scratch: u64,
149+
/// Total estimated effort saved across all checkpoint loads
150+
pub checkpoint_estimated_effort_saved: u64,
151+
/// Total estimated effort across all UMC calls (sum of virtual_gd.blue_score)
152+
pub checkpoint_estimated_effort_total: u64,
115153
#[cfg(feature = "baseline-debugging")]
116154
pub baseline_true_cascade_false: u64,
117155
#[cfg(feature = "baseline-debugging")]
@@ -129,6 +167,21 @@ impl DagknightCountersSnapshot {
129167
if self.total_calls == 0 { 0.0 } else { self.total_cascade_flips as f64 / self.total_calls as f64 }
130168
}
131169

170+
/// Percentage of UMC calls that used a checkpoint.
171+
pub fn checkpoint_hit_rate(&self) -> f64 {
172+
let total = self.checkpoint_from_checkpoint + self.checkpoint_from_scratch;
173+
if total == 0 { 0.0 } else { self.checkpoint_from_checkpoint as f64 / total as f64 * 100.0 }
174+
}
175+
176+
/// Percentage of blue work saved by checkpointing relative to total blue work in conflict zones.
177+
pub fn checkpoint_effort_saved(&self) -> f64 {
178+
if self.checkpoint_estimated_effort_total == 0 {
179+
0.0
180+
} else {
181+
self.checkpoint_estimated_effort_saved as f64 / self.checkpoint_estimated_effort_total as f64 * 100.0
182+
}
183+
}
184+
132185
/// Percentage of calls where baseline accepted and cascade rejected.
133186
#[cfg(feature = "baseline-debugging")]
134187
pub fn baseline_true_cascade_false_percentage(&self) -> f64 {
@@ -151,6 +204,14 @@ impl std::ops::Sub for &DagknightCountersSnapshot {
151204
total_voting_blocks: self.total_voting_blocks.saturating_sub(rhs.total_voting_blocks),
152205
total_cascade_flips: self.total_cascade_flips.saturating_sub(rhs.total_cascade_flips),
153206
max_cascade_flips: 0, // max doesn't subtract meaningfully
207+
checkpoint_from_checkpoint: self.checkpoint_from_checkpoint.saturating_sub(rhs.checkpoint_from_checkpoint),
208+
checkpoint_from_scratch: self.checkpoint_from_scratch.saturating_sub(rhs.checkpoint_from_scratch),
209+
checkpoint_estimated_effort_saved: self
210+
.checkpoint_estimated_effort_saved
211+
.saturating_sub(rhs.checkpoint_estimated_effort_saved),
212+
checkpoint_estimated_effort_total: self
213+
.checkpoint_estimated_effort_total
214+
.saturating_sub(rhs.checkpoint_estimated_effort_total),
154215
#[cfg(feature = "baseline-debugging")]
155216
baseline_true_cascade_false: self.baseline_true_cascade_false.saturating_sub(rhs.baseline_true_cascade_false),
156217
#[cfg(feature = "baseline-debugging")]

0 commit comments

Comments
 (0)