Improve LNC realiability by being nicer (#1658)

* be nicer to lnc

* decrease timeout to 4 seconds
This commit is contained in:
Riccardo Balbo 2024-11-27 19:46:40 +01:00 committed by GitHub
parent 6630899e79
commit e05989d371
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 80 additions and 77 deletions

View File

@ -3,106 +3,109 @@ import { bolt11Tags } from '@/lib/bolt11'
import { Mutex } from 'async-mutex' import { Mutex } from 'async-mutex'
export * from '@/wallets/lnc' export * from '@/wallets/lnc'
async function disconnect (lnc, logger) { const mutex = new Mutex()
if (lnc) { const serverHost = 'mailbox.terminal.lightning.today:443'
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 }) { export async function testSendPayment (credentials, { logger }) {
let lnc const lnc = await getLNC(credentials, { logger })
try { logger?.info('validating permissions ...')
lnc = await getLNC(credentials) await validateNarrowPerms(lnc)
logger?.info('permissions ok')
logger.info('connecting ...') return lnc.credentials.credentials
await lnc.connect()
logger.info('connected')
logger.info('validating permissions ...')
await validateNarrowPerms(lnc)
logger.info('permissions ok')
return lnc.credentials.credentials
} finally {
await disconnect(lnc, logger)
}
} }
const mutex = new Mutex()
export async function sendPayment (bolt11, credentials, { logger }) { export async function sendPayment (bolt11, credentials, { logger }) {
const hash = bolt11Tags(bolt11).payment_hash const hash = bolt11Tags(bolt11).payment_hash
return await mutex.runExclusive(async () => { return await mutex.runExclusive(async () => {
let lnc
try { try {
lnc = await getLNC(credentials) const lnc = await getLNC(credentials, { logger })
const { paymentError, paymentPreimage: preimage } = await lnc.lnd.lightning.sendPaymentSync({ payment_request: bolt11 })
await lnc.connect()
const { paymentError, paymentPreimage: preimage } =
await lnc.lnd.lightning.sendPaymentSync({ payment_request: bolt11 })
if (paymentError) throw new Error(paymentError) if (paymentError) throw new Error(paymentError)
if (!preimage) throw new Error('No preimage in response') if (!preimage) throw new Error('No preimage in response')
return preimage return preimage
} catch (err) { } catch (err) {
const msg = err.message || err.toString?.() const msg = err.message || err.toString?.()
if (msg.includes('invoice expired')) { if (msg.includes('invoice expired')) throw new InvoiceExpiredError(hash)
throw new InvoiceExpiredError(hash) if (msg.includes('canceled')) throw new InvoiceCanceledError(hash)
}
if (msg.includes('canceled')) {
throw new InvoiceCanceledError(hash)
}
throw err throw err
} finally {
await disconnect(lnc, logger)
} }
}) })
} }
async function getLNC (credentials = {}) { async function disconnectLNC (lnc, { logger } = {}) {
const serverHost = 'mailbox.terminal.lightning.today:443' try {
// XXX we MUST reuse the same instance of LNC because it references a global Go object if (!lnc?.isConnected) return
// that holds closures to the first LNC instance it's created with lnc.disconnect()
if (window.lnc) { logger?.info('disconnecting...')
window.lnc.credentials.credentials = { // wait for lnc to disconnect
...window.lnc.credentials.credentials, 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)
}
}
async function getLNC (credentials = {}, { logger } = {}) {
if (window.snLncKillerTimeout) clearTimeout(window.snLncKillerTimeout)
if (!window.snLnc) { // create new instance
const { default: LNC } = await import('@lightninglabs/lnc-web')
window.snLnc = new LNC({
credentialStore: new LncCredentialStore({
...credentials,
serverHost
})
})
window.addEventListener('beforeunload', () => {
// try to disconnect gracefully when the page is closed
disconnectLNC(window.snLnc, { logger })
})
} else if (JSON.stringify(window.snLncCredentials ?? {}) !== JSON.stringify(credentials)) {
console.log('LNC instance has new credentials')
// disconnect and update credentials if they've changed
await disconnectLNC(window.snLnc, { logger })
// 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
window.snLnc.credentials.credentials = {
...window.snLnc.credentials.credentials,
...credentials, ...credentials,
serverHost serverHost
} }
return window.lnc
} }
const { default: LNC } = await import('@lightninglabs/lnc-web')
window.lnc = new LNC({ if (!window.snLnc.isConnected) {
credentialStore: new LncCredentialStore({ logger?.info('connecting ...')
...credentials, await window.snLnc.connect()
serverHost logger?.info('connected')
}
window.snLncCredentials = {
...credentials
}
window.snLncKillerTimeout = setTimeout(() => {
logger?.info('disconnecting from lnc due to inactivity ...')
mutex.runExclusive(async () => {
await disconnectLNC(window.snLnc, { logger })
}) })
}) }, 4000)
return window.lnc
return window.snLnc
} }
function validateNarrowPerms (lnc) { function validateNarrowPerms (lnc) {