xud
Version:
Exchange Union Daemon
253 lines • 12.3 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
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) : adopt(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 });
exports.reputationEventWeight = void 0;
const events_1 = require("events");
const enums_1 = require("../constants/enums");
const addressUtils_1 = __importDefault(require("../utils/addressUtils"));
const aliasUtils_1 = require("../utils/aliasUtils");
const errors_1 = __importDefault(require("./errors"));
exports.reputationEventWeight = {
[enums_1.ReputationEvent.ManualBan]: Number.NEGATIVE_INFINITY,
[enums_1.ReputationEvent.ManualUnban]: 0,
[enums_1.ReputationEvent.PacketTimeout]: -1,
[enums_1.ReputationEvent.SwapFailure]: -10,
[enums_1.ReputationEvent.SwapSuccess]: 1,
[enums_1.ReputationEvent.WireProtocolErr]: -5,
[enums_1.ReputationEvent.InvalidAuth]: -20,
[enums_1.ReputationEvent.SwapTimeout]: -15,
[enums_1.ReputationEvent.SwapMisbehavior]: -20,
[enums_1.ReputationEvent.SwapAbuse]: Number.NEGATIVE_INFINITY,
[enums_1.ReputationEvent.SwapDelay]: -25,
};
/** Represents a list of nodes for managing network peers activity */
let NodeList = /** @class */ (() => {
class NodeList extends events_1.EventEmitter {
constructor(repository) {
super();
this.repository = repository;
/** A map of node pub keys to node instances. */
this.nodes = new Map();
/** A map of node ids to node instances. */
this.nodeIdMap = new Map();
/** A map of node pub keys to aliases. */
this.pubKeyToAliasMap = new Map();
/** A map of aliases to node pub keys. */
this.aliasToPubKeyMap = new Map();
/**
* Check if a node with a given nodePubKey exists.
*/
this.has = (nodePubKey) => {
return this.nodes.has(nodePubKey);
};
this.forEach = (callback) => {
this.nodes.forEach(callback);
};
/**
* Get the internal node id for a given nodePubKey.
*/
this.getNodeById = (nodeId) => {
return this.nodeIdMap.get(nodeId);
};
/**
* Get the alias for a given nodePubKey.
*/
this.getAlias = (nodePubKey) => {
return this.pubKeyToAliasMap.get(nodePubKey);
};
this.getId = (nodePubKey) => {
var _a;
return (_a = this.nodes.get(nodePubKey)) === null || _a === void 0 ? void 0 : _a.id;
};
this.get = (nodePubKey) => {
return this.nodes.get(nodePubKey);
};
this.getPubKeyForAlias = (alias) => {
const nodePubKey = this.aliasToPubKeyMap.get(alias);
if (!nodePubKey) {
throw errors_1.default.ALIAS_NOT_FOUND(alias);
}
if (nodePubKey === 'CONFLICT') {
throw errors_1.default.ALIAS_CONFLICT(alias);
}
return nodePubKey;
};
/**
* Ban a node by nodePubKey.
* @returns true if the node was banned, false otherwise
*/
this.ban = (nodePubKey) => __awaiter(this, void 0, void 0, function* () {
return yield this.addReputationEvent(nodePubKey, enums_1.ReputationEvent.ManualBan);
});
/**
* Remove ban from node by nodePubKey.
* @returns true if ban was removed, false otherwise
*/
this.unBan = (nodePubKey) => __awaiter(this, void 0, void 0, function* () {
return yield this.addReputationEvent(nodePubKey, enums_1.ReputationEvent.ManualUnban);
});
this.isBanned = (nodePubKey) => {
var _a;
return ((_a = this.nodes.get(nodePubKey)) === null || _a === void 0 ? void 0 : _a.banned) || false;
};
/**
* Load this NodeList from the database.
*/
this.load = () => __awaiter(this, void 0, void 0, function* () {
const nodes = yield this.repository.getNodes();
const reputationLoadPromises = [];
nodes.forEach((node) => {
this.addNode(node);
const reputationLoadPromise = this.repository.getReputationEvents(node).then((events) => {
node.reputationScore = 0;
events.forEach(({ event }) => {
NodeList.updateReputationScore(node, event);
});
});
reputationLoadPromises.push(reputationLoadPromise);
});
yield Promise.all(reputationLoadPromises);
});
/**
* Persists a node to the database and adds it to the node list.
*/
this.createNode = (nodeCreationAttributes) => __awaiter(this, void 0, void 0, function* () {
const node = yield this.repository.addNodeIfNotExists(nodeCreationAttributes);
if (node) {
node.reputationScore = 0;
this.addNode(node);
}
});
/**
* Update a node's addresses.
* @return true if the specified node exists and was updated, false otherwise
*/
this.updateAddresses = (nodePubKey, addresses = [], lastAddress) => __awaiter(this, void 0, void 0, function* () {
const node = this.nodes.get(nodePubKey);
if (node) {
// avoid overriding the `lastConnected` field for existing matching addresses unless a new value was set
node.addresses = addresses.map((newAddress) => {
const oldAddress = node.addresses.find(address => addressUtils_1.default.areEqual(address, newAddress));
if (oldAddress && !newAddress.lastConnected) {
return oldAddress;
}
else {
return newAddress;
}
});
if (lastAddress) {
node.lastAddress = lastAddress;
}
yield node.save();
return true;
}
return false;
});
/**
* Retrieves up to 10 of the most recent negative reputation events for a node
* from the repository.
* @param node the node for which to retrieve events
* @param newEvent a reputation event that hasn't been added to the repository yet
*/
this.getNegativeReputationEvents = (node, newEvent) => __awaiter(this, void 0, void 0, function* () {
const reputationEvents = yield this.repository.getReputationEvents(node);
const negativeReputationEvents = reputationEvents
.filter(e => exports.reputationEventWeight[e.event] < 0).slice(0, 9)
.map(e => e.event);
if (newEvent) {
negativeReputationEvents.unshift(newEvent);
}
return negativeReputationEvents;
});
/**
* Add a reputation event to the node's history
* @return true if the specified node exists and the event was added, false otherwise
*/
this.addReputationEvent = (nodePubKey, event) => __awaiter(this, void 0, void 0, function* () {
const node = this.nodes.get(nodePubKey);
if (node) {
const promises = [];
NodeList.updateReputationScore(node, event);
if (node.reputationScore < NodeList.BAN_THRESHOLD && !node.banned) {
promises.push(this.setBanStatus(node, true));
const negativeReputationEvents = yield this.getNegativeReputationEvents(node);
this.emit('node.ban', nodePubKey, negativeReputationEvents);
}
else if (node.reputationScore >= NodeList.BAN_THRESHOLD && node.banned) {
// If the reputationScore is not below the banThreshold but node.banned
// is true that means that the node was unbanned
promises.push(this.setBanStatus(node, false));
}
promises.push(this.repository.addReputationEvent({ event, nodeId: node.id }));
yield Promise.all(promises);
return true;
}
return false;
});
this.removeAddress = (nodePubKey, address) => __awaiter(this, void 0, void 0, function* () {
const node = this.nodes.get(nodePubKey);
if (node) {
const index = node.addresses.findIndex(existingAddress => addressUtils_1.default.areEqual(address, existingAddress));
if (index > -1) {
node.addresses = [...node.addresses.slice(0, index), ...node.addresses.slice(index + 1)];
yield node.save();
return true;
}
// if the lastAddress is removed, then re-assigning lastAddress with the latest connected advertised address
if (node.lastAddress && addressUtils_1.default.areEqual(address, node.lastAddress)) {
node.lastAddress = addressUtils_1.default.sortByLastConnected(node.addresses)[0];
}
}
return false;
});
this.setBanStatus = (node, status) => {
node.banned = status;
return node.save();
};
this.addNode = (node) => {
const { nodePubKey } = node;
const alias = aliasUtils_1.pubKeyToAlias(nodePubKey);
if (this.aliasToPubKeyMap.has(alias)) {
this.aliasToPubKeyMap.set(alias, 'CONFLICT');
}
else {
this.aliasToPubKeyMap.set(alias, nodePubKey);
}
this.nodes.set(nodePubKey, node);
this.nodeIdMap.set(node.id, node);
this.pubKeyToAliasMap.set(nodePubKey, alias);
};
}
get count() {
return this.nodes.size;
}
}
NodeList.BAN_THRESHOLD = -50;
NodeList.MAX_REPUTATION_SCORE = 100;
NodeList.updateReputationScore = (node, event) => {
if (event === enums_1.ReputationEvent.ManualUnban) {
node.reputationScore = exports.reputationEventWeight[event];
}
else {
// events that carry a negative infinity weight will set the
// reputationScore to negative infinity and result in a ban
node.reputationScore += exports.reputationEventWeight[event];
// reputation score for a node cannot exceed the maximum
node.reputationScore = Math.min(node.reputationScore, NodeList.MAX_REPUTATION_SCORE);
}
};
return NodeList;
})();
exports.default = NodeList;
//# sourceMappingURL=NodeList.js.map