@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 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 rate limiting.
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 500ms rate limit per peer to prevent overwhelming any single node:
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.coin_additions.forEach((coin: CoinRecord) => {
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.coin_spends.forEach((spend: CoinSpend) => {
584
+ block.coinSpends.forEach((spend: CoinSpend) => {
519
585
  console.log(`Spend: ${spend.coin.amount} mojos`)
520
- console.log(`Puzzle: ${spend.puzzle_reveal}`)
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.header_hash}`)
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.coin_spends.forEach((spend) => {
591
- if (spend.coin.puzzle_hash === targetPuzzleHash) {
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);