Skip to content

Commit

Permalink
chore(core): improve lightning resilience
Browse files Browse the repository at this point in the history
  • Loading branch information
dolcalmi committed Nov 9, 2023
1 parent 8779294 commit b9028be
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 14 deletions.
93 changes: 79 additions & 14 deletions core/api/src/services/lnd/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,8 @@ import {
settleHodlInvoice,
} from "lightning"
import lnService from "ln-service"

import sumBy from "lodash.sumby"

import { KnownLndErrorDetails } from "./errors"

import {
getActiveLnd,
getActiveOnchainLnd,
Expand All @@ -42,9 +39,12 @@ import {
parseLndErrorDetails,
} from "./config"

import { checkAllLndHealth } from "./health"

import { KnownLndErrorDetails } from "./errors"

import { NETWORK, SECS_PER_5_MINS } from "@/config"

import { toMilliSatsFromString, toSats } from "@/domain/bitcoin"
import {
BadPaymentDataError,
CorruptLndDbError,
Expand Down Expand Up @@ -76,9 +76,10 @@ import {
UnknownRouteNotFoundError,
decodeInvoice,
} from "@/domain/bitcoin/lightning"
import { IncomingOnChainTransaction } from "@/domain/bitcoin/onchain"
import { CacheKeys } from "@/domain/cache"
import { LnFees } from "@/domain/payments"
import { toMilliSatsFromString, toSats } from "@/domain/bitcoin"
import { IncomingOnChainTransaction } from "@/domain/bitcoin/onchain"
import { WalletCurrency, paymentAmountFromNumber } from "@/domain/shared"

import { LocalCacheService } from "@/services/cache"
Expand Down Expand Up @@ -106,6 +107,9 @@ export const LndService = (): ILightningService | LightningServiceError => {
const listActivePubkeys = (): Pubkey[] =>
getLnds({ active: true, type: "offchain" }).map((lndAuth) => lndAuth.pubkey as Pubkey)

const listActiveLnd = (): AuthenticatedLnd[] =>
getLnds({ active: true, type: "offchain" }).map((lndAuth) => lndAuth.lnd)

const listAllPubkeys = (): Pubkey[] =>
getLnds({ type: "offchain" }).map((lndAuth) => lndAuth.pubkey as Pubkey)

Expand Down Expand Up @@ -478,15 +482,18 @@ export const LndService = (): ILightningService | LightningServiceError => {
}
}

const registerInvoice = async ({
const registerLndInvoice = async ({
lnd,
paymentHash,
sats,
description,
descriptionHash,
expiresAt,
}: RegisterInvoiceArgs): Promise<RegisteredInvoice | LightningServiceError> => {
}: RegisterInvoiceArgs & { lnd: AuthenticatedLnd }): Promise<
RegisteredInvoice | LightningServiceError
> => {
const input = {
lnd: defaultLnd,
lnd,
id: paymentHash,
description,
description_hash: descriptionHash,
Expand All @@ -511,6 +518,30 @@ export const LndService = (): ILightningService | LightningServiceError => {
}
}

const registerInvoice = async ({
paymentHash,
sats,
description,
descriptionHash,
expiresAt,
}: RegisterInvoiceArgs): Promise<RegisteredInvoice | LightningServiceError> => {
const lnds = listActiveLnd()
for (const lnd of lnds) {
const result = await registerLndInvoice({
lnd,
paymentHash,
sats,
description,
descriptionHash,
expiresAt,
})
if (isConnectionError(result)) continue
return result
}

return new OffChainServiceUnavailableError("no active lightning node (for offchain)")
}

const lookupInvoice = async ({
pubkey,
paymentHash,
Expand Down Expand Up @@ -782,11 +813,13 @@ export const LndService = (): ILightningService | LightningServiceError => {
}
}

const payInvoiceViaPaymentDetails = async ({
const payInvoiceViaPaymentDetailsWithLnd = async ({
lnd,
decodedInvoice,
btcPaymentAmount,
maxFeeAmount,
}: {
lnd: AuthenticatedLnd
decodedInvoice: LnInvoice
btcPaymentAmount: BtcPaymentAmount
maxFeeAmount: BtcPaymentAmount | undefined
Expand All @@ -808,7 +841,7 @@ export const LndService = (): ILightningService | LightningServiceError => {
}

const paymentDetailsArgs: PayViaPaymentDetailsArgs = {
lnd: defaultLnd,
lnd,
id: decodedInvoice.paymentHash,
destination: decodedInvoice.destination,
mtokens: milliSatsAmount.toString(),
Expand Down Expand Up @@ -856,6 +889,30 @@ export const LndService = (): ILightningService | LightningServiceError => {
}
}

const payInvoiceViaPaymentDetails = async ({
decodedInvoice,
btcPaymentAmount,
maxFeeAmount,
}: {
decodedInvoice: LnInvoice
btcPaymentAmount: BtcPaymentAmount
maxFeeAmount: BtcPaymentAmount | undefined
}): Promise<PayInvoiceResult | LightningServiceError> => {
const lnds = listActiveLnd()
for (const lnd of lnds) {
const result = await payInvoiceViaPaymentDetailsWithLnd({
lnd,
decodedInvoice,
btcPaymentAmount,
maxFeeAmount,
})
if (isConnectionError(result)) continue
return result
}

return new OffChainServiceUnavailableError("no active lightning node (for offchain)")
}

return wrapAsyncFunctionsToRunInSpan({
namespace: "services.lnd.offchain",
fns: {
Expand Down Expand Up @@ -982,16 +1039,17 @@ const lookupPaymentByPubkeyAndHash = async ({
}
}

/* eslint @typescript-eslint/ban-ts-comment: "off" */
// @ts-ignore-next-line no-implicit-any error
const translateLnPaymentLookup = (p): LnPaymentLookup => ({
const isPaymentConfirmed = (p: PaymentResult): p is ConfirmedPaymentResult =>
p.is_confirmed

const translateLnPaymentLookup = (p: PaymentResult): LnPaymentLookup => ({
createdAt: new Date(p.created_at),
status: p.is_confirmed ? PaymentStatus.Settled : PaymentStatus.Pending,
paymentHash: p.id as PaymentHash,
paymentRequest: p.request as EncodedPaymentRequest,
milliSatsAmount: toMilliSatsFromString(p.mtokens),
roundedUpAmount: toSats(p.safe_tokens),
confirmedDetails: p.is_confirmed
confirmedDetails: isPaymentConfirmed(p)
? {
confirmedAt: new Date(p.confirmed_at),
destination: p.destination as Pubkey,
Expand Down Expand Up @@ -1139,8 +1197,10 @@ const handleCommonLightningServiceErrors = (err: Error | unknown) => {
switch (true) {
case match(KnownLndErrorDetails.ConnectionDropped):
case match(KnownLndErrorDetails.NoConnectionEstablished):
checkAllLndHealth()
return new OffChainServiceUnavailableError()
case match(KnownLndErrorDetails.ConnectionRefused):
checkAllLndHealth()
return new OffChainServiceBusyError()
default:
return new UnknownLightningServiceError(msgForUnknown(err as LnError))
Expand All @@ -1153,6 +1213,7 @@ const handleCommonRouteNotFoundErrors = (err: Error | unknown) => {
switch (true) {
case match(KnownLndErrorDetails.ConnectionDropped):
case match(KnownLndErrorDetails.NoConnectionEstablished):
checkAllLndHealth()
return new OffChainServiceUnavailableError()

case match(KnownLndErrorDetails.MissingDependentFeature):
Expand All @@ -1163,6 +1224,10 @@ const handleCommonRouteNotFoundErrors = (err: Error | unknown) => {
}
}

const isConnectionError = (result: unknown | LightningServiceError): boolean =>
result instanceof OffChainServiceUnavailableError ||
result instanceof OffChainServiceBusyError

const msgForUnknown = (err: LnError) =>
JSON.stringify({
parsedLndErrorDetails: parseLndErrorDetails(err),
Expand Down
14 changes: 14 additions & 0 deletions core/api/src/services/lnd/index.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ type GetPaymentsArgs = import("lightning").GetPaymentsArgs
type GetPendingPaymentsArgs = import("lightning").GetPendingPaymentsArgs
type GetPendingPaymentsResult = import("lightning").GetPendingPaymentsResult

type ConfirmedPaymentResult = Extract<
GetPaymentsResult,
{ payments: unknown }
>["payments"][0]
type PendingPaymentResult = Extract<
GetPendingPaymentsResult,
{ payments: unknown }
>["payments"][0]
type FailedPaymentResult = Extract<
GetFailedPaymentsResult,
{ payments: unknown }
>["payments"][0]
type PaymentResult = ConfirmedPaymentResult | PendingPaymentResult | FailedPaymentResult

type PaymentFnFactory =
| import("lightning").AuthenticatedLightningMethod<
GetFailedPaymentsArgs,
Expand Down

0 comments on commit b9028be

Please sign in to comment.