Skip to content

Commit 291cab1

Browse files
AIQnetLabclaude
andcommitted
Fix system-TX inclusion (heartbeat/activation) + decouple identity from region
Producer sites classified system TXs via drifted inline lists instead of the canonical Transaction::is_system_tx(). NodeActivation/Heartbeat/NodeReactivation were treated as user TXs and dropped by the user-only ParallelExecutor, so they never reached a block: heartbeats were never tallied (no liveness -> no emission) and super-node activation never applied (the pre-activation sync gate stayed closed). Route all three producer sites (node.rs 18167/18249/19096) through is_system_tx(). - generate_super_node_pseudonym no longer embeds QNET_REGION: the sync gate recomputes the id per-peer, so a region mismatch made one wallet resolve to two different ids and the gate never opened. - Drop geo-IP region auto-detection at boot; region is now an inert constant with no consensus/topology/port role (ports come from QNET_P2P_PORT/DOCKER_ENV). - Activation client logs on_chain_inclusion=pending instead of a false "registered successfully" (it only confirms broadcast). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 6ac36fd commit 291cab1

4 files changed

Lines changed: 30 additions & 80 deletions

File tree

development/qnet-integration/src/activation_validation.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -888,7 +888,9 @@ impl BlockchainActivationRegistry {
888888

889889
// NOTE: DHT propagation removed - activation syncs through blockchain and ReputationSync
890890

891-
println!("✅ Activation registered on blockchain successfully");
891+
// Local record only — the NodeActivation TX is broadcast separately and its
892+
// on-chain inclusion is NOT confirmed here (verify via node/status before trusting).
893+
println!("[INFO][ACTIVATION] activation_recorded_local on_chain_inclusion=pending");
892894
Ok(())
893895
}
894896

development/qnet-integration/src/bin/qnet-node.rs

