Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): record correct pubkey for ln payments #4428

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
6 changes: 3 additions & 3 deletions core/api/src/app/lightning/delete-ln-payments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ export const deleteLnPaymentsBefore = async (
): Promise<true | ApplicationError> => {
const paymentHashesBefore = listAllPaymentsBefore(timestamp)

for await (const paymentHash of paymentHashesBefore) {
if (paymentHash instanceof Error) return paymentHash
await checkAndDeletePaymentForHash(paymentHash)
for await (const paymentHashAndPubkey of paymentHashesBefore) {
if (paymentHashAndPubkey instanceof Error) return paymentHashAndPubkey
await checkAndDeletePaymentForHash(paymentHashAndPubkey)
}

return true
Expand Down
2 changes: 2 additions & 0 deletions core/api/src/app/lightning/update-ln-payments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ const updateLnPaymentsPaginated = async ({
)
if (!persistedPaymentLookup) return { after: updatedAfter, processedLnPaymentsHashes }

persistedPaymentLookup.sentFromPubkey = pubkey

persistedPaymentLookup.createdAt = payment.createdAt
persistedPaymentLookup.status = payment.status
persistedPaymentLookup.milliSatsAmount = payment.milliSatsAmount
Expand Down
53 changes: 36 additions & 17 deletions core/api/src/app/payments/send-lightning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import { AccountValidator } from "@/domain/accounts"
import {
decodeInvoice,
defaultTimeToExpiryInSeconds,
LnAlreadyPaidError,
LnPaymentPendingError,
PaymentSendStatus,
LnPaymentAttemptResult,
LnPaymentAttemptResultType,
} from "@/domain/bitcoin/lightning"
import { ResourceExpiredLockServiceError } from "@/domain/lock"
import { AlreadyPaidError, CouldNotFindLightningPaymentFlowError } from "@/domain/errors"
import { DisplayAmountsConverter } from "@/domain/fiat"
import {
Expand Down Expand Up @@ -74,8 +75,6 @@ import {
validateIsUsdWallet,
} from "@/app/wallets"

import { ResourceExpiredLockServiceError } from "@/domain/lock"

const dealer = DealerPriceService()
const paymentFlowRepo = PaymentFlowStateRepository(defaultTimeToExpiryInSeconds)

Expand Down Expand Up @@ -879,7 +878,7 @@ const lockedPaymentViaLnSteps = async ({
displayCurrency: senderDisplayCurrency,

paymentAmounts: paymentFlow,
pubkey: outgoingNodePubkey || lndService.defaultPubkey(),
pubkey: outgoingNodePubkey,
paymentHash,
feeKnownInAdvance: !!rawRoute,

Expand Down Expand Up @@ -916,7 +915,7 @@ const lockedPaymentViaLnSteps = async ({
const { journalId } = journal

// Execute payment
let payResult: PayInvoiceResult | LightningServiceError
let payResult: LnPaymentAttemptResult
if (rawRoute) {
payResult = await lndService.payInvoiceViaRoutes({
paymentHash,
Expand All @@ -937,28 +936,44 @@ const lockedPaymentViaLnSteps = async ({

payResult =
maxFeeCheck instanceof Error
? maxFeeCheck
? LnPaymentAttemptResult.err(maxFeeCheck)
: await lndService.payInvoiceViaPaymentDetails({
...maxFeeCheckArgs,
decodedInvoice,
})
}

if (!(payResult instanceof LnAlreadyPaidError)) {
if (!(payResult.type === LnPaymentAttemptResultType.AlreadyPaid)) {
await LnPaymentsRepository().persistNew({
paymentHash: decodedInvoice.paymentHash,
paymentRequest: decodedInvoice.paymentRequest,
sentFromPubkey: outgoingNodePubkey || lndService.defaultPubkey(),
sentFromPubkey:
payResult.type === LnPaymentAttemptResultType.Error
? outgoingNodePubkey
: payResult.result.sentFromPubkey,
})

if (!(payResult instanceof Error))
if (payResult.type === LnPaymentAttemptResultType.Ok)
await LedgerFacade.updateMetadataByHash({
hash: paymentHash,
revealedPreImage: payResult.revealedPreImage,
revealedPreImage: payResult.result.revealedPreImage,
})
}

if (payResult instanceof LnPaymentPendingError) {
if (
outgoingNodePubkey === undefined &&
!(payResult.type === LnPaymentAttemptResultType.Error)
) {
const updatedPubkey = await LedgerFacade.updatePubkeyByHash({
paymentHash,
pubkey: payResult.result.sentFromPubkey,
})
if (updatedPubkey instanceof Error) {
recordExceptionInCurrentSpan({ error: updatedPubkey })
}
}

if (payResult.type === LnPaymentAttemptResultType.Pending) {
paymentFlow.paymentSentAndPending = true
const updateResult = await paymentFlowRepo.updateLightningPaymentFlow(paymentFlow)
if (updateResult instanceof Error) {
Expand All @@ -977,7 +992,10 @@ const lockedPaymentViaLnSteps = async ({
}

// Settle and record reversion entries
if (payResult instanceof Error) {
if (
payResult.type === LnPaymentAttemptResultType.Error ||
payResult.type === LnPaymentAttemptResultType.AlreadyPaid
) {
const settled = await LedgerFacade.settlePendingLnSend(paymentHash)
if (settled instanceof Error) return LnSendAttemptResult.err(settled)

Expand All @@ -995,9 +1013,10 @@ const lockedPaymentViaLnSteps = async ({
if (updateJournalTxnsState instanceof Error) {
return LnSendAttemptResult.err(updateJournalTxnsState)
}
return payResult instanceof LnAlreadyPaidError

return payResult.type === LnPaymentAttemptResultType.AlreadyPaid
? LnSendAttemptResult.alreadyPaid(journalId)
: LnSendAttemptResult.errWithJournal({ journalId, error: payResult })
: LnSendAttemptResult.errWithJournal({ journalId, error: payResult.error })
}

// Settle and conditionally record reimbursement entries
Expand All @@ -1010,8 +1029,8 @@ const lockedPaymentViaLnSteps = async ({
senderDisplayAmount: toDisplayBaseAmount(displayAmount),
senderDisplayCurrency,
journalId,
actualFee: payResult.roundedUpFee,
revealedPreImage: payResult.revealedPreImage,
actualFee: payResult.result.roundedUpFee,
revealedPreImage: payResult.result.revealedPreImage,
})
if (reimbursed instanceof Error) return LnSendAttemptResult.err(reimbursed)
}
Expand Down
5 changes: 4 additions & 1 deletion core/api/src/app/payments/update-pending-payments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,10 @@ const updatePendingPayment = wrapAsyncToRunInSpan({

const lndService = LndService()
if (lndService instanceof Error) return lndService
const lnPaymentLookup = await lndService.lookupPayment({ paymentHash })
const lnPaymentLookup = await lndService.lookupPayment({
pubkey,
paymentHash,
})
if (lnPaymentLookup instanceof Error) {
logger.error(
{
Expand Down
2 changes: 0 additions & 2 deletions core/api/src/domain/bitcoin/lightning/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ export class SecretDoesNotMatchAnyExistingHodlInvoiceError extends LightningServ

export class InvoiceNotFoundError extends LightningServiceError {}
export class InvoiceAlreadySettledError extends LightningServiceError {}
export class LnPaymentPendingError extends LightningServiceError {}
export class LnAlreadyPaidError extends LightningServiceError {}
export class MaxFeeTooLargeForRoutelessPaymentError extends LightningServiceError {
level = ErrorLevel.Critical
}
Expand Down
1 change: 1 addition & 0 deletions core/api/src/domain/bitcoin/lightning/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export {
invoiceExpirationForCurrency,
defaultTimeToExpiryInSeconds,
} from "./invoice-expiration"
export * from "./ln-payment-result"
export * from "./errors"

export const PaymentStatus = {
Expand Down
33 changes: 31 additions & 2 deletions core/api/src/domain/bitcoin/lightning/index.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,12 @@ type LnPaymentLookup = {

readonly confirmedDetails: LnPaymentConfirmedDetails | undefined
readonly attempts: LnPaymentAttempt[] | undefined
readonly sentFromPubkey: Pubkey
}

type LnFailedPartialPaymentLookup = {
readonly status: FailedPaymentStatus
readonly sentFromPubkey: Pubkey
}

type LnInvoice = {
Expand Down Expand Up @@ -135,6 +137,10 @@ type PayInvoiceResult = {
sentFromPubkey: Pubkey
}

type PayInvoicePartialResult = {
sentFromPubkey: Pubkey
}

type ListLnPaymentsArgs = {
after: PagingStartToken | PagingContinueToken
pubkey: Pubkey
Expand All @@ -158,6 +164,29 @@ type ListLnInvoicesArgs = {
createdAfter?: Date
}

type LnPaymentAttemptResultTypeObj =
typeof import("./ln-payment-result").LnPaymentAttemptResultType
type LnPaymentAttemptResultType =
LnPaymentAttemptResultTypeObj[keyof LnPaymentAttemptResultTypeObj]

type LnPaymentAttemptResult =
| {
type: LnPaymentAttemptResultTypeObj["Ok"]
result: PayInvoiceResult
}
| {
type: LnPaymentAttemptResultTypeObj["Pending"]
result: PayInvoicePartialResult
}
| {
type: LnPaymentAttemptResultTypeObj["AlreadyPaid"]
result: PayInvoicePartialResult
}
| {
type: LnPaymentAttemptResultTypeObj["Error"]
error: LightningServiceError
}

interface ILightningService {
isLocal(pubkey: Pubkey): boolean

Expand Down Expand Up @@ -276,7 +305,7 @@ interface ILightningService {
paymentHash: PaymentHash
rawRoute: RawRoute | undefined
pubkey: Pubkey | undefined
}): Promise<PayInvoiceResult | LightningServiceError>
}): Promise<LnPaymentAttemptResult>

payInvoiceViaPaymentDetails({
decodedInvoice,
Expand All @@ -286,7 +315,7 @@ interface ILightningService {
decodedInvoice: LnInvoice
btcPaymentAmount: BtcPaymentAmount
maxFeeAmount: BtcPaymentAmount | undefined
}): Promise<PayInvoiceResult | LightningServiceError>
}): Promise<LnPaymentAttemptResult>
}

// from Alex Bosworth invoice library
Expand Down
25 changes: 25 additions & 0 deletions core/api/src/domain/bitcoin/lightning/ln-payment-result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export const LnPaymentAttemptResultType = {
Ok: "success",
Pending: "pending",
AlreadyPaid: "alreadyPaid",
Error: "error",
} as const

export const LnPaymentAttemptResult = {
ok: (result: PayInvoiceResult): LnPaymentAttemptResult => ({
type: LnPaymentAttemptResultType.Ok,
result,
}),
pending: (result: PayInvoicePartialResult): LnPaymentAttemptResult => ({
type: LnPaymentAttemptResultType.Pending,
result,
}),
alreadyPaid: (result: PayInvoicePartialResult): LnPaymentAttemptResult => ({
type: LnPaymentAttemptResultType.AlreadyPaid,
result,
}),
err: (error: LightningServiceError): LnPaymentAttemptResult => ({
type: LnPaymentAttemptResultType.Error,
error,
}),
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
type LnPaymentPartial = {
readonly paymentHash: PaymentHash
readonly paymentRequest: EncodedPaymentRequest | undefined
readonly sentFromPubkey: Pubkey
readonly sentFromPubkey: Pubkey | undefined
}

// Makes all properties non-readonly except the properties passed in as K
Expand All @@ -10,7 +10,7 @@ type Writeable<T, K extends keyof T> = Pick<T, K> & {
}

type PersistedLnPaymentLookup = Writeable<LnPaymentLookup, "paymentHash"> & {
readonly sentFromPubkey: Pubkey
sentFromPubkey: Pubkey
isCompleteRecord: boolean
}

Expand Down
2 changes: 0 additions & 2 deletions core/api/src/graphql/error-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -563,8 +563,6 @@ export const mapError = (error: ApplicationError): CustomGraphQLError => {
case "InvoiceNotFoundError":
case "InvoiceAlreadySettledError":
case "InvoiceNotPaidError":
case "LnPaymentPendingError":
case "LnAlreadyPaidError":
case "PaymentNotFoundError":
case "OperationInterruptedError":
case "InconsistentDataError":
Expand Down
20 changes: 20 additions & 0 deletions core/api/src/services/ledger/facade/offchain-send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { staticAccountIds } from "./static-account-ids"

import { UnknownLedgerError } from "@/domain/ledger"
import { ZERO_CENTS, ZERO_SATS } from "@/domain/shared"
import { NoTransactionToUpdateError } from "@/domain/errors"

export const recordSendOffChain = async ({
description,
Expand Down Expand Up @@ -91,3 +92,22 @@ export const settlePendingLnSend = async (
return new UnknownLedgerError(err)
}
}

export const updatePubkeyByHash = async ({
paymentHash,
pubkey,
}: {
paymentHash: PaymentHash
pubkey: Pubkey
}): Promise<true | LedgerServiceError> => {
try {
const result = await Transaction.updateMany({ hash: paymentHash }, { pubkey })
const success = result.modifiedCount > 0
if (!success) {
return new NoTransactionToUpdateError(paymentHash)
}
return true
} catch (err) {
return new UnknownLedgerError(err)
}
}
2 changes: 1 addition & 1 deletion core/api/src/services/ledger/facade/tx-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export const LnSendLedgerMetadata = ({
memoOfPayer,
}: {
paymentHash: PaymentHash
pubkey: Pubkey
pubkey: Pubkey | undefined
paymentAmounts: AmountsAndFees

feeDisplayCurrency: DisplayCurrencyBaseAmount
Expand Down
2 changes: 1 addition & 1 deletion core/api/src/services/ledger/index.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ type AddLnSendLedgerMetadata = LedgerMetadata &
LedgerSendMetadata &
SendAmountsMetadata & {
hash: PaymentHash
pubkey: Pubkey
pubkey: Pubkey | undefined
feeKnownInAdvance: boolean
}

Expand Down
Loading
Loading