Skip to content

Commit 554f2c0

Browse files
committed
Mining Rule Engine: No Transactions Rule
1 parent b9bbd71 commit 554f2c0

File tree

7 files changed

+101
-6
lines changed

7 files changed

+101
-6
lines changed

consensus/core/src/api/counters.rs

+10
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 submit_block_bad_merkle_root_count: AtomicU64,
15+
pub submit_block_success_count: AtomicU64,
1416
}
1517

1618
impl ProcessingCounters {
@@ -25,6 +27,8 @@ impl ProcessingCounters {
2527
chain_block_counts: self.chain_block_counts.load(Ordering::Relaxed),
2628
chain_disqualified_counts: self.chain_disqualified_counts.load(Ordering::Relaxed),
2729
mass_counts: self.mass_counts.load(Ordering::Relaxed),
30+
submit_block_bad_merkle_root_count: self.submit_block_bad_merkle_root_count.load(Ordering::Relaxed),
31+
submit_block_success_count: self.submit_block_success_count.load(Ordering::Relaxed),
2832
}
2933
}
3034
}
@@ -40,6 +44,8 @@ pub struct ProcessingCountersSnapshot {
4044
pub chain_block_counts: u64,
4145
pub chain_disqualified_counts: u64,
4246
pub mass_counts: u64,
47+
pub submit_block_bad_merkle_root_count: u64,
48+
pub submit_block_success_count: u64,
4349
}
4450

4551
impl core::ops::Sub for &ProcessingCountersSnapshot {
@@ -56,6 +62,10 @@ impl core::ops::Sub for &ProcessingCountersSnapshot {
5662
chain_block_counts: self.chain_block_counts.saturating_sub(rhs.chain_block_counts),
5763
chain_disqualified_counts: self.chain_disqualified_counts.saturating_sub(rhs.chain_disqualified_counts),
5864
mass_counts: self.mass_counts.saturating_sub(rhs.mass_counts),
65+
submit_block_bad_merkle_root_count: self
66+
.submit_block_bad_merkle_root_count
67+
.saturating_sub(rhs.submit_block_bad_merkle_root_count),
68+
submit_block_success_count: self.submit_block_success_count.saturating_sub(rhs.submit_block_success_count),
5969
}
6070
}
6171
}

consensus/core/src/mining_rules.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
use std::sync::{atomic::AtomicBool, Arc};
2+
13
#[derive(Debug)]
2-
pub struct MiningRules {}
4+
pub struct MiningRules {
5+
pub no_transactions: Arc<AtomicBool>,
6+
}
37

48
impl MiningRules {
59
pub fn new() -> Self {
6-
Self {}
10+
Self { no_transactions: Arc::new(AtomicBool::new(false)) }
711
}
812
}
913

