UNPKG

xud

Version:
176 lines 10.1 kB
"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 }); const assert_1 = __importDefault(require("assert")); const events_1 = require("events"); const enums_1 = require("../constants/enums"); const SwapClient_1 = require("./SwapClient"); /** * A class that's responsible for recovering swap deals that were interrupted due to a system or xud crash, * ensuring that we do not lose funds on a partially completed swap. */ let SwapRecovery = /** @class */ (() => { class SwapRecovery extends events_1.EventEmitter { /** The time in milliseconds between checks on the status of pending swaps. */ constructor(swapClientManager, logger) { super(); this.swapClientManager = swapClientManager; this.logger = logger; /** A map of payment hashes to swaps where we have a pending outgoing payment but don't know the preimage. */ this.pendingSwaps = new Map(); this.beginTimer = () => { if (!this.pendingSwapsTimer) { this.pendingSwapsTimer = setInterval(this.checkPendingSwaps, SwapRecovery.PENDING_SWAP_RECHECK_INTERVAL); } }; this.stopTimer = () => { if (this.pendingSwapsTimer) { clearInterval(this.pendingSwapsTimer); this.pendingSwapsTimer = undefined; } }; this.getPendingSwapHashes = () => { return Array.from(this.pendingSwaps.keys()); }; this.checkPendingSwaps = () => { this.pendingSwaps.forEach(pendingSwap => this.checkPaymentStatus(pendingSwap).catch(this.logger.error)); }; this.failDeal = (deal, receivingSwapClient) => __awaiter(this, void 0, void 0, function* () { if (receivingSwapClient) { try { yield receivingSwapClient.removeInvoice(deal.rHash); } catch (err) { this.logger.warn(`could not remove invoice for ${deal.rHash}: ${err}`); } } if (deal.state !== enums_1.SwapState.Error) { deal.state = enums_1.SwapState.Error; deal.failureReason = enums_1.SwapFailureReason.Crash; } this.logger.info(`failed swap ${deal.rHash}`); this.pendingSwaps.delete(deal.rHash); yield deal.save(); }); /** * Claims the incoming payment for a deal where the outgoing payment has * already gone through and where we already know the preimage. */ this.claimPayment = (deal) => __awaiter(this, void 0, void 0, function* () { assert_1.default(deal.rPreimage); // the maker payment is always the one that is claimed second, after the payment to taker const makerSwapClient = this.swapClientManager.get(deal.makerCurrency); if (!makerSwapClient || !makerSwapClient.isConnected()) { this.logger.warn(`could not claim payment for ${deal.rHash} because ${deal.makerCurrency} swap client is offline`); return; } try { yield makerSwapClient.settleInvoice(deal.rHash, deal.rPreimage, deal.makerCurrency); deal.state = enums_1.SwapState.Recovered; this.logger.info(`recovered ${deal.makerCurrency} swap payment of ${deal.makerAmount} using preimage ${deal.rPreimage}`); this.pendingSwaps.delete(deal.rHash); yield deal.save(); this.emit('recovered', deal); } catch (err) { this.logger.error(`could not settle ${deal.makerCurrency} invoice for payment ${deal.rHash}`, err); this.logger.alert(`incoming ${deal.makerCurrency} payment with hash ${deal.rHash} could not be settled with preimage ${deal.rPreimage}, **funds may be lost and this must be investigated manually**`); // TODO: determine when we are permanently unable (due to htlc expiration or unknown invoice hash) to // settle an invoice and fail the deal, rather than endlessly retrying settle invoice calls } }); /** * Checks the status of the outgoing payment for a swap where we have begun * sending a payment and handles the resolution of the swap once a final * status for the payment is determined. */ this.checkPaymentStatus = (deal) => __awaiter(this, void 0, void 0, function* () { // ensure that we are tracking this pending swap this.pendingSwaps.set(deal.rHash, deal); if (deal.rPreimage) { // if we already have the preimage for this deal, we can attempt to claim our payment right away yield this.claimPayment(deal); return; } this.logger.debug(`checking outgoing payment status for swap ${deal.rHash}`); const takerSwapClient = this.swapClientManager.get(deal.takerCurrency); if (!takerSwapClient || !takerSwapClient.isConnected()) { this.logger.warn(`could not recover deal ${deal.rHash} because ${deal.takerCurrency} swap client is offline`); return; } if (deal.role === enums_1.SwapRole.Maker) { // we should check to see if our payment went through // if it did, we can claim payment with the preimage for our side of the swap const makerSwapClient = this.swapClientManager.get(deal.makerCurrency); if (!makerSwapClient || !makerSwapClient.isConnected()) { this.logger.warn(`could not recover deal ${deal.rHash} because ${deal.makerCurrency} swap client is offline`); return; } const paymentStatus = yield takerSwapClient.lookupPayment(deal.rHash, deal.takerCurrency); if (paymentStatus.state === SwapClient_1.PaymentState.Succeeded) { deal.rPreimage = paymentStatus.preimage; yield deal.save(); // persist the preimage to the database once we retrieve it yield this.claimPayment(deal); } else if (paymentStatus.state === SwapClient_1.PaymentState.Failed) { // the payment failed, so cancel the open invoice if we have one yield this.failDeal(deal, makerSwapClient); } else { // the payment is pending, we will need to follow up on this this.logger.debug(`swap for ${deal.rHash} still has pending payments and will be monitored`); } } else if (deal.role === enums_1.SwapRole.Taker) { // we are not at risk of losing funds, but we should cancel any open invoices yield this.failDeal(deal, takerSwapClient); } }); /** * Attempts to recover a swap deal from whichever state it was left in * including canceling or settling any related invoices & payments. */ this.recoverDeal = (deal) => __awaiter(this, void 0, void 0, function* () { if (this.pendingSwaps.has(deal.rHash)) { return; // we are already monitoring & attempting to recover this deal } this.logger.info(`recovering swap deal ${deal.rHash}`); switch (deal.phase) { case enums_1.SwapPhase.SwapAccepted: // we accepted the deal but stopped before sending payment // cancel the open invoice if we have one const makerSwapClient = this.swapClientManager.get(deal.makerCurrency); yield this.failDeal(deal, makerSwapClient); break; case enums_1.SwapPhase.SendingPayment: case enums_1.SwapPhase.PreimageResolved: // we started sending payment but didn't claim our payment yield this.checkPaymentStatus(deal); break; case enums_1.SwapPhase.PaymentReceived: // we've claimed our payment deal.state = enums_1.SwapState.Recovered; yield deal.save(); break; default: break; } }); } } SwapRecovery.PENDING_SWAP_RECHECK_INTERVAL = 300000; return SwapRecovery; })(); exports.default = SwapRecovery; //# sourceMappingURL=SwapRecovery.js.map