ekzyis 72e2d19433
supercharged wallet logs (#1516)
* Inject wallet logger interface

* Include method in NWC logs

* Fix wrong page total

* Poll for new logs every second

* Fix overlapping pagination

* Remove unused total

* Better logs for incoming payments

* Use _setLogs instead of wrapper

* Remove inconsistent receive log

* Remove console.log from wallet logger on server

* Fix missing 'wallet detached' log

* Fix confirm_withdrawl code

* Remove duplicate autowithdrawal log

* Add context to log

* Add more context

* Better table styling

* Move CSS for wallet logs into one file

* remove unused logNav class
* rename classes

* Align key with second column

* Fix TypeError if context empty

* Check content-type header before calling res.json()

* Fix duplicate 'failed to create invoice'

* Parse details from LND error

* Fix invalid DOM property 'colspan'

* P2P zap logs with context

* Remove unnecessary withdrawal error log

* the code assignment was broken anyway
* we already log withdrawal errors using .catch on payViaPaymentRequest

* Don't show outgoing fee to receiver to avoid confusion

* Fix typo in comment

* Log if invoice was canceled by payer

* Automatically populate context from bolt11

* Fix missing context

* Fix wrap errors not logged

* Only log cancel if client canceled

* Remove unused imports

* Log withdrawal/forward success/error in payment flow

* Fix boss not passed to checkInvoice

* Fix TypeError

* Fix database timeouts caused by logger

The logger shares the same connection pool with any currently running transaction.

This means that we enter a classic deadlock when we await logger calls: the logger call is waiting for a connection but the currently running transaction is waiting for the logger call to finish before it can release a connection.

* Fix cache returning undefined

* Fix typo in comment

* Add padding-right to key in log context

* Always use 'incoming payment failed:'

---------

Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
2024-11-08 13:26:40 -06:00

178 lines
4.3 KiB
JavaScript

import { InvoiceCanceledError, InvoiceExpiredError } from '@/wallets/errors'
import { bolt11Tags } from '@/lib/bolt11'
import { Mutex } from 'async-mutex'
export * from 'wallets/lnc'
async function disconnect (lnc, logger) {
if (lnc) {
try {
lnc.disconnect()
logger.info('disconnecting...')
// wait for lnc to disconnect before releasing the mutex
await new Promise((resolve, reject) => {
let counter = 0
const interval = setInterval(() => {
if (lnc?.isConnected) {
if (counter++ > 100) {
logger.error('failed to disconnect from lnc')
clearInterval(interval)
reject(new Error('failed to disconnect from lnc'))
}
return
}
clearInterval(interval)
resolve()
})
}, 50)
logger.info('disconnected')
} catch (err) {
logger.error('failed to disconnect from lnc: ' + err)
}
}
}
export async function testSendPayment (credentials, { logger }) {
let lnc
try {
lnc = await getLNC(credentials)
logger.info('connecting ...')
await lnc.connect()
logger.ok('connected')
logger.info('validating permissions ...')
await validateNarrowPerms(lnc)
logger.ok('permissions ok')
return lnc.credentials.credentials
} finally {
await disconnect(lnc, logger)
}
}
const mutex = new Mutex()
export async function sendPayment (bolt11, credentials, { logger }) {
const hash = bolt11Tags(bolt11).payment_hash
return await mutex.runExclusive(async () => {
let lnc
try {
lnc = await getLNC(credentials)
await lnc.connect()
const { paymentError, paymentPreimage: preimage } =
await lnc.lnd.lightning.sendPaymentSync({ payment_request: bolt11 })
if (paymentError) throw new Error(paymentError)
if (!preimage) throw new Error('No preimage in response')
return preimage
} catch (err) {
const msg = err.message || err.toString?.()
if (msg.includes('invoice expired')) {
throw new InvoiceExpiredError(hash)
}
if (msg.includes('canceled')) {
throw new InvoiceCanceledError(hash)
}
throw err
} finally {
await disconnect(lnc, logger)
}
})
}
async function getLNC (credentials = {}) {
const serverHost = 'mailbox.terminal.lightning.today:443'
// XXX we MUST reuse the same instance of LNC because it references a global Go object
// that holds closures to the first LNC instance it's created with
if (window.lnc) {
window.lnc.credentials.credentials = {
...window.lnc.credentials.credentials,
...credentials,
serverHost
}
return window.lnc
}
const { default: { default: LNC } } = await import('@lightninglabs/lnc-web')
window.lnc = new LNC({
credentialStore: new LncCredentialStore({
...credentials,
serverHost
})
})
return window.lnc
}
function validateNarrowPerms (lnc) {
if (!lnc.hasPerms('lnrpc.Lightning.SendPaymentSync')) {
throw new Error('missing permission: lnrpc.Lightning.SendPaymentSync')
}
if (lnc.hasPerms('lnrpc.Lightning.SendCoins')) {
throw new Error('too broad permission: lnrpc.Wallet.SendCoins')
}
// TODO: need to check for more narrow permissions
// blocked by https://github.com/lightninglabs/lnc-web/issues/112
}
// default credential store can go fuck itself
class LncCredentialStore {
credentials = {
localKey: '',
remoteKey: '',
pairingPhrase: '',
serverHost: ''
}
constructor (credentials = {}) {
this.credentials = { ...this.credentials, ...credentials }
}
get password () {
return ''
}
set password (password) { }
get serverHost () {
return this.credentials.serverHost
}
set serverHost (host) {
this.credentials.serverHost = host
}
get pairingPhrase () {
return this.credentials.pairingPhrase
}
set pairingPhrase (phrase) {
this.credentials.pairingPhrase = phrase
}
get localKey () {
return this.credentials.localKey
}
set localKey (key) {
this.credentials.localKey = key
}
get remoteKey () {
return this.credentials.remoteKey
}
set remoteKey (key) {
this.credentials.remoteKey = key
}
get isPaired () {
return !!this.credentials.remoteKey || !!this.credentials.pairingPhrase
}
clear () {
this.credentials = {}
}
}