@@ -3676,31 +3676,43 @@ impl BlockchainNode {
36763676 // reject so the caller re-syncs from canonical instead of forking.
36773677 // No binding at this boundary ⇒ cannot verify, accept with warning
36783678 // (legitimate at pre-binding/early heights).
3679+ // Fail-closed by default: the ONLY accepted outcome is a recomputed state_root that
3680+ // equals the 2f+1-bound macroblock snapshot_root (Pattern C). Every other path —
3681+ // pre-finality height, missing/undecodable anchor, no binding, mismatch, recompute
3682+ // error — returns Err so the caller discards and resyncs from canonical QC state.
36793683 let mb_idx = target_height / 90;
3680- if mb_idx > 0 {
3681- if let Ok(Some(mb_bytes)) = storage.get_macroblock_by_height(mb_idx) {
3682- if let Ok(mb) = bincode::deserialize::<qnet_state::MacroBlock>(&mb_bytes) {
3683- if let Some(expected_root) = mb.consensus_data.snapshot_root {
3684- match storage.compute_canonical_state_root(target_height) {
3685- Ok(computed) if computed == expected_root => {
3686- println!("[INFO][STATE] reconcile_verified mb={} target={} root={} pattern=C",
3687- mb_idx, target_height, hex::encode(&computed[..8]));
3688- }
3689- Ok(computed) => {
3690- return Err(format!(
3691- "reconcile_root_mismatch target={} mb={} expected={} computed={} action=resync",
3692- target_height, mb_idx,
3693- hex::encode(&expected_root[..8]), hex::encode(&computed[..8]),
3694- ));
3695- }
3696- Err(e) => {
3697- println!("[WARN][STATE] reconcile_verify_compute_err target={} err={:?}", target_height, e);
3698- }
3699- }
3700- } else {
3701- println!("[WARN][STATE] reconcile_unverified mb={} reason=no_snapshot_root_binding", mb_idx);
3702- }
3703- }
3684+ if mb_idx == 0 {
3685+ // h<90: no finalized macroblock to prove canonicity ⇒ resync (block-sync from
3686+ // genesis is cheap pre-finality). Never accept unverified recovery state.
3687+ return Err(format!("reconcile_pre_finality target={} action=resync", target_height));
3688+ }
3689+ let mb_bytes = match storage.get_macroblock_by_height(mb_idx) {
3690+ Ok(Some(b)) => b,
3691+ _ => return Err(format!("reconcile_anchor_unavailable mb={} target={} action=resync", mb_idx, target_height)),
3692+ };
3693+ let mb = match bincode::deserialize::<qnet_state::MacroBlock>(&mb_bytes) {
3694+ Ok(m) => m,
3695+ Err(e) => return Err(format!("reconcile_anchor_decode mb={} err={:?} action=resync", mb_idx, e)),
3696+ };
3697+ let expected_root = match mb.consensus_data.snapshot_root {
3698+ Some(r) => r,
3699+ None => return Err(format!("reconcile_no_binding mb={} target={} action=resync", mb_idx, target_height)),
3700+ };
3701+ match storage.compute_canonical_state_root(target_height) {
3702+ Ok(computed) if computed == expected_root => {
3703+ println!("[INFO][STATE] reconcile_verified mb={} target={} root={} pattern=C",
3704+ mb_idx, target_height, hex::encode(&computed[..8]));
3705+ }
3706+ Ok(computed) => {
3707+ return Err(format!(
3708+ "reconcile_root_mismatch target={} mb={} expected={} computed={} action=resync",
3709+ target_height, mb_idx, hex::encode(&expected_root[..8]), hex::encode(&computed[..8]),
3710+ ));
3711+ }
3712+ Err(e) => {
3713+ return Err(format!(
3714+ "reconcile_verify_unavailable target={} mb={} err={:?} action=resync", target_height, mb_idx, e,
3715+ ));
37043716 }
37053717 }
37063718 Ok(())
@@ -4219,7 +4231,11 @@ impl BlockchainNode {
42194231 // activation cost. Mirrors select_consensus_committee (same beacon-seeded VRF, one
42204232 // tier up). node_id is a total-order tiebreak for the (cryptographically
42214233 // unreachable) SHA3-collision case.
4222- eligible.sort_by(|a, b| {
4234+ // O(N) partial-select (quickselect) instead of a full O(N log N) sort of the whole
4235+ // pool — the committee-selection ceiling at 100k+ eligible. Yields the identical set
4236+ // (the MAX_VALIDATORS lowest VRF scores; node_id total-order tiebreak), then sort only
4237+ // the selected for deterministic order. Reached only when len > MAX_VALIDATORS.
4238+ eligible.select_nth_unstable_by(MAX_VALIDATORS, |a, b| {
42234239 vrf_scores[&a.node_id].cmp(&vrf_scores[&b.node_id])
42244240 .then_with(|| a.node_id.cmp(&b.node_id))
42254241 });
@@ -13914,10 +13930,20 @@ impl BlockchainNode {
1391413930 &storage,
1391513931 rollback_to,
1391613932 ).await {
13933+ // Reconcile could not PROVE the rebuilt state canonical.
13934+ // Never proceed on unverified state: discard it and reload a
13935+ // 2f+1-QC-bound snapshot (fast_sync verifies the binding,
13936+ // fail-closed), then the tail re-syncs verify-then-apply.
1391713937 println!(
13918- "[ERR ][STATE] reconcile_after_pipeline_fork_failed target={} err={} action=resync_required ",
13938+ "[WARN ][STATE] reconcile_unproven target={} err={} action=clean_state_sync ",
1391913939 rollback_to, e,
1392013940 );
13941+ let tip = crate::node::qc_verified_frontier_height()
13942+ .max(p2p.get_best_peer_height());
13943+ match storage.fast_sync_with_snapshot(p2p, tip).await {
13944+ Ok(()) => println!("[INFO][STATE] clean_state_sync_ok target={}", tip),
13945+ Err(se) => println!("[WARN][STATE] clean_state_sync_failed err={:?} fallback=block_sync", se),
13946+ }
1392113947 } else {
1392213948 println!(
1392313949 "[INFO][STATE] reconcile_after_pipeline_fork_ok target={}",
@@ -16505,7 +16531,11 @@ impl BlockchainNode {
1650516531 }).collect();
1650616532 txs.retain(|t| {
1650716533 if matches!(t.tx_type, qnet_state::TransactionType::NodeActivation { .. }) {
16508- let backed = this_block_burned.contains(&t.from) || storage.wallet_is_burn_registered(&t.from);
16534+ // Genesis nodes self-activate without a 1DEV burn (they ARE the bootstrap),
16535+ // so exempt them — same genesis exemption the registration burn-gate uses.
16536+ let backed = this_block_burned.contains(&t.from)
16537+ || storage.wallet_is_burn_registered(&t.from)
16538+ || storage.wallet_is_genesis_node(&t.from);
1650916539 if !backed && is_warn() { println!("[WARN][MB] drop_unbacked_activation h={}", next_block_height); }
1651016540 backed
1651116541 } else { true }
0 commit comments