Skip to content

Commit aa6e1ce

Browse files
committed
Mining Rule Engine: Blue Only Mergeset Rule
1 parent 554f2c0 commit aa6e1ce

File tree

6 files changed

+150
-20
lines changed

6 files changed

+150
-20
lines changed

consensus/core/src/api/counters.rs

+12
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ pub struct ProcessingCounters {
1111
pub chain_block_counts: AtomicU64,
1212
pub chain_disqualified_counts: AtomicU64,
1313
pub mass_counts: AtomicU64,
14+
pub build_block_template_above_threshold: AtomicU64,
15+
pub build_block_template_within_threshold: AtomicU64,
1416
pub submit_block_bad_merkle_root_count: AtomicU64,
1517
pub submit_block_success_count: AtomicU64,
1618
}
@@ -27,6 +29,8 @@ impl ProcessingCounters {
2729
chain_block_counts: self.chain_block_counts.load(Ordering::Relaxed),
2830
chain_disqualified_counts: self.chain_disqualified_counts.load(Ordering::Relaxed),
2931
mass_counts: self.mass_counts.load(Ordering::Relaxed),
32+
build_block_template_above_threshold: self.build_block_template_above_threshold.load(Ordering::Relaxed),
33+
build_block_template_within_threshold: self.build_block_template_within_threshold.load(Ordering::Relaxed),
3034
submit_block_bad_merkle_root_count: self.submit_block_bad_merkle_root_count.load(Ordering::Relaxed),
3135
submit_block_success_count: self.submit_block_success_count.load(Ordering::Relaxed),
3236
}
@@ -44,6 +48,8 @@ pub struct ProcessingCountersSnapshot {
4448
pub chain_block_counts: u64,
4549
pub chain_disqualified_counts: u64,
4650
pub mass_counts: u64,
51+
pub build_block_template_above_threshold: u64,
52+
pub build_block_template_within_threshold: u64,
4753
pub submit_block_bad_merkle_root_count: u64,
4854
pub submit_block_success_count: u64,
4955
}
@@ -62,6 +68,12 @@ impl core::ops::Sub for &ProcessingCountersSnapshot {
6268
chain_block_counts: self.chain_block_counts.saturating_sub(rhs.chain_block_counts),
6369
chain_disqualified_counts: self.chain_disqualified_counts.saturating_sub(rhs.chain_disqualified_counts),
6470
mass_counts: self.mass_counts.saturating_sub(rhs.mass_counts),
71+
build_block_template_above_threshold: self
72+
.build_block_template_above_threshold
73+
.saturating_sub(rhs.build_block_template_above_threshold),
74+
build_block_template_within_threshold: self
75+
.build_block_template_within_threshold
76+
.saturating_sub(rhs.build_block_template_within_threshold),
6577
submit_block_bad_merkle_root_count: self
6678
.submit_block_bad_merkle_root_count
6779
.saturating_sub(rhs.submit_block_bad_merkle_root_count),

consensus/core/src/mining_rules.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ use std::sync::{atomic::AtomicBool, Arc};
33
#[derive(Debug)]
44
pub struct MiningRules {
55
pub no_transactions: Arc<AtomicBool>,
6+
pub blue_only_mergeset: Arc<AtomicBool>,
67
}
78

89
impl MiningRules {
910
pub fn new() -> Self {
10-
Self { no_transactions: Arc::new(AtomicBool::new(false)) }
11+
Self { no_transactions: Arc::new(AtomicBool::new(false)), blue_only_mergeset: Arc::new(AtomicBool::new(false)) }
1112
}
1213
}
1314

consensus/src/pipeline/virtual_processor/processor.rs

+44-18
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,11 @@ use kaspa_consensus_notify::{
7777
root::ConsensusNotificationRoot,
7878
};
7979
use kaspa_consensusmanager::SessionLock;
80-
use kaspa_core::{debug, info, time::unix_now, trace, warn};
80+
use kaspa_core::{
81+
debug, info,
82+
time::{unix_now, Stopwatch},
83+
trace, warn,
84+
};
8185
use kaspa_database::prelude::{StoreError, StoreResultEmptyTuple, StoreResultExtensions};
8286
use kaspa_hashes::{Hash, ZERO_HASH};
8387
use kaspa_muhash::MuHash;
@@ -106,6 +110,10 @@ use std::{
106110
sync::{atomic::Ordering, Arc},
107111
};
108112

113+
// 100ms - since at 10BPS, average block time is 100ms and so must expect
114+
// the block template to build faster than that
115+
pub const BUILD_BLOCK_TEMPLATE_SPEED_THRESHOLD: u128 = 100;
116+
109117
pub struct VirtualStateProcessor {
110118
// Channels
111119
receiver: CrossbeamReceiver<VirtualStateProcessingMessage>,
@@ -715,7 +723,10 @@ impl VirtualStateProcessor {
715723
let max_candidates = self.max_virtual_parent_candidates(max_block_parents);
716724

717725
// Prioritize half the blocks with highest blue work and pick the rest randomly to ensure diversity between nodes
718-
if candidates.len() > max_candidates {
726+
if self.mining_rules.blue_only_mergeset.load(Ordering::Relaxed) {
727+
// pick 100% of the top blue work blocks
728+
candidates.truncate(max_candidates);
729+
} else if candidates.len() > max_candidates {
719730
// make_contiguous should be a no op since the deque was just built
720731
let slice = candidates.make_contiguous();
721732

@@ -804,25 +815,32 @@ impl VirtualStateProcessor {
804815
let mut ghostdag_data = self.ghostdag_manager.ghostdag(&virtual_parents);
805816
let merge_depth_root = self.depth_manager.calc_merge_depth_root(&ghostdag_data, current_pruning_point);
806817
let mut kosherizing_blues: Option<Vec<Hash>> = None;
807-
let mut bad_reds = Vec::new();
818+
let bad_reds = if self.mining_rules.blue_only_mergeset.load(Ordering::Relaxed) {
819+
// Treat all reds as bad reds when this rule is triggered
820+
ghostdag_data.mergeset_reds.as_ref().to_vec()
821+
} else {
822+
let mut inner_bad_reds = Vec::new();
808823

809-
//
810-
// Note that the code below optimizes for the usual case where there are no merge-bound-violating blocks.
811-
//
824+
//
825+
// Note that the code below optimizes for the usual case where there are no merge-bound-violating blocks.
826+
//
812827

813-
// Find red blocks violating the merge bound and which are not kosherized by any blue
814-
for red in ghostdag_data.mergeset_reds.iter().copied() {
815-
if self.reachability_service.is_dag_ancestor_of(merge_depth_root, red) {
816-
continue;
817-
}
818-
// Lazy load the kosherizing blocks since this case is extremely rare
819-
if kosherizing_blues.is_none() {
820-
kosherizing_blues = Some(self.depth_manager.kosherizing_blues(&ghostdag_data, merge_depth_root).collect());
821-
}
822-
if !self.reachability_service.is_dag_ancestor_of_any(red, &mut kosherizing_blues.as_ref().unwrap().iter().copied()) {
823-
bad_reds.push(red);
828+
// Find red blocks violating the merge bound and which are not kosherized by any blue
829+
for red in ghostdag_data.mergeset_reds.iter().copied() {
830+
if self.reachability_service.is_dag_ancestor_of(merge_depth_root, red) {
831+
continue;
832+
}
833+
// Lazy load the kosherizing blocks since this case is extremely rare
834+
if kosherizing_blues.is_none() {
835+
kosherizing_blues = Some(self.depth_manager.kosherizing_blues(&ghostdag_data, merge_depth_root).collect());
836+
}
837+
if !self.reachability_service.is_dag_ancestor_of_any(red, &mut kosherizing_blues.as_ref().unwrap().iter().copied()) {
838+
inner_bad_reds.push(red);
839+
}
824840
}
825-
}
841+
842+
inner_bad_reds
843+
};
826844

827845
if !bad_reds.is_empty() {
828846
// Remove all parents which lead to merging a bad red
@@ -1053,6 +1071,7 @@ impl VirtualStateProcessor {
10531071
mut txs: Vec<Transaction>,
10541072
calculated_fees: Vec<u64>,
10551073
) -> Result<BlockTemplate, RuleError> {
1074+
let swo = Stopwatch::new("virtual_processor.resolve_virtual");
10561075
// [`calc_block_parents`] can use deep blocks below the pruning point for this calculation, so we
10571076
// need to hold the pruning lock.
10581077
let _prune_guard = self.pruning_lock.blocking_read();
@@ -1102,6 +1121,13 @@ impl VirtualStateProcessor {
11021121
let selected_parent_hash = virtual_state.ghostdag_data.selected_parent;
11031122
let selected_parent_timestamp = self.headers_store.get_timestamp(selected_parent_hash).unwrap();
11041123
let selected_parent_daa_score = self.headers_store.get_daa_score(selected_parent_hash).unwrap();
1124+
1125+
if swo.elapsed().as_millis() <= BUILD_BLOCK_TEMPLATE_SPEED_THRESHOLD {
1126+
self.counters.build_block_template_within_threshold.fetch_add(1, Ordering::SeqCst);
1127+
} else {
1128+
self.counters.build_block_template_above_threshold.fetch_add(1, Ordering::SeqCst);
1129+
}
1130+
11051131
Ok(BlockTemplate::new(
11061132
MutableBlock::new(header, txs),
11071133
miner_data,

protocol/mining/src/rule_engine.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ use kaspa_core::{
2121
};
2222
use kaspa_p2p_lib::Hub;
2323

24-
use crate::rules::{mining_rule::MiningRule, no_transactions_rule::NoTransactionsRule, sync_rate_rule::SyncRateRule, ExtraData};
24+
use crate::rules::{
25+
blue_only_mergeset_rule::BlueOnlyMergesetRule, mining_rule::MiningRule, no_transactions_rule::NoTransactionsRule,
26+
sync_rate_rule::SyncRateRule, ExtraData,
27+
};
2528

2629
const RULE_ENGINE: &str = "mining-rule-engine";
2730
pub const SNAPSHOT_INTERVAL: u64 = 10;
@@ -103,6 +106,7 @@ impl MiningRuleEngine {
103106
let use_sync_rate_rule = Arc::new(AtomicBool::new(false));
104107
let rules: Vec<Arc<(dyn MiningRule + 'static)>> = vec![
105108
Arc::new(SyncRateRule::new(use_sync_rate_rule.clone())),
109+
Arc::new(BlueOnlyMergesetRule::new(mining_rules.blue_only_mergeset.clone())),
106110
Arc::new(NoTransactionsRule::new(mining_rules.no_transactions.clone())),
107111
];
108112

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use std::sync::{
2+
atomic::{AtomicBool, AtomicU64, Ordering},
3+
Arc,
4+
};
5+
6+
use kaspa_consensus_core::api::counters::ProcessingCountersSnapshot;
7+
use kaspa_core::{trace, warn};
8+
9+
use super::{mining_rule::MiningRule, ExtraData};
10+
11+
/// BlueOnlyMergesetRule
12+
/// Attempt to recover from high build block template times (possibly caused by merging red blocks)
13+
/// by disallowing reds in the mergeset.
14+
///
15+
/// Trigger:
16+
/// - build block template call durations above threshold were observed, AND
17+
/// - there were no build block template calls that were within threshold
18+
///
19+
/// Recovery:
20+
/// - build block template call durations within threshold observed, AND
21+
/// - a merge depth bound period has passed
22+
pub struct BlueOnlyMergesetRule {
23+
pub is_enabled: Arc<AtomicBool>,
24+
pub trigger_daa_score: AtomicU64,
25+
pub within_threshold_calls_after_trigger: AtomicU64,
26+
pub above_threshold_calls_after_trigger: AtomicU64,
27+
}
28+
29+
impl BlueOnlyMergesetRule {
30+
pub fn new(is_enabled: Arc<AtomicBool>) -> Self {
31+
Self {
32+
is_enabled,
33+
trigger_daa_score: AtomicU64::new(0),
34+
within_threshold_calls_after_trigger: AtomicU64::new(0),
35+
above_threshold_calls_after_trigger: AtomicU64::new(0),
36+
}
37+
}
38+
}
39+
40+
impl MiningRule for BlueOnlyMergesetRule {
41+
fn check_rule(&self, delta: &ProcessingCountersSnapshot, extra_data: &ExtraData) {
42+
let sink_daa_score = extra_data.sink_daa_score_timestamp.daa_score;
43+
// DAA score may not be monotonic, so use saturating_sub
44+
let score_since_trigger = sink_daa_score.saturating_sub(self.trigger_daa_score.load(Ordering::Relaxed));
45+
46+
if self.is_enabled.load(Ordering::SeqCst) {
47+
// Rule is triggered. Check for recovery
48+
let within_threshold_calls =
49+
self.within_threshold_calls_after_trigger.fetch_add(delta.build_block_template_within_threshold, Ordering::SeqCst)
50+
+ delta.build_block_template_within_threshold;
51+
let above_threshold_calls =
52+
self.above_threshold_calls_after_trigger.fetch_add(delta.build_block_template_above_threshold, Ordering::SeqCst)
53+
+ delta.build_block_template_above_threshold;
54+
55+
if score_since_trigger >= extra_data.merge_depth && within_threshold_calls > 0 {
56+
// Recovery condition met: A merge depth bound has passed and calls within threshold were observed
57+
self.is_enabled.store(false, Ordering::SeqCst);
58+
self.within_threshold_calls_after_trigger.store(0, Ordering::SeqCst);
59+
self.above_threshold_calls_after_trigger.store(0, Ordering::SeqCst);
60+
warn!("BlueParentsOnlyRule: recovered | No. of Block Template Build Times within/above threshold since trigger: {}/{} | Score since trigger: {}",
61+
within_threshold_calls, above_threshold_calls, score_since_trigger);
62+
} else {
63+
warn!(
64+
"BlueParentsOnlyRule: active | No. of Block Template Build Times within/above threshold since trigger: {}/{} | Score since trigger: {}",
65+
within_threshold_calls, above_threshold_calls, score_since_trigger
66+
);
67+
}
68+
} else {
69+
// Rule is not triggered. Check for trigger
70+
if delta.build_block_template_within_threshold == 0 && delta.build_block_template_above_threshold > 0 {
71+
self.is_enabled.store(true, Ordering::SeqCst);
72+
self.trigger_daa_score.store(sink_daa_score, Ordering::SeqCst);
73+
warn!(
74+
"BlueParentsOnlyRule: triggered | No. of Block Template Build Times within/above threshold: {}/{}",
75+
delta.build_block_template_within_threshold, delta.build_block_template_above_threshold
76+
);
77+
} else {
78+
trace!(
79+
"BlueParentsOnlyRule: normal | No. of Block Template Build Times within/above threshold: {}/{}",
80+
delta.build_block_template_within_threshold,
81+
delta.build_block_template_above_threshold
82+
);
83+
}
84+
}
85+
}
86+
}

protocol/mining/src/rules/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::time::Duration;
22

33
use kaspa_consensus_core::daa_score_timestamp::DaaScoreTimestamp;
44

5+
pub mod blue_only_mergeset_rule;
56
pub mod no_transactions_rule;
67
pub mod sync_rate_rule;
78

0 commit comments

Comments
 (0)