chia-network-scanner
Version:
Scans the Chia network for active full nodes
181 lines (148 loc) • 5.92 kB
text/typescript
import { log } from './log';
import { MessageChannel } from './MessageChannel';
import { Peer } from './peer';
import { decodeMessage, encodeMessage, ProtocolMessageTypes } from './encoder';
interface PeerConnectionOptions {
networkId: string;
protocolVersion: string;
softwareVersion: string;
nodeType: number;
hostname: string;
port: number;
connectionTimeout: number;
cert: Buffer;
key: Buffer;
}
class PeerConnection {
private readonly messageChannel: MessageChannel;
private readonly messageHandlers: Map<number, Function> = new Map();
public constructor({
networkId,
protocolVersion,
softwareVersion,
nodeType,
hostname,
port,
connectionTimeout,
cert,
key
}: PeerConnectionOptions) {
this.messageChannel = new MessageChannel({
networkId,
protocolVersion,
softwareVersion,
nodeType,
hostname,
port,
connectionTimeout,
onMessage: data => this.onMessage(data),
cert,
key
});
}
public async connect(): Promise<void> {
return new Promise(async(resolve, reject) => {
const timeout = setTimeout(() => reject(new Error('Failed to connect to peer within 2 seconds')), 2000);
try {
await this.messageChannel.connect();
// Connected within timeout
clearTimeout(timeout);
resolve();
} catch (err) {
clearTimeout(timeout);
reject(err);
}
});
}
/**
* Chia application level handshake required before using the peer protocol.
*/
public async handshake(): Promise<this> {
const { hostname, port, connectionTimeout, networkId, protocolVersion, softwareVersion, nodeType } = this.messageChannel;
return new Promise(async(resolve, reject) => {
const timeout = setTimeout(() => reject(new Error(`${hostname}:${port} did not respond to handshake within ${(connectionTimeout / 1000).toFixed(2)} seconds. Bailing.`)), connectionTimeout);
// Handle handshake response messages
const handshakeResponse = [
this.expectMessage(ProtocolMessageTypes.handshake),
this.expectMessage(ProtocolMessageTypes.handshake_ack)
];
// Initiate handshake
this.sendMessage(ProtocolMessageTypes.handshake, {
network_id: networkId,
protocol_version: protocolVersion,
software_version: softwareVersion,
server_port: port,
node_type: nodeType
});
try {
// Wait for handshake response
await Promise.race(handshakeResponse);
} catch (err) {
return reject(err);
}
// Handshake completed within timeout
clearTimeout(timeout);
// We can now use the peer protocol
resolve(this);
});
}
public sendMessage(messageType: number, data: any) {
const message = encodeMessage(messageType, data);
log.info(`Sending ${messageType} message`);
this.messageChannel.sendMessage(message);
}
/**
* Get the peers of this peer.
*/
public getPeers(): Promise<Peer[]> {
const { hostname, port, connectionTimeout } = this.messageChannel;
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error(`${hostname}:${port} did not respond after ${(connectionTimeout / 1000).toFixed(2)} seconds. Bailing.`)), connectionTimeout);
this.addMessageHandler(ProtocolMessageTypes.respond_peers, (respondPeers: any) => {
clearTimeout(timeout);
resolve(respondPeers.peer_list);
});
this.sendMessage(ProtocolMessageTypes.request_peers, {});
});
}
public close(): void {
return this.messageChannel.close();
}
private addMessageHandler(messageType: number, handler: Function) {
log.debug(`Adding message handler for ${messageType} messages`);
this.messageHandlers.set(messageType, handler);
}
private onMessage(data: Buffer): void {
try {
const messageType = data[0];
log.info(`Received message of type ${messageType}`);
const message = decodeMessage(data);
log.info(`Decoded message to ${JSON.stringify(message)}`)
const handler = this.messageHandlers.get(messageType);
if (handler) {
return handler(message);
}
log.warn(`No handler for ${messageType} message. Discarding it.`);
} catch (err) {
// Anybody could send any old rubbish down the wire and we don't want that to crash our process
log.error(err, 'Error handling inbound message');
log.warn(`Hex of message that could not be decoded: ${data.toString('hex')}`);
}
}
/**
* Expects a message of a messageType to be received within a timeout.
*
* @param messageType expected
*/
private expectMessage(messageType: number): Promise<void> {
const { hostname, port, connectionTimeout } = this.messageChannel;
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error(`${hostname}:${port} did not receive ${messageType} message within ${(connectionTimeout / 1000).toFixed(2)} seconds. Bailing.`)), connectionTimeout);
this.addMessageHandler(messageType, () => {
clearTimeout(timeout);
resolve();
});
});
}
}
export { PeerConnection };