@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 +52 -11
- 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 +2 -9
- 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/scripts/post-build.js +47 -25
- package/src/peer_pool.rs +710 -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
|
|
|
@@ -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
|
|
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
|
|
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.
|
|
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.
|
|
559
|
+
block.coinSpends.forEach((spend: CoinSpend) => {
|
|
519
560
|
console.log(`Spend: ${spend.coin.amount} mojos`)
|
|
520
|
-
console.log(`Puzzle: ${spend.
|
|
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.
|
|
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.
|
|
591
|
-
if (spend.coin.
|
|
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);
|