Skip to content

Commit caedaa2

Browse files
IBD: fix some syncer-syncee miscommunications which are more apparent with 10 bps (kaspanet#221)
* comments * extend resolution range to 8 max * do not sync missing bodies in the past of `syncer_header_selected_tip` * typos * no need to lock the pruning store throughout locator building * the two conditions can be done in one * rollback previous change (will be fixed more correctly by the coming switch HSC-> VSC) * change selected chain store from *headers* selected chain to *virtual* selected chain (wip: test fix; renaming of various variables) * fix selected-chain test by adding a way to (test-)build utxo valid blocks with specific parents * make pruning point getter non-Option * rename `headers_selected_chain` -> `virtual_selected_parent` * add temp logic for upgrading from prev DB version * get tip if high is none through the selected chain store itself * add virtual chain assertion to relevant tests * added selected_chain_store_iterator which is more idiomatic * wrap with TestBlockBuilder to avoid direct access through virtual processor * keep the pruning point read guard throughout building the locator * break if parent is missing * extend comment
1 parent 27d61e1 commit caedaa2

File tree

19 files changed

+300
-124
lines changed

19 files changed

+300
-124
lines changed

components/consensusmanager/src/session.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -256,12 +256,12 @@ impl ConsensusSessionOwned {
256256
self.clone().spawn_blocking(|c| c.get_pruning_point_proof()).await
257257
}
258258

259-
pub async fn async_create_headers_selected_chain_block_locator(
259+
pub async fn async_create_virtual_selected_chain_block_locator(
260260
&self,
261261
low: Option<Hash>,
262262
high: Option<Hash>,
263263
) -> ConsensusResult<Vec<Hash>> {
264-
self.clone().spawn_blocking(move |c| c.create_headers_selected_chain_block_locator(low, high)).await
264+
self.clone().spawn_blocking(move |c| c.create_virtual_selected_chain_block_locator(low, high)).await
265265
}
266266

267267
pub async fn async_create_block_locator_from_pruning_point(&self, high: Hash, limit: usize) -> ConsensusResult<Vec<Hash>> {
@@ -331,7 +331,7 @@ impl ConsensusSessionOwned {
331331
self.clone().spawn_blocking(move |c| c.get_missing_block_body_hashes(high)).await
332332
}
333333

334-
pub async fn async_pruning_point(&self) -> Option<Hash> {
334+
pub async fn async_pruning_point(&self) -> Hash {
335335
self.clone().spawn_blocking(|c| c.pruning_point()).await
336336
}
337337

consensus/core/src/api/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ pub trait ConsensusApi: Send + Sync {
169169
unimplemented!()
170170
}
171171

172-
fn create_headers_selected_chain_block_locator(&self, low: Option<Hash>, high: Option<Hash>) -> ConsensusResult<Vec<Hash>> {
172+
fn create_virtual_selected_chain_block_locator(&self, low: Option<Hash>, high: Option<Hash>) -> ConsensusResult<Vec<Hash>> {
173173
unimplemented!()
174174
}
175175

@@ -238,7 +238,7 @@ pub trait ConsensusApi: Send + Sync {
238238
unimplemented!()
239239
}
240240

241-
fn pruning_point(&self) -> Option<Hash> {
241+
fn pruning_point(&self) -> Hash {
242242
unimplemented!()
243243
}
244244

consensus/core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ impl HashMapCustomHasher for BlockHashSet {
7474
}
7575
}
7676

77+
#[derive(Default, Debug)]
7778
pub struct ChainPath {
7879
pub added: Vec<Hash>,
7980
pub removed: Vec<Hash>,

consensus/src/consensus/mod.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,7 @@ impl ConsensusApi for Consensus {
557557
self.services.pruning_proof_manager.get_pruning_point_proof()
558558
}
559559

560-
fn create_headers_selected_chain_block_locator(&self, low: Option<Hash>, high: Option<Hash>) -> ConsensusResult<Vec<Hash>> {
560+
fn create_virtual_selected_chain_block_locator(&self, low: Option<Hash>, high: Option<Hash>) -> ConsensusResult<Vec<Hash>> {
561561
if let Some(low) = low {
562562
self.validate_block_exists(low)?;
563563
}
@@ -566,7 +566,7 @@ impl ConsensusApi for Consensus {
566566
self.validate_block_exists(high)?;
567567
}
568568

569-
Ok(self.services.sync_manager.create_headers_selected_chain_block_locator(low, high)?)
569+
Ok(self.services.sync_manager.create_virtual_selected_chain_block_locator(low, high)?)
570570
}
571571

572572
fn pruning_point_headers(&self) -> Vec<Arc<Header>> {
@@ -652,8 +652,8 @@ impl ConsensusApi for Consensus {
652652
Ok(self.services.sync_manager.get_missing_block_body_hashes(high)?)
653653
}
654654

655-
fn pruning_point(&self) -> Option<Hash> {
656-
self.pruning_point_store.read().pruning_point().unwrap_option()
655+
fn pruning_point(&self) -> Hash {
656+
self.pruning_point_store.read().pruning_point().unwrap()
657657
}
658658

659659
fn get_daa_window(&self, hash: Hash) -> ConsensusResult<Vec<Hash>> {
@@ -673,27 +673,30 @@ impl ConsensusApi for Consensus {
673673
self.validate_block_exists(hash)?;
674674

675675
// In order to guarantee the chain height is at least k, we check that the pruning point is not genesis.
676-
if self.pruning_point().unwrap() == self.config.genesis.hash {
676+
if self.pruning_point() == self.config.genesis.hash {
677677
return Err(ConsensusError::UnexpectedPruningPoint);
678678
}
679679

680680
let mut hashes = Vec::with_capacity(self.config.params.ghostdag_k as usize);
681681
let mut current = hash;
682-
// TODO: This will crash if we don't have the data for k blocks in the past of
683-
// current. The syncee should validate it got all of the associated data.
684682
for _ in 0..=self.config.params.ghostdag_k {
685683
hashes.push(current);
686-
current = self.ghostdag_primary_store.get_selected_parent(current).unwrap();
684+
// TODO: ideally the syncee should validate it got all of the associated data up
685+
// to k blocks back and then we would be able to safely unwrap here. For now we
686+
// just break the loop, since if the data was truly missing we wouldn't accept
687+
// the staging consensus in the first place
688+
let Some(parent) = self.ghostdag_primary_store.get_selected_parent(current).unwrap_option() else { break; };
689+
current = parent;
687690
}
688691
Ok(hashes)
689692
}
690693

691694
fn create_block_locator_from_pruning_point(&self, high: Hash, limit: usize) -> ConsensusResult<Vec<Hash>> {
692695
self.validate_block_exists(high)?;
693-
694-
let pp_read_guard = self.pruning_point_store.read();
695-
let pp = pp_read_guard.pruning_point().unwrap();
696-
Ok(self.services.sync_manager.create_block_locator_from_pruning_point(high, pp, Some(limit))?)
696+
// Keep the pruning point read guard throughout building the locator
697+
let pruning_point_read = self.pruning_point_store.read();
698+
let pruning_point = pruning_point_read.pruning_point().unwrap();
699+
Ok(self.services.sync_manager.create_block_locator_from_pruning_point(high, pruning_point, Some(limit))?)
697700
}
698701

699702
fn estimate_network_hashes_per_second(&self, start_hash: Option<Hash>, window_size: usize) -> ConsensusResult<u64> {

consensus/src/consensus/test_consensus.rs

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use async_channel::Sender;
2+
use kaspa_consensus_core::coinbase::MinerData;
3+
use kaspa_consensus_core::tx::ScriptPublicKey;
24
use kaspa_consensus_core::{
35
api::ConsensusApi, block::MutableBlock, blockstatus::BlockStatus, header::Header, merkle::calc_hash_merkle_root,
46
subnets::SUBNETWORK_ID_COINBASE, tx::Transaction,
@@ -13,6 +15,7 @@ use parking_lot::RwLock;
1315
use std::future::Future;
1416
use std::{sync::Arc, thread::JoinHandle};
1517

18+
use crate::pipeline::virtual_processor::test_block_builder::TestBlockBuilder;
1619
use crate::processes::window::WindowManager;
1720
use crate::{
1821
config::Config,
@@ -34,8 +37,9 @@ use super::services::{DbDagTraversalManager, DbGhostdagManager, DbWindowManager}
3437
use super::Consensus;
3538

3639
pub struct TestConsensus {
37-
consensus: Arc<Consensus>,
3840
params: Params,
41+
consensus: Arc<Consensus>,
42+
block_builder: TestBlockBuilder,
3943
db_lifetime: DbLifetime,
4044
}
4145

@@ -44,23 +48,21 @@ impl TestConsensus {
4448
pub fn with_db(db: Arc<DB>, config: &Config, notification_sender: Sender<Notification>) -> Self {
4549
let notification_root = Arc::new(ConsensusNotificationRoot::new(notification_sender));
4650
let counters = Arc::new(ProcessingCounters::default());
47-
Self {
48-
consensus: Arc::new(Consensus::new(db, Arc::new(config.clone()), Default::default(), notification_root, counters)),
49-
params: config.params.clone(),
50-
db_lifetime: Default::default(),
51-
}
51+
let consensus = Arc::new(Consensus::new(db, Arc::new(config.clone()), Default::default(), notification_root, counters));
52+
let block_builder = TestBlockBuilder::new(consensus.virtual_processor.clone());
53+
54+
Self { params: config.params.clone(), consensus, block_builder, db_lifetime: Default::default() }
5255
}
5356

5457
/// Creates a test consensus instance based on `config` with a temp DB and the provided `notification_sender`
5558
pub fn with_notifier(config: &Config, notification_sender: Sender<Notification>) -> Self {
5659
let (db_lifetime, db) = create_temp_db();
5760
let notification_root = Arc::new(ConsensusNotificationRoot::new(notification_sender));
5861
let counters = Arc::new(ProcessingCounters::default());
59-
Self {
60-
consensus: Arc::new(Consensus::new(db, Arc::new(config.clone()), Default::default(), notification_root, counters)),
61-
params: config.params.clone(),
62-
db_lifetime,
63-
}
62+
let consensus = Arc::new(Consensus::new(db, Arc::new(config.clone()), Default::default(), notification_root, counters));
63+
let block_builder = TestBlockBuilder::new(consensus.virtual_processor.clone());
64+
65+
Self { consensus, block_builder, params: config.params.clone(), db_lifetime }
6466
}
6567

6668
/// Creates a test consensus instance based on `config` with a temp DB and no notifier
@@ -69,11 +71,10 @@ impl TestConsensus {
6971
let (dummy_notification_sender, _) = async_channel::unbounded();
7072
let notification_root = Arc::new(ConsensusNotificationRoot::new(dummy_notification_sender));
7173
let counters = Arc::new(ProcessingCounters::default());
72-
Self {
73-
consensus: Arc::new(Consensus::new(db, Arc::new(config.clone()), Default::default(), notification_root, counters)),
74-
params: config.params.clone(),
75-
db_lifetime,
76-
}
74+
let consensus = Arc::new(Consensus::new(db, Arc::new(config.clone()), Default::default(), notification_root, counters));
75+
let block_builder = TestBlockBuilder::new(consensus.virtual_processor.clone());
76+
77+
Self { consensus, block_builder, params: config.params.clone(), db_lifetime }
7778
}
7879

7980
/// Clone the inner consensus Arc. For general usage of the underlying consensus simply deref
@@ -107,6 +108,28 @@ impl TestConsensus {
107108
self.validate_and_insert_block(self.build_block_with_parents(hash, parents).to_immutable())
108109
}
109110

111+
pub fn add_utxo_valid_block_with_parents(
112+
&self,
113+
hash: Hash,
114+
parents: Vec<Hash>,
115+
txs: Vec<Transaction>,
116+
) -> impl Future<Output = BlockProcessResult<BlockStatus>> {
117+
let miner_data = MinerData::new(ScriptPublicKey::from_vec(0, vec![]), vec![]);
118+
self.validate_and_insert_block(self.build_utxo_valid_block_with_parents(hash, parents, miner_data, txs).to_immutable())
119+
}
120+
121+
pub fn build_utxo_valid_block_with_parents(
122+
&self,
123+
hash: Hash,
124+
parents: Vec<Hash>,
125+
miner_data: MinerData,
126+
txs: Vec<Transaction>,
127+
) -> MutableBlock {
128+
let mut template = self.block_builder.build_block_template_with_parents(parents, miner_data, txs).unwrap();
129+
template.block.header.hash = hash;
130+
template.block
131+
}
132+
110133
pub fn build_block_with_parents_and_transactions(
111134
&self,
112135
hash: Hash,

consensus/src/model/stores/selected_chain.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ use super::U64Key;
1717
pub trait SelectedChainStoreReader {
1818
fn get_by_hash(&self, hash: Hash) -> StoreResult<u64>;
1919
fn get_by_index(&self, index: u64) -> StoreResult<Hash>;
20+
fn get_tip(&self) -> StoreResult<(u64, Hash)>;
2021
}
2122

2223
/// Write API for `SelectedChainStore`. The set function is deliberately `mut`
2324
/// since chain index is not append-only and thus needs to be guarded.
2425
pub trait SelectedChainStore: SelectedChainStoreReader {
25-
fn apply_changes(&mut self, batch: &mut WriteBatch, changes: ChainPath) -> StoreResult<()>;
26+
fn apply_changes(&mut self, batch: &mut WriteBatch, changes: &ChainPath) -> StoreResult<()>;
2627
fn prune_below_pruning_point(&mut self, writer: impl DbWriter, pruning_point: Hash) -> StoreResult<()>;
2728
fn init_with_pruning_point(&mut self, batch: &mut WriteBatch, block: Hash) -> StoreResult<()>;
2829
}
@@ -68,22 +69,28 @@ impl SelectedChainStoreReader for DbSelectedChainStore {
6869
fn get_by_index(&self, index: u64) -> StoreResult<Hash> {
6970
self.access_hash_by_index.read(index.into())
7071
}
72+
73+
fn get_tip(&self) -> StoreResult<(u64, Hash)> {
74+
let idx = self.access_highest_index.read()?;
75+
let hash = self.access_hash_by_index.read(idx.into())?;
76+
Ok((idx, hash))
77+
}
7178
}
7279

7380
impl SelectedChainStore for DbSelectedChainStore {
74-
fn apply_changes(&mut self, batch: &mut WriteBatch, changes: ChainPath) -> StoreResult<()> {
81+
fn apply_changes(&mut self, batch: &mut WriteBatch, changes: &ChainPath) -> StoreResult<()> {
7582
let added_len = changes.added.len() as u64;
7683
let current_highest_index = self.access_highest_index.read().unwrap();
7784
let split_index = current_highest_index - changes.removed.len() as u64;
7885
let new_highest_index = added_len + split_index;
7986

80-
for to_remove in changes.removed {
87+
for to_remove in changes.removed.iter().copied() {
8188
let index = self.access_index_by_hash.read(to_remove).unwrap();
8289
self.access_index_by_hash.delete(BatchDbWriter::new(batch), to_remove).unwrap();
8390
self.access_hash_by_index.delete(BatchDbWriter::new(batch), index.into()).unwrap();
8491
}
8592

86-
for (i, to_add) in changes.added.into_iter().enumerate() {
93+
for (i, to_add) in changes.added.iter().copied().enumerate() {
8794
self.access_index_by_hash.write(BatchDbWriter::new(batch), to_add, i as u64 + split_index + 1).unwrap();
8895
self.access_hash_by_index.write(BatchDbWriter::new(batch), (i as u64 + split_index + 1).into(), to_add).unwrap();
8996
}

consensus/src/pipeline/header_processor/processor.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ use crate::{
1919
pruning::{DbPruningStore, PruningPointInfo, PruningStoreReader},
2020
reachability::{DbReachabilityStore, StagingReachabilityStore},
2121
relations::{DbRelationsStore, RelationsStoreReader},
22-
selected_chain::{DbSelectedChainStore, SelectedChainStore},
2322
statuses::{DbStatusesStore, StatusesStore, StatusesStoreBatchExtensions, StatusesStoreReader},
2423
DB,
2524
},
@@ -136,7 +135,6 @@ pub struct HeaderProcessor {
136135
pub(super) daa_excluded_store: Arc<DbDaaStore>,
137136
pub(super) headers_store: Arc<DbHeadersStore>,
138137
pub(super) headers_selected_tip_store: Arc<RwLock<DbHeadersSelectedTipStore>>,
139-
pub(super) selected_chain_store: Arc<RwLock<DbSelectedChainStore>>,
140138
pub(super) depth_store: Arc<DbDepthStore>,
141139

142140
// Managers and services
@@ -187,7 +185,6 @@ impl HeaderProcessor {
187185
headers_store: storage.headers_store.clone(),
188186
depth_store: storage.depth_store.clone(),
189187
headers_selected_tip_store: storage.headers_selected_tip_store.clone(),
190-
selected_chain_store: storage.selected_chain_store.clone(),
191188
block_window_cache_for_difficulty: storage.block_window_cache_for_difficulty.clone(),
192189
block_window_cache_for_past_median_time: storage.block_window_cache_for_past_median_time.clone(),
193190

@@ -392,18 +389,13 @@ impl HeaderProcessor {
392389
// Non-append only stores need to use write locks.
393390
// Note we need to keep the lock write guards until the batch is written.
394391
let mut hst_write = self.headers_selected_tip_store.write();
395-
let mut sc_write = self.selected_chain_store.write();
396392
let prev_hst = hst_write.get().unwrap();
397-
// We can't calculate chain path for blocks that do not have the pruning point in their chain, so we just skip them.
398393
if SortableBlock::new(ctx.hash, header.blue_work) > prev_hst
399394
&& reachability::is_chain_ancestor_of(&staging, pp, ctx.hash).unwrap()
400395
{
401396
// Hint reachability about the new tip.
402397
reachability::hint_virtual_selected_parent(&mut staging, ctx.hash).unwrap();
403398
hst_write.set_batch(&mut batch, SortableBlock::new(ctx.hash, header.blue_work)).unwrap();
404-
let mut chain_path = self.dag_traversal_manager.calculate_chain_path(prev_hst.hash, ghostdag_data[0].selected_parent);
405-
chain_path.added.push(ctx.hash);
406-
sc_write.apply_changes(&mut batch, chain_path).unwrap();
407399
}
408400

409401
//
@@ -437,7 +429,6 @@ impl HeaderProcessor {
437429
drop(reachability_relations_write);
438430
drop(relations_write);
439431
drop(hst_write);
440-
drop(sc_write);
441432
}
442433

443434
fn commit_trusted_header(&self, ctx: HeaderProcessingContext, _header: &Header) {
@@ -463,13 +454,10 @@ impl HeaderProcessor {
463454
pub fn process_genesis(&self) {
464455
// Init headers selected tip and selected chain stores
465456
let mut batch = WriteBatch::default();
466-
let mut sc_write = self.selected_chain_store.write();
467-
sc_write.init_with_pruning_point(&mut batch, self.genesis.hash).unwrap();
468457
let mut hst_write = self.headers_selected_tip_store.write();
469458
hst_write.set_batch(&mut batch, SortableBlock::new(self.genesis.hash, 0.into())).unwrap();
470459
self.db.write(batch).unwrap();
471460
drop(hst_write);
472-
drop(sc_write);
473461

474462
// Write the genesis header
475463
let mut genesis_header: Header = (&self.genesis).into();

consensus/src/pipeline/virtual_processor/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ pub mod errors;
22
mod processor;
33
mod utxo_validation;
44
pub use processor::*;
5+
pub mod test_block_builder;
56
#[cfg(test)]
67
mod tests;

0 commit comments

Comments
 (0)