Merge pull request #282 from shocknet/central-lnd

Central lnd
This commit is contained in:
Daniel Lugo 2021-01-14 15:42:45 -04:00 committed by GitHub
commit 831ca1c77d
5 changed files with 403 additions and 184 deletions

33
.vscode/snippets.code-snippets vendored Normal file
View file

@ -0,0 +1,33 @@
{
// Place your api workspace snippets here. Each snippet is defined under a
// snippet name and has a scope, prefix, body and description. Add comma
// separated ids of the languages where the snippet is applicable in the scope
// field. If scope is left empty or omitted, the snippet gets applied to all
// languages. The prefix is what is used to trigger the snippet and the body
// will be expanded and inserted. Possible variables are: $1, $2 for tab
// stops, $0 for the final cursor position, and ${1:label}, ${2:another} for
// placeholders. Placeholders with the same ids are connected. Example: "Print
// to console": {"scope": "javascript,typescript", "prefix": "log", "body":
// ["console.log('$1');", "$2"
// ],
// "description": "Log output to console"
// }
"Route Body": {
"body": [
"try {",
" return res.json({",
"",
" })",
"} catch (e) {",
" console.log(e)",
" return res.status(500).json({",
" errorMessage: e.message",
" })",
"}"
],
"description": "Route Body",
"prefix": "rbody",
"scope": "javascript"
}
}

View file

