@dignetwork/chia-block-listener 0.1.16 → 0.1.18
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 +1427 -1030
- package/examples/block-parser-example.mjs +55 -0
- package/index.d.ts +83 -62
- package/index.js +2 -2
- 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/block_parser_napi.rs +265 -0
- package/src/event_emitter.rs +2 -2
- package/src/lib.rs +2 -0
- package/src/peer_pool.rs +3 -3
package/README.md
CHANGED
|
@@ -1,1031 +1,1428 @@
|
|
|
1
|
-
# Chia Block Listener
|
|
2
|
-
|
|
3
|
-
A high-performance Chia blockchain listener for Node.js, built with Rust and NAPI bindings. This library provides real-time monitoring of the Chia blockchain with efficient peer connections and block parsing capabilities.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **Real-time Block Monitoring**: Listen for new blocks as they're produced on the Chia network
|
|
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
|
|
11
|
-
- **Efficient Parsing**: Fast extraction of coin spends, additions, and removals from blocks
|
|
12
|
-
- **Event-Driven Architecture**: TypeScript-friendly event system with full type safety
|
|
13
|
-
- **Transaction Analysis**: Parse CLVM puzzles and solutions from coin spends
|
|
14
|
-
- **Historical Block Access**: Retrieve blocks by height or ranges with automatic load balancing
|
|
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 with IPv4/IPv6 support
|
|
18
|
-
- **Cross-platform Support**: Works on Windows, macOS, and Linux (x64 and ARM64)
|
|
19
|
-
- **TypeScript Support**: Complete TypeScript definitions with IntelliSense
|
|
20
|
-
|
|
21
|
-
## Installation
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
npm install @dignetwork/chia-block-listener
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Quick Start
|
|
28
|
-
|
|
29
|
-
```javascript
|
|
30
|
-
const { ChiaBlockListener, initTracing } = require('@dignetwork/chia-block-listener')
|
|
31
|
-
|
|
32
|
-
// Initialize tracing for debugging (optional)
|
|
33
|
-
initTracing()
|
|
34
|
-
|
|
35
|
-
// Create a new listener instance
|
|
36
|
-
const listener = new ChiaBlockListener()
|
|
37
|
-
|
|
38
|
-
// Listen for block events
|
|
39
|
-
listener.on('blockReceived', (block) => {
|
|
40
|
-
console.log(`New block received: ${block.height}`)
|
|
41
|
-
console.log(`Header hash: ${block.headerHash}`)
|
|
42
|
-
console.log(`Timestamp: ${new Date(block.timestamp * 1000)}`)
|
|
43
|
-
console.log(`Coin additions: ${block.coinAdditions.length}`)
|
|
44
|
-
console.log(`Coin removals: ${block.coinRemovals.length}`)
|
|
45
|
-
console.log(`Coin spends: ${block.coinSpends.length}`)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
// Listen for peer connection events
|
|
49
|
-
listener.on('peerConnected', (peer) => {
|
|
50
|
-
console.log(`Connected to peer: ${peer.peerId} (${peer.host}:${peer.port})`)
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
listener.on('peerDisconnected', (peer) => {
|
|
54
|
-
console.log(`Disconnected from peer: ${peer.peerId}`)
|
|
55
|
-
if (peer.message) {
|
|
56
|
-
console.log(`Reason: ${peer.message}`)
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
// Connect to a Chia full node
|
|
61
|
-
const peerId = listener.addPeer('localhost', 8444, 'mainnet')
|
|
62
|
-
console.log(`Added peer: ${peerId}`)
|
|
63
|
-
|
|
64
|
-
// Keep the process running
|
|
65
|
-
process.on('SIGINT', () => {
|
|
66
|
-
console.log('Shutting down...')
|
|
67
|
-
listener.disconnectAllPeers()
|
|
68
|
-
process.exit(0)
|
|
69
|
-
})
|
|
70
|
-
```
|
|
71
|
-
## API Reference
|
|
72
|
-
|
|
73
|
-
### ChiaBlockListener Class
|
|
74
|
-
|
|
75
|
-
#### Constructor
|
|
76
|
-
|
|
77
|
-
```javascript
|
|
78
|
-
const listener = new ChiaBlockListener()
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Creates a new Chia block listener instance.
|
|
82
|
-
|
|
83
|
-
#### Methods
|
|
84
|
-
|
|
85
|
-
##### `addPeer(host, port, networkId): string`
|
|
86
|
-
|
|
87
|
-
Connects to a Chia full node and starts listening for blocks.
|
|
88
|
-
|
|
89
|
-
**Parameters:**
|
|
90
|
-
- `host` (string): The hostname or IP address of the Chia node
|
|
91
|
-
- `port` (number): The port number (typically 8444 for mainnet)
|
|
92
|
-
- `networkId` (string): The network identifier ('mainnet', 'testnet', etc.)
|
|
93
|
-
|
|
94
|
-
**Returns:** A unique peer ID string for this connection
|
|
95
|
-
|
|
96
|
-
##### `disconnectPeer(peerId): boolean`
|
|
97
|
-
|
|
98
|
-
Disconnects from a specific peer.
|
|
99
|
-
|
|
100
|
-
**Parameters:**
|
|
101
|
-
- `peerId` (string): The peer ID returned by `addPeer()`
|
|
102
|
-
|
|
103
|
-
**Returns:** `true` if the peer was successfully disconnected, `false` otherwise
|
|
104
|
-
|
|
105
|
-
##### `disconnectAllPeers(): void`
|
|
106
|
-
|
|
107
|
-
Disconnects from all connected peers.
|
|
108
|
-
|
|
109
|
-
##### `getConnectedPeers(): string[]`
|
|
110
|
-
|
|
111
|
-
Returns an array of currently connected peer IDs.
|
|
112
|
-
|
|
113
|
-
##### `getBlockByHeight(peerId, height): BlockReceivedEvent`
|
|
114
|
-
|
|
115
|
-
Retrieves a specific block by its height from a connected peer.
|
|
116
|
-
|
|
117
|
-
**Parameters:**
|
|
118
|
-
- `peerId` (string): The peer ID to query
|
|
119
|
-
- `height` (number): The block height to retrieve
|
|
120
|
-
|
|
121
|
-
**Returns:** A `BlockReceivedEvent` object containing the block data
|
|
122
|
-
|
|
123
|
-
##### `getBlocksRange(peerId, startHeight, endHeight): BlockReceivedEvent[]`
|
|
124
|
-
|
|
125
|
-
Retrieves a range of blocks from a connected peer.
|
|
126
|
-
|
|
127
|
-
**Parameters:**
|
|
128
|
-
- `peerId` (string): The peer ID to query
|
|
129
|
-
- `startHeight` (number): The starting block height (inclusive)
|
|
130
|
-
- `endHeight` (number): The ending block height (inclusive)
|
|
131
|
-
|
|
132
|
-
**Returns:** An array of `BlockReceivedEvent` objects
|
|
133
|
-
|
|
134
|
-
### ChiaPeerPool Class
|
|
135
|
-
|
|
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.
|
|
137
|
-
|
|
138
|
-
#### Constructor
|
|
139
|
-
|
|
140
|
-
```javascript
|
|
141
|
-
const pool = new ChiaPeerPool()
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
Creates a new peer pool instance with built-in rate limiting (500ms per peer).
|
|
145
|
-
|
|
146
|
-
#### Methods
|
|
147
|
-
|
|
148
|
-
##### `addPeer(host, port, networkId): Promise<string>`
|
|
149
|
-
|
|
150
|
-
Adds a peer to the connection pool.
|
|
151
|
-
|
|
152
|
-
**Parameters:**
|
|
153
|
-
- `host` (string): The hostname or IP address of the Chia node
|
|
154
|
-
- `port` (number): The port number (typically 8444 for mainnet)
|
|
155
|
-
- `networkId` (string): The network identifier ('mainnet', 'testnet', etc.)
|
|
156
|
-
|
|
157
|
-
**Returns:** A Promise that resolves to a unique peer ID string
|
|
158
|
-
|
|
159
|
-
##### `getBlockByHeight(height): Promise<BlockReceivedEvent>`
|
|
160
|
-
|
|
161
|
-
Retrieves a specific block by height using automatic peer selection and load balancing.
|
|
162
|
-
|
|
163
|
-
**Parameters:**
|
|
164
|
-
- `height` (number): The block height to retrieve
|
|
165
|
-
|
|
166
|
-
**Returns:** A Promise that resolves to a `BlockReceivedEvent` object
|
|
167
|
-
|
|
168
|
-
##### `removePeer(peerId): Promise<boolean>`
|
|
169
|
-
|
|
170
|
-
Removes a peer from the pool.
|
|
171
|
-
|
|
172
|
-
**Parameters:**
|
|
173
|
-
- `peerId` (string): The peer ID to remove
|
|
174
|
-
|
|
175
|
-
**Returns:** A Promise that resolves to `true` if the peer was removed, `false` otherwise
|
|
176
|
-
|
|
177
|
-
##### `shutdown(): Promise<void>`
|
|
178
|
-
|
|
179
|
-
Shuts down the pool and disconnects all peers.
|
|
180
|
-
|
|
181
|
-
##### `getConnectedPeers(): Promise<string[]>`
|
|
182
|
-
|
|
183
|
-
Gets the list of currently connected peer IDs.
|
|
184
|
-
|
|
185
|
-
**Returns:** Array of peer ID strings (format: "host:port")
|
|
186
|
-
|
|
187
|
-
##### `getPeakHeight(): Promise<number | null>`
|
|
188
|
-
|
|
189
|
-
Gets the highest blockchain peak height seen across all connected peers.
|
|
190
|
-
|
|
191
|
-
**Returns:** The highest peak height as a number, or null if no peaks have been received yet
|
|
192
|
-
|
|
193
|
-
##### `on(event, callback): void`
|
|
194
|
-
|
|
195
|
-
Registers an event handler for pool events.
|
|
196
|
-
|
|
197
|
-
**Parameters:**
|
|
198
|
-
- `event` (string): The event name ('peerConnected' or 'peerDisconnected')
|
|
199
|
-
- `callback` (function): The event handler function
|
|
200
|
-
|
|
201
|
-
##### `off(event, callback): void`
|
|
202
|
-
|
|
203
|
-
Removes an event handler.
|
|
204
|
-
|
|
205
|
-
**Parameters:**
|
|
206
|
-
- `event` (string): The event name to stop listening for
|
|
207
|
-
|
|
208
|
-
### Events
|
|
209
|
-
|
|
210
|
-
The `ChiaBlockListener` emits the following events:
|
|
211
|
-
|
|
212
|
-
#### `blockReceived`
|
|
213
|
-
|
|
214
|
-
Fired when a new block is received from any connected peer.
|
|
215
|
-
|
|
216
|
-
**Callback:** `(event: BlockReceivedEvent) => void`
|
|
217
|
-
|
|
218
|
-
#### `peerConnected`
|
|
219
|
-
|
|
220
|
-
Fired when a connection to a peer is established.
|
|
221
|
-
|
|
222
|
-
**Callback:** `(event: PeerConnectedEvent) => void`
|
|
223
|
-
|
|
224
|
-
#### `peerDisconnected`
|
|
225
|
-
|
|
226
|
-
Fired when a peer connection is lost.
|
|
227
|
-
|
|
228
|
-
**Callback:** `(event: PeerDisconnectedEvent) => void`
|
|
229
|
-
|
|
230
|
-
### ChiaPeerPool Events
|
|
231
|
-
|
|
232
|
-
The `ChiaPeerPool` emits the following events:
|
|
233
|
-
|
|
234
|
-
#### `peerConnected`
|
|
235
|
-
|
|
236
|
-
Fired when a peer is successfully added to the pool.
|
|
237
|
-
|
|
238
|
-
**Callback:** `(event: PeerConnectedEvent) => void`
|
|
239
|
-
|
|
240
|
-
#### `peerDisconnected`
|
|
241
|
-
|
|
242
|
-
Fired when a peer is removed from the pool or disconnects.
|
|
243
|
-
|
|
244
|
-
**Callback:** `(event: PeerDisconnectedEvent) => void`
|
|
245
|
-
|
|
246
|
-
#### `newPeakHeight`
|
|
247
|
-
|
|
248
|
-
Fired when a new highest blockchain peak is discovered.
|
|
249
|
-
|
|
250
|
-
**Callback:** `(event: NewPeakHeightEvent) => void`
|
|
251
|
-
|
|
252
|
-
### DnsDiscoveryClient Class
|
|
253
|
-
|
|
254
|
-
The `DnsDiscoveryClient` provides automatic peer discovery using Chia network DNS introducers with full IPv4 and IPv6 support.
|
|
255
|
-
|
|
256
|
-
#### Constructor
|
|
257
|
-
|
|
258
|
-
```javascript
|
|
259
|
-
const client = new DnsDiscoveryClient()
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
Creates a new DNS discovery client instance.
|
|
263
|
-
|
|
264
|
-
#### Methods
|
|
265
|
-
|
|
266
|
-
##### `discoverMainnetPeers(): Promise<DiscoveryResultJS>`
|
|
267
|
-
|
|
268
|
-
Discovers peers for Chia mainnet using built-in DNS introducers.
|
|
269
|
-
|
|
270
|
-
**Returns:** Promise resolving to discovery results with separate IPv4 and IPv6 peer lists
|
|
271
|
-
|
|
272
|
-
##### `discoverTestnet11Peers(): Promise<DiscoveryResultJS>`
|
|
273
|
-
|
|
274
|
-
Discovers peers for Chia testnet11 using built-in DNS introducers.
|
|
275
|
-
|
|
276
|
-
**Returns:** Promise resolving to discovery results
|
|
277
|
-
|
|
278
|
-
##### `discoverPeers(introducers, port): Promise<DiscoveryResultJS>`
|
|
279
|
-
|
|
280
|
-
Discovers peers using custom DNS introducers.
|
|
281
|
-
|
|
282
|
-
**Parameters:**
|
|
283
|
-
- `introducers` (string[]): Array of DNS introducer hostnames
|
|
284
|
-
- `port` (number): Default port for discovered peers
|
|
285
|
-
|
|
286
|
-
**Returns:** Promise resolving to discovery results
|
|
287
|
-
|
|
288
|
-
##### `resolveIpv4(hostname): Promise<AddressResult>`
|
|
289
|
-
|
|
290
|
-
Resolves IPv4 addresses (A records) for a hostname.
|
|
291
|
-
|
|
292
|
-
**Parameters:**
|
|
293
|
-
- `hostname` (string): Hostname to resolve
|
|
294
|
-
|
|
295
|
-
**Returns:** Promise resolving to IPv4 addresses
|
|
296
|
-
|
|
297
|
-
##### `resolveIpv6(hostname): Promise<AddressResult>`
|
|
298
|
-
|
|
299
|
-
Resolves IPv6 addresses (AAAA records) for a hostname.
|
|
300
|
-
|
|
301
|
-
**Parameters:**
|
|
302
|
-
- `hostname` (string): Hostname to resolve
|
|
303
|
-
|
|
304
|
-
**Returns:** Promise resolving to IPv6 addresses
|
|
305
|
-
|
|
306
|
-
##### `resolveBoth(hostname, port): Promise<DiscoveryResultJS>`
|
|
307
|
-
|
|
308
|
-
Resolves both IPv4 and IPv6 addresses for a hostname.
|
|
309
|
-
|
|
310
|
-
**Parameters:**
|
|
311
|
-
- `hostname` (string): Hostname to resolve
|
|
312
|
-
- `port` (number): Port for the peer addresses
|
|
313
|
-
|
|
314
|
-
**Returns:** Promise resolving to discovery results
|
|
315
|
-
|
|
316
|
-
### Event Data Types
|
|
317
|
-
|
|
318
|
-
#### `BlockReceivedEvent`
|
|
319
|
-
|
|
320
|
-
```typescript
|
|
321
|
-
interface BlockReceivedEvent {
|
|
322
|
-
peerId: string // IP address of the peer that sent this block
|
|
323
|
-
height: number // Block height
|
|
324
|
-
weight: string // Block weight as string
|
|
325
|
-
headerHash: string // Block header hash (hex)
|
|
326
|
-
timestamp: number // Block timestamp (Unix time)
|
|
327
|
-
coinAdditions: CoinRecord[] // New coins created in this block
|
|
328
|
-
coinRemovals: CoinRecord[] // Coins spent in this block
|
|
329
|
-
coinSpends: CoinSpend[] // Detailed spend information
|
|
330
|
-
coinCreations: CoinRecord[] // Coins created by puzzles
|
|
331
|
-
hasTransactionsGenerator: boolean // Whether block has a generator
|
|
332
|
-
generatorSize: number // Size of the generator bytecode
|
|
333
|
-
}
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
#### `PeerConnectedEvent`
|
|
337
|
-
|
|
338
|
-
```typescript
|
|
339
|
-
interface PeerConnectedEvent {
|
|
340
|
-
peerId: string // Peer IP address
|
|
341
|
-
host: string // Peer hostname/IP
|
|
342
|
-
port: number // Peer port number
|
|
343
|
-
}
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
#### `PeerDisconnectedEvent`
|
|
347
|
-
|
|
348
|
-
```typescript
|
|
349
|
-
interface PeerDisconnectedEvent {
|
|
350
|
-
peerId: string // Peer IP address
|
|
351
|
-
host: string // Peer hostname/IP
|
|
352
|
-
port: number // Peer port number
|
|
353
|
-
message?: string // Optional disconnection reason
|
|
354
|
-
}
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
#### `NewPeakHeightEvent`
|
|
358
|
-
|
|
359
|
-
```typescript
|
|
360
|
-
interface NewPeakHeightEvent {
|
|
361
|
-
oldPeak: number | null // Previous highest peak (null if first peak)
|
|
362
|
-
newPeak: number // New highest peak height
|
|
363
|
-
peerId: string // Peer that discovered this peak
|
|
364
|
-
}
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
#### `CoinRecord`
|
|
368
|
-
|
|
369
|
-
```typescript
|
|
370
|
-
interface CoinRecord {
|
|
371
|
-
parentCoinInfo: string // Parent coin ID (hex)
|
|
372
|
-
puzzleHash: string // Puzzle hash (hex)
|
|
373
|
-
amount: string // Coin amount as string
|
|
374
|
-
}
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
#### `CoinSpend`
|
|
378
|
-
|
|
379
|
-
```typescript
|
|
380
|
-
interface CoinSpend {
|
|
381
|
-
coin: CoinRecord // The coin being spent
|
|
382
|
-
puzzleReveal: string // CLVM puzzle bytecode (hex)
|
|
383
|
-
solution: string // CLVM solution bytecode (hex)
|
|
384
|
-
offset: number // Offset in the generator bytecode
|
|
385
|
-
}
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
#### `DiscoveryResultJS`
|
|
389
|
-
|
|
390
|
-
```typescript
|
|
391
|
-
interface DiscoveryResultJS {
|
|
392
|
-
ipv4Peers: PeerAddressJS[] // IPv4 peer addresses
|
|
393
|
-
ipv6Peers: PeerAddressJS[] // IPv6 peer addresses
|
|
394
|
-
totalCount: number // Total peers found
|
|
395
|
-
}
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
#### `PeerAddressJS`
|
|
399
|
-
|
|
400
|
-
```typescript
|
|
401
|
-
interface PeerAddressJS {
|
|
402
|
-
host: string // IP address as string
|
|
403
|
-
port: number // Port number
|
|
404
|
-
isIpv6: boolean // Protocol indicator
|
|
405
|
-
displayAddress: string // Formatted for display/URLs
|
|
406
|
-
}
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
#### `AddressResult`
|
|
410
|
-
|
|
411
|
-
```typescript
|
|
412
|
-
interface AddressResult {
|
|
413
|
-
addresses: string[] // List of IP addresses
|
|
414
|
-
count: number // Number of addresses
|
|
415
|
-
}
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
## ChiaPeerPool Usage
|
|
419
|
-
|
|
420
|
-
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.
|
|
421
|
-
|
|
422
|
-
### Basic Usage
|
|
423
|
-
|
|
424
|
-
```javascript
|
|
425
|
-
const { ChiaPeerPool, initTracing } = require('@dignetwork/chia-block-listener')
|
|
426
|
-
|
|
427
|
-
async function main() {
|
|
428
|
-
// Initialize tracing
|
|
429
|
-
initTracing()
|
|
430
|
-
|
|
431
|
-
// Create a peer pool
|
|
432
|
-
const pool = new ChiaPeerPool()
|
|
433
|
-
|
|
434
|
-
// Listen for pool events
|
|
435
|
-
pool.on('peerConnected', (event) => {
|
|
436
|
-
console.log(`Peer connected to pool: ${event.peerId}`)
|
|
437
|
-
})
|
|
438
|
-
|
|
439
|
-
pool.on('peerDisconnected', (event) => {
|
|
440
|
-
console.log(`Peer disconnected from pool: ${event.peerId}`)
|
|
441
|
-
})
|
|
442
|
-
|
|
443
|
-
pool.on('newPeakHeight', (event) => {
|
|
444
|
-
console.log(`New blockchain peak detected!`)
|
|
445
|
-
console.log(` Previous: ${event.oldPeak || 'None'}`)
|
|
446
|
-
console.log(` New: ${event.newPeak}`)
|
|
447
|
-
console.log(` Discovered by: ${event.peerId}`)
|
|
448
|
-
})
|
|
449
|
-
|
|
450
|
-
// Add multiple peers
|
|
451
|
-
await pool.addPeer('node1.chia.net', 8444, 'mainnet')
|
|
452
|
-
await pool.addPeer('node2.chia.net', 8444, 'mainnet')
|
|
453
|
-
await pool.addPeer('node3.chia.net', 8444, 'mainnet')
|
|
454
|
-
|
|
455
|
-
// Fetch blocks with automatic load balancing
|
|
456
|
-
const block1 = await pool.getBlockByHeight(5000000)
|
|
457
|
-
const block2 = await pool.getBlockByHeight(5000001)
|
|
458
|
-
const block3 = await pool.getBlockByHeight(5000002)
|
|
459
|
-
|
|
460
|
-
console.log(`Block ${block1.height}: ${block1.coinSpends.length} spends`)
|
|
461
|
-
console.log(`Block ${block2.height}: ${block2.coinSpends.length} spends`)
|
|
462
|
-
console.log(`Block ${block3.height}: ${block3.coinSpends.length} spends`)
|
|
463
|
-
|
|
464
|
-
// Shutdown the pool
|
|
465
|
-
await pool.shutdown()
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
main().catch(console.error)
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
### Advanced Pool Features
|
|
472
|
-
|
|
473
|
-
#### Rate Limiting
|
|
474
|
-
|
|
475
|
-
The pool automatically enforces a
|
|
476
|
-
|
|
477
|
-
```javascript
|
|
478
|
-
// Rapid requests are automatically queued and distributed
|
|
479
|
-
const promises = []
|
|
480
|
-
for (let i = 5000000; i < 5000100; i++) {
|
|
481
|
-
promises.push(pool.getBlockByHeight(i))
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// All requests will be processed efficiently across all peers
|
|
485
|
-
// with automatic load balancing and rate limiting
|
|
486
|
-
const blocks = await Promise.all(promises)
|
|
487
|
-
console.log(`Retrieved ${blocks.length} blocks`)
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
#### Automatic Failover and Error Handling
|
|
491
|
-
|
|
492
|
-
The pool provides robust error handling with automatic failover:
|
|
493
|
-
|
|
494
|
-
```javascript
|
|
495
|
-
// The pool automatically handles various error scenarios:
|
|
496
|
-
|
|
497
|
-
// 1. Connection failures - automatically tries other peers
|
|
498
|
-
try {
|
|
499
|
-
const block = await pool.getBlockByHeight(5000000)
|
|
500
|
-
console.log(`Retrieved block ${block.height}`)
|
|
501
|
-
} catch (error) {
|
|
502
|
-
// If all peers fail, you'll get an error after all retry attempts
|
|
503
|
-
console.error('All peers failed:', error.message)
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// 2. Protocol errors - peers that refuse blocks are automatically disconnected
|
|
507
|
-
pool.on('peerDisconnected', (event) => {
|
|
508
|
-
console.log(`Peer ${event.peerId} disconnected: ${event.reason}`)
|
|
509
|
-
// Reasons include: "Block request rejected", "Protocol error", "Connection timeout"
|
|
510
|
-
})
|
|
511
|
-
|
|
512
|
-
// 3. Automatic peer cleanup - problematic peers are removed from the pool
|
|
513
|
-
console.log('Active peers before:', await pool.getConnectedPeers())
|
|
514
|
-
await pool.getBlockByHeight(5000000) // May trigger peer removal
|
|
515
|
-
console.log('Active peers after:', await pool.getConnectedPeers())
|
|
516
|
-
|
|
517
|
-
// 4. Multiple retry attempts - tries up to 3 different peers per request
|
|
518
|
-
// This happens automatically and transparently
|
|
519
|
-
const block = await pool.getBlockByHeight(5000000) // Will try multiple peers if needed
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
**Error Types Handled Automatically:**
|
|
523
|
-
- **Connection Errors**: Timeouts, network failures, WebSocket errors
|
|
524
|
-
- **Protocol Errors**: Block rejections, parsing failures, handshake failures
|
|
525
|
-
- **Peer Misbehavior**: Unexpected responses, invalid data formats
|
|
526
|
-
|
|
527
|
-
#### Dynamic Peer Management
|
|
528
|
-
|
|
529
|
-
```javascript
|
|
530
|
-
// Monitor pool health
|
|
531
|
-
const peers = await pool.getConnectedPeers()
|
|
532
|
-
console.log(`Active peers in pool: ${peers.length}`)
|
|
533
|
-
|
|
534
|
-
// Remove underperforming peers
|
|
535
|
-
if (slowPeer) {
|
|
536
|
-
await pool.removePeer(slowPeer)
|
|
537
|
-
console.log('Removed slow peer from pool')
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// Add new peers dynamically
|
|
541
|
-
if (peers.length < 3) {
|
|
542
|
-
await pool.addPeer('backup-node.chia.net', 8444, 'mainnet')
|
|
543
|
-
}
|
|
544
|
-
```
|
|
545
|
-
|
|
546
|
-
#### Error Handling
|
|
547
|
-
|
|
548
|
-
```javascript
|
|
549
|
-
try {
|
|
550
|
-
const block = await pool.getBlockByHeight(5000000)
|
|
551
|
-
console.log(`Retrieved block ${block.height}`)
|
|
552
|
-
} catch (error) {
|
|
553
|
-
console.error('Failed to retrieve block:', error)
|
|
554
|
-
|
|
555
|
-
// The pool will automatically try other peers
|
|
556
|
-
// You can also add more peers if needed
|
|
557
|
-
const peers = await pool.getConnectedPeers()
|
|
558
|
-
if (peers.length === 0) {
|
|
559
|
-
console.log('No peers available, adding new ones...')
|
|
560
|
-
await pool.addPeer('node1.chia.net', 8444, 'mainnet')
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
```
|
|
564
|
-
|
|
565
|
-
#### Peak Height Tracking
|
|
566
|
-
|
|
567
|
-
```javascript
|
|
568
|
-
// Monitor blockchain sync progress
|
|
569
|
-
const pool = new ChiaPeerPool()
|
|
570
|
-
|
|
571
|
-
// Track peak changes
|
|
572
|
-
let currentPeak = null
|
|
573
|
-
pool.on('newPeakHeight', (event) => {
|
|
574
|
-
currentPeak = event.newPeak
|
|
575
|
-
const progress = event.oldPeak
|
|
576
|
-
? `+${event.newPeak - event.oldPeak} blocks`
|
|
577
|
-
: 'Initial peak'
|
|
578
|
-
console.log(`Peak update: ${event.newPeak} (${progress})`)
|
|
579
|
-
})
|
|
580
|
-
|
|
581
|
-
// Add peers
|
|
582
|
-
await pool.addPeer('node1.chia.net', 8444, 'mainnet')
|
|
583
|
-
await pool.addPeer('node2.chia.net', 8444, 'mainnet')
|
|
584
|
-
|
|
585
|
-
// Check current peak
|
|
586
|
-
const peak = await pool.getPeakHeight()
|
|
587
|
-
console.log(`Current highest peak: ${peak || 'None yet'}`)
|
|
588
|
-
|
|
589
|
-
// Fetch some blocks to trigger peak updates
|
|
590
|
-
await pool.getBlockByHeight(5000000)
|
|
591
|
-
await pool.getBlockByHeight(5100000)
|
|
592
|
-
await pool.getBlockByHeight(5200000)
|
|
593
|
-
|
|
594
|
-
// Monitor sync status
|
|
595
|
-
setInterval(async () => {
|
|
596
|
-
const peak = await pool.getPeakHeight()
|
|
597
|
-
if (peak) {
|
|
598
|
-
const estimatedCurrent = 5200000 + Math.floor((Date.now() / 1000 - 1700000000) / 18.75)
|
|
599
|
-
const syncPercentage = (peak / estimatedCurrent * 100).toFixed(2)
|
|
600
|
-
console.log(`Sync status: ${syncPercentage}% (peak: ${peak})`)
|
|
601
|
-
}
|
|
602
|
-
}, 60000) // Check every minute
|
|
603
|
-
```
|
|
604
|
-
|
|
605
|
-
### When to Use ChiaPeerPool vs ChiaBlockListener
|
|
606
|
-
|
|
607
|
-
- **Use ChiaPeerPool when:**
|
|
608
|
-
- You need to fetch historical blocks
|
|
609
|
-
- You want automatic load balancing across multiple peers
|
|
610
|
-
- You're making many block requests and need rate limiting
|
|
611
|
-
- You don't need real-time block notifications
|
|
612
|
-
|
|
613
|
-
- **Use ChiaBlockListener when:**
|
|
614
|
-
- You need real-time notifications of new blocks
|
|
615
|
-
- You want to monitor the blockchain as it grows
|
|
616
|
-
- You need to track specific addresses or puzzle hashes in real-time
|
|
617
|
-
- You're building applications that react to blockchain events
|
|
618
|
-
|
|
619
|
-
Both classes can be used together in the same application for different purposes.
|
|
620
|
-
|
|
621
|
-
## DNS Discovery Usage
|
|
622
|
-
|
|
623
|
-
The `DnsDiscoveryClient` enables automatic discovery of Chia network peers using DNS introducers, with full support for both IPv4 and IPv6 addresses.
|
|
624
|
-
|
|
625
|
-
### Basic DNS Discovery
|
|
626
|
-
|
|
627
|
-
```javascript
|
|
628
|
-
const { DnsDiscoveryClient, initTracing } = require('@dignetwork/chia-block-listener')
|
|
629
|
-
|
|
630
|
-
async function discoverPeers() {
|
|
631
|
-
// Initialize tracing
|
|
632
|
-
initTracing()
|
|
633
|
-
|
|
634
|
-
// Create DNS discovery client
|
|
635
|
-
const client = new DnsDiscoveryClient()
|
|
636
|
-
|
|
637
|
-
// Discover mainnet peers
|
|
638
|
-
const result = await client.discoverMainnetPeers()
|
|
639
|
-
|
|
640
|
-
console.log(`Found ${result.totalCount} total peers:`)
|
|
641
|
-
console.log(` IPv4 peers: ${result.ipv4Peers.length}`)
|
|
642
|
-
console.log(` IPv6 peers: ${result.ipv6Peers.length}`)
|
|
643
|
-
|
|
644
|
-
// Use with peer connections
|
|
645
|
-
for (const peer of result.ipv4Peers.slice(0, 3)) {
|
|
646
|
-
console.log(`IPv4 peer: ${peer.displayAddress}`)
|
|
647
|
-
// peer.host and peer.port can be used with addPeer()
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
for (const peer of result.ipv6Peers.slice(0, 3)) {
|
|
651
|
-
console.log(`IPv6 peer: ${peer.displayAddress}`) // [2001:db8::1]:8444
|
|
652
|
-
// IPv6 addresses are properly formatted with brackets
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
discoverPeers().catch(console.error)
|
|
657
|
-
```
|
|
658
|
-
|
|
659
|
-
### Integration with Peer Pool
|
|
660
|
-
|
|
661
|
-
```javascript
|
|
662
|
-
const { ChiaPeerPool, DnsDiscoveryClient } = require('@dignetwork/chia-block-listener')
|
|
663
|
-
|
|
664
|
-
async function setupPoolWithDnsDiscovery() {
|
|
665
|
-
const pool = new ChiaPeerPool()
|
|
666
|
-
const discovery = new DnsDiscoveryClient()
|
|
667
|
-
|
|
668
|
-
// Discover peers automatically
|
|
669
|
-
const peers = await discovery.discoverMainnetPeers()
|
|
670
|
-
|
|
671
|
-
// Add discovered peers to pool (both IPv4 and IPv6)
|
|
672
|
-
const allPeers = [...peers.ipv4Peers, ...peers.ipv6Peers]
|
|
673
|
-
for (const peer of allPeers.slice(0, 5)) {
|
|
674
|
-
await pool.addPeer(peer.host, peer.port, 'mainnet')
|
|
675
|
-
console.log(`Added peer: ${peer.displayAddress}`)
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
// Now use the pool for block retrieval
|
|
679
|
-
const block = await pool.getBlockByHeight(5000000)
|
|
680
|
-
console.log(`Retrieved block ${block.height}`)
|
|
681
|
-
|
|
682
|
-
await pool.shutdown()
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
setupPoolWithDnsDiscovery().catch(console.error)
|
|
686
|
-
```
|
|
687
|
-
|
|
688
|
-
### Custom DNS Introducers
|
|
689
|
-
|
|
690
|
-
```javascript
|
|
691
|
-
const client = new DnsDiscoveryClient()
|
|
692
|
-
|
|
693
|
-
// Use custom introducers
|
|
694
|
-
const customIntroducers = [
|
|
695
|
-
'seeder.dexie.space',
|
|
696
|
-
'chia.hoffmang.com'
|
|
697
|
-
]
|
|
698
|
-
|
|
699
|
-
const result = await client.discoverPeers(customIntroducers, 8444)
|
|
700
|
-
console.log(`Found ${result.totalCount} peers from custom introducers`)
|
|
701
|
-
```
|
|
702
|
-
|
|
703
|
-
### Individual DNS Resolution
|
|
704
|
-
|
|
705
|
-
```javascript
|
|
706
|
-
const client = new DnsDiscoveryClient()
|
|
707
|
-
const hostname = 'dns-introducer.chia.net'
|
|
708
|
-
|
|
709
|
-
// Resolve specific protocols
|
|
710
|
-
try {
|
|
711
|
-
const ipv4 = await client.resolveIpv4(hostname)
|
|
712
|
-
console.log(`IPv4 addresses: ${ipv4.addresses.join(', ')}`)
|
|
713
|
-
} catch (error) {
|
|
714
|
-
console.log(`IPv4 resolution failed: ${error.message}`)
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
try {
|
|
718
|
-
const ipv6 = await client.resolveIpv6(hostname)
|
|
719
|
-
console.log(`IPv6 addresses: ${ipv6.addresses.join(', ')}`)
|
|
720
|
-
} catch (error) {
|
|
721
|
-
console.log(`IPv6 resolution failed: ${error.message}`)
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
// Or resolve both at once
|
|
725
|
-
const both = await client.resolveBoth(hostname, 8444)
|
|
726
|
-
console.log(`Combined: ${both.totalCount} addresses`)
|
|
727
|
-
```
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
### Error Handling
|
|
732
|
-
|
|
733
|
-
```javascript
|
|
734
|
-
const client = new DnsDiscoveryClient()
|
|
735
|
-
|
|
736
|
-
try {
|
|
737
|
-
const result = await client.discoverMainnetPeers()
|
|
738
|
-
console.log(`Discovery successful: ${result.totalCount} peers`)
|
|
739
|
-
} catch (error) {
|
|
740
|
-
console.error('Discovery failed:', error.message)
|
|
741
|
-
|
|
742
|
-
// Handle different error types
|
|
743
|
-
if (error.message.includes('NoPeersFound')) {
|
|
744
|
-
console.log('No peers found from any introducer')
|
|
745
|
-
} else if (error.message.includes('ResolutionFailed')) {
|
|
746
|
-
console.log('DNS resolution failed')
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
```
|
|
750
|
-
|
|
751
|
-
### Key Features
|
|
752
|
-
|
|
753
|
-
- **Dual Stack Support**: Separate IPv4 and IPv6 peer lists
|
|
754
|
-
- **Proper DNS Lookups**: Uses A records for IPv4, AAAA records for IPv6
|
|
755
|
-
- **Built-in Networks**: Ready configurations for mainnet and testnet11
|
|
756
|
-
- **Custom Introducers**: Support for any DNS introducers
|
|
757
|
-
- **IPv6 URL Formatting**: Automatic bracket formatting for IPv6 addresses
|
|
758
|
-
- **Type Safety**: Full TypeScript support with detailed type definitions
|
|
759
|
-
|
|
760
|
-
##
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
console.log(`
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
})
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1
|
+
# Chia Block Listener
|
|
2
|
+
|
|
3
|
+
A high-performance Chia blockchain listener for Node.js, built with Rust and NAPI bindings. This library provides real-time monitoring of the Chia blockchain with efficient peer connections and block parsing capabilities.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Real-time Block Monitoring**: Listen for new blocks as they're produced on the Chia network
|
|
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
|
|
11
|
+
- **Efficient Parsing**: Fast extraction of coin spends, additions, and removals from blocks
|
|
12
|
+
- **Event-Driven Architecture**: TypeScript-friendly event system with full type safety
|
|
13
|
+
- **Transaction Analysis**: Parse CLVM puzzles and solutions from coin spends
|
|
14
|
+
- **Historical Block Access**: Retrieve blocks by height or ranges with automatic load balancing
|
|
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 with IPv4/IPv6 support
|
|
18
|
+
- **Cross-platform Support**: Works on Windows, macOS, and Linux (x64 and ARM64)
|
|
19
|
+
- **TypeScript Support**: Complete TypeScript definitions with IntelliSense
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @dignetwork/chia-block-listener
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
const { ChiaBlockListener, initTracing } = require('@dignetwork/chia-block-listener')
|
|
31
|
+
|
|
32
|
+
// Initialize tracing for debugging (optional)
|
|
33
|
+
initTracing()
|
|
34
|
+
|
|
35
|
+
// Create a new listener instance
|
|
36
|
+
const listener = new ChiaBlockListener()
|
|
37
|
+
|
|
38
|
+
// Listen for block events
|
|
39
|
+
listener.on('blockReceived', (block) => {
|
|
40
|
+
console.log(`New block received: ${block.height}`)
|
|
41
|
+
console.log(`Header hash: ${block.headerHash}`)
|
|
42
|
+
console.log(`Timestamp: ${new Date(block.timestamp * 1000)}`)
|
|
43
|
+
console.log(`Coin additions: ${block.coinAdditions.length}`)
|
|
44
|
+
console.log(`Coin removals: ${block.coinRemovals.length}`)
|
|
45
|
+
console.log(`Coin spends: ${block.coinSpends.length}`)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// Listen for peer connection events
|
|
49
|
+
listener.on('peerConnected', (peer) => {
|
|
50
|
+
console.log(`Connected to peer: ${peer.peerId} (${peer.host}:${peer.port})`)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
listener.on('peerDisconnected', (peer) => {
|
|
54
|
+
console.log(`Disconnected from peer: ${peer.peerId}`)
|
|
55
|
+
if (peer.message) {
|
|
56
|
+
console.log(`Reason: ${peer.message}`)
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// Connect to a Chia full node
|
|
61
|
+
const peerId = listener.addPeer('localhost', 8444, 'mainnet')
|
|
62
|
+
console.log(`Added peer: ${peerId}`)
|
|
63
|
+
|
|
64
|
+
// Keep the process running
|
|
65
|
+
process.on('SIGINT', () => {
|
|
66
|
+
console.log('Shutting down...')
|
|
67
|
+
listener.disconnectAllPeers()
|
|
68
|
+
process.exit(0)
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
## API Reference
|
|
72
|
+
|
|
73
|
+
### ChiaBlockListener Class
|
|
74
|
+
|
|
75
|
+
#### Constructor
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
const listener = new ChiaBlockListener()
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Creates a new Chia block listener instance.
|
|
82
|
+
|
|
83
|
+
#### Methods
|
|
84
|
+
|
|
85
|
+
##### `addPeer(host, port, networkId): string`
|
|
86
|
+
|
|
87
|
+
Connects to a Chia full node and starts listening for blocks.
|
|
88
|
+
|
|
89
|
+
**Parameters:**
|
|
90
|
+
- `host` (string): The hostname or IP address of the Chia node
|
|
91
|
+
- `port` (number): The port number (typically 8444 for mainnet)
|
|
92
|
+
- `networkId` (string): The network identifier ('mainnet', 'testnet', etc.)
|
|
93
|
+
|
|
94
|
+
**Returns:** A unique peer ID string for this connection
|
|
95
|
+
|
|
96
|
+
##### `disconnectPeer(peerId): boolean`
|
|
97
|
+
|
|
98
|
+
Disconnects from a specific peer.
|
|
99
|
+
|
|
100
|
+
**Parameters:**
|
|
101
|
+
- `peerId` (string): The peer ID returned by `addPeer()`
|
|
102
|
+
|
|
103
|
+
**Returns:** `true` if the peer was successfully disconnected, `false` otherwise
|
|
104
|
+
|
|
105
|
+
##### `disconnectAllPeers(): void`
|
|
106
|
+
|
|
107
|
+
Disconnects from all connected peers.
|
|
108
|
+
|
|
109
|
+
##### `getConnectedPeers(): string[]`
|
|
110
|
+
|
|
111
|
+
Returns an array of currently connected peer IDs.
|
|
112
|
+
|
|
113
|
+
##### `getBlockByHeight(peerId, height): BlockReceivedEvent`
|
|
114
|
+
|
|
115
|
+
Retrieves a specific block by its height from a connected peer.
|
|
116
|
+
|
|
117
|
+
**Parameters:**
|
|
118
|
+
- `peerId` (string): The peer ID to query
|
|
119
|
+
- `height` (number): The block height to retrieve
|
|
120
|
+
|
|
121
|
+
**Returns:** A `BlockReceivedEvent` object containing the block data
|
|
122
|
+
|
|
123
|
+
##### `getBlocksRange(peerId, startHeight, endHeight): BlockReceivedEvent[]`
|
|
124
|
+
|
|
125
|
+
Retrieves a range of blocks from a connected peer.
|
|
126
|
+
|
|
127
|
+
**Parameters:**
|
|
128
|
+
- `peerId` (string): The peer ID to query
|
|
129
|
+
- `startHeight` (number): The starting block height (inclusive)
|
|
130
|
+
- `endHeight` (number): The ending block height (inclusive)
|
|
131
|
+
|
|
132
|
+
**Returns:** An array of `BlockReceivedEvent` objects
|
|
133
|
+
|
|
134
|
+
### ChiaPeerPool Class
|
|
135
|
+
|
|
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.
|
|
137
|
+
|
|
138
|
+
#### Constructor
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
const pool = new ChiaPeerPool()
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Creates a new peer pool instance with built-in rate limiting (500ms per peer).
|
|
145
|
+
|
|
146
|
+
#### Methods
|
|
147
|
+
|
|
148
|
+
##### `addPeer(host, port, networkId): Promise<string>`
|
|
149
|
+
|
|
150
|
+
Adds a peer to the connection pool.
|
|
151
|
+
|
|
152
|
+
**Parameters:**
|
|
153
|
+
- `host` (string): The hostname or IP address of the Chia node
|
|
154
|
+
- `port` (number): The port number (typically 8444 for mainnet)
|
|
155
|
+
- `networkId` (string): The network identifier ('mainnet', 'testnet', etc.)
|
|
156
|
+
|
|
157
|
+
**Returns:** A Promise that resolves to a unique peer ID string
|
|
158
|
+
|
|
159
|
+
##### `getBlockByHeight(height): Promise<BlockReceivedEvent>`
|
|
160
|
+
|
|
161
|
+
Retrieves a specific block by height using automatic peer selection and load balancing.
|
|
162
|
+
|
|
163
|
+
**Parameters:**
|
|
164
|
+
- `height` (number): The block height to retrieve
|
|
165
|
+
|
|
166
|
+
**Returns:** A Promise that resolves to a `BlockReceivedEvent` object
|
|
167
|
+
|
|
168
|
+
##### `removePeer(peerId): Promise<boolean>`
|
|
169
|
+
|
|
170
|
+
Removes a peer from the pool.
|
|
171
|
+
|
|
172
|
+
**Parameters:**
|
|
173
|
+
- `peerId` (string): The peer ID to remove
|
|
174
|
+
|
|
175
|
+
**Returns:** A Promise that resolves to `true` if the peer was removed, `false` otherwise
|
|
176
|
+
|
|
177
|
+
##### `shutdown(): Promise<void>`
|
|
178
|
+
|
|
179
|
+
Shuts down the pool and disconnects all peers.
|
|
180
|
+
|
|
181
|
+
##### `getConnectedPeers(): Promise<string[]>`
|
|
182
|
+
|
|
183
|
+
Gets the list of currently connected peer IDs.
|
|
184
|
+
|
|
185
|
+
**Returns:** Array of peer ID strings (format: "host:port")
|
|
186
|
+
|
|
187
|
+
##### `getPeakHeight(): Promise<number | null>`
|
|
188
|
+
|
|
189
|
+
Gets the highest blockchain peak height seen across all connected peers.
|
|
190
|
+
|
|
191
|
+
**Returns:** The highest peak height as a number, or null if no peaks have been received yet
|
|
192
|
+
|
|
193
|
+
##### `on(event, callback): void`
|
|
194
|
+
|
|
195
|
+
Registers an event handler for pool events.
|
|
196
|
+
|
|
197
|
+
**Parameters:**
|
|
198
|
+
- `event` (string): The event name ('peerConnected' or 'peerDisconnected')
|
|
199
|
+
- `callback` (function): The event handler function
|
|
200
|
+
|
|
201
|
+
##### `off(event, callback): void`
|
|
202
|
+
|
|
203
|
+
Removes an event handler.
|
|
204
|
+
|
|
205
|
+
**Parameters:**
|
|
206
|
+
- `event` (string): The event name to stop listening for
|
|
207
|
+
|
|
208
|
+
### Events
|
|
209
|
+
|
|
210
|
+
The `ChiaBlockListener` emits the following events:
|
|
211
|
+
|
|
212
|
+
#### `blockReceived`
|
|
213
|
+
|
|
214
|
+
Fired when a new block is received from any connected peer.
|
|
215
|
+
|
|
216
|
+
**Callback:** `(event: BlockReceivedEvent) => void`
|
|
217
|
+
|
|
218
|
+
#### `peerConnected`
|
|
219
|
+
|
|
220
|
+
Fired when a connection to a peer is established.
|
|
221
|
+
|
|
222
|
+
**Callback:** `(event: PeerConnectedEvent) => void`
|
|
223
|
+
|
|
224
|
+
#### `peerDisconnected`
|
|
225
|
+
|
|
226
|
+
Fired when a peer connection is lost.
|
|
227
|
+
|
|
228
|
+
**Callback:** `(event: PeerDisconnectedEvent) => void`
|
|
229
|
+
|
|
230
|
+
### ChiaPeerPool Events
|
|
231
|
+
|
|
232
|
+
The `ChiaPeerPool` emits the following events:
|
|
233
|
+
|
|
234
|
+
#### `peerConnected`
|
|
235
|
+
|
|
236
|
+
Fired when a peer is successfully added to the pool.
|
|
237
|
+
|
|
238
|
+
**Callback:** `(event: PeerConnectedEvent) => void`
|
|
239
|
+
|
|
240
|
+
#### `peerDisconnected`
|
|
241
|
+
|
|
242
|
+
Fired when a peer is removed from the pool or disconnects.
|
|
243
|
+
|
|
244
|
+
**Callback:** `(event: PeerDisconnectedEvent) => void`
|
|
245
|
+
|
|
246
|
+
#### `newPeakHeight`
|
|
247
|
+
|
|
248
|
+
Fired when a new highest blockchain peak is discovered.
|
|
249
|
+
|
|
250
|
+
**Callback:** `(event: NewPeakHeightEvent) => void`
|
|
251
|
+
|
|
252
|
+
### DnsDiscoveryClient Class
|
|
253
|
+
|
|
254
|
+
The `DnsDiscoveryClient` provides automatic peer discovery using Chia network DNS introducers with full IPv4 and IPv6 support.
|
|
255
|
+
|
|
256
|
+
#### Constructor
|
|
257
|
+
|
|
258
|
+
```javascript
|
|
259
|
+
const client = new DnsDiscoveryClient()
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Creates a new DNS discovery client instance.
|
|
263
|
+
|
|
264
|
+
#### Methods
|
|
265
|
+
|
|
266
|
+
##### `discoverMainnetPeers(): Promise<DiscoveryResultJS>`
|
|
267
|
+
|
|
268
|
+
Discovers peers for Chia mainnet using built-in DNS introducers.
|
|
269
|
+
|
|
270
|
+
**Returns:** Promise resolving to discovery results with separate IPv4 and IPv6 peer lists
|
|
271
|
+
|
|
272
|
+
##### `discoverTestnet11Peers(): Promise<DiscoveryResultJS>`
|
|
273
|
+
|
|
274
|
+
Discovers peers for Chia testnet11 using built-in DNS introducers.
|
|
275
|
+
|
|
276
|
+
**Returns:** Promise resolving to discovery results
|
|
277
|
+
|
|
278
|
+
##### `discoverPeers(introducers, port): Promise<DiscoveryResultJS>`
|
|
279
|
+
|
|
280
|
+
Discovers peers using custom DNS introducers.
|
|
281
|
+
|
|
282
|
+
**Parameters:**
|
|
283
|
+
- `introducers` (string[]): Array of DNS introducer hostnames
|
|
284
|
+
- `port` (number): Default port for discovered peers
|
|
285
|
+
|
|
286
|
+
**Returns:** Promise resolving to discovery results
|
|
287
|
+
|
|
288
|
+
##### `resolveIpv4(hostname): Promise<AddressResult>`
|
|
289
|
+
|
|
290
|
+
Resolves IPv4 addresses (A records) for a hostname.
|
|
291
|
+
|
|
292
|
+
**Parameters:**
|
|
293
|
+
- `hostname` (string): Hostname to resolve
|
|
294
|
+
|
|
295
|
+
**Returns:** Promise resolving to IPv4 addresses
|
|
296
|
+
|
|
297
|
+
##### `resolveIpv6(hostname): Promise<AddressResult>`
|
|
298
|
+
|
|
299
|
+
Resolves IPv6 addresses (AAAA records) for a hostname.
|
|
300
|
+
|
|
301
|
+
**Parameters:**
|
|
302
|
+
- `hostname` (string): Hostname to resolve
|
|
303
|
+
|
|
304
|
+
**Returns:** Promise resolving to IPv6 addresses
|
|
305
|
+
|
|
306
|
+
##### `resolveBoth(hostname, port): Promise<DiscoveryResultJS>`
|
|
307
|
+
|
|
308
|
+
Resolves both IPv4 and IPv6 addresses for a hostname.
|
|
309
|
+
|
|
310
|
+
**Parameters:**
|
|
311
|
+
- `hostname` (string): Hostname to resolve
|
|
312
|
+
- `port` (number): Port for the peer addresses
|
|
313
|
+
|
|
314
|
+
**Returns:** Promise resolving to discovery results
|
|
315
|
+
|
|
316
|
+
### Event Data Types
|
|
317
|
+
|
|
318
|
+
#### `BlockReceivedEvent`
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
interface BlockReceivedEvent {
|
|
322
|
+
peerId: string // IP address of the peer that sent this block
|
|
323
|
+
height: number // Block height
|
|
324
|
+
weight: string // Block weight as string
|
|
325
|
+
headerHash: string // Block header hash (hex)
|
|
326
|
+
timestamp: number // Block timestamp (Unix time)
|
|
327
|
+
coinAdditions: CoinRecord[] // New coins created in this block
|
|
328
|
+
coinRemovals: CoinRecord[] // Coins spent in this block
|
|
329
|
+
coinSpends: CoinSpend[] // Detailed spend information
|
|
330
|
+
coinCreations: CoinRecord[] // Coins created by puzzles
|
|
331
|
+
hasTransactionsGenerator: boolean // Whether block has a generator
|
|
332
|
+
generatorSize: number // Size of the generator bytecode
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
#### `PeerConnectedEvent`
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
interface PeerConnectedEvent {
|
|
340
|
+
peerId: string // Peer IP address
|
|
341
|
+
host: string // Peer hostname/IP
|
|
342
|
+
port: number // Peer port number
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
#### `PeerDisconnectedEvent`
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
interface PeerDisconnectedEvent {
|
|
350
|
+
peerId: string // Peer IP address
|
|
351
|
+
host: string // Peer hostname/IP
|
|
352
|
+
port: number // Peer port number
|
|
353
|
+
message?: string // Optional disconnection reason
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
#### `NewPeakHeightEvent`
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
interface NewPeakHeightEvent {
|
|
361
|
+
oldPeak: number | null // Previous highest peak (null if first peak)
|
|
362
|
+
newPeak: number // New highest peak height
|
|
363
|
+
peerId: string // Peer that discovered this peak
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
#### `CoinRecord`
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
interface CoinRecord {
|
|
371
|
+
parentCoinInfo: string // Parent coin ID (hex)
|
|
372
|
+
puzzleHash: string // Puzzle hash (hex)
|
|
373
|
+
amount: string // Coin amount as string
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
#### `CoinSpend`
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
interface CoinSpend {
|
|
381
|
+
coin: CoinRecord // The coin being spent
|
|
382
|
+
puzzleReveal: string // CLVM puzzle bytecode (hex)
|
|
383
|
+
solution: string // CLVM solution bytecode (hex)
|
|
384
|
+
offset: number // Offset in the generator bytecode
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
#### `DiscoveryResultJS`
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
interface DiscoveryResultJS {
|
|
392
|
+
ipv4Peers: PeerAddressJS[] // IPv4 peer addresses
|
|
393
|
+
ipv6Peers: PeerAddressJS[] // IPv6 peer addresses
|
|
394
|
+
totalCount: number // Total peers found
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
#### `PeerAddressJS`
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
interface PeerAddressJS {
|
|
402
|
+
host: string // IP address as string
|
|
403
|
+
port: number // Port number
|
|
404
|
+
isIpv6: boolean // Protocol indicator
|
|
405
|
+
displayAddress: string // Formatted for display/URLs
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
#### `AddressResult`
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
interface AddressResult {
|
|
413
|
+
addresses: string[] // List of IP addresses
|
|
414
|
+
count: number // Number of addresses
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## ChiaPeerPool Usage
|
|
419
|
+
|
|
420
|
+
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.
|
|
421
|
+
|
|
422
|
+
### Basic Usage
|
|
423
|
+
|
|
424
|
+
```javascript
|
|
425
|
+
const { ChiaPeerPool, initTracing } = require('@dignetwork/chia-block-listener')
|
|
426
|
+
|
|
427
|
+
async function main() {
|
|
428
|
+
// Initialize tracing
|
|
429
|
+
initTracing()
|
|
430
|
+
|
|
431
|
+
// Create a peer pool
|
|
432
|
+
const pool = new ChiaPeerPool()
|
|
433
|
+
|
|
434
|
+
// Listen for pool events
|
|
435
|
+
pool.on('peerConnected', (event) => {
|
|
436
|
+
console.log(`Peer connected to pool: ${event.peerId}`)
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
pool.on('peerDisconnected', (event) => {
|
|
440
|
+
console.log(`Peer disconnected from pool: ${event.peerId}`)
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
pool.on('newPeakHeight', (event) => {
|
|
444
|
+
console.log(`New blockchain peak detected!`)
|
|
445
|
+
console.log(` Previous: ${event.oldPeak || 'None'}`)
|
|
446
|
+
console.log(` New: ${event.newPeak}`)
|
|
447
|
+
console.log(` Discovered by: ${event.peerId}`)
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
// Add multiple peers
|
|
451
|
+
await pool.addPeer('node1.chia.net', 8444, 'mainnet')
|
|
452
|
+
await pool.addPeer('node2.chia.net', 8444, 'mainnet')
|
|
453
|
+
await pool.addPeer('node3.chia.net', 8444, 'mainnet')
|
|
454
|
+
|
|
455
|
+
// Fetch blocks with automatic load balancing
|
|
456
|
+
const block1 = await pool.getBlockByHeight(5000000)
|
|
457
|
+
const block2 = await pool.getBlockByHeight(5000001)
|
|
458
|
+
const block3 = await pool.getBlockByHeight(5000002)
|
|
459
|
+
|
|
460
|
+
console.log(`Block ${block1.height}: ${block1.coinSpends.length} spends`)
|
|
461
|
+
console.log(`Block ${block2.height}: ${block2.coinSpends.length} spends`)
|
|
462
|
+
console.log(`Block ${block3.height}: ${block3.coinSpends.length} spends`)
|
|
463
|
+
|
|
464
|
+
// Shutdown the pool
|
|
465
|
+
await pool.shutdown()
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
main().catch(console.error)
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### Advanced Pool Features
|
|
472
|
+
|
|
473
|
+
#### Rate Limiting
|
|
474
|
+
|
|
475
|
+
The pool automatically enforces a 500ms rate limit per peer for maximum performance while preventing node overload:
|
|
476
|
+
|
|
477
|
+
```javascript
|
|
478
|
+
// Rapid requests are automatically queued and distributed
|
|
479
|
+
const promises = []
|
|
480
|
+
for (let i = 5000000; i < 5000100; i++) {
|
|
481
|
+
promises.push(pool.getBlockByHeight(i))
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// All requests will be processed efficiently across all peers
|
|
485
|
+
// with automatic load balancing and rate limiting
|
|
486
|
+
const blocks = await Promise.all(promises)
|
|
487
|
+
console.log(`Retrieved ${blocks.length} blocks`)
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
#### Automatic Failover and Error Handling
|
|
491
|
+
|
|
492
|
+
The pool provides robust error handling with automatic failover:
|
|
493
|
+
|
|
494
|
+
```javascript
|
|
495
|
+
// The pool automatically handles various error scenarios:
|
|
496
|
+
|
|
497
|
+
// 1. Connection failures - automatically tries other peers
|
|
498
|
+
try {
|
|
499
|
+
const block = await pool.getBlockByHeight(5000000)
|
|
500
|
+
console.log(`Retrieved block ${block.height}`)
|
|
501
|
+
} catch (error) {
|
|
502
|
+
// If all peers fail, you'll get an error after all retry attempts
|
|
503
|
+
console.error('All peers failed:', error.message)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// 2. Protocol errors - peers that refuse blocks are automatically disconnected
|
|
507
|
+
pool.on('peerDisconnected', (event) => {
|
|
508
|
+
console.log(`Peer ${event.peerId} disconnected: ${event.reason}`)
|
|
509
|
+
// Reasons include: "Block request rejected", "Protocol error", "Connection timeout"
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
// 3. Automatic peer cleanup - problematic peers are removed from the pool
|
|
513
|
+
console.log('Active peers before:', await pool.getConnectedPeers())
|
|
514
|
+
await pool.getBlockByHeight(5000000) // May trigger peer removal
|
|
515
|
+
console.log('Active peers after:', await pool.getConnectedPeers())
|
|
516
|
+
|
|
517
|
+
// 4. Multiple retry attempts - tries up to 3 different peers per request
|
|
518
|
+
// This happens automatically and transparently
|
|
519
|
+
const block = await pool.getBlockByHeight(5000000) // Will try multiple peers if needed
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
**Error Types Handled Automatically:**
|
|
523
|
+
- **Connection Errors**: Timeouts, network failures, WebSocket errors
|
|
524
|
+
- **Protocol Errors**: Block rejections, parsing failures, handshake failures
|
|
525
|
+
- **Peer Misbehavior**: Unexpected responses, invalid data formats
|
|
526
|
+
|
|
527
|
+
#### Dynamic Peer Management
|
|
528
|
+
|
|
529
|
+
```javascript
|
|
530
|
+
// Monitor pool health
|
|
531
|
+
const peers = await pool.getConnectedPeers()
|
|
532
|
+
console.log(`Active peers in pool: ${peers.length}`)
|
|
533
|
+
|
|
534
|
+
// Remove underperforming peers
|
|
535
|
+
if (slowPeer) {
|
|
536
|
+
await pool.removePeer(slowPeer)
|
|
537
|
+
console.log('Removed slow peer from pool')
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Add new peers dynamically
|
|
541
|
+
if (peers.length < 3) {
|
|
542
|
+
await pool.addPeer('backup-node.chia.net', 8444, 'mainnet')
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
#### Error Handling
|
|
547
|
+
|
|
548
|
+
```javascript
|
|
549
|
+
try {
|
|
550
|
+
const block = await pool.getBlockByHeight(5000000)
|
|
551
|
+
console.log(`Retrieved block ${block.height}`)
|
|
552
|
+
} catch (error) {
|
|
553
|
+
console.error('Failed to retrieve block:', error)
|
|
554
|
+
|
|
555
|
+
// The pool will automatically try other peers
|
|
556
|
+
// You can also add more peers if needed
|
|
557
|
+
const peers = await pool.getConnectedPeers()
|
|
558
|
+
if (peers.length === 0) {
|
|
559
|
+
console.log('No peers available, adding new ones...')
|
|
560
|
+
await pool.addPeer('node1.chia.net', 8444, 'mainnet')
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
#### Peak Height Tracking
|
|
566
|
+
|
|
567
|
+
```javascript
|
|
568
|
+
// Monitor blockchain sync progress
|
|
569
|
+
const pool = new ChiaPeerPool()
|
|
570
|
+
|
|
571
|
+
// Track peak changes
|
|
572
|
+
let currentPeak = null
|
|
573
|
+
pool.on('newPeakHeight', (event) => {
|
|
574
|
+
currentPeak = event.newPeak
|
|
575
|
+
const progress = event.oldPeak
|
|
576
|
+
? `+${event.newPeak - event.oldPeak} blocks`
|
|
577
|
+
: 'Initial peak'
|
|
578
|
+
console.log(`Peak update: ${event.newPeak} (${progress})`)
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
// Add peers
|
|
582
|
+
await pool.addPeer('node1.chia.net', 8444, 'mainnet')
|
|
583
|
+
await pool.addPeer('node2.chia.net', 8444, 'mainnet')
|
|
584
|
+
|
|
585
|
+
// Check current peak
|
|
586
|
+
const peak = await pool.getPeakHeight()
|
|
587
|
+
console.log(`Current highest peak: ${peak || 'None yet'}`)
|
|
588
|
+
|
|
589
|
+
// Fetch some blocks to trigger peak updates
|
|
590
|
+
await pool.getBlockByHeight(5000000)
|
|
591
|
+
await pool.getBlockByHeight(5100000)
|
|
592
|
+
await pool.getBlockByHeight(5200000)
|
|
593
|
+
|
|
594
|
+
// Monitor sync status
|
|
595
|
+
setInterval(async () => {
|
|
596
|
+
const peak = await pool.getPeakHeight()
|
|
597
|
+
if (peak) {
|
|
598
|
+
const estimatedCurrent = 5200000 + Math.floor((Date.now() / 1000 - 1700000000) / 18.75)
|
|
599
|
+
const syncPercentage = (peak / estimatedCurrent * 100).toFixed(2)
|
|
600
|
+
console.log(`Sync status: ${syncPercentage}% (peak: ${peak})`)
|
|
601
|
+
}
|
|
602
|
+
}, 60000) // Check every minute
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### When to Use ChiaPeerPool vs ChiaBlockListener
|
|
606
|
+
|
|
607
|
+
- **Use ChiaPeerPool when:**
|
|
608
|
+
- You need to fetch historical blocks
|
|
609
|
+
- You want automatic load balancing across multiple peers
|
|
610
|
+
- You're making many block requests and need rate limiting
|
|
611
|
+
- You don't need real-time block notifications
|
|
612
|
+
|
|
613
|
+
- **Use ChiaBlockListener when:**
|
|
614
|
+
- You need real-time notifications of new blocks
|
|
615
|
+
- You want to monitor the blockchain as it grows
|
|
616
|
+
- You need to track specific addresses or puzzle hashes in real-time
|
|
617
|
+
- You're building applications that react to blockchain events
|
|
618
|
+
|
|
619
|
+
Both classes can be used together in the same application for different purposes.
|
|
620
|
+
|
|
621
|
+
## DNS Discovery Usage
|
|
622
|
+
|
|
623
|
+
The `DnsDiscoveryClient` enables automatic discovery of Chia network peers using DNS introducers, with full support for both IPv4 and IPv6 addresses.
|
|
624
|
+
|
|
625
|
+
### Basic DNS Discovery
|
|
626
|
+
|
|
627
|
+
```javascript
|
|
628
|
+
const { DnsDiscoveryClient, initTracing } = require('@dignetwork/chia-block-listener')
|
|
629
|
+
|
|
630
|
+
async function discoverPeers() {
|
|
631
|
+
// Initialize tracing
|
|
632
|
+
initTracing()
|
|
633
|
+
|
|
634
|
+
// Create DNS discovery client
|
|
635
|
+
const client = new DnsDiscoveryClient()
|
|
636
|
+
|
|
637
|
+
// Discover mainnet peers
|
|
638
|
+
const result = await client.discoverMainnetPeers()
|
|
639
|
+
|
|
640
|
+
console.log(`Found ${result.totalCount} total peers:`)
|
|
641
|
+
console.log(` IPv4 peers: ${result.ipv4Peers.length}`)
|
|
642
|
+
console.log(` IPv6 peers: ${result.ipv6Peers.length}`)
|
|
643
|
+
|
|
644
|
+
// Use with peer connections
|
|
645
|
+
for (const peer of result.ipv4Peers.slice(0, 3)) {
|
|
646
|
+
console.log(`IPv4 peer: ${peer.displayAddress}`)
|
|
647
|
+
// peer.host and peer.port can be used with addPeer()
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
for (const peer of result.ipv6Peers.slice(0, 3)) {
|
|
651
|
+
console.log(`IPv6 peer: ${peer.displayAddress}`) // [2001:db8::1]:8444
|
|
652
|
+
// IPv6 addresses are properly formatted with brackets
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
discoverPeers().catch(console.error)
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
### Integration with Peer Pool
|
|
660
|
+
|
|
661
|
+
```javascript
|
|
662
|
+
const { ChiaPeerPool, DnsDiscoveryClient } = require('@dignetwork/chia-block-listener')
|
|
663
|
+
|
|
664
|
+
async function setupPoolWithDnsDiscovery() {
|
|
665
|
+
const pool = new ChiaPeerPool()
|
|
666
|
+
const discovery = new DnsDiscoveryClient()
|
|
667
|
+
|
|
668
|
+
// Discover peers automatically
|
|
669
|
+
const peers = await discovery.discoverMainnetPeers()
|
|
670
|
+
|
|
671
|
+
// Add discovered peers to pool (both IPv4 and IPv6)
|
|
672
|
+
const allPeers = [...peers.ipv4Peers, ...peers.ipv6Peers]
|
|
673
|
+
for (const peer of allPeers.slice(0, 5)) {
|
|
674
|
+
await pool.addPeer(peer.host, peer.port, 'mainnet')
|
|
675
|
+
console.log(`Added peer: ${peer.displayAddress}`)
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Now use the pool for block retrieval
|
|
679
|
+
const block = await pool.getBlockByHeight(5000000)
|
|
680
|
+
console.log(`Retrieved block ${block.height}`)
|
|
681
|
+
|
|
682
|
+
await pool.shutdown()
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
setupPoolWithDnsDiscovery().catch(console.error)
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
### Custom DNS Introducers
|
|
689
|
+
|
|
690
|
+
```javascript
|
|
691
|
+
const client = new DnsDiscoveryClient()
|
|
692
|
+
|
|
693
|
+
// Use custom introducers
|
|
694
|
+
const customIntroducers = [
|
|
695
|
+
'seeder.dexie.space',
|
|
696
|
+
'chia.hoffmang.com'
|
|
697
|
+
]
|
|
698
|
+
|
|
699
|
+
const result = await client.discoverPeers(customIntroducers, 8444)
|
|
700
|
+
console.log(`Found ${result.totalCount} peers from custom introducers`)
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
### Individual DNS Resolution
|
|
704
|
+
|
|
705
|
+
```javascript
|
|
706
|
+
const client = new DnsDiscoveryClient()
|
|
707
|
+
const hostname = 'dns-introducer.chia.net'
|
|
708
|
+
|
|
709
|
+
// Resolve specific protocols
|
|
710
|
+
try {
|
|
711
|
+
const ipv4 = await client.resolveIpv4(hostname)
|
|
712
|
+
console.log(`IPv4 addresses: ${ipv4.addresses.join(', ')}`)
|
|
713
|
+
} catch (error) {
|
|
714
|
+
console.log(`IPv4 resolution failed: ${error.message}`)
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
try {
|
|
718
|
+
const ipv6 = await client.resolveIpv6(hostname)
|
|
719
|
+
console.log(`IPv6 addresses: ${ipv6.addresses.join(', ')}`)
|
|
720
|
+
} catch (error) {
|
|
721
|
+
console.log(`IPv6 resolution failed: ${error.message}`)
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Or resolve both at once
|
|
725
|
+
const both = await client.resolveBoth(hostname, 8444)
|
|
726
|
+
console.log(`Combined: ${both.totalCount} addresses`)
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
### Error Handling
|
|
732
|
+
|
|
733
|
+
```javascript
|
|
734
|
+
const client = new DnsDiscoveryClient()
|
|
735
|
+
|
|
736
|
+
try {
|
|
737
|
+
const result = await client.discoverMainnetPeers()
|
|
738
|
+
console.log(`Discovery successful: ${result.totalCount} peers`)
|
|
739
|
+
} catch (error) {
|
|
740
|
+
console.error('Discovery failed:', error.message)
|
|
741
|
+
|
|
742
|
+
// Handle different error types
|
|
743
|
+
if (error.message.includes('NoPeersFound')) {
|
|
744
|
+
console.log('No peers found from any introducer')
|
|
745
|
+
} else if (error.message.includes('ResolutionFailed')) {
|
|
746
|
+
console.log('DNS resolution failed')
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
### Key Features
|
|
752
|
+
|
|
753
|
+
- **Dual Stack Support**: Separate IPv4 and IPv6 peer lists
|
|
754
|
+
- **Proper DNS Lookups**: Uses A records for IPv4, AAAA records for IPv6
|
|
755
|
+
- **Built-in Networks**: Ready configurations for mainnet and testnet11
|
|
756
|
+
- **Custom Introducers**: Support for any DNS introducers
|
|
757
|
+
- **IPv6 URL Formatting**: Automatic bracket formatting for IPv6 addresses
|
|
758
|
+
- **Type Safety**: Full TypeScript support with detailed type definitions
|
|
759
|
+
|
|
760
|
+
## ChiaBlockParser Usage
|
|
761
|
+
|
|
762
|
+
The `ChiaBlockParser` provides direct access to the Rust-based block parsing engine, enabling efficient parsing of Chia FullBlock data with complete control over the parsing process. This is ideal for applications that need to process block data from external sources or implement custom block analysis.
|
|
763
|
+
|
|
764
|
+
### Basic Block Parsing
|
|
765
|
+
|
|
766
|
+
```javascript
|
|
767
|
+
const { ChiaBlockParser, initTracing } = require('@dignetwork/chia-block-listener')
|
|
768
|
+
|
|
769
|
+
async function parseBlockData() {
|
|
770
|
+
// Initialize tracing
|
|
771
|
+
initTracing()
|
|
772
|
+
|
|
773
|
+
// Create a block parser instance
|
|
774
|
+
const parser = new ChiaBlockParser()
|
|
775
|
+
|
|
776
|
+
// Parse a block from hex string
|
|
777
|
+
const blockHex = "your_full_block_hex_data_here"
|
|
778
|
+
const parsedBlock = parser.parseFullBlockFromHex(blockHex)
|
|
779
|
+
|
|
780
|
+
console.log(`Parsed block ${parsedBlock.height}:`)
|
|
781
|
+
console.log(` Header hash: ${parsedBlock.headerHash}`)
|
|
782
|
+
console.log(` Weight: ${parsedBlock.weight}`)
|
|
783
|
+
console.log(` Timestamp: ${new Date(parsedBlock.timestamp * 1000)}`)
|
|
784
|
+
console.log(` Coin additions: ${parsedBlock.coinAdditions.length}`)
|
|
785
|
+
console.log(` Coin removals: ${parsedBlock.coinRemovals.length}`)
|
|
786
|
+
console.log(` Coin spends: ${parsedBlock.coinSpends.length}`)
|
|
787
|
+
console.log(` Has generator: ${parsedBlock.hasTransactionsGenerator}`)
|
|
788
|
+
|
|
789
|
+
// Access detailed coin spend information
|
|
790
|
+
parsedBlock.coinSpends.forEach((spend, index) => {
|
|
791
|
+
console.log(` Spend ${index + 1}:`)
|
|
792
|
+
console.log(` Coin: ${spend.coin.amount} mojos`)
|
|
793
|
+
console.log(` Puzzle hash: ${spend.coin.puzzleHash}`)
|
|
794
|
+
console.log(` Puzzle reveal: ${spend.puzzleReveal.substring(0, 100)}...`)
|
|
795
|
+
console.log(` Solution: ${spend.solution.substring(0, 100)}...`)
|
|
796
|
+
console.log(` Created coins: ${spend.createdCoins.length}`)
|
|
797
|
+
})
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
parseBlockData().catch(console.error)
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
### ChiaBlockParser Class
|
|
804
|
+
|
|
805
|
+
#### Constructor
|
|
806
|
+
|
|
807
|
+
```javascript
|
|
808
|
+
const parser = new ChiaBlockParser()
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
Creates a new block parser instance with access to the full Rust parsing engine.
|
|
812
|
+
|
|
813
|
+
#### Methods
|
|
814
|
+
|
|
815
|
+
##### `parseFullBlockFromBytes(blockBytes): ParsedBlockJs`
|
|
816
|
+
|
|
817
|
+
Parses a FullBlock from raw bytes.
|
|
818
|
+
|
|
819
|
+
**Parameters:**
|
|
820
|
+
- `blockBytes` (Buffer): The serialized FullBlock data
|
|
821
|
+
|
|
822
|
+
**Returns:** A `ParsedBlockJs` object containing all parsed block information
|
|
823
|
+
|
|
824
|
+
```javascript
|
|
825
|
+
const fs = require('fs')
|
|
826
|
+
const blockData = fs.readFileSync('block.bin')
|
|
827
|
+
const parsedBlock = parser.parseFullBlockFromBytes(blockData)
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
##### `parseFullBlockFromHex(blockHex): ParsedBlockJs`
|
|
831
|
+
|
|
832
|
+
Parses a FullBlock from a hex-encoded string.
|
|
833
|
+
|
|
834
|
+
**Parameters:**
|
|
835
|
+
- `blockHex` (string): The hex-encoded FullBlock data
|
|
836
|
+
|
|
837
|
+
**Returns:** A `ParsedBlockJs` object containing all parsed block information
|
|
838
|
+
|
|
839
|
+
```javascript
|
|
840
|
+
const blockHex = "deadbeef..." // Your hex-encoded block data
|
|
841
|
+
const parsedBlock = parser.parseFullBlockFromHex(blockHex)
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
##### `extractGeneratorFromBlockBytes(blockBytes): string | null`
|
|
845
|
+
|
|
846
|
+
Extracts only the transactions generator from a block without full parsing.
|
|
847
|
+
|
|
848
|
+
**Parameters:**
|
|
849
|
+
- `blockBytes` (Buffer): The serialized FullBlock data
|
|
850
|
+
|
|
851
|
+
**Returns:** Hex-encoded generator bytecode or `null` if no generator exists
|
|
852
|
+
|
|
853
|
+
```javascript
|
|
854
|
+
const blockData = fs.readFileSync('block.bin')
|
|
855
|
+
const generator = parser.extractGeneratorFromBlockBytes(blockData)
|
|
856
|
+
if (generator) {
|
|
857
|
+
console.log(`Generator size: ${generator.length / 2} bytes`)
|
|
858
|
+
}
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
##### `getHeightAndTxStatusFromBlockBytes(blockBytes): BlockHeightInfoJs`
|
|
862
|
+
|
|
863
|
+
Quickly extracts basic block information without full parsing.
|
|
864
|
+
|
|
865
|
+
**Parameters:**
|
|
866
|
+
- `blockBytes` (Buffer): The serialized FullBlock data
|
|
867
|
+
|
|
868
|
+
**Returns:** A `BlockHeightInfoJs` object with height and transaction status
|
|
869
|
+
|
|
870
|
+
```javascript
|
|
871
|
+
const blockData = fs.readFileSync('block.bin')
|
|
872
|
+
const info = parser.getHeightAndTxStatusFromBlockBytes(blockData)
|
|
873
|
+
console.log(`Block ${info.height}, has transactions: ${info.isTransactionBlock}`)
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
##### `parseBlockInfoFromBytes(blockBytes): GeneratorBlockInfoJs`
|
|
877
|
+
|
|
878
|
+
Extracts generator-related block metadata.
|
|
879
|
+
|
|
880
|
+
**Parameters:**
|
|
881
|
+
- `blockBytes` (Buffer): The serialized FullBlock data
|
|
882
|
+
|
|
883
|
+
**Returns:** A `GeneratorBlockInfoJs` object with generator metadata
|
|
884
|
+
|
|
885
|
+
```javascript
|
|
886
|
+
const blockData = fs.readFileSync('block.bin')
|
|
887
|
+
const blockInfo = parser.parseBlockInfoFromBytes(blockData)
|
|
888
|
+
console.log(`Previous hash: ${blockInfo.prevHeaderHash}`)
|
|
889
|
+
console.log(`Generator refs: ${blockInfo.transactionsGeneratorRefList.length}`)
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
### Advanced Parsing Features
|
|
893
|
+
|
|
894
|
+
#### Batch Block Processing
|
|
895
|
+
|
|
896
|
+
```javascript
|
|
897
|
+
const parser = new ChiaBlockParser()
|
|
898
|
+
const fs = require('fs')
|
|
899
|
+
const path = require('path')
|
|
900
|
+
|
|
901
|
+
// Process multiple block files
|
|
902
|
+
const blockFiles = fs.readdirSync('./blocks/').filter(f => f.endsWith('.bin'))
|
|
903
|
+
|
|
904
|
+
const stats = {
|
|
905
|
+
totalBlocks: 0,
|
|
906
|
+
totalSpends: 0,
|
|
907
|
+
totalCoins: 0,
|
|
908
|
+
generatorBlocks: 0
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
for (const filename of blockFiles) {
|
|
912
|
+
const blockData = fs.readFileSync(path.join('./blocks/', filename))
|
|
913
|
+
|
|
914
|
+
try {
|
|
915
|
+
const parsed = parser.parseFullBlockFromBytes(blockData)
|
|
916
|
+
|
|
917
|
+
stats.totalBlocks++
|
|
918
|
+
stats.totalSpends += parsed.coinSpends.length
|
|
919
|
+
stats.totalCoins += parsed.coinAdditions.length
|
|
920
|
+
|
|
921
|
+
if (parsed.hasTransactionsGenerator) {
|
|
922
|
+
stats.generatorBlocks++
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
console.log(`Processed block ${parsed.height} with ${parsed.coinSpends.length} spends`)
|
|
926
|
+
|
|
927
|
+
} catch (error) {
|
|
928
|
+
console.error(`Failed to parse ${filename}:`, error.message)
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
console.log('\nBatch processing complete:')
|
|
933
|
+
console.log(` Blocks processed: ${stats.totalBlocks}`)
|
|
934
|
+
console.log(` Total spends: ${stats.totalSpends}`)
|
|
935
|
+
console.log(` Total coins: ${stats.totalCoins}`)
|
|
936
|
+
console.log(` Generator blocks: ${stats.generatorBlocks}`)
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
#### Generator Analysis
|
|
940
|
+
|
|
941
|
+
```javascript
|
|
942
|
+
const parser = new ChiaBlockParser()
|
|
943
|
+
|
|
944
|
+
function analyzeBlockGenerator(blockHex) {
|
|
945
|
+
// Parse the full block
|
|
946
|
+
const parsed = parser.parseFullBlockFromHex(blockHex)
|
|
947
|
+
|
|
948
|
+
if (!parsed.hasTransactionsGenerator) {
|
|
949
|
+
console.log('Block has no generator')
|
|
950
|
+
return
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// Extract just the generator for analysis
|
|
954
|
+
const blockBytes = Buffer.from(blockHex, 'hex')
|
|
955
|
+
const generator = parser.extractGeneratorFromBlockBytes(blockBytes)
|
|
956
|
+
|
|
957
|
+
console.log(`Generator Analysis for Block ${parsed.height}:`)
|
|
958
|
+
console.log(` Generator size: ${parsed.generatorSize} bytes`)
|
|
959
|
+
console.log(` Hex length: ${generator.length} characters`)
|
|
960
|
+
console.log(` Coin spends extracted: ${parsed.coinSpends.length}`)
|
|
961
|
+
console.log(` Coins created: ${parsed.coinCreations.length}`)
|
|
962
|
+
|
|
963
|
+
// Analyze coin spends
|
|
964
|
+
parsed.coinSpends.forEach((spend, i) => {
|
|
965
|
+
console.log(` Spend ${i + 1}:`)
|
|
966
|
+
console.log(` Amount: ${spend.coin.amount} mojos`)
|
|
967
|
+
console.log(` Puzzle size: ${spend.puzzleReveal.length / 2} bytes`)
|
|
968
|
+
console.log(` Solution size: ${spend.solution.length / 2} bytes`)
|
|
969
|
+
console.log(` Creates ${spend.createdCoins.length} new coins`)
|
|
970
|
+
})
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Example usage
|
|
974
|
+
const blockHex = "your_generator_block_hex"
|
|
975
|
+
analyzeBlockGenerator(blockHex)
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
#### Integration with Other Components
|
|
979
|
+
|
|
980
|
+
```javascript
|
|
981
|
+
const { ChiaBlockParser, ChiaPeerPool, DnsDiscoveryClient } = require('@dignetwork/chia-block-listener')
|
|
982
|
+
|
|
983
|
+
async function integratedBlockAnalysis() {
|
|
984
|
+
// Set up components
|
|
985
|
+
const parser = new ChiaBlockParser()
|
|
986
|
+
const pool = new ChiaPeerPool()
|
|
987
|
+
const discovery = new DnsDiscoveryClient()
|
|
988
|
+
|
|
989
|
+
// Discover and connect to peers
|
|
990
|
+
const peers = await discovery.discoverMainnetPeers()
|
|
991
|
+
for (const peer of peers.ipv4Peers.slice(0, 3)) {
|
|
992
|
+
await pool.addPeer(peer.host, peer.port, 'mainnet')
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// Fetch blocks and parse with enhanced detail
|
|
996
|
+
const heights = [5000000, 5000001, 5000002]
|
|
997
|
+
|
|
998
|
+
for (const height of heights) {
|
|
999
|
+
// Get block using peer pool
|
|
1000
|
+
const blockEvent = await pool.getBlockByHeight(height)
|
|
1001
|
+
|
|
1002
|
+
// For more detailed analysis, you can also parse with ChiaBlockParser
|
|
1003
|
+
// if you have access to the raw block bytes
|
|
1004
|
+
console.log(`Block ${height}:`)
|
|
1005
|
+
console.log(` From peer: ${blockEvent.peerId}`)
|
|
1006
|
+
console.log(` Coin spends: ${blockEvent.coinSpends.length}`)
|
|
1007
|
+
console.log(` Has generator: ${blockEvent.hasTransactionsGenerator}`)
|
|
1008
|
+
|
|
1009
|
+
// Analyze puzzle patterns
|
|
1010
|
+
const puzzleHashes = new Set()
|
|
1011
|
+
blockEvent.coinSpends.forEach(spend => {
|
|
1012
|
+
puzzleHashes.add(spend.coin.puzzleHash)
|
|
1013
|
+
})
|
|
1014
|
+
console.log(` Unique puzzle hashes: ${puzzleHashes.size}`)
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
await pool.shutdown()
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
integratedBlockAnalysis().catch(console.error)
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
### Data Types
|
|
1024
|
+
|
|
1025
|
+
#### `ParsedBlockJs`
|
|
1026
|
+
|
|
1027
|
+
```typescript
|
|
1028
|
+
interface ParsedBlockJs {
|
|
1029
|
+
height: number // Block height
|
|
1030
|
+
weight: string // Block weight as string
|
|
1031
|
+
headerHash: string // Block header hash (hex)
|
|
1032
|
+
timestamp?: number // Block timestamp (Unix time)
|
|
1033
|
+
coinAdditions: CoinInfoJs[] // New coins created
|
|
1034
|
+
coinRemovals: CoinInfoJs[] // Coins spent
|
|
1035
|
+
coinSpends: CoinSpendInfoJs[] // Detailed spend information
|
|
1036
|
+
coinCreations: CoinInfoJs[] // Coins created by spends
|
|
1037
|
+
hasTransactionsGenerator: boolean // Whether block has generator
|
|
1038
|
+
generatorSize?: number // Generator size in bytes
|
|
1039
|
+
}
|
|
1040
|
+
```
|
|
1041
|
+
|
|
1042
|
+
#### `CoinInfoJs`
|
|
1043
|
+
|
|
1044
|
+
```typescript
|
|
1045
|
+
interface CoinInfoJs {
|
|
1046
|
+
parentCoinInfo: string // Parent coin ID (hex)
|
|
1047
|
+
puzzleHash: string // Puzzle hash (hex)
|
|
1048
|
+
amount: string // Amount as string (to avoid JS precision issues)
|
|
1049
|
+
}
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
#### `CoinSpendInfoJs`
|
|
1053
|
+
|
|
1054
|
+
```typescript
|
|
1055
|
+
interface CoinSpendInfoJs {
|
|
1056
|
+
coin: CoinInfoJs // The coin being spent
|
|
1057
|
+
puzzleReveal: string // CLVM puzzle bytecode (hex)
|
|
1058
|
+
solution: string // CLVM solution bytecode (hex)
|
|
1059
|
+
realData: boolean // Whether this is real transaction data
|
|
1060
|
+
parsingMethod: string // Method used for parsing
|
|
1061
|
+
offset: number // Offset in generator bytecode
|
|
1062
|
+
createdCoins: CoinInfoJs[] // Coins created by this spend
|
|
1063
|
+
}
|
|
1064
|
+
```
|
|
1065
|
+
|
|
1066
|
+
#### `GeneratorBlockInfoJs`
|
|
1067
|
+
|
|
1068
|
+
```typescript
|
|
1069
|
+
interface GeneratorBlockInfoJs {
|
|
1070
|
+
prevHeaderHash: string // Previous block hash (hex)
|
|
1071
|
+
transactionsGenerator?: string // Generator bytecode (hex)
|
|
1072
|
+
transactionsGeneratorRefList: number[] // Referenced block heights
|
|
1073
|
+
}
|
|
1074
|
+
```
|
|
1075
|
+
|
|
1076
|
+
#### `BlockHeightInfoJs`
|
|
1077
|
+
|
|
1078
|
+
```typescript
|
|
1079
|
+
interface BlockHeightInfoJs {
|
|
1080
|
+
height: number // Block height
|
|
1081
|
+
isTransactionBlock: boolean // Whether block contains transactions
|
|
1082
|
+
}
|
|
1083
|
+
```
|
|
1084
|
+
|
|
1085
|
+
### When to Use ChiaBlockParser
|
|
1086
|
+
|
|
1087
|
+
- **Use ChiaBlockParser when:**
|
|
1088
|
+
- You have raw block data from external sources
|
|
1089
|
+
- You need detailed CLVM puzzle and solution analysis
|
|
1090
|
+
- You're implementing custom block processing logic
|
|
1091
|
+
- You need to extract generators for analysis
|
|
1092
|
+
- You want fine-grained control over parsing
|
|
1093
|
+
|
|
1094
|
+
- **Use with ChiaPeerPool when:**
|
|
1095
|
+
- You need both block retrieval and detailed parsing
|
|
1096
|
+
- You're analyzing historical blocks in detail
|
|
1097
|
+
- You want to combine network access with parsing
|
|
1098
|
+
|
|
1099
|
+
- **Use with ChiaBlockListener when:**
|
|
1100
|
+
- You're processing real-time blocks with custom logic
|
|
1101
|
+
- You need both live monitoring and detailed parsing
|
|
1102
|
+
|
|
1103
|
+
The `ChiaBlockParser` complements the other classes by providing the lowest-level access to the Chia block parsing engine, enabling sophisticated analysis and processing workflows.
|
|
1104
|
+
|
|
1105
|
+
## TypeScript Usage
|
|
1106
|
+
|
|
1107
|
+
```typescript
|
|
1108
|
+
import {
|
|
1109
|
+
ChiaBlockListener,
|
|
1110
|
+
ChiaPeerPool,
|
|
1111
|
+
ChiaBlockParser,
|
|
1112
|
+
DnsDiscoveryClient,
|
|
1113
|
+
BlockReceivedEvent,
|
|
1114
|
+
PeerConnectedEvent,
|
|
1115
|
+
PeerDisconnectedEvent,
|
|
1116
|
+
NewPeakHeightEvent,
|
|
1117
|
+
DiscoveryResultJS,
|
|
1118
|
+
PeerAddressJS,
|
|
1119
|
+
AddressResult,
|
|
1120
|
+
CoinRecord,
|
|
1121
|
+
CoinSpend,
|
|
1122
|
+
ParsedBlockJs,
|
|
1123
|
+
CoinInfoJs,
|
|
1124
|
+
CoinSpendInfoJs,
|
|
1125
|
+
GeneratorBlockInfoJs,
|
|
1126
|
+
BlockHeightInfoJs,
|
|
1127
|
+
initTracing,
|
|
1128
|
+
getEventTypes
|
|
1129
|
+
} from '@dignetwork/chia-block-listener'
|
|
1130
|
+
|
|
1131
|
+
// Initialize tracing for debugging
|
|
1132
|
+
initTracing()
|
|
1133
|
+
|
|
1134
|
+
// Create listener with proper typing
|
|
1135
|
+
const listener = new ChiaBlockListener()
|
|
1136
|
+
|
|
1137
|
+
// Type-safe event handlers
|
|
1138
|
+
listener.on('blockReceived', (block: BlockReceivedEvent) => {
|
|
1139
|
+
console.log(`Block ${block.height} from peer ${block.peerId}`)
|
|
1140
|
+
|
|
1141
|
+
// Process coin additions
|
|
1142
|
+
block.coinAdditions.forEach((coin: CoinRecord) => {
|
|
1143
|
+
console.log(`New coin: ${coin.amount} mojos`)
|
|
1144
|
+
})
|
|
1145
|
+
|
|
1146
|
+
// Process coin spends
|
|
1147
|
+
block.coinSpends.forEach((spend: CoinSpend) => {
|
|
1148
|
+
console.log(`Spend: ${spend.coin.amount} mojos`)
|
|
1149
|
+
console.log(`Puzzle: ${spend.puzzleReveal}`)
|
|
1150
|
+
console.log(`Solution: ${spend.solution}`)
|
|
1151
|
+
})
|
|
1152
|
+
})
|
|
1153
|
+
|
|
1154
|
+
listener.on('peerConnected', (peer: PeerConnectedEvent) => {
|
|
1155
|
+
console.log(`Connected: ${peer.peerId} at ${peer.host}:${peer.port}`)
|
|
1156
|
+
})
|
|
1157
|
+
|
|
1158
|
+
listener.on('peerDisconnected', (peer: PeerDisconnectedEvent) => {
|
|
1159
|
+
console.log(`Disconnected: ${peer.peerId}`)
|
|
1160
|
+
if (peer.message) {
|
|
1161
|
+
console.log(`Reason: ${peer.message}`)
|
|
1162
|
+
}
|
|
1163
|
+
})
|
|
1164
|
+
|
|
1165
|
+
// Connect to peers
|
|
1166
|
+
const mainnetPeer = listener.addPeer('localhost', 8444, 'mainnet')
|
|
1167
|
+
const testnetPeer = listener.addPeer('testnet-node.chia.net', 58444, 'testnet')
|
|
1168
|
+
|
|
1169
|
+
// Get historical blocks
|
|
1170
|
+
async function getHistoricalBlocks() {
|
|
1171
|
+
try {
|
|
1172
|
+
const block = listener.getBlockByHeight(mainnetPeer, 1000000)
|
|
1173
|
+
console.log(`Block 1000000 hash: ${block.headerHash}`)
|
|
1174
|
+
|
|
1175
|
+
const blocks = listener.getBlocksRange(mainnetPeer, 1000000, 1000010)
|
|
1176
|
+
console.log(`Retrieved ${blocks.length} blocks`)
|
|
1177
|
+
} catch (error) {
|
|
1178
|
+
console.error('Error getting blocks:', error)
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// Get event type constants
|
|
1183
|
+
const eventTypes = getEventTypes()
|
|
1184
|
+
console.log('Available events:', eventTypes)
|
|
1185
|
+
|
|
1186
|
+
// TypeScript support for ChiaPeerPool
|
|
1187
|
+
const pool = new ChiaPeerPool()
|
|
1188
|
+
|
|
1189
|
+
// Type-safe event handling
|
|
1190
|
+
pool.on('peerConnected', (event: PeerConnectedEvent) => {
|
|
1191
|
+
console.log(`Pool peer connected: ${event.peerId}`)
|
|
1192
|
+
})
|
|
1193
|
+
|
|
1194
|
+
pool.on('newPeakHeight', (event: NewPeakHeightEvent) => {
|
|
1195
|
+
console.log(`New peak: ${event.oldPeak} → ${event.newPeak}`)
|
|
1196
|
+
})
|
|
1197
|
+
|
|
1198
|
+
// Async/await with proper typing
|
|
1199
|
+
async function fetchHistoricalData() {
|
|
1200
|
+
const block: BlockReceivedEvent = await pool.getBlockByHeight(5000000)
|
|
1201
|
+
const peers: string[] = await pool.getConnectedPeers()
|
|
1202
|
+
const peak: number | null = await pool.getPeakHeight()
|
|
1203
|
+
|
|
1204
|
+
console.log(`Block ${block.height} has ${block.coinSpends.length} spends`)
|
|
1205
|
+
console.log(`Pool has ${peers.length} active peers`)
|
|
1206
|
+
console.log(`Current peak: ${peak || 'No peak yet'}`)
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// TypeScript DNS Discovery
|
|
1210
|
+
async function typedDnsDiscovery(): Promise<void> {
|
|
1211
|
+
const client = new DnsDiscoveryClient()
|
|
1212
|
+
|
|
1213
|
+
// Type-safe discovery
|
|
1214
|
+
const result: DiscoveryResultJS = await client.discoverMainnetPeers()
|
|
1215
|
+
|
|
1216
|
+
// Access with full type safety
|
|
1217
|
+
result.ipv4Peers.forEach((peer: PeerAddressJS) => {
|
|
1218
|
+
console.log(`IPv4: ${peer.host}:${peer.port} (${peer.displayAddress})`)
|
|
1219
|
+
})
|
|
1220
|
+
|
|
1221
|
+
result.ipv6Peers.forEach((peer: PeerAddressJS) => {
|
|
1222
|
+
console.log(`IPv6: ${peer.displayAddress} (isIpv6: ${peer.isIpv6})`)
|
|
1223
|
+
})
|
|
1224
|
+
|
|
1225
|
+
// Individual resolution with types
|
|
1226
|
+
const ipv4Result: AddressResult = await client.resolveIpv4('dns-introducer.chia.net')
|
|
1227
|
+
const ipv6Result: AddressResult = await client.resolveIpv6('dns-introducer.chia.net')
|
|
1228
|
+
|
|
1229
|
+
console.log(`IPv4 count: ${ipv4Result.count}, IPv6 count: ${ipv6Result.count}`)
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// TypeScript ChiaBlockParser
|
|
1233
|
+
async function typedBlockParsing(): Promise<void> {
|
|
1234
|
+
const parser = new ChiaBlockParser()
|
|
1235
|
+
|
|
1236
|
+
// Type-safe block parsing from hex
|
|
1237
|
+
const blockHex = "your_block_hex_data"
|
|
1238
|
+
const parsedBlock: ParsedBlockJs = parser.parseFullBlockFromHex(blockHex)
|
|
1239
|
+
|
|
1240
|
+
console.log(`Parsed block ${parsedBlock.height}:`)
|
|
1241
|
+
console.log(` Header hash: ${parsedBlock.headerHash}`)
|
|
1242
|
+
console.log(` Weight: ${parsedBlock.weight}`)
|
|
1243
|
+
console.log(` Coin additions: ${parsedBlock.coinAdditions.length}`)
|
|
1244
|
+
console.log(` Coin spends: ${parsedBlock.coinSpends.length}`)
|
|
1245
|
+
console.log(` Has generator: ${parsedBlock.hasTransactionsGenerator}`)
|
|
1246
|
+
|
|
1247
|
+
// Type-safe access to coin spend details
|
|
1248
|
+
parsedBlock.coinSpends.forEach((spend: CoinSpendInfoJs) => {
|
|
1249
|
+
const coin: CoinInfoJs = spend.coin
|
|
1250
|
+
console.log(`Spend: ${coin.amount} mojos from ${coin.puzzleHash}`)
|
|
1251
|
+
console.log(` Puzzle reveal size: ${spend.puzzleReveal.length / 2} bytes`)
|
|
1252
|
+
console.log(` Solution size: ${spend.solution.length / 2} bytes`)
|
|
1253
|
+
console.log(` Creates ${spend.createdCoins.length} new coins`)
|
|
1254
|
+
console.log(` Real data: ${spend.realData}`)
|
|
1255
|
+
console.log(` Parsing method: ${spend.parsingMethod}`)
|
|
1256
|
+
})
|
|
1257
|
+
|
|
1258
|
+
// Type-safe generator extraction
|
|
1259
|
+
const blockBytes = Buffer.from(blockHex, 'hex')
|
|
1260
|
+
const generator: string | null = parser.extractGeneratorFromBlockBytes(blockBytes)
|
|
1261
|
+
if (generator) {
|
|
1262
|
+
console.log(`Generator found: ${generator.length / 2} bytes`)
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// Type-safe height and tx status
|
|
1266
|
+
const heightInfo: BlockHeightInfoJs = parser.getHeightAndTxStatusFromBlockBytes(blockBytes)
|
|
1267
|
+
console.log(`Block ${heightInfo.height}, is transaction block: ${heightInfo.isTransactionBlock}`)
|
|
1268
|
+
|
|
1269
|
+
// Type-safe block info extraction
|
|
1270
|
+
const blockInfo: GeneratorBlockInfoJs = parser.parseBlockInfoFromBytes(blockBytes)
|
|
1271
|
+
console.log(`Previous hash: ${blockInfo.prevHeaderHash}`)
|
|
1272
|
+
console.log(`Generator refs: ${blockInfo.transactionsGeneratorRefList.length}`)
|
|
1273
|
+
if (blockInfo.transactionsGenerator) {
|
|
1274
|
+
console.log(`Generator size: ${blockInfo.transactionsGenerator.length / 2} bytes`)
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
```
|
|
1278
|
+
|
|
1279
|
+
## Advanced Usage
|
|
1280
|
+
|
|
1281
|
+
### Monitoring Specific Transactions
|
|
1282
|
+
|
|
1283
|
+
```javascript
|
|
1284
|
+
// Monitor all coin spends for a specific puzzle hash
|
|
1285
|
+
listener.on('blockReceived', (block) => {
|
|
1286
|
+
const targetPuzzleHash = '0x1234...' // Your puzzle hash
|
|
1287
|
+
|
|
1288
|
+
block.coinSpends.forEach((spend) => {
|
|
1289
|
+
if (spend.coin.puzzleHash === targetPuzzleHash) {
|
|
1290
|
+
console.log('Found spend for our puzzle!')
|
|
1291
|
+
console.log('Amount:', spend.coin.amount)
|
|
1292
|
+
console.log('Solution:', spend.solution)
|
|
1293
|
+
}
|
|
1294
|
+
})
|
|
1295
|
+
})
|
|
1296
|
+
```
|
|
1297
|
+
|
|
1298
|
+
### Multiple Network Monitoring
|
|
1299
|
+
|
|
1300
|
+
```javascript
|
|
1301
|
+
// Monitor both mainnet and testnet
|
|
1302
|
+
const mainnetPeer = listener.addPeer('localhost', 8444, 'mainnet')
|
|
1303
|
+
const testnetPeer = listener.addPeer('localhost', 58444, 'testnet')
|
|
1304
|
+
|
|
1305
|
+
listener.on('blockReceived', (block) => {
|
|
1306
|
+
if (block.peerId === mainnetPeer) {
|
|
1307
|
+
console.log(`Mainnet block ${block.height}`)
|
|
1308
|
+
} else if (block.peerId === testnetPeer) {
|
|
1309
|
+
console.log(`Testnet block ${block.height}`)
|
|
1310
|
+
}
|
|
1311
|
+
})
|
|
1312
|
+
```
|
|
1313
|
+
|
|
1314
|
+
### Connection Management
|
|
1315
|
+
|
|
1316
|
+
```javascript
|
|
1317
|
+
// Automatic reconnection
|
|
1318
|
+
listener.on('peerDisconnected', (peer) => {
|
|
1319
|
+
console.log(`Lost connection to ${peer.peerId}, reconnecting...`)
|
|
1320
|
+
|
|
1321
|
+
// Reconnect after 5 seconds
|
|
1322
|
+
setTimeout(() => {
|
|
1323
|
+
try {
|
|
1324
|
+
listener.addPeer(peer.host, peer.port, 'mainnet')
|
|
1325
|
+
console.log('Reconnected successfully')
|
|
1326
|
+
} catch (error) {
|
|
1327
|
+
console.error('Reconnection failed:', error)
|
|
1328
|
+
}
|
|
1329
|
+
}, 5000)
|
|
1330
|
+
})
|
|
1331
|
+
```
|
|
1332
|
+
|
|
1333
|
+
## Utility Functions
|
|
1334
|
+
|
|
1335
|
+
### `initTracing(): void`
|
|
1336
|
+
|
|
1337
|
+
Initializes the Rust tracing system for debugging purposes. Call this before creating any `ChiaBlockListener` instances if you want to see debug output.
|
|
1338
|
+
|
|
1339
|
+
### `getEventTypes(): EventTypes`
|
|
1340
|
+
|
|
1341
|
+
Returns an object containing the event type constants:
|
|
1342
|
+
|
|
1343
|
+
```javascript
|
|
1344
|
+
const eventTypes = getEventTypes()
|
|
1345
|
+
console.log(eventTypes)
|
|
1346
|
+
// Output: { blockReceived: "blockReceived", peerConnected: "peerConnected", peerDisconnected: "peerDisconnected" }
|
|
1347
|
+
```
|
|
1348
|
+
|
|
1349
|
+
|
|
1350
|
+
|
|
1351
|
+
## Performance Tips
|
|
1352
|
+
|
|
1353
|
+
1. **Use specific event handlers**: Only listen for the events you need
|
|
1354
|
+
2. **Process blocks efficiently**: Avoid heavy computation in event handlers
|
|
1355
|
+
3. **Manage connections**: Don't create too many peer connections simultaneously
|
|
1356
|
+
4. **Handle errors gracefully**: Always wrap peer operations in try-catch blocks
|
|
1357
|
+
|
|
1358
|
+
## Development
|
|
1359
|
+
|
|
1360
|
+
### Prerequisites
|
|
1361
|
+
|
|
1362
|
+
- [Rust](https://rustup.rs/) (latest stable)
|
|
1363
|
+
- [Node.js](https://nodejs.org/) (20 or later)
|
|
1364
|
+
- [npm](https://www.npmjs.com/)
|
|
1365
|
+
|
|
1366
|
+
### Setup
|
|
1367
|
+
|
|
1368
|
+
```bash
|
|
1369
|
+
# Clone and install dependencies
|
|
1370
|
+
git clone <repository-url>
|
|
1371
|
+
cd chia-block-listener
|
|
1372
|
+
npm install
|
|
1373
|
+
|
|
1374
|
+
# Build the native module
|
|
1375
|
+
npm run build
|
|
1376
|
+
|
|
1377
|
+
# Run tests
|
|
1378
|
+
npm test
|
|
1379
|
+
```
|
|
1380
|
+
|
|
1381
|
+
### Project Structure
|
|
1382
|
+
|
|
1383
|
+
```
|
|
1384
|
+
chia-block-listener/
|
|
1385
|
+
├── src/ # Rust source code
|
|
1386
|
+
│ ├── lib.rs # Main NAPI bindings
|
|
1387
|
+
│ ├── peer.rs # Peer connection management
|
|
1388
|
+
│ ├── protocol.rs # Chia protocol implementation
|
|
1389
|
+
│ ├── event_emitter.rs # Event system
|
|
1390
|
+
│ └── tls.rs # TLS connection handling
|
|
1391
|
+
├── crate/ # Additional Rust crates
|
|
1392
|
+
│ └── chia-generator-parser/ # CLVM parser
|
|
1393
|
+
├── __test__/ # Test suite
|
|
1394
|
+
├── npm/ # Platform-specific binaries
|
|
1395
|
+
├── .github/workflows/ # CI/CD pipeline
|
|
1396
|
+
├── Cargo.toml # Rust configuration
|
|
1397
|
+
├── package.json # Node.js configuration
|
|
1398
|
+
└── index.d.ts # TypeScript definitions
|
|
1399
|
+
```
|
|
1400
|
+
|
|
1401
|
+
## CI/CD & Publishing
|
|
1402
|
+
|
|
1403
|
+
This project uses GitHub Actions for:
|
|
1404
|
+
- Cross-platform builds (Windows, macOS, Linux)
|
|
1405
|
+
- Multiple architectures (x64, ARM64)
|
|
1406
|
+
- Automated testing on all platforms
|
|
1407
|
+
- npm publishing based on git tags
|
|
1408
|
+
|
|
1409
|
+
## License
|
|
1410
|
+
|
|
1411
|
+
MIT
|
|
1412
|
+
|
|
1413
|
+
## Contributing
|
|
1414
|
+
|
|
1415
|
+
1. Fork the repository
|
|
1416
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
1417
|
+
3. Make your changes and add tests
|
|
1418
|
+
4. Ensure all tests pass (`npm test`)
|
|
1419
|
+
5. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
1420
|
+
6. Push to the branch (`git push origin feature/amazing-feature`)
|
|
1421
|
+
7. Open a Pull Request
|
|
1422
|
+
|
|
1423
|
+
## Support
|
|
1424
|
+
|
|
1425
|
+
For issues and questions:
|
|
1426
|
+
- GitHub Issues: [Report bugs or request features](https://github.com/DIG-Network/chia-block-listener/issues)
|
|
1427
|
+
- Documentation: Check the TypeScript definitions in `index.d.ts`
|
|
1031
1428
|
- Examples: See the `examples/` directory for more usage examples
|