Skip to content

Commit 6cad853

Browse files
committed
fix: improve BFT consensus stability, update genesis config and faucet
consensus / p2p (node.rs, unified_p2p.rs): - BFT timeout votes now keyed by macroblock index (height/90) instead of exact microblock height so nodes at different heights within the same macroblock can form quorum and issue a TimeoutCertificate - Track consecutive sync failures per height (SYNC_FAIL_COUNT/SYNC_FAIL_FIRST_TS); after 3 failures force QUIC reconnect via reconnect_all_bootstrap_peers(), after 600 s stuck trigger self-restart via process::exit(1) - Add reconnect_all_bootstrap_peers() to UnifiedP2P: flushes idle QUIC connections, removes stale peer entries and re-dials all bootstrap nodes - Align TIMEOUT_VOTES / TIMEOUT_CERTIFICATES / TIMEOUT_VOTED_HEIGHTS cleanup to use macroblock index keys (retain last 20 macroblocks) genesis node addresses (6 files): - Update all 5 genesis node wallet addresses in genesis_constants.rs, fork_resolution.rs, config.py, genesis_whitelist.py, onedev_phase_handler.py and WalletScreen.js faucet / explorer (3 files): - Replace unreliable requestAirdrop with SystemProgram.transfer from faucet wallet in claim/route.ts - Add parallel dual-token claim (1500 1DEV + 0.001 SOL) in testnet/page.tsx and token-faucet.tsx with unified success modal showing both tx hashes
1 parent ce3251b commit 6cad853

11 files changed

Lines changed: 370 additions & 172 deletions

File tree

applications/qnet-explorer/frontend/src/app/api/faucet/claim/route.ts

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -193,37 +193,78 @@ async function send1DEVTokens(
193193
}
194194