@ -30,11 +30,7 @@ const {
const GunActions = require('../services/gunDB/contact-api/actions')
const GunGetters = require('../services/gunDB/contact-api/getters')
const GunKey = require('../services/gunDB/contact-api/key')
const {
sendPaymentV2Keysend,
sendPaymentV2Invoice,
listPayments
} = require('../utils/lightningServices/v2')
const LV2 = require('../utils/lightningServices/v2')
const GunWriteRPC = require('../services/gunDB/rpc')
const DEFAULT_MAX_NUM_ROUTES_TO_QUERY = 10
@ -1024,30 +1020,15 @@ module.exports = async (
)
})
// get lnd chan info
app.post('/api/lnd/getchaninfo', (req, res) => {
const { lightning } = LightningServices.services
lightning.getChanInfo(
{ chan_id: req.body.chan_id },
async (err, response) => {
if (err) {
logger.debug('GetChanInfo Error:', err)
const health = await checkHealth()
if (health.LNDStatus.success) {
res.status(400)
res.json({
field: 'getChanInfo',
errorMessage: sanitizeLNDError(err.message)
})
} else {
res.status(500)
res.json({ errorMessage: 'LND is down' })
}
}
logger.debug('GetChanInfo:', response)
res.json(response)
}
)
app.post('/api/lnd/getchaninfo', async (req, res) => {
try {
return res.json(await LV2.getChanInfo(req.body.chan_id))
} catch (e) {
console.log(e)
return res.status(500).json({
errorMessage: e.message
})
}
})
app.get('/api/lnd/getnetworkinfo', (req, res) => {
@ -1072,47 +1053,30 @@ module.exports = async (
})
// get lnd node active channels list
app.get('/api/lnd/listpeers', (req, res) => {
const { lightning } = LightningServices.services
lightning.listPeers({}, async (err, response) => {
if (err) {
logger.debug('ListPeers Error:', err)
const health = await checkHealth()
if (health.LNDStatus.success) {
res.status(400).json({
field: 'listPeers',
errorMessage: sanitizeLNDError(err.message)
})
} else {
res.status(500)
res.json({ errorMessage: 'LND is down' })
}
}
logger.debug('ListPeers:', response)
res.json(response)
})
app.get('/api/lnd/listpeers', async (req, res) => {
try {
return res.json({
peers: await LV2.listPeers(req.body.latestError)
})
} catch (e) {
console.log(e)
return res.status(500).json({
errorMessage: e.message
})
}
})
// newaddress
app.post('/api/lnd/newaddress', (req, res) => {
const { lightning } = LightningServices.services
lightning.newAddress({ type: req.body.type }, async (err, response) => {
if (err) {
logger.debug('NewAddress Error:', err)
const health = await checkHealth()
if (health.LNDStatus.success) {
res.status(400).json({
field: 'newAddress',
errorMessage: sanitizeLNDError(err.message)
})
} else {
res.status(500)
res.json({ errorMessage: 'LND is down' })
}
}
logger.debug('NewAddress:', response)
res.json(response)
})
app.post('/api/lnd/newaddress', async (req, res) => {
try {
return res.json({
address: await LV2.newAddress(req.body.type)
})
} catch (e) {
return res.status(500).json({
errorMessage: e.message
})
}
})
// connect peer to lnd node
@ -1157,47 +1121,28 @@ module.exports = async (
})
// get lnd node opened channels list
app.get('/api/lnd/listchannels', (req, res) => {
const { lightning } = LightningServices.services
lightning.listChannels({}, async (err, response) => {
if (err) {
logger.debug('ListChannels Error:', err)
const health = await checkHealth()
if (health.LNDStatus.success) {
res.status(400).json({
field: 'listChannels',
errorMessage: sanitizeLNDError(err.message)
})
} else {
res.status(500)
res.json({ errorMessage: 'LND is down' })
}
}
logger.debug('ListChannels:', response)
res.json(response)
})
app.get('/api/lnd/listchannels', async (_, res) => {
try {
return res.json({
channels: await LV2.listChannels()
})
} catch (e) {
console.log(e)
return res.status(500).json({
errorMessage: e.message
})
}
})
// get lnd node pending channels list
app.get('/api/lnd/pendingchannels', (req, res) => {
const { lightning } = LightningServices.services
lightning.pendingChannels({}, async (err, response) => {
if (err) {
logger.debug('PendingChannels Error:', err)
const health = await checkHealth()
if (health.LNDStatus.success) {
res.status(400).json({
field: 'pendingChannels',
errorMessage: sanitizeLNDError(err.message)
})
} else {
res.status(500)
res.json({ errorMessage: 'LND is down' })
}
}
logger.debug('PendingChannels:', response)
res.json(response)
})
app.get('/api/lnd/pendingchannels', async (req, res) => {
try {
return res.json(await LV2.pendingChannels())
} catch (e) {
console.log(e)
return res.status(500).json({
errorMessage: e.message
})
}
})
app.get('/api/lnd/unifiedTrx', (req, res) => {
@ -1375,7 +1320,7 @@ module.exports = async (
}
return res.status(200).json(
await listPayments({
await LV2.listPayments({
include_incomplete,
index_offset,
max_payments,
@ -1662,7 +1607,7 @@ module.exports = async (
})
}
const payment = await sendPaymentV2Keysend({
const payment = await LV2.sendPaymentV2Keysend({
amt,
dest,
feeLimit,
@ -1675,7 +1620,7 @@ module.exports = async (
}
const { payreq } = req.body
const payment = await sendPaymentV2Invoice({
const payment = await LV2.sendPaymentV2Invoice({
feeLimit,
payment_request: payreq,
amt: req.body.amt,
@ -1764,64 +1709,32 @@ module.exports = async (
})
// addinvoice
app.post('/api/lnd/addinvoice', (req, res) => {
const { lightning } = LightningServices.services
const invoiceRequest = { memo: req.body.memo, private: true }
if (req.body.value) {
invoiceRequest.value = req.body.value
}
if (req.body.expiry) {
invoiceRequest.expiry = req.body.expiry
}
lightning.addInvoice(invoiceRequest, async (err, newInvoice) => {
if (err) {
logger.debug('AddInvoice Error:', err)
const health = await checkHealth()
if (health.LNDStatus.success) {
res.status(400).json({
field: 'addInvoice',
errorMessage: sanitizeLNDError(err.message)
})
} else {
res.status(500)
res.json({ errorMessage: 'LND is down' })
app.post('/api/lnd/addinvoice', async (req, res) => {
const { expiry, value, memo } = req.body
const addInvoiceRes = await LV2.addInvoice(value, memo, true, expiry)
if (value) {
const channelsList = await LV2.listChannels({ active_only: true })
let remoteBalance = Big(0)
channelsList.forEach(element => {
const remB = Big(element.remote_balance)
if (remB.gt(remoteBalance)) {
remoteBalance = remB
}
return err
}
logger.debug('AddInvoice:', newInvoice)
if (req.body.value) {
logger.debug('AddInvoice liquidity check:')
lightning.listChannels({ active_only: true }, async (err, response) => {
if (err) {
logger.debug('ListChannels Error:', err)
const health = await checkHealth()
if (health.LNDStatus.success) {
res.status(400).json({
field: 'listChannels',
errorMessage: sanitizeLNDError(err.message)
})
} else {
res.status(500)
res.json({ errorMessage: 'LND is down' })
}
}
logger.debug('ListChannels:', response)
const channelsList = response.channels
let remoteBalance = Big(0)
channelsList.forEach(element => {
const remB = Big(element.remote_balance)
if (remB.gt(remoteBalance)) {
remoteBalance = remB
}
})
newInvoice.liquidityCheck = remoteBalance > req.body.value
//newInvoice.remoteBalance = remoteBalance
res.json(newInvoice)
})
} else {
res.json(newInvoice)
}
})
})
addInvoiceRes.liquidityCheck = remoteBalance > value
//newInvoice.remoteBalance = remoteBalance
}
try {
return res.json(addInvoiceRes)
} catch (e) {
console.log(e)
return res.status(500).json({
errorMessage: e.message
})
}
})
// signmessage
@ -1959,23 +1872,25 @@ module.exports = async (
)
})
app.post('/api/lnd/listunspent', (req, res) => {
const { lightning } = LightningServices.services
const { minConfirmations = 3, maxConfirmations = 6 } = req.body
lightning.listUnspent(
{
min_confs: minConfirmations,
max_confs: maxConfirmations
},
(err, unspent) => {
if (err) {
return handleError(res, err)
}
logger.debug('ListUnspent:', unspent)
res.json(unspent)
}
)
})
const listunspent = async (req, res) => {
try {
return res.status(200).json({
utxos: await LV2.listUnspent(
req.body.minConfirmations,
req.body.maxConfirmations
)
})
} catch (e) {
return res.status(500).json({
errorMessage: e.message
})
}
}
app.get('/api/lnd/listunspent', listunspent)
// TODO: should be GET
app.post('/api/lnd/listunspent', listunspent)
app.get('/api/lnd/transactions', (req, res) => {
const { lightning } = LightningServices.services

View file

@ -74,6 +74,13 @@ class LightningServices {
}
}
/**
* @returns {import('./types').Services}
*/
getServices() {
return this.services
}
get servicesData() {
return this.lnServicesData
}

View file

@ -1,6 +1,7 @@
/**
* @format
*/
import * as Common from 'shock-common'
export interface PaymentV2 {
payment_hash: string
@ -106,3 +107,90 @@ export interface SendPaymentInvoiceParams {
payment_request: string
timeoutSeconds?: number
}
type StreamListener = (data: any) => void
/**
* Caution: Not all methods return an stream.
*/
interface LightningStream {
on(ev: 'data' | 'end' | 'error' | 'status', listener: StreamListener): void
}
type LightningCB = (err: Error, data: Record<string, any>) => void
type LightningMethod = (
args: Record<string, any>,
cb?: LightningCB
) => LightningStream
/**
* Makes it easier for code calling services.
*/
export interface Services {
lightning: Record<string, LightningMethod>
walletUnlocker: Record<string, LightningMethod>
router: Record<string, LightningMethod>
}
export interface ListChannelsReq {
active_only: boolean
inactive_only: boolean
public_only: boolean
private_only: boolean
/**
* Filters the response for channels with a target peer's pubkey. If peer is
* empty, all channels will be returned.
*/
peer: Common.Bytes
}
/**
* https://api.lightning.community/#pendingchannels
*/
export interface PendingChannelsRes {
/**
* The balance in satoshis encumbered in pending channels.
*/
total_limbo_balance: string
/**
* Channels pending opening.
*/
pending_open_channels: Common.PendingOpenChannel[]
/**
* Channels pending force closing.
*/
pending_force_closing_channels: Common.ForceClosedChannel[]
/**
* Channels waiting for closing tx to confirm.
*/
waiting_close_channels: Common.WaitingCloseChannel[]
}
/**
* https://github.com/lightningnetwork/lnd/blob/daf7c8a85420fc67fffa18fa5f7d08c2040946e4/lnrpc/rpc.proto#L2948
*/
export interface AddInvoiceRes {
/**
*
*/
r_hash: Common.Bytes
/**
* A bare-bones invoice for a payment within the Lightning Network. With the
* details of the invoice, the sender has all the data necessary to send a
* payment to the recipient.
*/
payment_request: string
/**
* The "add" index of this invoice. Each newly created invoice will increment
* this index making it monotonically increasing. Callers to the
* SubscribeInvoices call can use this to instantly get notified of all added
* invoices with an add_index greater than this one.
*/
add_index: string
/**
* The payment address of the generated invoice. This value should be used in
* all payments for this invoice as we require it for end to end security.
*/
payment_addr: Common.Bytes
}

View file

@ -402,9 +402,185 @@ const decodePayReq = payReq =>
)
})
/**
* @param {0|1} type
* @returns {Promise<string>}
*/
const newAddress = (type = 0) => {
const { lightning } = lightningServices.getServices()
return Common.Utils.makePromise((res, rej) => {
lightning.newAddress({ type }, (err, response) => {
if (err) {
rej(new Error(err.message))
} else {
res(response.address)
}
})
})
}
/**
* @param {number} minConfs
* @param {number} maxConfs
* @returns {Promise<Common.Utxo[]>}
*/
const listUnspent = (minConfs = 3, maxConfs = 6) =>
Common.makePromise((res, rej) => {
const { lightning } = lightningServices.getServices()
lightning.listUnspent(
{
min_confs: minConfs,
max_confs: maxConfs
},
(err, unspent) => {
if (err) {
rej(new Error(err.message))
} else {
res(unspent.utxos)
}
}
)
})
/**
* @typedef {import('./types').ListChannelsReq} ListChannelsReq
*/
/**
* @param {ListChannelsReq} req
* @returns {Promise<Common.Channel[]>}
*/
const listChannels = req =>
Common.makePromise((res, rej) => {
const { lightning } = lightningServices.getServices()
lightning.listChannels(req, (err, resp) => {
if (err) {
rej(new Error(err.message))
} else {
res(resp.channels)
}
})
})
/**
* https://api.lightning.community/#getchaninfo
* @param {string} chanID
* @returns {Promise<Common.ChannelEdge>}
*/
const getChanInfo = chanID =>
Common.makePromise((res, rej) => {
const { lightning } = lightningServices.getServices()
lightning.getChanInfo(
{
chan_id: chanID
},
(err, resp) => {
if (err) {
rej(new Error(err.message))
} else {
// Needs cast because typescript refuses to assign Record<string, any>
// to an actual object :shrugs
res(/** @type {Common.ChannelEdge} */ (resp))
}
}
)
})
/**
* https://api.lightning.community/#listpeers
* @param {boolean=} latestError If true, only the last error that our peer sent
* us will be returned with the peer's information, rather than the full set of
* historic errors we have stored.
* @returns {Promise<Common.Peer[]>}
*/
const listPeers = latestError =>
Common.makePromise((res, rej) => {
const { lightning } = lightningServices.getServices()
lightning.listPeers(
{
latest_error: latestError
},
(err, resp) => {
if (err) {
rej(new Error(err.message))
} else {
res(resp.peers)
}
}
)
})
/**
* @typedef {import('./types').PendingChannelsRes} PendingChannelsRes
*/
/**
* @returns {Promise<PendingChannelsRes>}
*/
const pendingChannels = () =>
Common.makePromise((res, rej) => {
const { lightning } = lightningServices.getServices()
lightning.pendingChannels({}, (err, resp) => {
if (err) {
rej(new Error(err.message))
} else {
// Needs cast because typescript refuses to assign Record<string, any>
// to an actual object :shrugs
res(/** @type {PendingChannelsRes} */ (resp))
}
})
})
/**
* @typedef {import('./types').AddInvoiceRes} AddInvoiceRes
*/
/**
* https://api.lightning.community/#addinvoice
* @param {number} value
* @param {string=} memo
* @param {boolean=} confidential Alias for `private`.
* @param {number=} expiry
* @returns {Promise<AddInvoiceRes>}
*/
const addInvoice = (value, memo = '', confidential = true, expiry = 180) =>
Common.makePromise((res, rej) => {
const { lightning } = lightningServices.getServices()
lightning.addInvoice(
{
value,
memo,
private: confidential,
expiry
},
(err, resp) => {
if (err) {
rej(new Error(err.message))
} else {
// Needs cast because typescript refuses to assign Record<string, any>
// to an actual object :shrugs
res(/** @type {AddInvoiceRes} */ (resp))
}
}
)
})
module.exports = {
sendPaymentV2Keysend,
sendPaymentV2Invoice,
listPayments,
decodePayReq
decodePayReq,
newAddress,
listUnspent,
listChannels,
getChanInfo,
listPeers,
pendingChannels,
addInvoice
}