Skip to content

Commit 2cbae63

Browse files
Refactor: Move difficulty adjustment ownership to lib-consensus (#641)
* Refactor: Move difficulty adjustment ownership to lib-consensus - Create DifficultyManager in lib-consensus - Move DifficultyConfig to lib-consensus - Update BlockchainConsensusCoordinator to use DifficultyManager - Update Blockchain to delegate difficulty adjustment to Coordinator - Add governance parameters for difficulty adjustment - Update documentation and architecture diagrams * fix: address all Copilot review comments for difficulty refactoring PR - Remove redundant height > 0 condition in should_adjust() - Fix double lock acquisition in blockchain.rs adjust_difficulty() - Add division by zero checks in legacy difficulty calculation - Make config_mut() private to prevent bypassing validation - Add min_timespan safeguard against integer division returning 0 - Document complete governance application flow for difficulty params - Remove duplicate DifficultyManager documentation - Add comprehensive tests for setter methods - Add 4 new integration tests for difficulty manager - Export initialize_consensus_integration_with_difficulty_config() All tests passing. Fixes address security, correctness, and maintainability issues. * Fix: Make DifficultyManager handle zero actual_timespan consistently with legacy fallback - Changed DifficultyManager::adjust_difficulty() to return Ok(None) instead of error when actual_timespan is zero - This matches the behavior of calculate_difficulty_legacy() which silently returns current difficulty - Both code paths now handle the edge case identically, preventing consensus divergence - Added warning log to trace when this rare condition occurs Fixes inconsistent error handling between coordinator and fallback paths that could cause chain forks when actual_timespan equals zero (identical block timestamps). --------- Co-authored-by: supertramp <hugo@kode.zone>
1 parent 0c7c57d commit 2cbae63

File tree

13 files changed

+1291
-13
lines changed

13 files changed

+1291
-13
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,5 @@ codegen-units = 1
3434
[profile.dev]
3535
opt-level = 0
3636
debug = true
37+
38+
[workspace.metadata.dev-tools]

lib-blockchain/docs/api-reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,7 @@ pub const INITIAL_DIFFICULTY: u64 = 0x00000FFF;
757757
pub const MAX_BLOCK_SIZE: usize = 1_048_576; // 1MB
758758
pub const MAX_TRANSACTIONS_PER_BLOCK: usize = 1000;
759759
pub const TARGET_BLOCK_TIME: u64 = 10; // seconds
760-
pub const DIFFICULTY_ADJUSTMENT_INTERVAL: u64 = 2016; // blocks
760+
pub const DIFFICULTY_ADJUSTMENT_INTERVAL: u64 = 2016; // blocks (fallback if consensus coordinator unavailable)
761761
```
762762

763763
### Contract Constants

lib-blockchain/docs/architecture.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ Coordinates with lib-consensus for:
195195

196196
- **Validator Management**: Register and manage blockchain validators
197197
- **Block Production**: Coordinate block proposal and validation
198+
- **Difficulty Management**: Coordinate difficulty adjustment and target calculation
198199
- **DAO Governance**: On-chain governance with proposal and voting
199200
- **Reward Distribution**: Distribute consensus rewards to participants
200201
- **Byzantine Fault Tolerance**: Handle malicious validators and network partitions

lib-blockchain/src/blockchain.rs

Lines changed: 92 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -783,34 +783,117 @@ impl Blockchain {
783783
crate::types::hash::blake3_hash(&data)
784784
}
785785

786-
/// Adjust mining difficulty based on block times
786+
/// Adjust mining difficulty based on block times.
787+
///
788+
/// This method delegates to the consensus coordinator's DifficultyManager when available,
789+
/// falling back to the legacy hardcoded constants for backward compatibility.
790+
///
791+
/// The consensus engine owns the difficulty policy per architectural design.
787792
fn adjust_difficulty(&mut self) -> Result<()> {
788-
if self.height % crate::DIFFICULTY_ADJUSTMENT_INTERVAL != 0 {
793+
// Get adjustment parameters from consensus coordinator if available
794+
let (adjustment_interval, target_timespan) = if let Some(coordinator) = &self.consensus_coordinator {
795+
// Use a single tokio block_in_place to call async methods from sync context
796+
// Acquire the coordinator read lock once to avoid race conditions and redundant locking
797+
tokio::task::block_in_place(|| {
798+
tokio::runtime::Handle::current().block_on(async {
799+
let coord = coordinator.read().await;
800+
let interval = coord.get_difficulty_adjustment_interval().await;
801+
let config = coord.get_difficulty_config().await;
802+
(interval, config.target_timespan)
803+
})
804+
})
805+
} else {
806+
// Fallback to hardcoded constants for backward compatibility
807+
(crate::DIFFICULTY_ADJUSTMENT_INTERVAL, crate::TARGET_TIMESPAN)
808+
};
809+
810+
// Check if we should adjust at this height
811+
if self.height % adjustment_interval != 0 {
789812
return Ok(());
790813
}
791814

792-
if self.height < crate::DIFFICULTY_ADJUSTMENT_INTERVAL {
815+
if self.height < adjustment_interval {
793816
return Ok(());
794817
}
795818

796819
let current_block = &self.blocks[self.height as usize];
797-
let interval_start = &self.blocks[(self.height - crate::DIFFICULTY_ADJUSTMENT_INTERVAL) as usize];
798-
799-
let actual_timespan = current_block.timestamp() - interval_start.timestamp();
800-
let actual_timespan = actual_timespan.max(crate::TARGET_TIMESPAN / 4).min(crate::TARGET_TIMESPAN * 4);
820+
let interval_start = &self.blocks[(self.height - adjustment_interval) as usize];
821+
822+
let interval_start_time = interval_start.timestamp();
823+
let interval_end_time = current_block.timestamp();
824+
825+
// Calculate new difficulty using consensus coordinator if available
826+
let new_difficulty_bits = if let Some(coordinator) = &self.consensus_coordinator {
827+
let result = tokio::task::block_in_place(|| {
828+
tokio::runtime::Handle::current().block_on(async {
829+
let coord = coordinator.read().await;
830+
coord.calculate_difficulty_adjustment(
831+
self.height,
832+
self.difficulty.bits(),
833+
interval_start_time,
834+
interval_end_time,
835+
).await
836+
})
837+
});
838+
839+
match result {
840+
Ok(Some(new_bits)) => new_bits,
841+
Ok(None) => return Ok(()), // No adjustment needed
842+
Err(e) => {
843+
tracing::warn!("Difficulty adjustment via coordinator failed: {}, using fallback", e);
844+
// Fallback to legacy calculation
845+
self.calculate_difficulty_legacy(interval_start_time, interval_end_time, target_timespan)
846+
}
847+
}
848+
} else {
849+
// Legacy calculation without coordinator
850+
self.calculate_difficulty_legacy(interval_start_time, interval_end_time, target_timespan)
851+
};
801852

802-
let new_difficulty_bits = (self.difficulty.bits() as u64 * crate::TARGET_TIMESPAN / actual_timespan) as u32;
853+
let old_difficulty = self.difficulty.bits();
803854
self.difficulty = Difficulty::from_bits(new_difficulty_bits);
804855

805856
tracing::info!(
806857
"Difficulty adjusted from {} to {} at height {}",
807-
self.difficulty.bits(),
858+
old_difficulty,
808859
new_difficulty_bits,
809860
self.height
810861
);
811862

812863
Ok(())
813864
}
865+
866+
/// Legacy difficulty calculation using hardcoded constants.
867+
/// Used when consensus coordinator is not available.
868+
fn calculate_difficulty_legacy(&self, interval_start_time: u64, interval_end_time: u64, target_timespan: u64) -> u32 {
869+
// Defensive check: target_timespan should be validated to be non-zero upstream,
870+
// but avoid panicking here if that validation is ever bypassed.
871+
if target_timespan == 0 {
872+
tracing::warn!(
873+
"calculate_difficulty_legacy called with target_timespan = 0; \
874+
returning current difficulty without adjustment"
875+
);
876+
return self.difficulty.bits();
877+
}
878+
879+
let actual_timespan = interval_end_time.saturating_sub(interval_start_time);
880+
// Clamp to prevent extreme adjustments (4x range)
881+
let actual_timespan = actual_timespan
882+
.max(target_timespan / 4)
883+
.min(target_timespan * 4);
884+
885+
// Additional defensive check in case clamping still results in zero
886+
// (can happen if target_timespan / 4 == 0 due to integer division with small values)
887+
if actual_timespan == 0 {
888+
tracing::warn!(
889+
"calculate_difficulty_legacy computed actual_timespan = 0 after clamping; \
890+
returning current difficulty without adjustment"
891+
);
892+
return self.difficulty.bits();
893+
}
894+
895+
(self.difficulty.bits() as u64 * target_timespan / actual_timespan) as u32
896+
}
814897

815898
/// Get the latest block
816899
pub fn latest_block(&self) -> Option<&Block> {

lib-blockchain/src/integration/consensus_integration.rs

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ use lib_consensus::{
1616
DaoEngine, DaoProposalType, DaoVoteChoice,
1717
RewardCalculator, RewardRound,
1818
ConsensusProposal, ConsensusVote, VoteType, ConsensusStep,
19-
ConsensusType, ConsensusProof, NoOpBroadcaster
19+
ConsensusType, ConsensusProof, NoOpBroadcaster,
20+
DifficultyConfig, DifficultyManager,
2021
};
2122
use lib_crypto::{Hash, hash_blake3, KeyPair};
2223
use lib_identity::IdentityId;
@@ -80,6 +81,8 @@ pub struct BlockchainConsensusCoordinator {
8081
pending_proposals: Arc<RwLock<VecDeque<ConsensusProposal>>>,
8182
/// Active consensus votes
8283
active_votes: Arc<RwLock<HashMap<Hash, Vec<ConsensusVote>>>>,
84+
/// Difficulty manager (owns difficulty adjustment policy)
85+
difficulty_manager: Arc<RwLock<DifficultyManager>>,
8386
}
8487

8588
// Manual Debug implementation because ConsensusEngine doesn't derive Debug
@@ -107,6 +110,9 @@ impl BlockchainConsensusCoordinator {
107110
));
108111

109112
let (event_sender, event_receiver) = mpsc::unbounded_channel();
113+
114+
// Initialize difficulty manager with default configuration
115+
let difficulty_manager = Arc::new(RwLock::new(DifficultyManager::default()));
110116

111117
Ok(Self {
112118
consensus_engine,
@@ -119,6 +125,38 @@ impl BlockchainConsensusCoordinator {
119125
current_round_cache: Arc::new(RwLock::new(None)),
120126
pending_proposals: Arc::new(RwLock::new(VecDeque::new())),
121127
active_votes: Arc::new(RwLock::new(HashMap::new())),
128+
difficulty_manager,
129+
})
130+
}
131+
132+
/// Create a new blockchain consensus coordinator with custom difficulty configuration
133+
pub async fn new_with_difficulty_config(
134+
blockchain: Arc<RwLock<Blockchain>>,
135+
mempool: Arc<RwLock<Mempool>>,
136+
consensus_config: ConsensusConfig,
137+
difficulty_config: DifficultyConfig,
138+
) -> Result<Self> {
139+
let consensus_engine = Arc::new(RwLock::new(
140+
ConsensusEngine::new(consensus_config, Arc::new(NoOpBroadcaster))?
141+
));
142+
143+
let (event_sender, event_receiver) = mpsc::unbounded_channel();
144+
145+
// Initialize difficulty manager with provided configuration
146+
let difficulty_manager = Arc::new(RwLock::new(DifficultyManager::new(difficulty_config)));
147+
148+
Ok(Self {
149+
consensus_engine,
150+
blockchain,
151+
mempool,
152+
local_validator_id: None,
153+
event_sender,
154+
event_receiver: Arc::new(RwLock::new(event_receiver)),
155+
is_producing_blocks: false,
156+
current_round_cache: Arc::new(RwLock::new(None)),
157+
pending_proposals: Arc::new(RwLock::new(VecDeque::new())),
158+
active_votes: Arc::new(RwLock::new(HashMap::new())),
159+
difficulty_manager,
122160
})
123161
}
124162

@@ -212,8 +250,68 @@ impl BlockchainConsensusCoordinator {
212250
current_round_cache: self.current_round_cache.clone(),
213251
pending_proposals: self.pending_proposals.clone(),
214252
active_votes: self.active_votes.clone(),
253+
difficulty_manager: self.difficulty_manager.clone(),
215254
}
216255
}
256+
257+
/// Get the difficulty manager
258+
pub fn difficulty_manager(&self) -> &Arc<RwLock<DifficultyManager>> {
259+
&self.difficulty_manager
260+
}
261+
262+
/// Get the current difficulty configuration
263+
pub async fn get_difficulty_config(&self) -> DifficultyConfig {
264+
let manager = self.difficulty_manager.read().await;
265+
manager.config().clone()
266+
}
267+
268+
/// Calculate new difficulty using the consensus-owned algorithm
269+
///
270+
/// This is the entry point for blockchain difficulty adjustment.
271+
/// The blockchain calls this method and the consensus engine owns the algorithm.
272+
pub async fn calculate_difficulty_adjustment(
273+
&self,
274+
height: u64,
275+
current_difficulty: u32,
276+
interval_start_time: u64,
277+
interval_end_time: u64,
278+
) -> Result<Option<u32>> {
279+
let manager = self.difficulty_manager.read().await;
280+
manager
281+
.adjust_difficulty(height, current_difficulty, interval_start_time, interval_end_time)
282+
.map_err(|e| anyhow!("Difficulty adjustment failed: {}", e))
283+
}
284+
285+
/// Check if difficulty should be adjusted at the given height
286+
pub async fn should_adjust_difficulty(&self, height: u64) -> bool {
287+
let manager = self.difficulty_manager.read().await;
288+
manager.should_adjust(height)
289+
}
290+
291+
/// Get the initial difficulty value from consensus policy
292+
pub async fn get_initial_difficulty(&self) -> u32 {
293+
let manager = self.difficulty_manager.read().await;
294+
manager.initial_difficulty()
295+
}
296+
297+
/// Get the difficulty adjustment interval from consensus policy
298+
pub async fn get_difficulty_adjustment_interval(&self) -> u64 {
299+
let manager = self.difficulty_manager.read().await;
300+
manager.adjustment_interval()
301+
}
302+
303+
/// Apply DAO governance updates to difficulty parameters
304+
pub async fn apply_difficulty_governance_update(
305+
&self,
306+
initial_difficulty: Option<u32>,
307+
adjustment_interval: Option<u64>,
308+
target_timespan: Option<u64>,
309+
) -> Result<()> {
310+
let mut manager = self.difficulty_manager.write().await;
311+
manager
312+
.apply_governance_update(initial_difficulty, adjustment_interval, target_timespan)
313+
.map_err(|e| anyhow!("Failed to apply difficulty governance update: {}", e))
314+
}
217315

218316
/// Main consensus event processing loop
219317
async fn consensus_event_loop(&self) {
@@ -1528,6 +1626,46 @@ pub async fn initialize_consensus_integration(
15281626
Ok(coordinator)
15291627
}
15301628

1629+
/// Initialize consensus integration with custom difficulty configuration
1630+
///
1631+
/// This variant allows specifying a custom `DifficultyConfig` for the blockchain
1632+
/// mining difficulty adjustment policy.
1633+
pub async fn initialize_consensus_integration_with_difficulty_config(
1634+
blockchain: Arc<RwLock<Blockchain>>,
1635+
mempool: Arc<RwLock<Mempool>>,
1636+
consensus_type: ConsensusType,
1637+
difficulty_config: DifficultyConfig,
1638+
) -> Result<BlockchainConsensusCoordinator> {
1639+
let consensus_config = ConsensusConfig {
1640+
consensus_type,
1641+
min_stake: 1000 * 1_000_000, // 1000 ZHTP minimum stake
1642+
min_storage: 100 * 1024 * 1024 * 1024, // 100 GB minimum storage
1643+
max_validators: 100,
1644+
block_time: 10, // 10 second blocks
1645+
epoch_length_blocks: 100,
1646+
propose_timeout: 3000,
1647+
prevote_timeout: 1000,
1648+
precommit_timeout: 1000,
1649+
max_transactions_per_block: 1000,
1650+
max_difficulty: 0x00000000FFFFFFFF,
1651+
target_difficulty: 0x00000FFF,
1652+
byzantine_threshold: 1.0 / 3.0,
1653+
slash_double_sign: 5,
1654+
slash_liveness: 1,
1655+
development_mode: false, // Production mode by default
1656+
};
1657+
1658+
let coordinator = BlockchainConsensusCoordinator::new_with_difficulty_config(
1659+
blockchain,
1660+
mempool,
1661+
consensus_config,
1662+
difficulty_config,
1663+
).await?;
1664+
1665+
info!("Consensus integration initialized with custom difficulty config");
1666+
Ok(coordinator)
1667+
}
1668+
15311669
/// Create a DAO proposal transaction (delegated to consensus engine)
15321670
pub fn create_dao_proposal_transaction(
15331671
proposer_keypair: &KeyPair,

lib-blockchain/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,14 @@ pub use integration::consensus_integration::{
5858
BlockchainConsensusCoordinator,
5959
ConsensusStatus,
6060
initialize_consensus_integration,
61+
initialize_consensus_integration_with_difficulty_config,
6162
create_dao_proposal_transaction,
6263
create_dao_vote_transaction,
6364
};
6465

66+
// Re-export difficulty types from lib-consensus for convenience
67+
pub use lib_consensus::{DifficultyConfig, DifficultyManager, DifficultyError, DifficultyResult};
68+
6569
// Re-export contracts when feature is enabled
6670
#[cfg(feature = "contracts")]
6771
pub use contracts::*;

0 commit comments

Comments
 (0)