chia-network-scanner
Version:
Scans the Chia network for active full nodes
141 lines (106 loc) • 4.53 kB
text/typescript
import fs from 'fs';
import * as async from 'async';
import { log } from './log';
import { NetworkScannerOptions, parseOptions } from './options';
import { Peer } from './peer';
import { PeerConnection } from './PeerConnection';
class ChiaNetworkScanner {
private readonly queue = async.queue(this.processPeer.bind(this), this.options.concurrency);
private peers = new Map<string, Peer>();
private scanInProgress = false;
public constructor(private readonly options: NetworkScannerOptions) {
this.options = parseOptions(options);
}
/**
* The scan is started from the full node provided in the options.
*/
public async scan(): Promise<Peer[]> {
if (this.scanInProgress) {
throw new Error('Only one scan be be performed at a time');
}
log.info('Starting scan of Chia Network');
// Prevents caller from executing async scan more than once at a time
this.scanInProgress = true;
const { startNodes } = this.options;
// Reset peers from any previous scans
this.peers = new Map<string, Peer>();
// The network scan is started from passed in start nodes
startNodes.forEach(node => {
this.queue.push(new Peer({
hostname: node.hostname,
port: node.port,
timestamp: Math.floor(Date.now() / 1000)
}), (e) => { console.log(`fin ${node.hostname}:${node.port}`, e); });
});
await this.queue.drain();
// Async network scan has finished, another could now be performed
this.scanInProgress = false;
return [
...this.peers.values()
];
}
/**
* Peers are added to the async queue and a graph traversal of the network is performed.
*
* The concurrency parameter passed in the constructor specifies how many of these are executed concurrently via the event loop.
*/
private async processPeer(proposedPeer: Peer): Promise<void> {
const peerLogger = log.child({
...proposedPeer
});
const ipv6 = proposedPeer.hostname.includes(':');
// Only scan ipv4 because ipv6 nodes can appear with both their ipv4 address and their ipv6 address
if (ipv6) {
return;
}
const { connectionTimeout, network, peer: peerOptions, certPath, keyPath } = this.options;
const peerHash = proposedPeer.hash();
if (!this.peers.has(peerHash)) {
peerLogger.debug('First time seeing peer');
this.peers.set(peerHash, proposedPeer);
}
// Ensures we get the visited value of peer from previous traversal
const peer = this.peers.get(peerHash) as Peer;
// We only visit each peer once
if (peer.visited) {
peerLogger.debug('Skipping already visited peer');
if (proposedPeer.timestamp > peer.timestamp) {
peerLogger.debug(`Updating visited peer to more recent timestamp`);
proposedPeer.visit();
this.peers.set(peerHash, proposedPeer);
}
return;
}
peerLogger.info('Visiting peer');
// Set to visited immediately to prevent async processing of the same peer
peer.visit();
// Opens a websocket connection with the peer we are processing
const peerConnection = new PeerConnection({
networkId: network.networkId,
protocolVersion: network.protocolVersion,
softwareVersion: network.softwareVersion,
nodeType: peerOptions.nodeType,
hostname: peer.hostname,
port: peer.port,
connectionTimeout,
cert: fs.readFileSync(certPath),
key: fs.readFileSync(keyPath)
});
peerLogger.info('Establishing websocket connection');
try {
// Establish websocket connection
await peerConnection.connect();
peerLogger.info('Websocket connection established');
// Performs application level handshake with peer
await peerConnection.handshake();
const peers = await peerConnection.getPeers();
// Enqueue peers of peer for async processing
this.queue.push(peers);
// Close websocket connection
await peerConnection.close();
} catch (err) {
log.error(err);
}
}
}
export { ChiaNetworkScanner };