Skip to content

Commit 1c7e35e

Browse files
AIQnetLabclaude
andcommitted
cold-join: fix sync throughput + unify height/sync oracle to one source
- block_pipeline: sync-aware apply horizon (DEFERRED_MAX while syncing, GOSSIP_HORIZON live) — closes the dispatch(2000)/drop(200) mismatch that throttled cold-join catch-up to a rolling-200 crawl - node: drop residual frontier+180 sync-target ceiling (keep frontier floor) - collapse sync_blockchain_height to get_max_peer_height (drop TTL/median staleness oracle that could collapse to local height) - remove dead NODE_IS_SYNCHRONIZED atomic + is_node_synchronized/SYNC_SLACK; the coordinator FSM is the sole synced authority - self_synced peer-eviction heuristic -> coordinator_is_synchronized - remove redundant legacy desync monitor (SyncManager.check_desync covers it) - identity: fail-closed BIP39 word-count guard (derivation bytes unchanged) - install-super-node.sh: plumb optional QNET_BURN_TX_HASH 185/185 lib tests, bins build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 9a5be50 commit 1c7e35e

6 files changed

Lines changed: 110 additions & 420 deletions

File tree

development/qnet-integration/src/block_pipeline.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1518,6 +1518,12 @@ impl BlockPipeline {
15181518
const GOSSIP_HORIZON: u64 = 200;
15191519
let mut horizon_cache_h: u64 = 0;
15201520
let mut horizon_cache_age: u32 = 0;
1521+
// During active sync the dispatcher fills its in-flight window up to
1522+
// MAX_INFLIGHT (== DEFERRED_MAX). Admit that far ahead so served blocks
1523+
// land in the deferred buffer instead of being dropped + refetched; on
1524+
// the live gossip path keep the tight horizon so far-future spam cannot
1525+
// grow the buffer. Refreshed alongside horizon_cache_h.
1526+
let mut horizon_cache_syncing = false;
15211527

15221528
'outer: while let Some(decoded) = rx.recv().await {
15231529
// v15.4 DIAG: a fresh block has just arrived — between recv()
@@ -1536,17 +1542,22 @@ impl BlockPipeline {
15361542
// amortises storage reads while keeping the horizon close to real.
15371543
if horizon_cache_age == 0 {
15381544
horizon_cache_h = storage.get_chain_height().unwrap_or(0);
1545+
horizon_cache_syncing = coordinator.snapshot().is_syncing();
15391546
}
15401547
horizon_cache_age = (horizon_cache_age + 1) & 0xF;
15411548

15421549
// Apply horizon filter at the entry point — never enters deferred
1543-
// buffer. Drops are non-failure (sync will refetch).
1544-
if decoded.microblock.height > horizon_cache_h.saturating_add(GOSSIP_HORIZON) {
1550+
// buffer. Drops are non-failure (sync will refetch). Sync widens the
1551+
// horizon to DEFERRED_MAX so the dispatcher's in-flight window is
1552+
// admitted, not dropped — closes the apply-horizon/dispatch mismatch
1553+
// that throttled cold-join catch-up to a rolling-200 crawl.
1554+
let horizon = if horizon_cache_syncing { DEFERRED_MAX as u64 } else { GOSSIP_HORIZON };
1555+
if decoded.microblock.height > horizon_cache_h.saturating_add(horizon) {
15451556
metrics.future_dropped.fetch_add(1, Ordering::Relaxed);
15461557
if is_debug() {
15471558
println!(
1548-
"[DBG][PIPELINE] gossip_horizon_drop h={} local_tip={} horizon={}",
1549-
decoded.microblock.height, horizon_cache_h, GOSSIP_HORIZON,
1559+
"[DBG][PIPELINE] horizon_drop h={} local_tip={} horizon={} syncing={}",
1560+
decoded.microblock.height, horizon_cache_h, horizon, horizon_cache_syncing,
15501561
);
15511562
}
15521563
continue;

development/qnet-integration/src/crypto/solana_derivation.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,20 @@ pub fn bip39_seed64(mnemonic: &str) -> [u8; 64] {
133133
bip39_mnemonic_to_seed(mnemonic)
134134
}
135135

136+
/// Structural BIP39 guard: reject a seed whose word count is not a valid BIP39
137+
/// length (12/15/18/21/24) — a paste/typo that would otherwise silently derive
138+
/// a valid-but-wrong identity (non-genesis nodes have no anchor to catch it).
139+
/// A correctly-formed mnemonic always has a valid count, so this never rejects
140+
/// a real seed, and derivation reads the original bytes unchanged (genesis-safe).
141+
/// Per-word wordlist+checksum validation needs the 2048-word list (follow-up).
142+
pub fn validate_bip39_structure(mnemonic: &str) -> Result<(), String> {
143+
let n = mnemonic.split_whitespace().count();
144+
match n {
145+
12 | 15 | 18 | 21 | 24 => Ok(()),
146+
_ => Err(format!("bip39_word_count={} (expected 12/15/18/21/24)", n)),
147+
}
148+
}
149+
136150
/// PBKDF2-HMAC-SHA512 implementation (RFC 8018).
137151
fn pbkdf2_hmac_sha512(password: &[u8], salt: &[u8], iterations: u32, output: &mut [u8]) {
138152
let hlen = 64usize; // SHA-512 output = 64 bytes

0 commit comments

Comments
 (0)