@dignetwork/chia-block-listener 0.1.8 → 0.1.11
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/.yarnrc.yml +1 -1
- package/README.md +301 -4
- package/crate/chia-generator-parser/src/parser.rs +91 -42
- package/crate/chia-generator-parser/src/types.rs +25 -117
- package/examples/coin-monitor.js +54 -1
- package/examples/historical-coin-monitor.js +308 -0
- package/examples/peer-pool-events-example.js +164 -0
- package/examples/peer-pool-example.js +151 -0
- package/examples/peer-pool-peak-tracking.js +179 -0
- package/examples/test-connection-diagnostic.js +267 -0
- package/index.d.ts +44 -0
- package/index.js +2 -1
- 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 +9 -6
- package/src/error.rs +15 -8
- package/src/event_emitter.rs +30 -23
- package/src/lib.rs +17 -4
- package/src/peer.rs +9 -9
- package/src/peer_pool.rs +492 -0
- package/src/peer_pool_napi.rs +221 -0
- package/src/protocol.rs +46 -39
- package/src/tls.rs +4 -4
package/.yarnrc.yml
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
nodeLinker: node-modules
|
|
1
|
+
nodeLinker: node-modules
|
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ A high-performance Chia blockchain listener for Node.js, built with Rust and NAP
|
|
|
10
10
|
- **Event-Driven Architecture**: TypeScript-friendly event system with full type safety
|
|
11
11
|
- **Transaction Analysis**: Parse CLVM puzzles and solutions from coin spends
|
|
12
12
|
- **Historical Block Access**: Retrieve blocks by height or ranges
|
|
13
|
+
- **Connection Pool**: ChiaPeerPool provides automatic load balancing and rate limiting for historical queries
|
|
13
14
|
- **Cross-platform Support**: Works on Windows, macOS, and Linux (x64 and ARM64)
|
|
14
15
|
- **TypeScript Support**: Complete TypeScript definitions with IntelliSense
|
|
15
16
|
|
|
@@ -33,11 +34,11 @@ const listener = new ChiaBlockListener()
|
|
|
33
34
|
// Listen for block events
|
|
34
35
|
listener.on('blockReceived', (block) => {
|
|
35
36
|
console.log(`New block received: ${block.height}`)
|
|
36
|
-
console.log(`Header hash: ${block.
|
|
37
|
+
console.log(`Header hash: ${block.headerHash}`)
|
|
37
38
|
console.log(`Timestamp: ${new Date(block.timestamp * 1000)}`)
|
|
38
|
-
console.log(`Coin additions: ${block.
|
|
39
|
-
console.log(`Coin removals: ${block.
|
|
40
|
-
console.log(`Coin spends: ${block.
|
|
39
|
+
console.log(`Coin additions: ${block.coinAdditions.length}`)
|
|
40
|
+
console.log(`Coin removals: ${block.coinRemovals.length}`)
|
|
41
|
+
console.log(`Coin spends: ${block.coinSpends.length}`)
|
|
41
42
|
})
|
|
42
43
|
|
|
43
44
|
// Listen for peer connection events
|
|
@@ -127,6 +128,80 @@ Retrieves a range of blocks from a connected peer.
|
|
|
127
128
|
|
|
128
129
|
**Returns:** An array of `BlockReceivedEvent` objects
|
|
129
130
|
|
|
131
|
+
### ChiaPeerPool Class
|
|
132
|
+
|
|
133
|
+
The `ChiaPeerPool` provides a managed pool of peer connections for retrieving historical blocks with automatic load balancing and rate limiting.
|
|
134
|
+
|
|
135
|
+
#### Constructor
|
|
136
|
+
|
|
137
|
+
```javascript
|
|
138
|
+
const pool = new ChiaPeerPool()
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Creates a new peer pool instance with built-in rate limiting (500ms per peer).
|
|
142
|
+
|
|
143
|
+
#### Methods
|
|
144
|
+
|
|
145
|
+
##### `addPeer(host, port, networkId): Promise<string>`
|
|
146
|
+
|
|
147
|
+
Adds a peer to the connection pool.
|
|
148
|
+
|
|
149
|
+
**Parameters:**
|
|
150
|
+
- `host` (string): The hostname or IP address of the Chia node
|
|
151
|
+
- `port` (number): The port number (typically 8444 for mainnet)
|
|
152
|
+
- `networkId` (string): The network identifier ('mainnet', 'testnet', etc.)
|
|
153
|
+
|
|
154
|
+
**Returns:** A Promise that resolves to a unique peer ID string
|
|
155
|
+
|
|
156
|
+
##### `getBlockByHeight(height): Promise<BlockReceivedEvent>`
|
|
157
|
+
|
|
158
|
+
Retrieves a specific block by height using automatic peer selection and load balancing.
|
|
159
|
+
|
|
160
|
+
**Parameters:**
|
|
161
|
+
- `height` (number): The block height to retrieve
|
|
162
|
+
|
|
163
|
+
**Returns:** A Promise that resolves to a `BlockReceivedEvent` object
|
|
164
|
+
|
|
165
|
+
##### `removePeer(peerId): Promise<boolean>`
|
|
166
|
+
|
|
167
|
+
Removes a peer from the pool.
|
|
168
|
+
|
|
169
|
+
**Parameters:**
|
|
170
|
+
- `peerId` (string): The peer ID to remove
|
|
171
|
+
|
|
172
|
+
**Returns:** A Promise that resolves to `true` if the peer was removed, `false` otherwise
|
|
173
|
+
|
|
174
|
+
##### `shutdown(): Promise<void>`
|
|
175
|
+
|
|
176
|
+
Shuts down the pool and disconnects all peers.
|
|
177
|
+
|
|
178
|
+
##### `getConnectedPeers(): Promise<string[]>`
|
|
179
|
+
|
|
180
|
+
Gets the list of currently connected peer IDs.
|
|
181
|
+
|
|
182
|
+
**Returns:** Array of peer ID strings (format: "host:port")
|
|
183
|
+
|
|
184
|
+
##### `getPeakHeight(): Promise<number | null>`
|
|
185
|
+
|
|
186
|
+
Gets the highest blockchain peak height seen across all connected peers.
|
|
187
|
+
|
|
188
|
+
**Returns:** The highest peak height as a number, or null if no peaks have been received yet
|
|
189
|
+
|
|
190
|
+
##### `on(event, callback): void`
|
|
191
|
+
|
|
192
|
+
Registers an event handler for pool events.
|
|
193
|
+
|
|
194
|
+
**Parameters:**
|
|
195
|
+
- `event` (string): The event name ('peerConnected' or 'peerDisconnected')
|
|
196
|
+
- `callback` (function): The event handler function
|
|
197
|
+
|
|
198
|
+
##### `off(event, callback): void`
|
|
199
|
+
|
|
200
|
+
Removes an event handler.
|
|
201
|
+
|
|
202
|
+
**Parameters:**
|
|
203
|
+
- `event` (string): The event name to stop listening for
|
|
204
|
+
|
|
130
205
|
### Events
|
|
131
206
|
|
|
132
207
|
The `ChiaBlockListener` emits the following events:
|
|
@@ -149,6 +224,28 @@ Fired when a peer connection is lost.
|
|
|
149
224
|
|
|
150
225
|
**Callback:** `(event: PeerDisconnectedEvent) => void`
|
|
151
226
|
|
|
227
|
+
### ChiaPeerPool Events
|
|
228
|
+
|
|
229
|
+
The `ChiaPeerPool` emits the following events:
|
|
230
|
+
|
|
231
|
+
#### `peerConnected`
|
|
232
|
+
|
|
233
|
+
Fired when a peer is successfully added to the pool.
|
|
234
|
+
|
|
235
|
+
**Callback:** `(event: PeerConnectedEvent) => void`
|
|
236
|
+
|
|
237
|
+
#### `peerDisconnected`
|
|
238
|
+
|
|
239
|
+
Fired when a peer is removed from the pool or disconnects.
|
|
240
|
+
|
|
241
|
+
**Callback:** `(event: PeerDisconnectedEvent) => void`
|
|
242
|
+
|
|
243
|
+
#### `newPeakHeight`
|
|
244
|
+
|
|
245
|
+
Fired when a new highest blockchain peak is discovered.
|
|
246
|
+
|
|
247
|
+
**Callback:** `(event: NewPeakHeightEvent) => void`
|
|
248
|
+
|
|
152
249
|
### Event Data Types
|
|
153
250
|
|
|
154
251
|
#### `BlockReceivedEvent`
|
|
@@ -190,6 +287,16 @@ interface PeerDisconnectedEvent {
|
|
|
190
287
|
}
|
|
191
288
|
```
|
|
192
289
|
|
|
290
|
+
#### `NewPeakHeightEvent`
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
interface NewPeakHeightEvent {
|
|
294
|
+
oldPeak: number | null // Previous highest peak (null if first peak)
|
|
295
|
+
newPeak: number // New highest peak height
|
|
296
|
+
peerId: string // Peer that discovered this peak
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
193
300
|
#### `CoinRecord`
|
|
194
301
|
|
|
195
302
|
```typescript
|
|
@@ -211,14 +318,181 @@ interface CoinSpend {
|
|
|
211
318
|
}
|
|
212
319
|
```
|
|
213
320
|
|
|
321
|
+
## ChiaPeerPool Usage
|
|
322
|
+
|
|
323
|
+
The `ChiaPeerPool` is designed for efficiently retrieving historical blocks with automatic load balancing across multiple peers.
|
|
324
|
+
|
|
325
|
+
### Basic Usage
|
|
326
|
+
|
|
327
|
+
```javascript
|
|
328
|
+
const { ChiaPeerPool, initTracing } = require('@dignetwork/chia-block-listener')
|
|
329
|
+
|
|
330
|
+
async function main() {
|
|
331
|
+
// Initialize tracing
|
|
332
|
+
initTracing()
|
|
333
|
+
|
|
334
|
+
// Create a peer pool
|
|
335
|
+
const pool = new ChiaPeerPool()
|
|
336
|
+
|
|
337
|
+
// Listen for pool events
|
|
338
|
+
pool.on('peerConnected', (event) => {
|
|
339
|
+
console.log(`Peer connected to pool: ${event.peerId}`)
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
pool.on('peerDisconnected', (event) => {
|
|
343
|
+
console.log(`Peer disconnected from pool: ${event.peerId}`)
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
pool.on('newPeakHeight', (event) => {
|
|
347
|
+
console.log(`New blockchain peak detected!`)
|
|
348
|
+
console.log(` Previous: ${event.oldPeak || 'None'}`)
|
|
349
|
+
console.log(` New: ${event.newPeak}`)
|
|
350
|
+
console.log(` Discovered by: ${event.peerId}`)
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
// Add multiple peers
|
|
354
|
+
await pool.addPeer('node1.chia.net', 8444, 'mainnet')
|
|
355
|
+
await pool.addPeer('node2.chia.net', 8444, 'mainnet')
|
|
356
|
+
await pool.addPeer('node3.chia.net', 8444, 'mainnet')
|
|
357
|
+
|
|
358
|
+
// Fetch blocks with automatic load balancing
|
|
359
|
+
const block1 = await pool.getBlockByHeight(5000000)
|
|
360
|
+
const block2 = await pool.getBlockByHeight(5000001)
|
|
361
|
+
const block3 = await pool.getBlockByHeight(5000002)
|
|
362
|
+
|
|
363
|
+
console.log(`Block ${block1.height}: ${block1.coinSpends.length} spends`)
|
|
364
|
+
console.log(`Block ${block2.height}: ${block2.coinSpends.length} spends`)
|
|
365
|
+
console.log(`Block ${block3.height}: ${block3.coinSpends.length} spends`)
|
|
366
|
+
|
|
367
|
+
// Shutdown the pool
|
|
368
|
+
await pool.shutdown()
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
main().catch(console.error)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Advanced Pool Features
|
|
375
|
+
|
|
376
|
+
#### Rate Limiting
|
|
377
|
+
|
|
378
|
+
The pool automatically enforces a 500ms rate limit per peer to prevent overwhelming any single node:
|
|
379
|
+
|
|
380
|
+
```javascript
|
|
381
|
+
// Rapid requests are automatically queued and distributed
|
|
382
|
+
const promises = []
|
|
383
|
+
for (let i = 5000000; i < 5000100; i++) {
|
|
384
|
+
promises.push(pool.getBlockByHeight(i))
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// All requests will be processed efficiently across all peers
|
|
388
|
+
const blocks = await Promise.all(promises)
|
|
389
|
+
console.log(`Retrieved ${blocks.length} blocks`)
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
#### Dynamic Peer Management
|
|
393
|
+
|
|
394
|
+
```javascript
|
|
395
|
+
// Monitor pool health
|
|
396
|
+
const peers = await pool.getConnectedPeers()
|
|
397
|
+
console.log(`Active peers in pool: ${peers.length}`)
|
|
398
|
+
|
|
399
|
+
// Remove underperforming peers
|
|
400
|
+
if (slowPeer) {
|
|
401
|
+
await pool.removePeer(slowPeer)
|
|
402
|
+
console.log('Removed slow peer from pool')
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Add new peers dynamically
|
|
406
|
+
if (peers.length < 3) {
|
|
407
|
+
await pool.addPeer('backup-node.chia.net', 8444, 'mainnet')
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
#### Error Handling
|
|
412
|
+
|
|
413
|
+
```javascript
|
|
414
|
+
try {
|
|
415
|
+
const block = await pool.getBlockByHeight(5000000)
|
|
416
|
+
console.log(`Retrieved block ${block.height}`)
|
|
417
|
+
} catch (error) {
|
|
418
|
+
console.error('Failed to retrieve block:', error)
|
|
419
|
+
|
|
420
|
+
// The pool will automatically try other peers
|
|
421
|
+
// You can also add more peers if needed
|
|
422
|
+
const peers = await pool.getConnectedPeers()
|
|
423
|
+
if (peers.length === 0) {
|
|
424
|
+
console.log('No peers available, adding new ones...')
|
|
425
|
+
await pool.addPeer('node1.chia.net', 8444, 'mainnet')
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
#### Peak Height Tracking
|
|
431
|
+
|
|
432
|
+
```javascript
|
|
433
|
+
// Monitor blockchain sync progress
|
|
434
|
+
const pool = new ChiaPeerPool()
|
|
435
|
+
|
|
436
|
+
// Track peak changes
|
|
437
|
+
let currentPeak = null
|
|
438
|
+
pool.on('newPeakHeight', (event) => {
|
|
439
|
+
currentPeak = event.newPeak
|
|
440
|
+
const progress = event.oldPeak
|
|
441
|
+
? `+${event.newPeak - event.oldPeak} blocks`
|
|
442
|
+
: 'Initial peak'
|
|
443
|
+
console.log(`Peak update: ${event.newPeak} (${progress})`)
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
// Add peers
|
|
447
|
+
await pool.addPeer('node1.chia.net', 8444, 'mainnet')
|
|
448
|
+
await pool.addPeer('node2.chia.net', 8444, 'mainnet')
|
|
449
|
+
|
|
450
|
+
// Check current peak
|
|
451
|
+
const peak = await pool.getPeakHeight()
|
|
452
|
+
console.log(`Current highest peak: ${peak || 'None yet'}`)
|
|
453
|
+
|
|
454
|
+
// Fetch some blocks to trigger peak updates
|
|
455
|
+
await pool.getBlockByHeight(5000000)
|
|
456
|
+
await pool.getBlockByHeight(5100000)
|
|
457
|
+
await pool.getBlockByHeight(5200000)
|
|
458
|
+
|
|
459
|
+
// Monitor sync status
|
|
460
|
+
setInterval(async () => {
|
|
461
|
+
const peak = await pool.getPeakHeight()
|
|
462
|
+
if (peak) {
|
|
463
|
+
const estimatedCurrent = 5200000 + Math.floor((Date.now() / 1000 - 1700000000) / 18.75)
|
|
464
|
+
const syncPercentage = (peak / estimatedCurrent * 100).toFixed(2)
|
|
465
|
+
console.log(`Sync status: ${syncPercentage}% (peak: ${peak})`)
|
|
466
|
+
}
|
|
467
|
+
}, 60000) // Check every minute
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### When to Use ChiaPeerPool vs ChiaBlockListener
|
|
471
|
+
|
|
472
|
+
- **Use ChiaPeerPool when:**
|
|
473
|
+
- You need to fetch historical blocks
|
|
474
|
+
- You want automatic load balancing across multiple peers
|
|
475
|
+
- You're making many block requests and need rate limiting
|
|
476
|
+
- You don't need real-time block notifications
|
|
477
|
+
|
|
478
|
+
- **Use ChiaBlockListener when:**
|
|
479
|
+
- You need real-time notifications of new blocks
|
|
480
|
+
- You want to monitor the blockchain as it grows
|
|
481
|
+
- You need to track specific addresses or puzzle hashes in real-time
|
|
482
|
+
- You're building applications that react to blockchain events
|
|
483
|
+
|
|
484
|
+
Both classes can be used together in the same application for different purposes.
|
|
485
|
+
|
|
214
486
|
## TypeScript Usage
|
|
215
487
|
|
|
216
488
|
```typescript
|
|
217
489
|
import {
|
|
218
490
|
ChiaBlockListener,
|
|
491
|
+
ChiaPeerPool,
|
|
219
492
|
BlockReceivedEvent,
|
|
220
493
|
PeerConnectedEvent,
|
|
221
494
|
PeerDisconnectedEvent,
|
|
495
|
+
NewPeakHeightEvent,
|
|
222
496
|
CoinRecord,
|
|
223
497
|
CoinSpend,
|
|
224
498
|
initTracing,
|
|
@@ -279,6 +553,29 @@ async function getHistoricalBlocks() {
|
|
|
279
553
|
// Get event type constants
|
|
280
554
|
const eventTypes = getEventTypes()
|
|
281
555
|
console.log('Available events:', eventTypes)
|
|
556
|
+
|
|
557
|
+
// TypeScript support for ChiaPeerPool
|
|
558
|
+
const pool = new ChiaPeerPool()
|
|
559
|
+
|
|
560
|
+
// Type-safe event handling
|
|
561
|
+
pool.on('peerConnected', (event: PeerConnectedEvent) => {
|
|
562
|
+
console.log(`Pool peer connected: ${event.peerId}`)
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
pool.on('newPeakHeight', (event: NewPeakHeightEvent) => {
|
|
566
|
+
console.log(`New peak: ${event.oldPeak} → ${event.newPeak}`)
|
|
567
|
+
})
|
|
568
|
+
|
|
569
|
+
// Async/await with proper typing
|
|
570
|
+
async function fetchHistoricalData() {
|
|
571
|
+
const block: BlockReceivedEvent = await pool.getBlockByHeight(5000000)
|
|
572
|
+
const peers: string[] = await pool.getConnectedPeers()
|
|
573
|
+
const peak: number | null = await pool.getPeakHeight()
|
|
574
|
+
|
|
575
|
+
console.log(`Block ${block.height} has ${block.coinSpends.length} spends`)
|
|
576
|
+
console.log(`Pool has ${peers.length} active peers`)
|
|
577
|
+
console.log(`Current peak: ${peak || 'No peak yet'}`)
|
|
578
|
+
}
|
|
282
579
|
```
|
|
283
580
|
|
|
284
581
|
## Advanced Usage
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
use crate::{
|
|
2
2
|
error::{GeneratorParserError, Result},
|
|
3
|
-
types::{
|
|
4
|
-
BlockHeightInfo, CoinInfo, CoinSpendInfo, GeneratorAnalysis, GeneratorBlockInfo,
|
|
5
|
-
ParsedBlock, ParsedGenerator,
|
|
6
|
-
},
|
|
3
|
+
types::{BlockHeightInfo, CoinInfo, CoinSpendInfo, GeneratorBlockInfo, ParsedBlock},
|
|
7
4
|
};
|
|
8
5
|
use chia_bls::Signature;
|
|
9
6
|
use chia_consensus::{
|
|
@@ -14,7 +11,7 @@ use chia_consensus::{
|
|
|
14
11
|
run_block_generator::{run_block_generator2, setup_generator_args},
|
|
15
12
|
validation_error::{atom, first, next, rest, ErrorCode},
|
|
16
13
|
};
|
|
17
|
-
use chia_protocol::
|
|
14
|
+
use chia_protocol::FullBlock;
|
|
18
15
|
use chia_traits::streamable::Streamable;
|
|
19
16
|
use clvm_utils::tree_hash;
|
|
20
17
|
use clvmr::{
|
|
@@ -63,7 +60,7 @@ impl BlockParser {
|
|
|
63
60
|
.map(|g| g.len() as u32);
|
|
64
61
|
|
|
65
62
|
// Extract generator info
|
|
66
|
-
let
|
|
63
|
+
let _generator_info = block
|
|
67
64
|
.transactions_generator
|
|
68
65
|
.as_ref()
|
|
69
66
|
.map(|gen| GeneratorBlockInfo {
|
|
@@ -101,7 +98,6 @@ impl BlockParser {
|
|
|
101
98
|
coin_creations,
|
|
102
99
|
has_transactions_generator,
|
|
103
100
|
generator_size,
|
|
104
|
-
generator_info,
|
|
105
101
|
})
|
|
106
102
|
}
|
|
107
103
|
|
|
@@ -306,9 +302,23 @@ impl BlockParser {
|
|
|
306
302
|
) -> Option<CoinSpendInfo> {
|
|
307
303
|
// Extract parent coin info
|
|
308
304
|
let parent_bytes = self.extract_parent_coin_info(allocator, coin_spend)?;
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
305
|
+
info!("🔍 DEBUG: parent_bytes length = {}", parent_bytes.len());
|
|
306
|
+
|
|
307
|
+
if parent_bytes.len() != 32 {
|
|
308
|
+
info!(
|
|
309
|
+
"❌ ERROR: parent_bytes wrong length: {} bytes (expected 32)",
|
|
310
|
+
parent_bytes.len()
|
|
311
|
+
);
|
|
312
|
+
return None;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// parent_bytes is already Vec<u8> with 32 bytes, just hex encode it directly
|
|
316
|
+
let parent_hex = hex::encode(&parent_bytes);
|
|
317
|
+
info!(
|
|
318
|
+
"🔍 DEBUG: parent_coin_info hex = {} (length: {})",
|
|
319
|
+
parent_hex,
|
|
320
|
+
parent_hex.len()
|
|
321
|
+
);
|
|
312
322
|
|
|
313
323
|
// Extract puzzle, amount, and solution
|
|
314
324
|
let rest1 = rest(allocator, coin_spend).ok()?;
|
|
@@ -324,14 +334,31 @@ impl BlockParser {
|
|
|
324
334
|
|
|
325
335
|
// Calculate puzzle hash
|
|
326
336
|
let puzzle_hash_vec = tree_hash(allocator, puzzle);
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
337
|
+
info!(
|
|
338
|
+
"🔍 DEBUG: tree_hash returned {} bytes",
|
|
339
|
+
puzzle_hash_vec.len()
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
if puzzle_hash_vec.len() != 32 {
|
|
343
|
+
info!(
|
|
344
|
+
"❌ ERROR: tree_hash returned wrong length: {} bytes (expected 32)",
|
|
345
|
+
puzzle_hash_vec.len()
|
|
346
|
+
);
|
|
347
|
+
return None;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// tree_hash returns Vec<u8> with 32 bytes, just hex encode it directly
|
|
351
|
+
let puzzle_hash_hex = hex::encode(&puzzle_hash_vec);
|
|
352
|
+
info!(
|
|
353
|
+
"🔍 DEBUG: puzzle_hash hex = {} (length: {})",
|
|
354
|
+
puzzle_hash_hex,
|
|
355
|
+
puzzle_hash_hex.len()
|
|
356
|
+
);
|
|
330
357
|
|
|
331
358
|
// Create coin info
|
|
332
359
|
let coin_info = CoinInfo {
|
|
333
|
-
parent_coin_info:
|
|
334
|
-
puzzle_hash:
|
|
360
|
+
parent_coin_info: parent_hex,
|
|
361
|
+
puzzle_hash: puzzle_hash_hex,
|
|
335
362
|
amount,
|
|
336
363
|
};
|
|
337
364
|
|
|
@@ -342,15 +369,15 @@ impl BlockParser {
|
|
|
342
369
|
// Get created coins from conditions
|
|
343
370
|
let created_coins = self.extract_created_coins(spend_index, spend_bundle_conditions);
|
|
344
371
|
|
|
345
|
-
Some(CoinSpendInfo
|
|
346
|
-
|
|
347
|
-
puzzle_reveal,
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
372
|
+
Some(CoinSpendInfo::new(
|
|
373
|
+
coin_info,
|
|
374
|
+
hex::encode(puzzle_reveal),
|
|
375
|
+
hex::encode(solution_bytes),
|
|
376
|
+
true,
|
|
377
|
+
"From transaction generator".to_string(),
|
|
378
|
+
0,
|
|
352
379
|
created_coins,
|
|
353
|
-
|
|
380
|
+
))
|
|
354
381
|
}
|
|
355
382
|
|
|
356
383
|
/// Extract parent coin info from a coin spend node
|
|
@@ -433,42 +460,62 @@ impl BlockParser {
|
|
|
433
460
|
})
|
|
434
461
|
}
|
|
435
462
|
|
|
463
|
+
/*
|
|
436
464
|
/// Parse generator from hex string
|
|
437
465
|
pub fn parse_generator_from_hex(&self, generator_hex: &str) -> Result<ParsedGenerator> {
|
|
438
466
|
let generator_bytes =
|
|
439
|
-
hex::decode(generator_hex).map_err(|e|
|
|
467
|
+
hex::decode(generator_hex).map_err(|e| ParseError::HexDecodingError(e))?;
|
|
440
468
|
self.parse_generator_from_bytes(&generator_bytes)
|
|
441
469
|
}
|
|
442
470
|
|
|
443
471
|
/// Parse generator from bytes
|
|
444
472
|
pub fn parse_generator_from_bytes(&self, generator_bytes: &[u8]) -> Result<ParsedGenerator> {
|
|
445
|
-
|
|
446
|
-
|
|
473
|
+
// Create a dummy GeneratorBlockInfo for now
|
|
447
474
|
Ok(ParsedGenerator {
|
|
448
|
-
block_info: GeneratorBlockInfo
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
475
|
+
block_info: GeneratorBlockInfo::new(
|
|
476
|
+
[0u8; 32].into(),
|
|
477
|
+
Some(generator_bytes.to_vec()),
|
|
478
|
+
vec![],
|
|
479
|
+
),
|
|
453
480
|
generator_hex: Some(hex::encode(generator_bytes)),
|
|
454
|
-
analysis
|
|
481
|
+
analysis: self.analyze_generator(generator_bytes)?,
|
|
455
482
|
})
|
|
456
483
|
}
|
|
457
484
|
|
|
458
485
|
/// Analyze generator bytecode
|
|
459
486
|
pub fn analyze_generator(&self, generator_bytes: &[u8]) -> Result<GeneratorAnalysis> {
|
|
460
487
|
let size_bytes = generator_bytes.len();
|
|
461
|
-
let is_empty =
|
|
462
|
-
|
|
463
|
-
// Check for CLVM patterns
|
|
464
|
-
let contains_clvm_patterns = generator_bytes
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
488
|
+
let is_empty = generator_bytes.is_empty();
|
|
489
|
+
|
|
490
|
+
// Check for common CLVM patterns
|
|
491
|
+
let contains_clvm_patterns = generator_bytes.windows(2).any(|w| {
|
|
492
|
+
w == [0x01, 0x00] || // pair
|
|
493
|
+
w == [0x02, 0x00] || // cons
|
|
494
|
+
w == [0x03, 0x00] || // first
|
|
495
|
+
w == [0x04, 0x00] // rest
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
// Check for coin patterns (32-byte sequences)
|
|
499
|
+
let contains_coin_patterns = generator_bytes.len() >= 32;
|
|
500
|
+
|
|
501
|
+
// Calculate simple entropy
|
|
502
|
+
let mut byte_counts = [0u64; 256];
|
|
503
|
+
for &byte in generator_bytes {
|
|
504
|
+
byte_counts[byte as usize] += 1;
|
|
505
|
+
}
|
|
470
506
|
|
|
471
|
-
let
|
|
507
|
+
let total = generator_bytes.len() as f64;
|
|
508
|
+
let entropy = if total > 0.0 {
|
|
509
|
+
byte_counts.iter()
|
|
510
|
+
.filter(|&&count| count > 0)
|
|
511
|
+
.map(|&count| {
|
|
512
|
+
let p = count as f64 / total;
|
|
513
|
+
-p * p.log2()
|
|
514
|
+
})
|
|
515
|
+
.sum()
|
|
516
|
+
} else {
|
|
517
|
+
0.0
|
|
518
|
+
};
|
|
472
519
|
|
|
473
520
|
Ok(GeneratorAnalysis {
|
|
474
521
|
size_bytes,
|
|
@@ -478,8 +525,10 @@ impl BlockParser {
|
|
|
478
525
|
entropy,
|
|
479
526
|
})
|
|
480
527
|
}
|
|
528
|
+
*/
|
|
481
529
|
|
|
482
530
|
/// Calculate Shannon entropy of data
|
|
531
|
+
#[allow(dead_code)]
|
|
483
532
|
fn calculate_entropy(&self, data: &[u8]) -> f64 {
|
|
484
533
|
if data.is_empty() {
|
|
485
534
|
return 0.0;
|