195195
// ---------------------------------------------------------------------------
196-
// SOL airdrop (Solana devnet, plain HTTP)
196+
// SOL transfer — sends from faucet wallet (same key as 1DEV)
197197
// ---------------------------------------------------------------------------
198198
async function sendSOLTokens(
199199
address: string,
200200
amount: number,
201-
environment: 'testnet' | 'mainnet',
202201
): Promise<{ success: boolean; txHash?: string; error?: string }> {
203-
if (environment !== 'testnet') {
204-
return { success: false, error: 'Production SOL faucet not available' };
205-
}
206-
207202
try {
208-
const response = await fetch('https://api.devnet.solana.com', {
209-
method: 'POST',
210-
headers: { 'Content-Type': 'application/json' },
211-
body: JSON.stringify({
212-
jsonrpc: '2.0',
213-
id: 1,
214-
method: 'requestAirdrop',
215-
params: [address, amount * 1e9],
216-
}),
217-
signal: AbortSignal.timeout(15000),
203+
const { Connection, Keypair, PublicKey, Transaction, SystemProgram, ComputeBudgetProgram } =
204+
await import('@solana/web3.js');
205+
206+
const connection = new Connection('https://api.devnet.solana.com', {
207+
commitment: 'processed',
208+
confirmTransactionInitialTimeout: 3000,
218209
});
219210

220-
const data = await response.json();
221-
if (data.result) {
222-
return { success: true, txHash: data.result };
211+
// Load faucet private key (same wallet as 1DEV)
212+
let faucetPrivateKey: number[] | undefined;
213+
const faucetPrivateKeyEnv = process.env.FAUCET_PRIVATE_KEY;
214+
if (faucetPrivateKeyEnv) {
215+
faucetPrivateKey = JSON.parse(faucetPrivateKeyEnv);
216+
} else {
217+
const path = await import('path');
218+
const fs = await import('fs');
219+
const candidates = [
220+
path.join(process.cwd(), '..', '..', '..', 'infrastructure', 'config', 'faucet-config-testnet.json'),
221+
'/var/qnet-fresh/infrastructure/config/faucet-config-testnet.json',
222+
];
223+
for (const p of candidates) {
224+
if (fs.existsSync(p)) {
225+
const cfg = JSON.parse(fs.readFileSync(p, 'utf8'));
226+
faucetPrivateKey = cfg.wallet?.secretKey;
227+
break;
228+
}
229+
}
223230
}
224-
return { success: false, error: data.error?.message || 'Airdrop failed' };
225-
} catch {
226-
return { success: false, error: 'Solana airdrop service unavailable' };
231+
232+
if (!faucetPrivateKey) {
233+
return { success: false, error: 'Faucet configuration error - private key not found' };
234+
}
235+
236+
const faucetWallet = Keypair.fromSecretKey(new Uint8Array(faucetPrivateKey));
237+
const recipientPubkey = new PublicKey(address);
238+
const lamports = Math.round(amount * 1e9);
239+
240+
const { blockhash } = await connection.getLatestBlockhash('processed');
241+
const transaction = new Transaction();
242+
transaction.recentBlockhash = blockhash;
243+
transaction.feePayer = faucetWallet.publicKey;
244+
245+
transaction.add(ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 50000 }));
246+
transaction.add(
247+
SystemProgram.transfer({
248+
fromPubkey: faucetWallet.publicKey,
249+
toPubkey: recipientPubkey,
250+
lamports,
251+
}),
252+
);
253+
254+
const signature = await connection.sendTransaction(transaction, [faucetWallet], {
255+
skipPreflight: true,
256+
preflightCommitment: 'processed',
257+
maxRetries: 0,
258+
});
259+
260+
setTimeout(async () => {
261+
try { await connection.confirmTransaction(signature, 'processed'); } catch { /* non-critical */ }
262+
}, 100);
263+
264+
return { success: true, txHash: signature };
265+
} catch (error: unknown) {
266+
const msg = error instanceof Error ? error.message : 'Failed to send SOL';
267+
return { success: false, error: msg };
227268
}
228269
}
229270

@@ -276,7 +317,7 @@ async function sendTokens(
276317
case '1DEV':
277318
return send1DEVTokens(address, amount);
278319
case 'SOL':
279-
return sendSOLTokens(address, amount, environment);
320+
return sendSOLTokens(address, amount);
280321
case 'QNC':
281322
return sendQNCTokens(address, amount);
282323
default:

applications/qnet-explorer/frontend/src/app/testnet/page.tsx

Lines changed: 61 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ export default function TestnetPage() {
99
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
1010
const [isLoading, setIsLoading] = useState(false);
1111
const [errorMessage, setErrorMessage] = useState('');
12-
const [txHash, setTxHash] = useState('');
12+
const [devTxHash, setDevTxHash] = useState('');
13+
const [solTxHash, setSolTxHash] = useState('');
1314

1415
const handleFaucetClaim = async () => {
1516
if (!faucetAddress.trim()) {
@@ -18,7 +19,7 @@ export default function TestnetPage() {
1819
}
1920

2021
const now = Date.now();
21-
const cooldownPeriod = 24 * 60 * 60 * 1000; // 24 hours
22+
const cooldownPeriod = 24 * 60 * 60 * 1000;
2223

2324
if (lastFaucetClaim && (now - lastFaucetClaim) < cooldownPeriod) {
2425
setShowFaucetAlert(true);
@@ -29,34 +30,37 @@ export default function TestnetPage() {
2930
setErrorMessage('');
3031

3132
try {
32-
const requestBody = {
33-
walletAddress: faucetAddress.trim(),
34-
amount: 1500,
35-
tokenType: '1DEV'
36-
};
37-
38-
const response = await fetch('/api/faucet/claim', {
39-
method: 'POST',
40-
headers: {
41-
'Content-Type': 'application/json',
42-
},
43-
body: JSON.stringify(requestBody),
44-
});
45-
46-
const data = await response.json();
33+
const address = faucetAddress.trim();
4734

48-
if (data.success && data.txHash) {
35+
const [devResponse, solResponse] = await Promise.all([
36+
fetch('/api/faucet/claim', {
37+
method: 'POST',
38+
headers: { 'Content-Type': 'application/json' },
39+
body: JSON.stringify({ walletAddress: address, amount: 1500, tokenType: '1DEV' }),
40+
}),
41+
fetch('/api/faucet/claim', {
42+
method: 'POST',
43+
headers: { 'Content-Type': 'application/json' },
44+
body: JSON.stringify({ walletAddress: address, amount: 0.001, tokenType: 'SOL' }),
45+
}),
46+
]);
47+
48+
const [devData, solData] = await Promise.all([devResponse.json(), solResponse.json()]);
49+
50+
if (devData.success || solData.success) {
4951
setLastFaucetClaim(now);
50-
setTxHash(data.txHash);
52+
setDevTxHash(devData.txHash || '');
53+
setSolTxHash(solData.txHash || '');
5154
setShowSuccessAlert(true);
5255
setFaucetAddress('');
56+
if (!devData.success) setErrorMessage('1DEV: ' + (devData.error || 'failed'));
57+
if (!solData.success) setErrorMessage('SOL: ' + (solData.error || 'failed'));
5358
} else {
54-
setErrorMessage(data.error || 'Failed to send tokens. Please try again.');
55-
alert('Error: ' + (data.error || 'Failed to send tokens'));
59+
const err = [devData.error, solData.error].filter(Boolean).join(' | ') || 'Failed to send tokens. Please try again.';
60+
setErrorMessage(err);
5661
}
5762
} catch (error) {
5863
setErrorMessage('Network error. Please check your connection.');
59-
alert('Network error: ' + (error as Error).message);
6064
} finally {
6165
setIsLoading(false);
6266
}
@@ -77,7 +81,7 @@ export default function TestnetPage() {
7781
<div className="card-header" style={{ marginBottom: '2rem', textAlign: 'center' }}>
7882
<h3 style={{ fontSize: '1.8rem', marginBottom: '1rem' }}>Token Faucet</h3>
7983
<div className="faucet-heading" style={{ fontSize: '1.1rem', color: '#e5e5e5', lineHeight: '1.5' }}>
80-
Get 1,500 test 1DEV tokens for development and testing on QNet testnet
84+
Get 1,500 1DEV tokens + 0.001 SOL for QNet node activation testing
8185
</div>
8286
</div>
8387

@@ -182,48 +186,60 @@ export default function TestnetPage() {
182186
maxWidth: '500px',
183187
textAlign: 'center'
184188
}}>
185-
<h3 style={{ color: '#00ffff', marginBottom: '1rem' }}>✅ Success!</h3>
189+
<h3 style={{ color: '#00ffff', marginBottom: '1rem' }}>Tokens sent!</h3>
186190
<p style={{ color: '#e5e5e5', marginBottom: '1rem' }}>
187-
1,500 test 1DEV tokens have been sent to your address successfully!
191+
1,500 1DEV + 0.001 SOL have been sent to your address
188192
</p>
189-
{txHash && (
193+
{devTxHash && (
190194
<div style={{
191195
background: 'rgba(0, 255, 255, 0.1)',
192196
border: '1px solid rgba(0, 255, 255, 0.3)',
193197
borderRadius: '8px',
194198
padding: '1rem',
199+
marginBottom: '0.75rem'
200+
}}>
201+
<p style={{ color: '#888', fontSize: '0.8rem', marginBottom: '0.5rem' }}>1DEV Transaction:</p>
202+
<p style={{ color: '#00ffff', fontSize: '0.85rem', fontFamily: 'monospace', wordBreak: 'break-all' }}>
203+
{devTxHash}
204+
</p>
205+
<a
206+
href={`https://explorer.solana.com/tx/${devTxHash}?cluster=devnet`}
207+
target="_blank"
208+
rel="noopener noreferrer"
209+
style={{ color: '#00ffff', fontSize: '0.8rem', textDecoration: 'underline', marginTop: '0.4rem', display: 'inline-block' }}
210+
>
211+
View on Solana Explorer →
212+
</a>
213+
</div>
214+
)}
215+
{solTxHash && (
216+
<div style={{
217+
background: 'rgba(0, 255, 180, 0.08)',
218+
border: '1px solid rgba(0, 255, 180, 0.3)',
219+
borderRadius: '8px',
220+
padding: '1rem',
195221
marginBottom: '1rem'
196222
}}>
197-
<p style={{ color: '#888', fontSize: '0.8rem', marginBottom: '0.5rem' }}>Transaction Hash:</p>
198-
<p style={{
199-
color: '#00ffff',
200-
fontSize: '0.9rem',
201-
fontFamily: 'monospace',
202-
wordBreak: 'break-all'
203-
}}>
204-
{txHash}
223+
<p style={{ color: '#888', fontSize: '0.8rem', marginBottom: '0.5rem' }}>SOL Transaction:</p>
224+
<p style={{ color: '#00ffb4', fontSize: '0.85rem', fontFamily: 'monospace', wordBreak: 'break-all' }}>
225+
{solTxHash}
205226
</p>
206-
<a
207-
href={`https://explorer.solana.com/tx/${txHash}?cluster=devnet`}
227+
<a
228+
href={`https://explorer.solana.com/tx/${solTxHash}?cluster=devnet`}
208229
target="_blank"
209230
rel="noopener noreferrer"
210-
style={{
211-
color: '#00ffff',
212-
fontSize: '0.85rem',
213-
textDecoration: 'underline',
214-
marginTop: '0.5rem',
215-
display: 'inline-block'
216-
}}
231+
style={{ color: '#00ffb4', fontSize: '0.8rem', textDecoration: 'underline', marginTop: '0.4rem', display: 'inline-block' }}
217232
>
218233
View on Solana Explorer →
219234
</a>
220235
</div>
221236
)}
222-
<button
237+
<button
223238
className="qnet-button"
224239
onClick={() => {
225240
setShowSuccessAlert(false);
226-
setTxHash('');
241+
setDevTxHash('');
242+
setSolTxHash('');
227243
}}
228244
style={{ fontSize: '1rem', padding: '0.75rem 1.5rem' }}
229245
>

0 commit comments

Comments
 (0)