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

Revert "feat: hodl invoice (on WalletInvoiceBuilder) (#1395)" #1483

Merged
merged 1 commit into from
Jul 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions src/app/wallets/get-balance-for-wallet.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import { LedgerService } from "@services/ledger"
import { updatePendingPaymentsByWalletId } from "@app/payments"

import { updatePendingInvoicesByWalletId } from "./update-pending-invoices"

export const getBalanceForWallet = async ({
walletId,
logger,
}: {
walletId: WalletId
logger: Logger
}): Promise<CurrencyBaseAmount | ApplicationError> => {
const updatePaymentsResult = await updatePendingPaymentsByWalletId({
walletId,
logger,
})
const [, updatePaymentsResult] = await Promise.all([
updatePendingInvoicesByWalletId({
walletId,
logger,
}),
updatePendingPaymentsByWalletId({
walletId,
logger,
}),
])
if (updatePaymentsResult instanceof Error) return updatePaymentsResult

return LedgerService().getWalletBalance(walletId)
Expand Down
146 changes: 53 additions & 93 deletions src/app/wallets/update-pending-invoices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,36 +12,57 @@ import {
WalletsRepository,
} from "@services/mongoose"
import { NotificationsService } from "@services/notifications"
import { elapsedSinceTimestamp, runInParallel } from "@utils"
import { runInParallel } from "@utils"
import { WalletInvoiceReceiver } from "@domain/wallet-invoices/wallet-invoice-receiver"
import * as LedgerFacade from "@services/ledger/facade"
import { usdFromBtcMidPriceFn } from "@app/shared"

export const declineHeldInvoices = async (logger: Logger): Promise<void> => {
export const updatePendingInvoices = async (logger: Logger): Promise<void> => {
const invoicesRepo = WalletInvoicesRepository()

const pendingInvoices = invoicesRepo.yieldPending()
const walletIdsWithPendingInvoices = invoicesRepo.listWalletIdsWithPendingInvoices()

if (pendingInvoices instanceof Error) {
if (walletIdsWithPendingInvoices instanceof Error) {
logger.error(
{ error: pendingInvoices },
{ error: walletIdsWithPendingInvoices },
"finish updating pending invoices with error",
)
return
}

await runInParallel({
iterator: pendingInvoices,
iterator: walletIdsWithPendingInvoices,
logger,
processor: async (walletInvoice: WalletInvoice, index: number) => {
logger.trace("updating pending invoices %s in worker %d", index)
await declineHeldInvoice({ walletInvoice, logger })
processor: async (walletId: WalletId, index: number) => {
logger.trace(
"updating pending invoices for wallet %s in worker %d",
walletId,
index,
)
await updatePendingInvoicesByWalletId({ walletId, logger })
},
})

logger.info("finish updating pending invoices")
}

export const updatePendingInvoicesByWalletId = async ({
walletId,
logger,
}: {
walletId: WalletId
logger: Logger
}) => {
const invoicesRepo = WalletInvoicesRepository()

const invoices = invoicesRepo.findPendingByWalletId(walletId)
if (invoices instanceof Error) return invoices

for await (const walletInvoice of invoices) {
await updatePendingInvoice({ walletInvoice, logger })
}
}

export const updatePendingInvoiceByPaymentHash = async ({
paymentHash,
logger,
Expand Down Expand Up @@ -73,43 +94,46 @@ const updatePendingInvoice = async ({

const walletInvoicesRepo = WalletInvoicesRepository()

const { pubkey, paymentHash, secret, recipientWalletDescriptor } = walletInvoice

const pendingInvoiceLogger = logger.child({
hash: paymentHash,
walletId: recipientWalletDescriptor.id,
topic: "payment",
protocol: "lightning",
transactionType: "receipt",
onUs: false,
})
const { pubkey, paymentHash, recipientWalletDescriptor } = walletInvoice

const lnInvoiceLookup = await lndService.lookupInvoice({ pubkey, paymentHash })
if (lnInvoiceLookup instanceof InvoiceNotFoundError) {
const isDeleted = await walletInvoicesRepo.deleteByPaymentHash(paymentHash)
if (isDeleted instanceof Error) {
pendingInvoiceLogger.error("impossible to delete WalletInvoice entry")
logger.error(
{ walletInvoice, error: isDeleted },
"impossible to delete WalletInvoice entry",
)
return isDeleted
}
return false
}
if (lnInvoiceLookup instanceof Error) return lnInvoiceLookup

if (!lnInvoiceLookup.isSettled) {
logger.debug({ invoice: lnInvoiceLookup }, "invoice has not been paid")
return false
}

const {
lnInvoice: { description },
roundedDownReceived,
} = lnInvoiceLookup

const pendingInvoiceLogger = logger.child({
hash: paymentHash,
walletId: recipientWalletDescriptor.id,
topic: "payment",
protocol: "lightning",
transactionType: "receipt",
onUs: false,
})

if (walletInvoice.paid) {
pendingInvoiceLogger.info("invoice has already been processed")
return true
}

if (!lnInvoiceLookup.isHeld) {
pendingInvoiceLogger.info("invoice has not been paid yet")
return false
}

const receivedBtc = paymentAmountFromNumber({
amount: roundedDownReceived,
currency: WalletCurrency.Btc,
Expand All @@ -133,25 +157,22 @@ const updatePendingInvoice = async ({
return false
}
if (invoiceToUpdate instanceof Error) return invoiceToUpdate
if (walletInvoice.paid) {
if (invoiceToUpdate.paid) {
pendingInvoiceLogger.info("invoice has already been processed")
return true
}

const displayCurrencyPerSat = await getCurrentPrice()
if (displayCurrencyPerSat instanceof Error) return displayCurrencyPerSat

const invoiceSettled = await lndService.settleInvoice({ pubkey, secret })
if (invoiceSettled instanceof Error) return invoiceSettled

const invoicePaid = await walletInvoicesRepo.markAsPaid(paymentHash)
if (invoicePaid instanceof Error) return invoicePaid

// TODO: this should be a in a mongodb transaction session with the ledger transaction below
// markAsPaid could be done after the transaction, but we should in that case not only look
// for walletInvoicesRepo, but also in the ledger to make sure in case the process crash in this
// loop that an eventual consistency doesn't lead to a double credit

const invoicePaid = await walletInvoicesRepo.markAsPaid(paymentHash)
if (invoicePaid instanceof Error) return invoicePaid

const metadata = LedgerFacade.LnReceiveLedgerMetadata({
paymentHash,
fee: walletInvoiceReceiver.btcBankFee,
Expand Down Expand Up @@ -217,64 +238,3 @@ const updatePendingInvoice = async ({
return true
})
}

const declineHeldInvoice = async ({
walletInvoice,
logger,
}: {
walletInvoice: WalletInvoice
logger: Logger
}): Promise<boolean | ApplicationError> => {
const lndService = LndService()
if (lndService instanceof Error) return lndService

const walletInvoicesRepo = WalletInvoicesRepository()

const { pubkey, paymentHash } = walletInvoice

const lnInvoiceLookup = await lndService.lookupInvoice({ pubkey, paymentHash })

const pendingInvoiceLogger = logger.child({
hash: paymentHash,
lnInvoiceLookup,
walletInvoice,
topic: "payment",
protocol: "lightning",
transactionType: "receipt",
onUs: false,
})

if (lnInvoiceLookup instanceof InvoiceNotFoundError) {
const isDeleted = await walletInvoicesRepo.deleteByPaymentHash(paymentHash)
if (isDeleted instanceof Error) {
pendingInvoiceLogger.error("impossible to delete WalletInvoice entry")
return isDeleted
}
return false
}
if (lnInvoiceLookup instanceof Error) return lnInvoiceLookup

if (!lnInvoiceLookup.isHeld) {
pendingInvoiceLogger.info({ lnInvoiceLookup }, "invoice has not been paid yet")
return false
}

let heldForMsg = ""
if (lnInvoiceLookup.heldAt) {
heldForMsg = `for ${elapsedSinceTimestamp(lnInvoiceLookup.heldAt)}s `
}
pendingInvoiceLogger.error(
{ lnInvoiceLookup },
`invoice has been held ${heldForMsg}and is now been cancelled`,
)

const invoiceSettled = await lndService.cancelInvoice({ pubkey, paymentHash })
if (invoiceSettled instanceof Error) return invoiceSettled

const isDeleted = await walletInvoicesRepo.deleteByPaymentHash(paymentHash)
if (isDeleted instanceof Error) {
pendingInvoiceLogger.error("impossible to delete WalletInvoice entry")
}

return true
}
4 changes: 0 additions & 4 deletions src/domain/bitcoin/lightning/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ export class CouldNotDecodeReturnedPaymentRequest extends LightningServiceError
export class UnknownLightningServiceError extends LightningServiceError {
level = ErrorLevel.Critical
}
export class SecretDoesNotMatchAnyExistingHodlInvoiceError extends LightningServiceError {
level = ErrorLevel.Critical
}

export class InvoiceNotFoundError extends LightningServiceError {}
export class LnPaymentPendingError extends LightningServiceError {}
export class LnAlreadyPaidError extends LightningServiceError {}
Expand Down
13 changes: 0 additions & 13 deletions src/domain/bitcoin/lightning/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { createHash, randomBytes } from "crypto"

import { InvalidPubKeyError } from "@domain/errors"

export { decodeInvoice } from "./ln-invoice"
Expand Down Expand Up @@ -29,14 +27,3 @@ export const checkedToPubkey = (pubkey: string): Pubkey | InvalidPubKeyError =>
}
return new InvalidPubKeyError("Pubkey conversion error")
}

export const sha256 = (buffer: Buffer) =>
createHash("sha256").update(buffer).digest("hex")
const randomSecret = () => randomBytes(32)

export const getSecretAndPaymentHash = () => {
const secret = randomSecret()
const paymentHash = sha256(secret) as PaymentHash

return { secret: secret.toString("hex") as SecretPreImage, paymentHash }
}
12 changes: 0 additions & 12 deletions src/domain/bitcoin/lightning/index.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ type LnInvoiceLookup = {
readonly createdAt: Date
readonly confirmedAt: Date | undefined
readonly isSettled: boolean
readonly isHeld: boolean
readonly heldAt: Date | undefined
readonly roundedDownReceived: Satoshis
readonly milliSatsReceived: MilliSatoshis
readonly secretPreImage: SecretPreImage
Expand Down Expand Up @@ -111,15 +109,13 @@ type LnInvoice = {
}

type RegisterInvoiceArgs = {
paymentHash: PaymentHash
description: string
descriptionHash?: string
sats: Satoshis
expiresAt: InvoiceExpiration
}

type NewRegisterInvoiceArgs = {
paymentHash: PaymentHash
description: string
descriptionHash?: string
btcPaymentAmount: BtcPaymentAmount
Expand Down Expand Up @@ -223,14 +219,6 @@ interface ILightningService {
pubkey?: Pubkey
}): Promise<true | LightningServiceError>

settleInvoice({
pubkey,
secret,
}: {
pubkey: Pubkey
secret: SecretPreImage
}): Promise<true | LightningServiceError>

cancelInvoice({
pubkey,
paymentHash,
Expand Down
5 changes: 0 additions & 5 deletions src/domain/wallet-invoices/errors.ts

This file was deleted.

7 changes: 5 additions & 2 deletions src/domain/wallet-invoices/index.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ type WIBWithAmount = {

type WalletInvoice = {
paymentHash: PaymentHash
secret: SecretPreImage
selfGenerated: boolean
pubkey: Pubkey
usdAmount?: UsdPaymentAmount
Expand Down Expand Up @@ -109,7 +108,11 @@ interface IWalletInvoicesRepository {
paymentHash: PaymentHash,
) => Promise<WalletInvoice | RepositoryError>

yieldPending: () => AsyncGenerator<WalletInvoice> | RepositoryError
findPendingByWalletId: (
walletId: WalletId,
) => AsyncGenerator<WalletInvoice> | RepositoryError

listWalletIdsWithPendingInvoices: () => AsyncGenerator<WalletId> | RepositoryError

deleteByPaymentHash: (paymentHash: PaymentHash) => Promise<boolean | RepositoryError>

Expand Down
Loading