Skip to content

Commit 5f2e509

Browse files
committed
KIP 7 implementation
1 parent 3bc2844 commit 5f2e509

File tree

12 files changed

+162
-43
lines changed

12 files changed

+162
-43
lines changed

components/consensusmanager/src/session.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use kaspa_consensus_core::{
88
block::Block,
99
blockstatus::BlockStatus,
1010
daa_score_timestamp::DaaScoreTimestamp,
11-
errors::consensus::ConsensusResult,
11+
errors::{consensus::ConsensusResult, pruning::PruningImportResult},
1212
header::Header,
1313
pruning::{PruningPointProof, PruningPointTrustedData, PruningPointsList},
1414
trusted::{ExternalGhostdagData, TrustedBlock},
@@ -437,8 +437,8 @@ impl ConsensusSessionOwned {
437437
self.clone().spawn_blocking(move |c| c.validate_pruning_points()).await
438438
}
439439

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
440+
pub async fn async_next_good_finality_point(&self, pp_list: PruningPointsList) -> PruningImportResult<Hash> {
441+
self.clone().spawn_blocking(move |c| c.next_good_finality_point(pp_list)).await
442442
}
443443

444444
pub async fn async_creation_timestamp(&self) -> u64 {

consensus/core/src/api/mod.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,12 @@ pub trait ConsensusApi: Send + Sync {
207207
unimplemented!()
208208
}
209209

210-
fn apply_pruning_proof(&self, proof: PruningPointProof, trusted_set: &[TrustedBlock]) -> PruningImportResult<()> {
210+
fn apply_pruning_proof(
211+
&self,
212+
proof: PruningPointProof,
213+
trusted_set: &[TrustedBlock],
214+
good_finality_point: Hash,
215+
) -> PruningImportResult<()> {
211216
unimplemented!()
212217
}
213218

@@ -351,7 +356,7 @@ pub trait ConsensusApi: Send + Sync {
351356
unimplemented!()
352357
}
353358

354-
fn are_pruning_points_violating_finality(&self, pp_list: PruningPointsList) -> bool {
359+
fn next_good_finality_point(&self, pp_list: PruningPointsList) -> PruningImportResult<Hash> {
355360
unimplemented!()
356361
}
357362

@@ -362,6 +367,10 @@ pub trait ConsensusApi: Send + Sync {
362367
fn finality_point(&self) -> Hash {
363368
unimplemented!()
364369
}
370+
371+
fn get_good_finality_point(&self) -> Hash {
372+
unimplemented!()
373+
}
365374
}
366375

367376
pub type DynConsensus = Arc<dyn ConsensusApi>;

consensus/core/src/errors/pruning.rs

+3
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ pub enum PruningImportError {
5959

6060
#[error("process exit was initiated while validating pruning point proof")]
6161
PruningValidationInterrupted,
62+
63+
#[error("pruning point list violates finality")]
64+
PruningPointListViolatesFinality,
6265
}
6366

6467
pub type PruningImportResult<T> = std::result::Result<T, PruningImportError>;

consensus/src/consensus/mod.rs

+19-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use crate::{
1717
acceptance_data::AcceptanceDataStoreReader,
1818
block_transactions::BlockTransactionsStoreReader,
1919
ghostdag::{GhostdagData, GhostdagStoreReader},
20+
good_finality_point::GoodFinalityPointStoreReader,
2021
headers::{CompactHeaderData, HeaderStoreReader},
2122
headers_selected_tip::HeadersSelectedTipStoreReader,
2223
past_pruning_points::PastPruningPointsStoreReader,
@@ -271,6 +272,7 @@ impl Consensus {
271272
pruning_lock.clone(),
272273
notification_root.clone(),
273274
counters.clone(),
275+
creation_timestamp,
274276
));
275277

276278
let pruning_processor = Arc::new(PruningProcessor::new(
@@ -760,8 +762,13 @@ impl ConsensusApi for Consensus {
760762
self.services.pruning_proof_manager.validate_pruning_point_proof(proof)
761763
}
762764

763-
fn apply_pruning_proof(&self, proof: PruningPointProof, trusted_set: &[TrustedBlock]) -> PruningImportResult<()> {
764-
self.services.pruning_proof_manager.apply_proof(proof, trusted_set)
765+
fn apply_pruning_proof(
766+
&self,
767+
proof: PruningPointProof,
768+
trusted_set: &[TrustedBlock],
769+
good_finality_point: Hash,
770+
) -> PruningImportResult<()> {
771+
self.services.pruning_proof_manager.apply_proof(proof, trusted_set, good_finality_point)
765772
}
766773

767774
fn import_pruning_points(&self, pruning_points: PruningPointsList) {
@@ -1031,8 +1038,8 @@ impl ConsensusApi for Consensus {
10311038
}
10321039
}
10331040

1034-
fn are_pruning_points_violating_finality(&self, pp_list: PruningPointsList) -> bool {
1035-
self.virtual_processor.are_pruning_points_violating_finality(pp_list)
1041+
fn next_good_finality_point(&self, pp_list: PruningPointsList) -> PruningImportResult<Hash> {
1042+
self.virtual_processor.next_good_finality_point(pp_list)
10361043
}
10371044

10381045
fn creation_timestamp(&self) -> u64 {
@@ -1042,4 +1049,12 @@ impl ConsensusApi for Consensus {
10421049
fn finality_point(&self) -> Hash {
10431050
self.virtual_processor.virtual_finality_point(&self.lkg_virtual_state.load().ghostdag_data, self.pruning_point())
10441051
}
1052+
1053+
fn get_good_finality_point(&self) -> Hash {
1054+
if self.virtual_processor.is_consensus_mature() {
1055+
self.pruning_point()
1056+
} else {
1057+
self.storage.good_finality_point_store.read().get().unwrap()
1058+
}
1059+
}
10451060
}

consensus/src/consensus/storage.rs

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::{
77
daa::DbDaaStore,
88
depth::DbDepthStore,
99
ghostdag::{CompactGhostdagData, DbGhostdagStore},
10+
good_finality_point::DbGoodFinalityPointStore,
1011
headers::{CompactHeaderData, DbHeadersStore},
1112
headers_selected_tip::DbHeadersSelectedTipStore,
1213
past_pruning_points::DbPastPruningPointsStore,
@@ -48,6 +49,7 @@ pub struct ConsensusStorage {
4849
pub pruning_utxoset_stores: Arc<RwLock<PruningUtxosetStores>>,
4950
pub virtual_stores: Arc<RwLock<VirtualStores>>,
5051
pub selected_chain_store: Arc<RwLock<DbSelectedChainStore>>,
52+
pub good_finality_point_store: Arc<RwLock<DbGoodFinalityPointStore>>,
5153

5254
// Append-only stores
5355
pub ghostdag_stores: Arc<Vec<Arc<DbGhostdagStore>>>,
@@ -235,6 +237,8 @@ impl ConsensusStorage {
235237
let virtual_stores =
236238
Arc::new(RwLock::new(VirtualStores::new(db.clone(), lkg_virtual_state.clone(), utxo_set_builder.build())));
237239

240+
let good_finality_point_store = Arc::new(RwLock::new(DbGoodFinalityPointStore::new(db.clone())));
241+
238242
// Ensure that reachability stores are initialized
239243
reachability::init(reachability_store.write().deref_mut()).unwrap();
240244
relations::init(reachability_relations_store.write().deref_mut());
@@ -264,6 +268,7 @@ impl ConsensusStorage {
264268
block_window_cache_for_difficulty,
265269
block_window_cache_for_past_median_time,
266270
lkg_virtual_state,
271+
good_finality_point_store,
267272
})
268273
}
269274
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use kaspa_database::prelude::StoreResult;
2+
use kaspa_database::prelude::DB;
3+
use kaspa_database::prelude::{BatchDbWriter, CachedDbItem, DirectDbWriter};
4+
use kaspa_database::registry::DatabaseStorePrefixes;
5+
use kaspa_hashes::Hash;
6+
use rocksdb::WriteBatch;
7+
use std::sync::Arc;
8+
9+
/// Reader API for `SelectedTipStore`.
10+
pub trait GoodFinalityPointStoreReader {
11+
fn get(&self) -> StoreResult<Hash>;
12+
}
13+
14+
pub trait GoodFinalityPointStore: GoodFinalityPointStoreReader {
15+
fn set(&mut self, hash: Hash) -> StoreResult<()>;
16+
}
17+
18+
/// A DB + cache implementation of `GoodFinalityPointStore` trait
19+
#[derive(Clone)]
20+
pub struct DbGoodFinalityPointStore {
21+
db: Arc<DB>,
22+
access: CachedDbItem<Hash>,
23+
}
24+
25+
impl DbGoodFinalityPointStore {
26+
pub fn new(db: Arc<DB>) -> Self {
27+
Self { db: Arc::clone(&db), access: CachedDbItem::new(db, DatabaseStorePrefixes::GoodFinalityPoint.into()) }
28+
}
29+
30+
pub fn clone_with_new_cache(&self) -> Self {
31+
Self::new(Arc::clone(&self.db))
32+
}
33+
34+
pub fn set_batch(&mut self, batch: &mut WriteBatch, hash: Hash) -> StoreResult<()> {
35+
self.access.write(BatchDbWriter::new(batch), &hash)
36+
}
37+
}
38+
39+
impl GoodFinalityPointStoreReader for DbGoodFinalityPointStore {
40+
fn get(&self) -> StoreResult<Hash> {
41+
self.access.read()
42+
}
43+
}
44+
45+
impl GoodFinalityPointStore for DbGoodFinalityPointStore {
46+
fn set(&mut self, hash: Hash) -> StoreResult<()> {
47+
self.access.write(DirectDbWriter::new(&self.db), &hash)
48+
}
49+
}

consensus/src/model/stores/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub mod children;
55
pub mod daa;
66
pub mod depth;
77
pub mod ghostdag;
8+
pub mod good_finality_point;
89
pub mod headers;
910
pub mod headers_selected_tip;
1011
pub mod past_pruning_points;

consensus/src/pipeline/virtual_processor/processor.rs

+47-24
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::{
1919
daa::DbDaaStore,
2020
depth::{DbDepthStore, DepthStoreReader},
2121
ghostdag::{DbGhostdagStore, GhostdagData, GhostdagStoreReader},
22+
good_finality_point::{DbGoodFinalityPointStore, GoodFinalityPointStore, GoodFinalityPointStoreReader},
2223
headers::{DbHeadersStore, HeaderStoreReader},
2324
past_pruning_points::DbPastPruningPointsStore,
2425
pruning::{DbPruningStore, PruningStoreReader},
@@ -113,6 +114,8 @@ pub struct VirtualStateProcessor {
113114
pub(super) max_block_parents: u8,
114115
pub(super) mergeset_size_limit: u64,
115116
pub(super) pruning_depth: u64,
117+
consensus_creation_time: u64,
118+
finality_duration: u64,
116119

117120
// Stores
118121
pub(super) statuses_store: Arc<RwLock<DbStatusesStore>>,
@@ -133,6 +136,8 @@ pub struct VirtualStateProcessor {
133136
pub(super) virtual_stores: Arc<RwLock<VirtualStores>>,
134137
pub(super) pruning_utxoset_stores: Arc<RwLock<PruningUtxosetStores>>,
135138

139+
pub(super) good_finality_point_store: Arc<RwLock<DbGoodFinalityPointStore>>,
140+
136141
/// The "last known good" virtual state. To be used by any logic which does not want to wait
137142
/// for a possible virtual state write to complete but can rather settle with the last known state
138143
pub lkg_virtual_state: LkgVirtualState,
@@ -176,6 +181,7 @@ impl VirtualStateProcessor {
176181
pruning_lock: SessionLock,
177182
notification_root: Arc<ConsensusNotificationRoot>,
178183
counters: Arc<ProcessingCounters>,
184+
consensus_creation_time: u64,
179185
) -> Self {
180186
Self {
181187
receiver,
@@ -205,6 +211,7 @@ impl VirtualStateProcessor {
205211
virtual_stores: storage.virtual_stores.clone(),
206212
pruning_utxoset_stores: storage.pruning_utxoset_stores.clone(),
207213
lkg_virtual_state: storage.lkg_virtual_state.clone(),
214+
good_finality_point_store: storage.good_finality_point_store.clone(),
208215

209216
ghostdag_manager: services.ghostdag_primary_manager.clone(),
210217
reachability_service: services.reachability_service.clone(),
@@ -221,6 +228,8 @@ impl VirtualStateProcessor {
221228
notification_root,
222229
counters,
223230
storage_mass_activation_daa_score: params.storage_mass_activation_daa_score,
231+
consensus_creation_time,
232+
finality_duration: params.finality_duration(),
224233
}
225234
}
226235

@@ -1027,6 +1036,7 @@ impl VirtualStateProcessor {
10271036
pruning_point_write.set_history_root(&mut batch, self.genesis.hash).unwrap();
10281037
pruning_utxoset_write.set_utxoset_position(&mut batch, self.genesis.hash).unwrap();
10291038
self.db.write(batch).unwrap();
1039+
self.good_finality_point_store.write().set(self.genesis.hash).unwrap(); // TODO: Wrong lock behavior?
10301040
drop(pruning_point_write);
10311041
drop(pruning_utxoset_write);
10321042
}
@@ -1132,33 +1142,46 @@ impl VirtualStateProcessor {
11321142
Ok(())
11331143
}
11341144

1135-
pub fn are_pruning_points_violating_finality(&self, pp_list: PruningPointsList) -> bool {
1136-
// Ideally we would want to check if the last known pruning point has the finality point
1137-
// in its chain, but in some cases it's impossible: let `lkp` be the last known pruning
1138-
// point from the list, and `fup` be the first unknown pruning point (the one following `lkp`).
1139-
// fup.blue_score - lkp.blue_score ≈ finality_depth (±k), so it's possible for `lkp` not to
1140-
// have the finality point in its past. So we have no choice but to check if `lkp`
1141-
// has `finality_point.finality_point` in its chain (in the worst case `fup` is one block
1142-
// above the current finality point, and in this case `lkp` will be a few blocks above the
1143-
// finality_point.finality_point), meaning this function can only detect finality violations
1144-
// in depth of 2*finality_depth, and can give false negatives for smaller finality violations.
1145-
let current_pp = self.pruning_point_store.read().pruning_point().unwrap();
1146-
let vf = self.virtual_finality_point(&self.lkg_virtual_state.load().ghostdag_data, current_pp);
1147-
let vff = self.depth_manager.calc_finality_point(&self.ghostdag_primary_store.get_data(vf).unwrap(), current_pp);
1148-
1149-
let last_known_pp = pp_list.iter().rev().find(|pp| match self.statuses_store.read().get(pp.hash).unwrap_option() {
1150-
Some(status) => status.is_valid(),
1151-
None => false,
1152-
});
1153-
1154-
if let Some(last_known_pp) = last_known_pp {
1155-
!self.reachability_service.is_chain_ancestor_of(vff, last_known_pp.hash)
1145+
pub fn next_good_finality_point(&self, pp_list: PruningPointsList) -> PruningImportResult<Hash> {
1146+
if self.is_consensus_mature() {
1147+
// TODO: Fix comment
1148+
// Ideally we would want to check if the last known pruning point has the finality point
1149+
// in its chain, but in some cases it's impossible: let `lkp` be the last known pruning
1150+
// point from the list, and `fup` be the first unknown pruning point (the one following `lkp`).
1151+
// fup.blue_score - lkp.blue_score ≈ finality_depth (±k), so it's possible for `lkp` not to
1152+
// have the finality point in its past. So we have no choice but to check if `lkp`
1153+
// has `finality_point.finality_point` in its chain (in the worst case `fup` is one block
1154+
// above the current finality point, and in this case `lkp` will be a few blocks above the
1155+
// finality_point.finality_point), meaning this function can only detect finality violations
1156+
// in depth of 2*finality_depth, and can give false negatives for smaller finality violations.
1157+
let current_pp = self.pruning_point_store.read().pruning_point().unwrap();
1158+
let vf = self.virtual_finality_point(&self.lkg_virtual_state.load().ghostdag_data, current_pp);
1159+
let vff = self.depth_manager.calc_finality_point(&self.ghostdag_primary_store.get_data(vf).unwrap(), current_pp);
1160+
1161+
let first_pp_in_future_of_vff = pp_list
1162+
.iter()
1163+
.map(|pp| pp.hash)
1164+
.filter(|pp| match self.statuses_store.read().get(*pp).unwrap_option() {
1165+
Some(status) => status.is_valid(),
1166+
None => false,
1167+
})
1168+
.find(|pp| self.reachability_service.is_chain_ancestor_of(vff, *pp));
1169+
1170+
first_pp_in_future_of_vff.ok_or(PruningImportError::PruningPointListViolatesFinality)
11561171
} else {
1157-
// If no pruning point is known, there's definitely a finality violation
1158-
// (normally at least genesis should be known).
1159-
true
1172+
let good_finality_point = self.good_finality_point_store.read().get().unwrap();
1173+
if pp_list.iter().map(|h| h.hash).contains(&good_finality_point) {
1174+
Ok(good_finality_point)
1175+
} else {
1176+
Err(PruningImportError::PruningPointListViolatesFinality)
1177+
}
11601178
}
11611179
}
1180+
1181+
pub fn is_consensus_mature(&self) -> bool {
1182+
// TODO: Maybe we should replace creation time with staging commitment time
1183+
unix_now() - self.consensus_creation_time > self.finality_duration
1184+
}
11621185
}
11631186

11641187
enum MergesetIncreaseResult {

consensus/src/processes/pruning_proof/mod.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ use crate::{
4242
stores::{
4343
depth::DbDepthStore,
4444
ghostdag::{DbGhostdagStore, GhostdagData, GhostdagStore, GhostdagStoreReader},
45+
good_finality_point::{DbGoodFinalityPointStore, GoodFinalityPointStore},
4546
headers::{DbHeadersStore, HeaderStore, HeaderStoreReader},
4647
headers_selected_tip::DbHeadersSelectedTipStore,
4748
past_pruning_points::{DbPastPruningPointsStore, PastPruningPointsStore},
@@ -104,6 +105,7 @@ pub struct PruningProofManager {
104105
headers_selected_tip_store: Arc<RwLock<DbHeadersSelectedTipStore>>,
105106
depth_store: Arc<DbDepthStore>,
106107
selected_chain_store: Arc<RwLock<DbSelectedChainStore>>,
108+
good_finality_point_store: Arc<RwLock<DbGoodFinalityPointStore>>,
107109

108110
ghostdag_managers: Arc<Vec<DbGhostdagManager>>,
109111
traversal_manager: DbDagTraversalManager,
@@ -154,6 +156,7 @@ impl PruningProofManager {
154156
headers_selected_tip_store: storage.headers_selected_tip_store.clone(),
155157
selected_chain_store: storage.selected_chain_store.clone(),
156158
depth_store: storage.depth_store.clone(),
159+
good_finality_point_store: storage.good_finality_point_store.clone(),
157160

158161
ghostdag_managers,
159162
traversal_manager,
@@ -199,7 +202,13 @@ impl PruningProofManager {
199202
drop(pruning_point_write);
200203
}
201204

202-
pub fn apply_proof(&self, mut proof: PruningPointProof, trusted_set: &[TrustedBlock]) -> PruningImportResult<()> {
205+
pub fn apply_proof(
206+
&self,
207+
mut proof: PruningPointProof,
208+
trusted_set: &[TrustedBlock],
209+
good_finality_point: Hash,
210+
) -> PruningImportResult<()> {
211+
self.good_finality_point_store.write().set(good_finality_point).unwrap();
203212
let pruning_point_header = proof[0].last().unwrap().clone();
204213
let pruning_point = pruning_point_header.hash;
205214

database/src/registry.rs

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ pub enum DatabaseStorePrefixes {
4141
ReachabilityTreeChildren = 30,
4242
ReachabilityFutureCoveringSet = 31,
4343

44+
GoodFinalityPoint = 32,
45+
4446
// ---- Metadata ----
4547
MultiConsensusMetadata = 124,
4648
ConsensusEntries = 125,

0 commit comments

Comments
 (0)