Lines changed: 4 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1213,44 +1213,10 @@ impl AutoConfig {
12131213
async fn new() -> Result<Self, Box<dyn std::error::Error>> {
12141214
println!("🔧 Auto-configuring QNet node...");
12151215

1216-
// PRODUCTION FIX: Allow region override for Genesis nodes to ensure stable P2P ports
1217-
let region = if let Ok(region_str) = std::env::var("QNET_REGION") {
1218-
match region_str.to_lowercase().as_str() {
1219-
"na" | "northamerica" | "north_america" => {
1220-
println!("🌍 Using fixed region from QNET_REGION: North America");
1221-
Region::NorthAmerica
1222-
},
1223-
"eu" | "europe" => {
1224-
println!("🌍 Using fixed region from QNET_REGION: Europe");
1225-
Region::Europe
1226-
},
1227-
"asia" | "ap" | "asia_pacific" => {
1228-
println!("🌍 Using fixed region from QNET_REGION: Asia");
1229-
Region::Asia
1230-
},
1231-
"sa" | "southamerica" | "south_america" => {
1232-
println!("🌍 Using fixed region from QNET_REGION: South America");
1233-
Region::SouthAmerica
1234-
},
1235-
"africa" | "af" => {
1236-
println!("🌍 Using fixed region from QNET_REGION: Africa");
1237-
Region::Africa
1238-
},
1239-
"oceania" | "oc" => {
1240-
println!("🌍 Using fixed region from QNET_REGION: Oceania");
1241-
Region::Oceania
1242-
},
1243-
_ => {
1244-
println!("⚠️ Unknown QNET_REGION: {}, auto-detecting...", region_str);
1245-
auto_detect_region().await?
1246-
}
1247-
}
1248-
} else {
1249-
// Auto-detect region if not specified
1250-
let region = auto_detect_region().await?;
1251-
println!("🌍 Auto-detected region: {:?}", region);
1252-
region
1253-
};
1216+
// Region is a vestigial cosmetic tag (no consensus/topology/port role) — fixed
1217+
// default, no QNET_REGION read, no geo-IP detection at boot. P2P ports come from
1218+
// QNET_P2P_PORT / DOCKER_ENV below, never from region.
1219+
let region = Region::Europe;
12541220

12551221
// PRODUCTION FIX: Use fixed P2P port from environment for Docker deployments
12561222
// This ensures Docker port mapping works correctly

development/qnet-integration/src/node.rs

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5923,9 +5923,9 @@ impl BlockchainNode {
59235923

59245924
/// Create a new blockchain node with default settings (backward compatibility)
59255925
pub async fn new(data_dir: &str, p2p_port: u16, bootstrap_peers: Vec<String>) -> Result<Self, QNetError> {
5926-
// Production region detection - no defaults allowed
5927-
let region = Self::auto_detect_region().await
5928-
.map_err(|e| QNetError::NetworkError(format!("Region detection failed: {}", e)))?;
5926+
// Region is a vestigial cosmetic tag (no consensus/topology role) — fixed
5927+
// default, no geo-IP/network detection at boot.
5928+
let region = Region::Europe;
59295929

59305930
Self::new_with_config(
59315931
data_dir,
@@ -18161,14 +18161,10 @@ impl BlockchainNode {
1816118161
// v2.87: System TX bypass nonce/balance validation
1816218162
// HeartbeatCommitment/PingCommitment are validator rewards - MUST be included!
1816318163
// v2.89: LightNodeEligibilityBitmap (Genesis bitmap TX)
18164-
let is_system_tx = tx.from == "system_emission"
18165-
|| tx.from == "system_ping_commitment"
18166-
|| tx.from.starts_with("system_")
18167-
|| matches!(tx.tx_type, qnet_state::TransactionType::NodeRegistration { .. })
18168-
|| matches!(tx.tx_type, qnet_state::TransactionType::NodeReactivation { .. })
18169-
|| matches!(tx.tx_type, qnet_state::TransactionType::HeartbeatCommitment { .. })
18170-
|| matches!(tx.tx_type, qnet_state::TransactionType::PingCommitmentWithSampling { .. })
18171-
|| matches!(tx.tx_type, qnet_state::TransactionType::LightNodeEligibilityBitmap { .. });
18164+
// Canonical system-TX predicate (single source of truth in qnet-state) +
18165+
// system_* sender net. Inline lists here had drifted and silently dropped
18166+
// NodeActivation/NodeReactivation from blocks (super-node onboarding stall).
18167+
let is_system_tx = tx.is_system_tx() || tx.from.starts_with("system_");
1817218168

1817318169
let (is_valid, reject_reason) = if is_benchmark || is_system_tx {
1817418170
// Benchmark OR System TX: skip balance/nonce validation
@@ -18246,13 +18242,11 @@ impl BlockchainNode {
1824618242
// v2.68: Separate system TX from user TX
1824718243
// v2.71: NodeRegistration is also system TX (no state execution needed)
1824818244
// v2.87: HeartbeatCommitment/PingCommitment are validator reward TX
18249-
let is_system = tx.from == "system_emission"
18250-
|| tx.from == "system_ping_commitment"
18251-
|| tx.from.starts_with("system_")
18252-
|| matches!(tx.tx_type, qnet_state::TransactionType::NodeRegistration { .. })
18253-
|| matches!(tx.tx_type, qnet_state::TransactionType::HeartbeatCommitment { .. })
18254-
|| matches!(tx.tx_type, qnet_state::TransactionType::PingCommitmentWithSampling { .. })
18255-
|| matches!(tx.tx_type, qnet_state::TransactionType::LightNodeEligibilityBitmap { .. });
18245+
// Canonical system-TX predicate (single source of truth) → route to
18246+
// system_txs so it bypasses the user-only ParallelExecutor, which drops
18247+
// anything it doesn't recognise → empty body. The inline list here had
18248+
// dropped Heartbeat (no liveness tally) AND NodeActivation (no onboarding).
18249+
let is_system = tx.is_system_tx() || tx.from.starts_with("system_");
1825618250

1825718251
// v15.12: state-aware producer dedup (closes the cross-block gap). The
1825818252
// v15.5 filter only deduped WITHIN a block; a commitment already
@@ -19095,18 +19089,12 @@ impl BlockchainNode {
1909519089
qnet_state::TransactionType::NodeRegistration { .. }
1909619090
) && tx.data.as_deref().unwrap_or("").starts_with("client_node_reg:");
1909719091

19098-
let is_system_tx = !is_client_nodereg && (
19099-
tx.from == "system_emission"
19100-
|| tx.from == "system_ping_commitment"
19101-
|| tx.from.starts_with("system_")
19102-
|| matches!(tx.tx_type,
19103-
qnet_state::TransactionType::HeartbeatCommitment { .. } |
19104-
qnet_state::TransactionType::PingCommitmentWithSampling { .. } |
19105-
qnet_state::TransactionType::LightNodeEligibilityBitmap { .. } |
19106-
qnet_state::TransactionType::RewardDistribution { .. } |
19107-
qnet_state::TransactionType::NodeRegistration { .. }
19108-
)
19109-
);
19092+
// Canonical system-TX predicate (single source of truth) + system_*
19093+
// net. Client-signed NodeRegistration stays NON-system so its
19094+
// Ed25519+Dilithium are verified below. The inline list had dropped
19095+
// NodeActivation → activation TX rejected → super-node never onboards.
19096+
let is_system_tx = !is_client_nodereg
19097+
&& (tx.is_system_tx() || tx.from.starts_with("system_"));
1911019098

1911119099
if is_system_tx {
1911219100
// System TX: only basic validation (no signature/amount check)

development/qnet-integration/src/rpc.rs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12473,17 +12473,11 @@ pub fn generate_super_node_pseudonym(wallet_address: &str) -> String {
1247312473
// independent pseudonyms in the two namespaces.
1247412474
let pseudonym_hash = blake3::hash(format!("SUPER_NODE_PRIVACY_{}", wallet_address).as_bytes());
1247512475

12476-
// PRIVACY: Region hint is a coarse operational tag (eu / us / asia / …).
12477-
// Default "node" keeps the pseudonym deterministic when QNET_REGION is
12478-
// unset; operators can override per deployment without affecting the
12479-
// wallet-derived suffix.
12480-
let region_hint = std::env::var("QNET_REGION")
12481-
.unwrap_or_else(|_| "node".to_string())
12482-
.to_lowercase();
12483-
12484-
format!("super_{}_{}",
12485-
region_hint,
12486-
&pseudonym_hash.to_hex()[..8])
12476+
// Identity MUST be region-independent: it is recomputed on every node (the P2P
12477+
// pre-activation sync gate compares this id), so it cannot depend on a per-node env
12478+
// var — a region mismatch would make the same wallet resolve to two different ids and
12479+
// the gate would never open. Fixed "node" segment preserves the historical format.
12480+
format!("super_node_{}", &pseudonym_hash.to_hex()[..8])
1248712481
}
1248812482

1248912483
/// Extract peer IP from HTTP headers (PRODUCTION ready)

0 commit comments

Comments
 (0)