@sangaman/xud
Version:
Exchange Union Daemon
345 lines • 16.2 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const net_1 = __importDefault(require("net"));
const events_1 = require("events");
const errors_1 = __importDefault(require("./errors"));
const Peer_1 = __importDefault(require("./Peer"));
const NodeList_1 = __importDefault(require("./NodeList"));
const PeerList_1 = __importDefault(require("./PeerList"));
const P2PRepository_1 = __importDefault(require("./P2PRepository"));
const packets_1 = require("./packets");
const addressUtils_1 = __importDefault(require("../utils/addressUtils"));
const SwapDeals_1 = __importDefault(require("../orderbook/SwapDeals"));
const utils_1 = require("../utils/utils");
/** A class representing a pool of peers that handles network activity. */
class Pool extends events_1.EventEmitter {
constructor(config, logger, db, lndBtcClient, lndLtcClient) {
super();
this.logger = logger;
this.lndBtcClient = lndBtcClient;
this.lndLtcClient = lndLtcClient;
this.peers = new PeerList_1.default();
this.connected = false;
/**
* Initialize the Pool by connecting to known nodes and listening to incoming peer connections, if configured to do so.
*/
this.init = (handshakeData) => __awaiter(this, void 0, void 0, function* () {
if (this.connected) {
return;
}
if (this.listenPort) {
if (!handshakeData.addresses) {
handshakeData.addresses = [];
}
// Append the external IP if no address was specified by the user
if (handshakeData.addresses.length === 0) {
try {
// TODO: verify that this address is reachable
const externlIp = yield utils_1.getExternalIp();
this.logger.info(`retrieved external IP: ${externlIp}`);
handshakeData.addresses.push({
host: externlIp,
port: this.listenPort,
});
}
catch (error) {
this.logger.error(error.message);
}
}
}
this.handshakeData = handshakeData;
this.handshakeData.addresses = this.addresses;
this.logger.info('Connecting to known / previously connected peers');
yield this.nodes.load();
yield this.connectNodes(this.nodes);
if (this.server && this.listenPort) {
yield this.listen(this.listenPort);
this.bindServer();
}
this.connected = true;
});
this.disconnect = () => __awaiter(this, void 0, void 0, function* () {
if (!this.connected) {
return;
}
// ensure we stop listening for new peers before disconnecting from peers
if (this.server && this.server.listening) {
yield this.unlisten();
}
this.closePeers();
this.connected = false;
});
/**
* Iterate over a collection of nodes and attempt to connect to them.
* If the node is banned, already connected, or has no listening addresses, then do nothing.
* @param nodes a collection of nodes with a `forEach` iterator to attempt to connect to
* @param ignoreKnown whether to ignore nodes we are already aware of, defaults to false
* @returns a promise that will resolve when all outbound connections resolve
*/
this.connectNodes = (nodes, ignoreKnown = false) => {
const connectionPromises = [];
nodes.forEach((node) => {
// check that this node is not ourselves, that it has listening addresses,
// and that either we haven't heard of it, or we're not ignoring known nodes and it's not banned
if (node.nodePubKey !== this.handshakeData.nodePubKey && node.addresses.length > 0 &&
(!this.nodes.has(node.nodePubKey) || (!ignoreKnown && !this.nodes.isBanned(node.nodePubKey)))) {
connectionPromises.push(this.connectNode(node));
}
});
return Promise.all(connectionPromises);
};
/**
* Attempt to create an outbound connection to a node using its known listening addresses.
*/
this.connectNode = ({ addresses, nodePubKey }) => __awaiter(this, void 0, void 0, function* () {
for (let n = 0; n < addresses.length; n += 1) {
try {
yield this.addOutbound(addresses[n], nodePubKey);
break; // once we've successfully established an outbound connection, stop attempting new connections
}
catch (err) {
this.logger.info(err);
}
}
});
/**
* Attempt to add an outbound peer by connecting to a given socket address.
* Throws an error if a connection to a node with the given nodePubKey exists or
* if the connection handshake shows a different nodePubKey than the one provided.
* @param nodePubKey the nodePubKey of the node to connect to
* @returns the connected peer
*/
this.addOutbound = (address, nodePubKey) => __awaiter(this, void 0, void 0, function* () {
if (nodePubKey === this.handshakeData.nodePubKey) {
const err = errors_1.default.ATTEMPTED_CONNECTION_TO_SELF;
this.logger.warn(err.message);
throw err;
}
else if (this.peers.has(nodePubKey)) {
const err = errors_1.default.NODE_ALREADY_CONNECTED(nodePubKey, address.host);
this.logger.warn(err.message);
throw err;
}
const peer = Peer_1.default.fromOutbound(address, this.logger, this);
yield this.tryOpenPeer(peer, nodePubKey);
return peer;
});
this.listPeers = () => {
const peerInfos = Array.from({ length: this.peers.length });
let i = 0;
this.peers.forEach((peer) => {
peerInfos[i] = peer.info;
i += 1;
});
return peerInfos;
};
this.tryOpenPeer = (peer, nodePubKey) => __awaiter(this, void 0, void 0, function* () {
try {
yield this.openPeer(peer, nodePubKey);
}
catch (err) {
this.logger.warn(`error while opening connection to peer ${nodePubKey}: ${err.message}`);
}
});
this.openPeer = (peer, nodePubKey) => __awaiter(this, void 0, void 0, function* () {
this.bindPeer(peer);
yield peer.open(this.handshakeData, nodePubKey);
});
this.closePeer = (nodePubKey) => __awaiter(this, void 0, void 0, function* () {
const peer = this.peers.get(nodePubKey);
if (peer) {
peer.close();
this.logger.info(`Disconnected from ${peer.nodePubKey} @ ${addressUtils_1.default.toString(peer.socketAddress)}`);
}
else {
throw (errors_1.default.NOT_CONNECTED(nodePubKey));
}
});
this.sendToPeer = (pubKey, packet) => {
const peer = this.peers.get(pubKey);
if (!peer) {
return new Error('Peer Not found');
}
peer.sendPacket(packet);
return undefined;
};
this.broadcastOrder = (order) => {
const orderPacket = new packets_1.OrderPacket(order);
this.peers.forEach(peer => peer.sendPacket(orderPacket));
// TODO: send only to peers which accepts the pairId
};
this.broadcastOrderInvalidation = (order) => {
const orderInvalidationPacket = new packets_1.OrderInvalidationPacket(order);
this.peers.forEach(peer => peer.sendPacket(orderInvalidationPacket));
// TODO: send only to peers which accepts the pairId
};
this.addInbound = (socket) => __awaiter(this, void 0, void 0, function* () {
const peer = Peer_1.default.fromInbound(socket, this.logger, this);
yield this.tryOpenPeer(peer);
});
this.handleSocket = (socket) => __awaiter(this, void 0, void 0, function* () {
if (!socket.remoteAddress) { // client disconnected, socket is destroyed
this.logger.debug('Ignoring disconnected peer');
socket.destroy();
return;
}
if (this.nodes.isBanned(socket.remoteAddress)) {
this.logger.debug(`Ignoring banned peer (${socket.remoteAddress})`);
socket.destroy();
return;
}
yield this.addInbound(socket);
});
this.handlePacket = (peer, packet) => __awaiter(this, void 0, void 0, function* () {
switch (packet.type) {
case packets_1.PacketType.ORDER: {
const order = packet.body;
this.emit('packet.order', Object.assign({}, order, { peerPubKey: peer.nodePubKey }));
break;
}
case packets_1.PacketType.ORDER_INVALIDATION: {
this.emit('packet.orderInvalidation', packet.body);
break;
}
case packets_1.PacketType.GET_ORDERS: {
this.emit('packet.getOrders', peer, packet.header.id);
break;
}
case packets_1.PacketType.ORDERS: {
const orders = packet.body;
orders.forEach((order) => {
this.emit('packet.order', Object.assign({}, order, { peerPubKey: peer.nodePubKey }));
});
break;
}
case packets_1.PacketType.GET_NODES: {
peer.sendNodes(this.nodes.toConnectionInfoArray(), packet.header.id);
break;
}
case packets_1.PacketType.NODES: {
const nodes = packet.body;
yield this.connectNodes(nodes);
break;
}
}
});
this.handleOpen = (peer) => __awaiter(this, void 0, void 0, function* () {
if (this.nodes.isBanned(peer.nodePubKey)) {
// TODO: Ban IP address for this session if banned peer attempts repeated connections.
peer.close();
}
else if (this.peers.has(peer.nodePubKey)) {
// TODO: Penalize peers that attempt to create duplicate connections to us
peer.close();
}
else {
this.peers.add(peer);
// request peer's orders and known nodes
peer.sendPacket(new packets_1.GetOrdersPacket());
peer.sendPacket(new packets_1.GetNodesPacket());
if (!this.nodes.has(peer.nodePubKey)) {
yield this.nodes.createNode({
nodePubKey: peer.nodePubKey,
addresses: peer.addresses,
});
}
else {
// the node is known, update its listening addresses
yield this.nodes.updateAddresses(peer.nodePubKey, peer.addresses);
}
}
});
this.bindServer = () => {
this.server.on('error', (err) => {
this.logger.error(err);
});
this.server.on('connection', (socket) => __awaiter(this, void 0, void 0, function* () {
yield this.handleSocket(socket);
}));
};
this.bindPeer = (peer) => {
peer.on('packet', (packet) => __awaiter(this, void 0, void 0, function* () {
yield this.handlePacket(peer, packet);
}));
peer.on('error', (err) => {
this.logger.error(`peer error (${peer.nodePubKey}): ${err.message}`);
});
peer.once('open', () => __awaiter(this, void 0, void 0, function* () {
yield this.handleOpen(peer);
}));
peer.once('close', () => {
if (peer.nodePubKey) {
this.peers.remove(peer.nodePubKey);
}
this.emit('peer.close', peer);
});
peer.once('ban', () => __awaiter(this, void 0, void 0, function* () {
this.logger.debug(`Banning peer (${peer.nodePubKey})`);
if (peer.nodePubKey) {
yield this.nodes.ban(peer.nodePubKey);
}
if (peer.connected) {
peer.close();
}
}));
};
this.closePeers = () => {
this.peers.forEach(peer => peer.close());
};
/**
* Start listening for incoming p2p connections on the given port.
* @return a promise that resolves once the server is listening, or rejects if it fails to listen
*/
this.listen = (port) => {
return new Promise((resolve, reject) => {
const listenErrHandler = (err) => {
reject(err);
};
this.server.listen(port, '0.0.0.0').on('listening', () => {
const { address, port } = this.server.address();
this.logger.info(`p2p server listening on ${address}:${port}`);
this.server.removeListener('error', listenErrHandler);
resolve();
}).on('error', listenErrHandler);
});
};
/**
* Stop listening for incoming p2p connections.
* @return a promise that resolves once the server is no longer listening
*/
this.unlisten = () => {
return new Promise((resolve) => {
this.server.close(() => {
resolve();
});
});
};
this.swapDeals = new SwapDeals_1.default(logger);
if (config.listen) {
this.listenPort = config.port;
this.server = net_1.default.createServer();
this.addresses = [];
config.addresses.forEach((addressString) => {
const address = addressUtils_1.default.fromString(addressString, config.port);
this.addresses.push(address);
});
}
this.nodes = new NodeList_1.default(new P2PRepository_1.default(logger, db));
}
get peerCount() {
return this.peers.length;
}
}
exports.default = Pool;
//# sourceMappingURL=Pool.js.map