consensus/src/pipeline/virtual_processor/processor.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -964,7 +964,9 @@ impl VirtualStateProcessor {
964964
// We call for the initial tx batch before acquiring the virtual read lock,
965965
// optimizing for the common case where all txs are valid. Following selection calls
966966
// are called within the lock in order to preserve validness of already validated txs
967-
let mut txs = tx_selector.select_transactions();
967+
let mut txs =
968+
if self.mining_rules.no_transactions.load(Ordering::Relaxed) { vec![] } else { tx_selector.select_transactions() };
969+
968970
let mut calculated_fees = Vec::with_capacity(txs.len());
969971
let virtual_read = self.virtual_stores.read();
970972
let virtual_state = virtual_read.state.get().unwrap();

protocol/mining/src/rule_engine.rs

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

24-
use crate::rules::{mining_rule::MiningRule, sync_rate_rule::SyncRateRule, ExtraData};
24+
use crate::rules::{mining_rule::MiningRule, no_transactions_rule::NoTransactionsRule, sync_rate_rule::SyncRateRule, ExtraData};
2525

2626
const RULE_ENGINE: &str = "mining-rule-engine";
2727
pub const SNAPSHOT_INTERVAL: u64 = 10;
@@ -75,6 +75,8 @@ impl MiningRuleEngine {
7575
has_sufficient_peer_connectivity: self.has_sufficient_peer_connectivity(),
7676
finality_duration: self.config.finality_duration_in_milliseconds().get(sink_daa_timestamp.daa_score),
7777
elapsed_time,
78+
sink_daa_score_timestamp: session.async_get_sink_daa_score_timestamp().await,
79+
merge_depth: self.config.merge_depth().get(sink_daa_timestamp.daa_score),
7880
};
7981

8082
trace!("Current Mining Rule: {:?}", self.mining_rules);
@@ -99,7 +101,10 @@ impl MiningRuleEngine {
99101
mining_rules: Arc<MiningRules>,
100102
) -> Self {
101103
let use_sync_rate_rule = Arc::new(AtomicBool::new(false));
102-
let rules: Vec<Arc<(dyn MiningRule + 'static)>> = vec![Arc::new(SyncRateRule::new(use_sync_rate_rule.clone()))];
104+
let rules: Vec<Arc<(dyn MiningRule + 'static)>> = vec![
105+
Arc::new(SyncRateRule::new(use_sync_rate_rule.clone())),
106+
Arc::new(NoTransactionsRule::new(mining_rules.no_transactions.clone())),
107+
];
103108

104109
Self { consensus_manager, config, processing_counters, tick_service, hub, use_sync_rate_rule, mining_rules, rules }
105110
}

protocol/mining/src/rules/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use std::time::Duration;
22

3+
use kaspa_consensus_core::daa_score_timestamp::DaaScoreTimestamp;
4+
5+
pub mod no_transactions_rule;
36
pub mod sync_rate_rule;
47

58
pub mod mining_rule;
@@ -10,4 +13,6 @@ pub struct ExtraData {
1013
pub has_sufficient_peer_connectivity: bool,
1114
pub finality_duration: u64,
1215
pub elapsed_time: Duration,
16+
pub sink_daa_score_timestamp: DaaScoreTimestamp,
17+
pub merge_depth: u64,
1318
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use std::sync::{
2+
atomic::{AtomicBool, AtomicU8, 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+
/// NoTransactionsRule
12+
/// Attempt to recover from consistent BadMerkleRoot errors by mining blocks without
13+
/// any transactions.
14+
///
15+
/// Trigger:
16+
/// - Submitted blocks resulted in BadMerkleRoot errors and there were no successfully submitted blocks
17+
///
18+
/// Recovery:
19+
/// - Two cooldown periods have passed, OR
20+
/// - Any submit block RPC call succeeded
21+
pub struct NoTransactionsRule {
22+
pub is_enabled: Arc<AtomicBool>,
23+
pub cooldown: AtomicU8,
24+
}
25+
26+
impl NoTransactionsRule {
27+
pub fn new(is_enabled: Arc<AtomicBool>) -> Self {
28+
Self { is_enabled, cooldown: AtomicU8::new(0) }
29+
}
30+
}
31+
32+
impl MiningRule for NoTransactionsRule {
33+
fn check_rule(&self, delta: &ProcessingCountersSnapshot, _extra_data: &ExtraData) {
34+
let cooldown_count = self.cooldown.load(Ordering::SeqCst);
35+
36+
if cooldown_count > 0 {
37+
// Recovering
38+
if delta.submit_block_success_count > 0 || self.cooldown.fetch_sub(1, Ordering::SeqCst) == 1 {
39+
// Recovery condition #1: Any submit block RPC call succeeded in this interval
40+
// Recovery condition #2: Cooldown period has passed (important for low hashrate miners whose successful blocks are few and far between)
41+
self.cooldown.store(0, Ordering::SeqCst);
42+
self.is_enabled.store(false, Ordering::SeqCst);
43+
warn!("NoTransactionsRule: recovered | Bad Merkle Root Count: {}", delta.submit_block_bad_merkle_root_count);
44+
}
45+
} else if delta.submit_block_bad_merkle_root_count > 0 && delta.submit_block_success_count == 0 {
46+
// Triggered state
47+
// When submit block BadMerkleRoot errors occurred and there were no successfully submitted blocks
48+
if let Ok(false) = self.is_enabled.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) {
49+
warn!(
50+
"NoTransactionsRule: triggered | Bad Merkle Root Count: {} | Successfully submitted blocks: {}",
51+
delta.submit_block_bad_merkle_root_count, delta.submit_block_success_count
52+
);
53+
self.cooldown.store(2, Ordering::Relaxed);
54+
}
55+
} else {
56+
// Normal state
57+
trace!(
58+
"NoTransactionsRule: normal | Bad Merkle Root Count: {} | Successfully submitted blocks: {}",
59+
delta.submit_block_bad_merkle_root_count,
60+
delta.submit_block_success_count,
61+
);
62+
}
63+
}
64+
}

rpc/service/src/service.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -334,8 +334,13 @@ impl RpcApi for RpcCoreService {
334334

335335
trace!("incoming SubmitBlockRequest for block {}", hash);
336336
match self.flow_context.submit_rpc_block(&session, block.clone()).await {
337-
Ok(_) => Ok(SubmitBlockResponse { report: SubmitBlockReport::Success }),
337+
Ok(_) => {
338+
self.processing_counters.submit_block_success_count.fetch_add(1, Ordering::Relaxed);
339+
Ok(SubmitBlockResponse { report: SubmitBlockReport::Success })
340+
}
338341
Err(ProtocolError::RuleError(RuleError::BadMerkleRoot(h1, h2))) => {
342+
// Count the number of bad merkle root errors as this may trigger NoTransactions rule
343+
self.processing_counters.submit_block_bad_merkle_root_count.fetch_add(1, Ordering::Relaxed);
339344
warn!(
340345
"The RPC submitted block triggered a {} error: {}.
341346
NOTE: This error usually indicates an RPC conversion error between the node and the miner. If you are on TN11 this is likely to reflect using a NON-SUPPORTED miner.",

0 commit comments

Comments
 (0)