@dignetwork/chia-block-listener 0.1.11 ā 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -10
- package/examples/basic-connection-test.js +55 -0
- package/examples/block-refusal-test.js +251 -0
- package/examples/failover-demo.js +225 -0
- package/examples/failover-test.js +122 -0
- package/examples/optimized-performance-test.js +267 -0
- package/examples/peer-disconnection-test.js +232 -0
- package/examples/peer-discovery-performance-test.js +345 -0
- package/examples/peer-pool-parallel-demo.js +61 -0
- package/examples/peer-pool-performance-test.js +274 -0
- package/examples/port-test.js +55 -0
- package/examples/quick-performance-test.js +112 -0
- package/examples/simple-performance-test.js +83 -0
- package/examples/test-peak-height.js +99 -0
- package/index.d.ts +0 -33
- package/npm/darwin-arm64/package.json +1 -1
- package/npm/darwin-x64/package.json +1 -1
- package/npm/linux-arm64-gnu/package.json +1 -1
- package/npm/linux-x64-gnu/package.json +1 -1
- package/npm/win32-x64-msvc/package.json +1 -1
- package/package.json +6 -6
- package/src/peer_pool.rs +705 -226
- package/src/peer_pool_napi.rs +5 -5
package/README.md
CHANGED
|
@@ -6,11 +6,15 @@ A high-performance Chia blockchain listener for Node.js, built with Rust and NAP
|
|
|
6
6
|
|
|
7
7
|
- **Real-time Block Monitoring**: Listen for new blocks as they're produced on the Chia network
|
|
8
8
|
- **Peer Management**: Connect to multiple Chia full nodes simultaneously
|
|
9
|
+
- **Automatic Failover**: Intelligent peer failover with automatic retry across multiple peers
|
|
10
|
+
- **Enhanced Error Handling**: Automatic disconnection of peers that refuse blocks or have protocol errors
|
|
9
11
|
- **Efficient Parsing**: Fast extraction of coin spends, additions, and removals from blocks
|
|
10
12
|
- **Event-Driven Architecture**: TypeScript-friendly event system with full type safety
|
|
11
13
|
- **Transaction Analysis**: Parse CLVM puzzles and solutions from coin spends
|
|
12
|
-
- **Historical Block Access**: Retrieve blocks by height or ranges
|
|
14
|
+
- **Historical Block Access**: Retrieve blocks by height or ranges with automatic load balancing
|
|
13
15
|
- **Connection Pool**: ChiaPeerPool provides automatic load balancing and rate limiting for historical queries
|
|
16
|
+
- **Peak Height Tracking**: Monitor blockchain sync progress across all connected peers
|
|
17
|
+
- **DNS Peer Discovery**: Automatic peer discovery using Chia network DNS introducers
|
|
14
18
|
- **Cross-platform Support**: Works on Windows, macOS, and Linux (x64 and ARM64)
|
|
15
19
|
- **TypeScript Support**: Complete TypeScript definitions with IntelliSense
|
|
16
20
|
|
|
@@ -65,6 +69,30 @@ process.on('SIGINT', () => {
|
|
|
65
69
|
})
|
|
66
70
|
```
|
|
67
71
|
|
|
72
|
+
## Recent Enhancements š
|
|
73
|
+
|
|
74
|
+
This library includes several powerful enhancements for production use:
|
|
75
|
+
|
|
76
|
+
### Automatic Failover System
|
|
77
|
+
- **Intelligent Retry Logic**: Automatically tries up to 3 different peers when block requests fail
|
|
78
|
+
- **Smart Peer Selection**: Chooses the best available peer for each request based on response times
|
|
79
|
+
- **Transparent Operation**: Failover happens automatically without user intervention
|
|
80
|
+
|
|
81
|
+
### Enhanced Error Handling
|
|
82
|
+
- **Protocol Error Detection**: Automatically detects and disconnects peers that refuse blocks or send invalid data
|
|
83
|
+
- **Connection Health Monitoring**: Tracks peer connection health and removes problematic peers
|
|
84
|
+
- **Error Classification**: Distinguishes between temporary issues and permanent peer problems
|
|
85
|
+
|
|
86
|
+
### Performance Optimizations
|
|
87
|
+
- **Aggressive Rate Limiting**: 50ms rate limiting per peer for maximum throughput
|
|
88
|
+
- **Persistent Connections**: Reuses WebSocket connections for optimal performance
|
|
89
|
+
- **Parallel Processing**: Efficient handling of multiple concurrent block requests
|
|
90
|
+
|
|
91
|
+
### Developer Experience
|
|
92
|
+
- **camelCase API**: All JavaScript methods use proper camelCase naming conventions
|
|
93
|
+
- **Comprehensive Events**: Detailed event system for monitoring peer lifecycle and errors
|
|
94
|
+
- **TypeScript Support**: Full type safety with complete TypeScript definitions
|
|
95
|
+
|
|
68
96
|
## API Reference
|
|
69
97
|
|
|
70
98
|
### ChiaBlockListener Class
|
|
@@ -130,7 +158,7 @@ Retrieves a range of blocks from a connected peer.
|
|
|
130
158
|
|
|
131
159
|
### ChiaPeerPool Class
|
|
132
160
|
|
|
133
|
-
The `ChiaPeerPool` provides a managed pool of peer connections for retrieving historical blocks with automatic load balancing and
|
|
161
|
+
The `ChiaPeerPool` provides a managed pool of peer connections for retrieving historical blocks with automatic load balancing and intelligent failover across multiple peers. When a peer fails to provide a block or experiences protocol errors, the pool automatically tries alternative peers and removes problematic peers from the pool.
|
|
134
162
|
|
|
135
163
|
#### Constructor
|
|
136
164
|
|
|
@@ -320,7 +348,7 @@ interface CoinSpend {
|
|
|
320
348
|
|
|
321
349
|
## ChiaPeerPool Usage
|
|
322
350
|
|
|
323
|
-
The `ChiaPeerPool` is designed for efficiently retrieving historical blocks with automatic load balancing across multiple peers.
|
|
351
|
+
The `ChiaPeerPool` is designed for efficiently retrieving historical blocks with automatic load balancing and intelligent failover across multiple peers. When a peer fails to provide a block or experiences protocol errors, the pool automatically tries alternative peers and removes problematic peers from the pool.
|
|
324
352
|
|
|
325
353
|
### Basic Usage
|
|
326
354
|
|
|
@@ -375,7 +403,7 @@ main().catch(console.error)
|
|
|
375
403
|
|
|
376
404
|
#### Rate Limiting
|
|
377
405
|
|
|
378
|
-
The pool automatically enforces a
|
|
406
|
+
The pool automatically enforces a 50ms rate limit per peer for maximum performance while preventing node overload:
|
|
379
407
|
|
|
380
408
|
```javascript
|
|
381
409
|
// Rapid requests are automatically queued and distributed
|
|
@@ -385,10 +413,48 @@ for (let i = 5000000; i < 5000100; i++) {
|
|
|
385
413
|
}
|
|
386
414
|
|
|
387
415
|
// All requests will be processed efficiently across all peers
|
|
416
|
+
// with automatic load balancing and rate limiting
|
|
388
417
|
const blocks = await Promise.all(promises)
|
|
389
418
|
console.log(`Retrieved ${blocks.length} blocks`)
|
|
390
419
|
```
|
|
391
420
|
|
|
421
|
+
#### Automatic Failover and Error Handling
|
|
422
|
+
|
|
423
|
+
The pool provides robust error handling with automatic failover:
|
|
424
|
+
|
|
425
|
+
```javascript
|
|
426
|
+
// The pool automatically handles various error scenarios:
|
|
427
|
+
|
|
428
|
+
// 1. Connection failures - automatically tries other peers
|
|
429
|
+
try {
|
|
430
|
+
const block = await pool.getBlockByHeight(5000000)
|
|
431
|
+
console.log(`Retrieved block ${block.height}`)
|
|
432
|
+
} catch (error) {
|
|
433
|
+
// If all peers fail, you'll get an error after all retry attempts
|
|
434
|
+
console.error('All peers failed:', error.message)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// 2. Protocol errors - peers that refuse blocks are automatically disconnected
|
|
438
|
+
pool.on('peerDisconnected', (event) => {
|
|
439
|
+
console.log(`Peer ${event.peerId} disconnected: ${event.reason}`)
|
|
440
|
+
// Reasons include: "Block request rejected", "Protocol error", "Connection timeout"
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
// 3. Automatic peer cleanup - problematic peers are removed from the pool
|
|
444
|
+
console.log('Active peers before:', await pool.getConnectedPeers())
|
|
445
|
+
await pool.getBlockByHeight(5000000) // May trigger peer removal
|
|
446
|
+
console.log('Active peers after:', await pool.getConnectedPeers())
|
|
447
|
+
|
|
448
|
+
// 4. Multiple retry attempts - tries up to 3 different peers per request
|
|
449
|
+
// This happens automatically and transparently
|
|
450
|
+
const block = await pool.getBlockByHeight(5000000) // Will try multiple peers if needed
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
**Error Types Handled Automatically:**
|
|
454
|
+
- **Connection Errors**: Timeouts, network failures, WebSocket errors
|
|
455
|
+
- **Protocol Errors**: Block rejections, parsing failures, handshake failures
|
|
456
|
+
- **Peer Misbehavior**: Unexpected responses, invalid data formats
|
|
457
|
+
|
|
392
458
|
#### Dynamic Peer Management
|
|
393
459
|
|
|
394
460
|
```javascript
|
|
@@ -510,14 +576,14 @@ listener.on('blockReceived', (block: BlockReceivedEvent) => {
|
|
|
510
576
|
console.log(`Block ${block.height} from peer ${block.peerId}`)
|
|
511
577
|
|
|
512
578
|
// Process coin additions
|
|
513
|
-
block.
|
|
579
|
+
block.coinAdditions.forEach((coin: CoinRecord) => {
|
|
514
580
|
console.log(`New coin: ${coin.amount} mojos`)
|
|
515
581
|
})
|
|
516
582
|
|
|
517
583
|
// Process coin spends
|
|
518
|
-
block.
|
|
584
|
+
block.coinSpends.forEach((spend: CoinSpend) => {
|
|
519
585
|
console.log(`Spend: ${spend.coin.amount} mojos`)
|
|
520
|
-
console.log(`Puzzle: ${spend.
|
|
586
|
+
console.log(`Puzzle: ${spend.puzzleReveal}`)
|
|
521
587
|
console.log(`Solution: ${spend.solution}`)
|
|
522
588
|
})
|
|
523
589
|
})
|
|
@@ -541,7 +607,7 @@ const testnetPeer = listener.addPeer('testnet-node.chia.net', 58444, 'testnet')
|
|
|
541
607
|
async function getHistoricalBlocks() {
|
|
542
608
|
try {
|
|
543
609
|
const block = listener.getBlockByHeight(mainnetPeer, 1000000)
|
|
544
|
-
console.log(`Block 1000000 hash: ${block.
|
|
610
|
+
console.log(`Block 1000000 hash: ${block.headerHash}`)
|
|
545
611
|
|
|
546
612
|
const blocks = listener.getBlocksRange(mainnetPeer, 1000000, 1000010)
|
|
547
613
|
console.log(`Retrieved ${blocks.length} blocks`)
|
|
@@ -587,8 +653,8 @@ async function fetchHistoricalData() {
|
|
|
587
653
|
listener.on('blockReceived', (block) => {
|
|
588
654
|
const targetPuzzleHash = '0x1234...' // Your puzzle hash
|
|
589
655
|
|
|
590
|
-
block.
|
|
591
|
-
if (spend.coin.
|
|
656
|
+
block.coinSpends.forEach((spend) => {
|
|
657
|
+
if (spend.coin.puzzleHash === targetPuzzleHash) {
|
|
592
658
|
console.log('Found spend for our puzzle!')
|
|
593
659
|
console.log('Amount:', spend.coin.amount)
|
|
594
660
|
console.log('Solution:', spend.solution)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const { ChiaPeerPool } = require('../index.js');
|
|
2
|
+
|
|
3
|
+
// Enable debug logging to see what's happening
|
|
4
|
+
process.env.RUST_LOG = 'chia_block_listener=debug';
|
|
5
|
+
|
|
6
|
+
async function basicConnectionTest() {
|
|
7
|
+
const pool = new ChiaPeerPool();
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
console.log('š Basic Connection Test');
|
|
11
|
+
console.log('=======================\n');
|
|
12
|
+
|
|
13
|
+
// Test with a single known-good peer
|
|
14
|
+
console.log('Testing connection to a reliable peer...');
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
console.log('1. Attempting to add peer...');
|
|
18
|
+
const peerId = await pool.addPeer('185.69.164.168', 8444, 'mainnet');
|
|
19
|
+
console.log(`ā
Peer added successfully: ${peerId}`);
|
|
20
|
+
|
|
21
|
+
console.log('2. Waiting for connection to stabilize...');
|
|
22
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
23
|
+
|
|
24
|
+
const connectedPeers = await pool.getConnectedPeers();
|
|
25
|
+
console.log(`ā
Connected peers: ${connectedPeers.length}`);
|
|
26
|
+
|
|
27
|
+
if (connectedPeers.length > 0) {
|
|
28
|
+
console.log('3. Attempting block request...');
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const block = await pool.getBlockByHeight(3200000);
|
|
32
|
+
console.log(`ā
Block request successful! Height: ${block.height}`);
|
|
33
|
+
} catch (blockError) {
|
|
34
|
+
console.log(`ā Block request failed: ${blockError.message}`);
|
|
35
|
+
console.log('This suggests connection establishment worked but block fetching failed');
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
console.log('ā No connected peers - connection establishment failed');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
} catch (peerError) {
|
|
42
|
+
console.log(`ā Failed to add peer: ${peerError.message}`);
|
|
43
|
+
console.log('This suggests the initial connection setup is failing');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('ā Test failed:', error);
|
|
48
|
+
} finally {
|
|
49
|
+
console.log('\nš Shutting down...');
|
|
50
|
+
await pool.shutdown();
|
|
51
|
+
console.log('ā
Test complete');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
basicConnectionTest().catch(console.error);
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
const { ChiaPeerPool } = require('../index.js');
|
|
2
|
+
const dns = require('dns').promises;
|
|
3
|
+
|
|
4
|
+
// DNS introducers for peer discovery
|
|
5
|
+
const MAINNET_DNS_INTRODUCERS = [
|
|
6
|
+
"dns-introducer.chia.net",
|
|
7
|
+
"chia.ctrlaltdel.ch",
|
|
8
|
+
"seeder.dexie.space",
|
|
9
|
+
"chia.hoffmang.com"
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const MAINNET_DEFAULT_PORT = 8444;
|
|
13
|
+
|
|
14
|
+
// Shuffle array for randomness
|
|
15
|
+
function shuffleArray(array) {
|
|
16
|
+
const shuffled = [...array];
|
|
17
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
18
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
19
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
20
|
+
}
|
|
21
|
+
return shuffled;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Discover actual peer IPs using DNS introducers
|
|
25
|
+
async function discoverPeers(networkId = 'mainnet') {
|
|
26
|
+
console.log(`š Discovering peers for ${networkId} using DNS introducers...`);
|
|
27
|
+
|
|
28
|
+
let allAddresses = [];
|
|
29
|
+
|
|
30
|
+
// Resolve all introducers to IP addresses
|
|
31
|
+
for (const introducer of MAINNET_DNS_INTRODUCERS) {
|
|
32
|
+
try {
|
|
33
|
+
console.log(` Resolving ${introducer}...`);
|
|
34
|
+
const addresses = await dns.lookup(introducer, { all: true });
|
|
35
|
+
for (const addr of addresses) {
|
|
36
|
+
allAddresses.push({
|
|
37
|
+
host: addr.address,
|
|
38
|
+
port: MAINNET_DEFAULT_PORT,
|
|
39
|
+
family: addr.family,
|
|
40
|
+
source: introducer
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
console.log(` Found ${addresses.length} peers from ${introducer}`);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.log(` Failed to resolve ${introducer}: ${error.message}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (allAddresses.length === 0) {
|
|
50
|
+
throw new Error('Failed to resolve any peer addresses from introducers');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Remove duplicates and shuffle
|
|
54
|
+
const uniqueAddresses = [];
|
|
55
|
+
const seen = new Set();
|
|
56
|
+
for (const addr of allAddresses) {
|
|
57
|
+
const key = `${addr.host}:${addr.port}`;
|
|
58
|
+
if (!seen.has(key)) {
|
|
59
|
+
seen.add(key);
|
|
60
|
+
uniqueAddresses.push(addr);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return shuffleArray(uniqueAddresses);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Format address for display (IPv6 needs brackets in URLs)
|
|
68
|
+
function formatAddress(host, port, family) {
|
|
69
|
+
if (family === 6) {
|
|
70
|
+
return `[${host}]:${port}`;
|
|
71
|
+
}
|
|
72
|
+
return `${host}:${port}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function testBlockRefusalHandling() {
|
|
76
|
+
console.log('š« Testing Block Refusal and Automatic Peer Disconnection');
|
|
77
|
+
console.log('='.repeat(60));
|
|
78
|
+
|
|
79
|
+
const pool = new ChiaPeerPool();
|
|
80
|
+
|
|
81
|
+
let disconnectedPeers = [];
|
|
82
|
+
let connectedPeers = [];
|
|
83
|
+
|
|
84
|
+
// Set up event handlers to monitor peer lifecycle
|
|
85
|
+
pool.on('peerConnected', (event) => {
|
|
86
|
+
connectedPeers.push(`${event.host}:${event.port}`);
|
|
87
|
+
console.log(`ā
Peer connected: ${event.host}:${event.port}`);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
pool.on('peerDisconnected', (event) => {
|
|
91
|
+
const peerAddr = `${event.host}:${event.port}`;
|
|
92
|
+
disconnectedPeers.push(peerAddr);
|
|
93
|
+
console.log(`ā Peer disconnected: ${peerAddr} - ${event.reason || 'Unknown reason'}`);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
console.log('\n1ļøā£ Discovering and connecting to peers...');
|
|
98
|
+
|
|
99
|
+
// Discover actual peer IPs
|
|
100
|
+
const discoveredPeers = await discoverPeers('mainnet');
|
|
101
|
+
|
|
102
|
+
// Try to connect to several peers
|
|
103
|
+
const peersToTry = discoveredPeers.slice(0, 8);
|
|
104
|
+
let successfulConnections = 0;
|
|
105
|
+
|
|
106
|
+
for (const peer of peersToTry) {
|
|
107
|
+
const displayAddress = formatAddress(peer.host, peer.port, peer.family);
|
|
108
|
+
try {
|
|
109
|
+
await pool.addPeer(peer.host, peer.port, 'mainnet');
|
|
110
|
+
successfulConnections++;
|
|
111
|
+
console.log(` ā
Connected: ${displayAddress}`);
|
|
112
|
+
|
|
113
|
+
// Stop once we have enough peers for testing
|
|
114
|
+
if (successfulConnections >= 4) {
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.log(` ā Failed: ${displayAddress} - ${error.message}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log(`\nš Connected to ${successfulConnections} peers`);
|
|
123
|
+
|
|
124
|
+
if (successfulConnections === 0) {
|
|
125
|
+
console.log('ā No peers connected. Cannot test block refusal handling.');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Wait for connections to stabilize
|
|
130
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
131
|
+
|
|
132
|
+
console.log('\n2ļøā£ Testing block requests to identify problematic peers...');
|
|
133
|
+
|
|
134
|
+
// Test with a series of block heights to see if any peers refuse
|
|
135
|
+
const testHeights = [4920000, 4920001, 4920002, 4920003, 4920004];
|
|
136
|
+
let requestCount = 0;
|
|
137
|
+
let successCount = 0;
|
|
138
|
+
let errorPatterns = new Map();
|
|
139
|
+
|
|
140
|
+
for (const height of testHeights) {
|
|
141
|
+
console.log(`\nTesting block ${height}...`);
|
|
142
|
+
const startTime = Date.now();
|
|
143
|
+
requestCount++;
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const block = await pool.getBlockByHeight(height);
|
|
147
|
+
const duration = Date.now() - startTime;
|
|
148
|
+
successCount++;
|
|
149
|
+
console.log(` ā
Success: Got block in ${duration}ms (${block.transactions?.length || 0} transactions)`);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
const duration = Date.now() - startTime;
|
|
152
|
+
console.log(` ā Failed after ${duration}ms: ${error.message}`);
|
|
153
|
+
|
|
154
|
+
// Track error patterns
|
|
155
|
+
const errorType = error.message.includes('rejected') ? 'Block Rejected' :
|
|
156
|
+
error.message.includes('Protocol') ? 'Protocol Error' :
|
|
157
|
+
error.message.includes('timeout') ? 'Timeout' :
|
|
158
|
+
error.message.includes('Connection') ? 'Connection Error' :
|
|
159
|
+
'Other';
|
|
160
|
+
|
|
161
|
+
errorPatterns.set(errorType, (errorPatterns.get(errorType) || 0) + 1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Check current peer status
|
|
165
|
+
const currentPeers = await pool.getConnectedPeers();
|
|
166
|
+
console.log(` š Active peers: ${currentPeers.length}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.log('\n3ļøā£ Testing parallel requests to stress test error handling...');
|
|
170
|
+
|
|
171
|
+
const parallelPromises = [];
|
|
172
|
+
const parallelHeights = [4920005, 4920006, 4920007, 4920008, 4920009];
|
|
173
|
+
|
|
174
|
+
for (const height of parallelHeights) {
|
|
175
|
+
parallelPromises.push(
|
|
176
|
+
pool.getBlockByHeight(height)
|
|
177
|
+
.then(block => ({
|
|
178
|
+
height,
|
|
179
|
+
success: true,
|
|
180
|
+
transactions: block.transactions?.length || 0
|
|
181
|
+
}))
|
|
182
|
+
.catch(error => ({
|
|
183
|
+
height,
|
|
184
|
+
success: false,
|
|
185
|
+
error: error.message
|
|
186
|
+
}))
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
console.log(' Launching 5 parallel requests...');
|
|
191
|
+
const parallelResults = await Promise.all(parallelPromises);
|
|
192
|
+
|
|
193
|
+
let parallelSuccessCount = 0;
|
|
194
|
+
parallelResults.forEach(result => {
|
|
195
|
+
if (result.success) {
|
|
196
|
+
parallelSuccessCount++;
|
|
197
|
+
console.log(` ā
Block ${result.height}: ${result.transactions} transactions`);
|
|
198
|
+
} else {
|
|
199
|
+
console.log(` ā Block ${result.height}: ${result.error}`);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Final status check
|
|
204
|
+
const finalPeers = await pool.getConnectedPeers();
|
|
205
|
+
|
|
206
|
+
console.log('\n4ļøā£ Test Results Summary');
|
|
207
|
+
console.log('='.repeat(40));
|
|
208
|
+
console.log(`š Sequential requests: ${successCount}/${requestCount} successful`);
|
|
209
|
+
console.log(`š Parallel requests: ${parallelSuccessCount}/${parallelResults.length} successful`);
|
|
210
|
+
console.log(`š Initial peers: ${connectedPeers.length}`);
|
|
211
|
+
console.log(`š Final peers: ${finalPeers.length}`);
|
|
212
|
+
console.log(`ā Disconnected peers: ${disconnectedPeers.length}`);
|
|
213
|
+
|
|
214
|
+
if (errorPatterns.size > 0) {
|
|
215
|
+
console.log('\nš Error Pattern Analysis:');
|
|
216
|
+
for (const [errorType, count] of errorPatterns.entries()) {
|
|
217
|
+
console.log(` ${errorType}: ${count} occurrences`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (disconnectedPeers.length > 0) {
|
|
222
|
+
console.log('\nš« Disconnected Peers:');
|
|
223
|
+
disconnectedPeers.forEach((peer, index) => {
|
|
224
|
+
console.log(` ${index + 1}. ${peer}`);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
console.log('\nā
Key Features Demonstrated:');
|
|
229
|
+
console.log('⢠ā
Automatic peer discovery using DNS introducers');
|
|
230
|
+
console.log('⢠ā
Detection of peers that refuse to provide blocks');
|
|
231
|
+
console.log('⢠ā
Automatic disconnection of problematic peers');
|
|
232
|
+
console.log('⢠ā
Failover to working peers when others fail');
|
|
233
|
+
console.log('⢠ā
Protocol error handling (block rejections, parsing errors)');
|
|
234
|
+
console.log('⢠ā
Connection error handling (timeouts, network issues)');
|
|
235
|
+
|
|
236
|
+
} catch (error) {
|
|
237
|
+
console.error('ā Test error:', error);
|
|
238
|
+
} finally {
|
|
239
|
+
console.log('\nš Shutting down...');
|
|
240
|
+
await pool.shutdown();
|
|
241
|
+
console.log('ā
Test completed.');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Handle graceful shutdown
|
|
246
|
+
process.on('SIGINT', () => {
|
|
247
|
+
console.log('\n\nā ļø Received interrupt signal, shutting down gracefully...');
|
|
248
|
+
process.exit(0);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
testBlockRefusalHandling().catch(console.error);
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
const { ChiaPeerPool } = require('../index.js');
|
|
2
|
+
const dns = require('dns').promises;
|
|
3
|
+
|
|
4
|
+
// DNS introducers for peer discovery
|
|
5
|
+
const MAINNET_DNS_INTRODUCERS = [
|
|
6
|
+
"dns-introducer.chia.net",
|
|
7
|
+
"chia.ctrlaltdel.ch",
|
|
8
|
+
"seeder.dexie.space",
|
|
9
|
+
"chia.hoffmang.com"
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const MAINNET_DEFAULT_PORT = 8444;
|
|
13
|
+
|
|
14
|
+
// Shuffle array for randomness
|
|
15
|
+
function shuffleArray(array) {
|
|
16
|
+
const shuffled = [...array];
|
|
17
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
18
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
19
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
20
|
+
}
|
|
21
|
+
return shuffled;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Discover actual peer IPs using DNS introducers
|
|
25
|
+
async function discoverPeers(networkId = 'mainnet') {
|
|
26
|
+
const introducers = MAINNET_DNS_INTRODUCERS;
|
|
27
|
+
const defaultPort = MAINNET_DEFAULT_PORT;
|
|
28
|
+
|
|
29
|
+
console.log(`š Discovering peers for ${networkId} using DNS introducers...`);
|
|
30
|
+
|
|
31
|
+
let allAddresses = [];
|
|
32
|
+
|
|
33
|
+
// Resolve all introducers to IP addresses
|
|
34
|
+
for (const introducer of introducers) {
|
|
35
|
+
try {
|
|
36
|
+
console.log(` Resolving ${introducer}...`);
|
|
37
|
+
const addresses = await dns.lookup(introducer, { all: true });
|
|
38
|
+
for (const addr of addresses) {
|
|
39
|
+
// Store the address with family information for proper handling
|
|
40
|
+
allAddresses.push({
|
|
41
|
+
host: addr.address,
|
|
42
|
+
port: defaultPort,
|
|
43
|
+
family: addr.family, // 4 for IPv4, 6 for IPv6
|
|
44
|
+
source: introducer
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
console.log(` Found ${addresses.length} peers from ${introducer}`);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.log(` Failed to resolve ${introducer}: ${error.message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (allAddresses.length === 0) {
|
|
54
|
+
throw new Error('Failed to resolve any peer addresses from introducers');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Shuffle for randomness and remove duplicates
|
|
58
|
+
const uniqueAddresses = [];
|
|
59
|
+
const seen = new Set();
|
|
60
|
+
for (const addr of allAddresses) {
|
|
61
|
+
const key = `${addr.host}:${addr.port}`;
|
|
62
|
+
if (!seen.has(key)) {
|
|
63
|
+
seen.add(key);
|
|
64
|
+
uniqueAddresses.push(addr);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const shuffledAddresses = shuffleArray(uniqueAddresses);
|
|
69
|
+
console.log(` Total unique peers discovered: ${shuffledAddresses.length}`);
|
|
70
|
+
|
|
71
|
+
return shuffledAddresses;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Format address for display (IPv6 needs brackets in URLs)
|
|
75
|
+
function formatAddress(host, port, family) {
|
|
76
|
+
if (family === 6) {
|
|
77
|
+
return `[${host}]:${port}`;
|
|
78
|
+
}
|
|
79
|
+
return `${host}:${port}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function demonstrateFailover() {
|
|
83
|
+
console.log('š Demonstrating Automatic Failover Functionality');
|
|
84
|
+
console.log('='.repeat(50));
|
|
85
|
+
|
|
86
|
+
const pool = new ChiaPeerPool();
|
|
87
|
+
|
|
88
|
+
// Set up event handlers to monitor peer lifecycle
|
|
89
|
+
pool.on('peerConnected', (event) => {
|
|
90
|
+
console.log(`ā
Peer connected: ${event.host}:${event.port}`);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
pool.on('peerDisconnected', (event) => {
|
|
94
|
+
console.log(`ā Peer disconnected: ${event.host}:${event.port} - ${event.reason}`);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
console.log('\n1ļøā£ Discovering peers using DNS introducers...');
|
|
99
|
+
|
|
100
|
+
// Discover actual peer IPs
|
|
101
|
+
const discoveredPeers = await discoverPeers('mainnet');
|
|
102
|
+
|
|
103
|
+
// Take first 6 peers: some may work, some may fail
|
|
104
|
+
const peersToTry = discoveredPeers.slice(0, 6);
|
|
105
|
+
|
|
106
|
+
console.log(`\n2ļøā£ Adding discovered peers to pool...`);
|
|
107
|
+
let connectedCount = 0;
|
|
108
|
+
|
|
109
|
+
for (const peer of peersToTry) {
|
|
110
|
+
const displayAddress = formatAddress(peer.host, peer.port, peer.family);
|
|
111
|
+
try {
|
|
112
|
+
await pool.addPeer(peer.host, peer.port, 'mainnet');
|
|
113
|
+
connectedCount++;
|
|
114
|
+
console.log(` ā
Successfully added: ${displayAddress} (from ${peer.source})`);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.log(` ā Failed to add: ${displayAddress} - ${error.message}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log(`\nš Result: ${connectedCount}/${peersToTry.length} peers connected`);
|
|
121
|
+
|
|
122
|
+
if (connectedCount === 0) {
|
|
123
|
+
console.log('ā No peers connected. Cannot demonstrate failover.');
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Wait for connections to stabilize
|
|
128
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
129
|
+
|
|
130
|
+
console.log('\n3ļøā£ Testing automatic failover...');
|
|
131
|
+
console.log('The system will try multiple peers if one fails.\n');
|
|
132
|
+
|
|
133
|
+
// Test with a known good block height
|
|
134
|
+
const testHeight = 4920000;
|
|
135
|
+
const attempts = 3;
|
|
136
|
+
|
|
137
|
+
for (let i = 1; i <= attempts; i++) {
|
|
138
|
+
console.log(`Attempt ${i}: Requesting block ${testHeight + i - 1}...`);
|
|
139
|
+
const startTime = Date.now();
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const block = await pool.getBlockByHeight(testHeight + i - 1);
|
|
143
|
+
const duration = Date.now() - startTime;
|
|
144
|
+
|
|
145
|
+
console.log(` ā
Success! Got block in ${duration}ms`);
|
|
146
|
+
console.log(` š Block info: ${block.transactions?.length || 0} transactions`);
|
|
147
|
+
|
|
148
|
+
// Check which peer provided the block
|
|
149
|
+
const currentPeers = await pool.getConnectedPeers();
|
|
150
|
+
console.log(` š Available peers: ${currentPeers.length}`);
|
|
151
|
+
|
|
152
|
+
} catch (error) {
|
|
153
|
+
const duration = Date.now() - startTime;
|
|
154
|
+
console.log(` ā Failed after ${duration}ms: ${error.message}`);
|
|
155
|
+
|
|
156
|
+
// Show remaining peers after failure
|
|
157
|
+
const remainingPeers = await pool.getConnectedPeers();
|
|
158
|
+
console.log(` š Remaining peers: ${remainingPeers.length}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log('');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log('4ļøā£ Testing rapid parallel requests (stress test)...');
|
|
165
|
+
|
|
166
|
+
const parallelRequests = [];
|
|
167
|
+
const baseHeight = 4920000;
|
|
168
|
+
|
|
169
|
+
for (let i = 0; i < 5; i++) {
|
|
170
|
+
const height = baseHeight + i;
|
|
171
|
+
parallelRequests.push(
|
|
172
|
+
pool.getBlockByHeight(height)
|
|
173
|
+
.then(block => ({
|
|
174
|
+
height,
|
|
175
|
+
success: true,
|
|
176
|
+
transactions: block.transactions?.length || 0,
|
|
177
|
+
duration: 'N/A' // Duration tracking would require more complex logic
|
|
178
|
+
}))
|
|
179
|
+
.catch(error => ({
|
|
180
|
+
height,
|
|
181
|
+
success: false,
|
|
182
|
+
error: error.message
|
|
183
|
+
}))
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
console.log(' Launching 5 parallel requests...');
|
|
188
|
+
const results = await Promise.all(parallelRequests);
|
|
189
|
+
|
|
190
|
+
let successCount = 0;
|
|
191
|
+
results.forEach(result => {
|
|
192
|
+
if (result.success) {
|
|
193
|
+
successCount++;
|
|
194
|
+
console.log(` ā
Block ${result.height}: ${result.transactions} transactions`);
|
|
195
|
+
} else {
|
|
196
|
+
console.log(` ā Block ${result.height}: ${result.error}`);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
console.log(`\nš Parallel test results: ${successCount}/${results.length} successful`);
|
|
201
|
+
|
|
202
|
+
console.log('\nā
Failover demonstration completed!');
|
|
203
|
+
console.log('\nKey points demonstrated:');
|
|
204
|
+
console.log('⢠ā
DNS introducers used to discover actual peer IPs');
|
|
205
|
+
console.log('⢠ā
Failed peers are automatically rejected during connection');
|
|
206
|
+
console.log('⢠ā
Block requests succeed even with some failed peers');
|
|
207
|
+
console.log('⢠ā
System handles parallel requests efficiently');
|
|
208
|
+
console.log('⢠ā
Automatic failover works transparently to the user');
|
|
209
|
+
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error('ā Test error:', error);
|
|
212
|
+
} finally {
|
|
213
|
+
console.log('\nš Shutting down...');
|
|
214
|
+
await pool.shutdown();
|
|
215
|
+
console.log('ā
Shutdown complete.');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Handle graceful shutdown
|
|
220
|
+
process.on('SIGINT', () => {
|
|
221
|
+
console.log('\n\nā ļø Received interrupt signal, shutting down gracefully...');
|
|
222
|
+
process.exit(0);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
demonstrateFailover().catch(console.error);
|