@dignetwork/chia-block-listener 0.1.11 → 0.1.14

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
 
@@ -64,7 +68,6 @@ process.on('SIGINT', () => {
64
68
  process.exit(0)
65
69
  })
66
70
  ```
67
-
68
71
  ## API Reference
69
72
 
70
73
  ### ChiaBlockListener Class
@@ -130,7 +133,7 @@ Retrieves a range of blocks from a connected peer.
130
133
 
131
134
  ### ChiaPeerPool Class
132
135
 
133
- The `ChiaPeerPool` provides a managed pool of peer connections for retrieving historical blocks with automatic load balancing and rate limiting.
136
+ 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
137
 
135
138
  #### Constructor
136
139
 
@@ -320,7 +323,7 @@ interface CoinSpend {
320
323
 
321
324
  ## ChiaPeerPool Usage
322
325
 
323
- The `ChiaPeerPool` is designed for efficiently retrieving historical blocks with automatic load balancing across multiple peers.
326
+ 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
327
 
325
328
  ### Basic Usage
326
329
 
@@ -375,7 +378,7 @@ main().catch(console.error)
375
378
 
376
379
  #### Rate Limiting
377
380
 
378
- The pool automatically enforces a 500ms rate limit per peer to prevent overwhelming any single node:
381
+ The pool automatically enforces a 50ms rate limit per peer for maximum performance while preventing node overload:
379
382
 
380
383
  ```javascript
381
384
  // Rapid requests are automatically queued and distributed
@@ -385,10 +388,48 @@ for (let i = 5000000; i < 5000100; i++) {
385
388
  }
386
389
 
387
390
  // All requests will be processed efficiently across all peers
391
+ // with automatic load balancing and rate limiting
388
392
  const blocks = await Promise.all(promises)
389
393
  console.log(`Retrieved ${blocks.length} blocks`)
390
394
  ```
391
395
 
396
+ #### Automatic Failover and Error Handling
397
+
398
+ The pool provides robust error handling with automatic failover:
399
+
400
+ ```javascript
401
+ // The pool automatically handles various error scenarios:
402
+
403
+ // 1. Connection failures - automatically tries other peers
404
+ try {
405
+ const block = await pool.getBlockByHeight(5000000)
406
+ console.log(`Retrieved block ${block.height}`)
407
+ } catch (error) {
408
+ // If all peers fail, you'll get an error after all retry attempts
409
+ console.error('All peers failed:', error.message)
410
+ }
411
+
412
+ // 2. Protocol errors - peers that refuse blocks are automatically disconnected
413
+ pool.on('peerDisconnected', (event) => {
414
+ console.log(`Peer ${event.peerId} disconnected: ${event.reason}`)
415
+ // Reasons include: "Block request rejected", "Protocol error", "Connection timeout"
416
+ })
417
+
418
+ // 3. Automatic peer cleanup - problematic peers are removed from the pool
419
+ console.log('Active peers before:', await pool.getConnectedPeers())
420
+ await pool.getBlockByHeight(5000000) // May trigger peer removal
421
+ console.log('Active peers after:', await pool.getConnectedPeers())
422
+
423
+ // 4. Multiple retry attempts - tries up to 3 different peers per request
424
+ // This happens automatically and transparently
425
+ const block = await pool.getBlockByHeight(5000000) // Will try multiple peers if needed
426
+ ```
427
+
428
+ **Error Types Handled Automatically:**
429
+ - **Connection Errors**: Timeouts, network failures, WebSocket errors
430
+ - **Protocol Errors**: Block rejections, parsing failures, handshake failures
431
+ - **Peer Misbehavior**: Unexpected responses, invalid data formats
432
+
392
433
  #### Dynamic Peer Management
393
434
 
394
435
  ```javascript
@@ -510,14 +551,14 @@ listener.on('blockReceived', (block: BlockReceivedEvent) => {
510
551
  console.log(`Block ${block.height} from peer ${block.peerId}`)
511
552
 
512
553
  // Process coin additions
513
- block.coin_additions.forEach((coin: CoinRecord) => {
554
+ block.coinAdditions.forEach((coin: CoinRecord) => {
514
555
  console.log(`New coin: ${coin.amount} mojos`)
515
556
  })
516
557
 
517
558
  // Process coin spends
518
- block.coin_spends.forEach((spend: CoinSpend) => {
559
+ block.coinSpends.forEach((spend: CoinSpend) => {
519
560
  console.log(`Spend: ${spend.coin.amount} mojos`)
520
- console.log(`Puzzle: ${spend.puzzle_reveal}`)
561
+ console.log(`Puzzle: ${spend.puzzleReveal}`)
521
562
  console.log(`Solution: ${spend.solution}`)
522
563
  })
523
564
  })
@@ -541,7 +582,7 @@ const testnetPeer = listener.addPeer('testnet-node.chia.net', 58444, 'testnet')
541
582
  async function getHistoricalBlocks() {
542
583
  try {
543
584
  const block = listener.getBlockByHeight(mainnetPeer, 1000000)
544
- console.log(`Block 1000000 hash: ${block.header_hash}`)
585
+ console.log(`Block 1000000 hash: ${block.headerHash}`)
545
586
 
546
587
  const blocks = listener.getBlocksRange(mainnetPeer, 1000000, 1000010)
547
588
  console.log(`Retrieved ${blocks.length} blocks`)
@@ -587,8 +628,8 @@ async function fetchHistoricalData() {
587
628
  listener.on('blockReceived', (block) => {
588
629
  const targetPuzzleHash = '0x1234...' // Your puzzle hash
589
630
 
590
- block.coin_spends.forEach((spend) => {
591
- if (spend.coin.puzzle_hash === targetPuzzleHash) {
631
+ block.coinSpends.forEach((spend) => {
632
+ if (spend.coin.puzzleHash === targetPuzzleHash) {
592
633
  console.log('Found spend for our puzzle!')
593
634
  console.log('Amount:', spend.coin.amount)
594
635
  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);