UNPKG

@sangaman/xud

Version:
345 lines 16.2 kB
"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