Repository navigation
fix(consensus): two SIP-6 pre-deploy blockers — address case normalization + epoch transition in libp2p sync #763
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -519,6 +519,82 @@ impl Blockchain { | |
| self.total_minted | ||
| } | ||
|
|
||
| /// Run epoch-boundary bookkeeping for the block at `height` if it is | ||
| /// an epoch boundary. Encapsulates process_unbonding → unbond releases | ||
| /// → update_active_set → epoch rotation → slashing.check_liveness so | ||
| /// both the BFT-finalize path (main.rs) and the libp2p sync paths | ||
| /// (gossip + GetBlocks catch-up) run identical logic. | ||
| /// | ||
| /// Call AFTER record_block + distribute_reward have already run for | ||
| /// this block. Returns without doing anything if the height is not | ||
| /// an epoch boundary. | ||
| pub fn run_epoch_bookkeeping(&mut self, height: u64) { | ||
| use sentrix_primitives::transaction::PROTOCOL_TREASURY; | ||
| use sentrix_staking::epoch::{EpochInfo, EpochManager, EPOCH_LENGTH}; | ||
|
|
||
| if !EpochManager::is_epoch_boundary(height) { | ||
| return; | ||
| } | ||
|
|
||
| tracing::info!("Epoch boundary at height {} — transitioning", height); | ||
| let released = self.stake_registry.process_unbonding(height); | ||
| for (delegator, amount) in &released { | ||
| let r = if Self::is_reward_v2_height(height) { | ||
| self.accounts.transfer(PROTOCOL_TREASURY, delegator, *amount, 0) | ||
| } else { | ||
| self.accounts.credit(delegator, *amount) | ||
| }; | ||
| r.unwrap_or_else(|e| tracing::warn!("unbonding release failed: {}", e)); | ||
| } | ||
|
Comment on lines
+540
to
+548
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== process_unbonding definitions and call sites =="
rg -n -C4 '\bfn\s+process_unbonding\b|\bprocess_unbonding\s*\(' --glob '*.rs'
echo
echo "== nearby unbonding mutation logic =="
rg -n -C4 '\bunbond|\brelease|\bretain\b|\bremove\b' --glob '*.rs' | rg 'process_unbonding|unbond'Repository: sentrix-labs/sentrix Length of output: 9218 process_unbonding mutates state before payout; swallowed transfer failures can drop releases 🤖 Prompt for AI Agents |
||
| if !released.is_empty() { | ||
| tracing::info!("Released {} unbonding entries", released.len()); | ||
| } | ||
|
|
||
| self.stake_registry.update_active_set(); | ||
| let active_set = self.stake_registry.active_set.clone(); | ||
| let total_staked: u64 = active_set | ||
| .iter() | ||
| .filter_map(|a| self.stake_registry.get_validator(a)) | ||
| .map(|v| v.total_stake()) | ||
| .sum(); | ||
|
|
||
| self.epoch_manager.record_block(0); | ||
| let finished = self.epoch_manager.current_epoch.clone(); | ||
| self.epoch_manager.history.push(finished); | ||
| if self.epoch_manager.history.len() > self.epoch_manager.max_history { | ||
| self.epoch_manager.history.remove(0); | ||
| } | ||
| let next_num = self.epoch_manager.current_epoch.epoch_number + 1; | ||
| let next_start = next_num * EPOCH_LENGTH; | ||
| self.epoch_manager.current_epoch = EpochInfo { | ||
| epoch_number: next_num, | ||
| start_height: next_start, | ||
| end_height: next_start + EPOCH_LENGTH - 1, | ||
| validator_set: active_set.clone(), | ||
| total_staked, | ||
| total_rewards: 0, | ||
| total_blocks_produced: 0, | ||
| }; | ||
|
|
||
| if let Some(emitter) = &self.event_emitter { | ||
| emitter.emit_validator_set(next_num, &active_set); | ||
| } | ||
| tracing::info!( | ||
| "Epoch {} started — {} validators, {} staked", | ||
| next_num, | ||
| active_set.len(), | ||
| total_staked | ||
| ); | ||
|
|
||
| let mut slashing = std::mem::take(&mut self.slashing); | ||
| let slashed = slashing.check_liveness(&mut self.stake_registry, &active_set, height); | ||
| self.slashing = slashing; | ||
| for (val, amt) in &slashed { | ||
| tracing::warn!("Slashed {} for {} sentri (downtime)", val, amt); | ||
| self.accounts.burn(*amt); | ||
| } | ||
| } | ||
|
|
||
| // Memory estimate reflects the in-memory window, not the full historical chain | ||
| pub fn get_memory_estimate(&self) -> String { | ||
| let window_blocks = self.chain.len(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1132,6 +1132,11 @@ async fn on_swarm_event( | |
| ); | ||
| // Epoch tracking — mirror main.rs validator-finalize. | ||
| chain.epoch_manager.record_block(reward); | ||
| // Epoch boundary transition — rotate active set, | ||
| // release unbonding, run liveness slashing. | ||
| // Previously missing here; libp2p-synced nodes | ||
| // diverged from BFT-finalize path at boundaries. | ||
| chain.run_epoch_bookkeeping(gossip.block.index); | ||
|
Comment on lines
+1135
to
+1139
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This wires Suggested minimal follow-up chain.epoch_manager.record_block(reward);
+ chain.run_epoch_bookkeeping(block_idx);As per coding guidelines, 🤖 Prompt for AI Agents |
||
| } | ||
| let updated = | ||
| chain.latest_block().ok().cloned().unwrap_or(gossip.block); | ||
|
|
@@ -2018,6 +2023,11 @@ async fn on_inbound_response( | |
| 0, | ||
| ); | ||
| chain.epoch_manager.record_block(reward); | ||
| // Epoch boundary transition — rotate active set, | ||
| // release unbonding, run liveness slashing. | ||
| // Previously missing here; batch-syncing nodes | ||
| // diverged from BFT-finalize path at boundaries. | ||
| chain.run_epoch_bookkeeping(block.index); | ||
| } | ||
| // Use H2 (post-add_block state_root hash) — not the raw peer block (PR #78). | ||
| let updated = chain | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the second
epoch_manager.record_blockfrom the boundary helper.The new contract says callers reach this helper after
record_blockhas already run, and the PR objective says the libp2p apply paths already did that. Callingself.epoch_manager.record_block(0)again on Line 561 double-counts every boundary block beforefinishedis pushed into history, so epoch block totals drift by one at each rollover.Suggested fix
- self.epoch_manager.record_block(0); let finished = self.epoch_manager.current_epoch.clone(); self.epoch_manager.history.push(finished);Also applies to: 561-563
🤖 Prompt for AI Agents