Skip to content

Commit 3210473

Browse files
AIQnetLabclaude
andcommitted
consensus: fix emission-boundary total_supply halt + 6th-node onboarding; single snapshot artifact
total_supply / emission halt: - producer applies its own block inline but never minted emission -> per-node supply skew -> checkpoint divergence -> halt. Add inline mint via shared emission_mb_index (watermark-idempotent). - checkpoint read total_supply from the live counter (races the in-block mint). Seal it at each checkpoint head (ts_seal, mirrors registry_root); readers use get_total_supply_at, not the live value. - restart/reconcile restored neither the emission watermark nor a correct replay floor -> replayed emissions double-minted -> halt. Restore both from the snapshot's microblock height. 6th-node onboarding: - NodeRegistration never landed on-chain (one-shot arm + loop break, finite rebroadcast budget, Ok-on-mempool-reject). Single-owner driver: arm only when synced, retry on arm failure, backoff-rebroadcast until on-chain. snapshots (single source): - two artifacts keyed by two denominators (state_snap_=macroblock ordinal, full_snap_=microblock height) -> ordinal/height confusion. Unify to microblock height. - state_snap_ was a live-captured (drifting), incomplete, redundant artifact -> restart drifted. Retire it: local restart + reconcile restore from the apply-bound full_snap_ with total_supply taken from the QC-bound checkpoint (same source as cold-join). P2P serves full_snap_ only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 9695844 commit 3210473

4 files changed

Lines changed: 287 additions & 389 deletions

File tree

development/qnet-integration/src/block_pipeline.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2650,6 +2650,9 @@ impl BlockPipeline {
26502650
// Fires once per checkpoint head incl. zero-registration heads.
26512651
if height % qnet_consensus::checkpoint_bft::CHECKPOINT_INTERVAL == 0 {
26522652
let _ = ctx.storage.seal_registry_root(height);
2653+
// Same head: seal total_supply as-of this height (apply-deterministic on both paths)
2654+
// so the checkpoint reads a height-bound value, never the live counter.
2655+
let _ = ctx.storage.seal_total_supply(height, state_guard.get_total_supply());
26532656
}
26542657

26552658
// State verified — save block.

development/qnet-integration/src/consensus_v2_node.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,13 +516,15 @@ pub async fn run(
516516
match &msg {
517517
ConsensusMsg::Proposal(cp) => match window_buf.get(&(cp.window_head_height / qnet_consensus::checkpoint_bft::CHECKPOINT_INTERVAL)) {
518518
Some(c) => println!(
519-
"[WARN][BFT2] proposal_content_rejected idx={} eq state_root={} mb_hashes={} beacon={} epoch_commit={} reward_root={}",
519+
"[WARN][BFT2] proposal_content_rejected idx={} eq state_root={} mb_hashes={} beacon={} epoch_commit={} reward_root={} registry_root={} total_supply={}",
520520
msg_index(&msg),
521521
cp.state_root == c.state_root,
522522
cp.window_mb_hashes == c.mb_hashes,
523523
cp.beacon == c.beacon,
524524
qnet_consensus::checkpoint_bft::epoch_commitment(&c.eligible, &c.committee, &c.banned) == cp.epoch_commitment,
525525
cp.reward_root == c.reward_root,
526+
cp.registry_root == c.registry_root,
527+
cp.total_supply == c.total_supply,
526528
),
527529
None => println!(
528530
"[WARN][BFT2] proposal_content_rejected idx={} window_buf_MISS win={}",

0 commit comments

Comments
 (0)