Merge pull request #148 from shocknet/fix/sendpayment-fix

clean send payment
This commit is contained in:
CapDog 2020-08-20 16:25:52 -04:00 committed by GitHub
commit 135d52da08
3 changed files with 147 additions and 75 deletions

View file

@ -509,6 +509,7 @@ class Mediator {
Action.GENERATE_NEW_HANDSHAKE_NODE, Action.GENERATE_NEW_HANDSHAKE_NODE,
this.generateHandshakeNode this.generateHandshakeNode
) )
this.socket.on('GENERATE_ORDER_ADDRESS', this.generateOrderAddress)
this.socket.on(Action.SEND_HANDSHAKE_REQUEST, this.sendHandshakeRequest) this.socket.on(Action.SEND_HANDSHAKE_REQUEST, this.sendHandshakeRequest)
this.socket.on( this.socket.on(
Action.SEND_HANDSHAKE_REQUEST_WITH_INITIAL_MSG, Action.SEND_HANDSHAKE_REQUEST_WITH_INITIAL_MSG,
@ -745,6 +746,32 @@ class Mediator {
} }
} }
/**
* @param {Readonly<{ token: string }>} body
*/
generateOrderAddress = async body => {
try {
const { token } = body
await throwOnInvalidToken(token)
await API.Actions.generateOrderAddress(user)
this.socket.emit('GENERATE_ORDER_ADDRESS', {
ok: true,
msg: null,
origBody: body
})
} catch (err) {
logger.info(err)
this.socket.emit('GENERATE_ORDER_ADDRESS', {
ok: false,
msg: err.message,
origBody: body
})
}
}
/** /**
* @param {Readonly<{ recipientPublicKey: string , token: string }>} body * @param {Readonly<{ recipientPublicKey: string , token: string }>} body
*/ */
@ -861,15 +888,20 @@ class Mediator {
} }
/** /**
* @param {Readonly<{ uuid: string, recipientPub: string, amount: number, memo: string, token: string }>} reqBody * @param {Readonly<{ uuid: string, recipientPub: string, amount: number, memo: string, token: string, feeLimit:number }>} reqBody
*/ */
sendPayment = async reqBody => { sendPayment = async reqBody => {
try { try {
const { recipientPub, amount, memo, token } = reqBody const { recipientPub, amount, memo, feeLimit, token } = reqBody
await throwOnInvalidToken(token) await throwOnInvalidToken(token)
const preimage = await API.Actions.sendPayment(recipientPub, amount, memo) const preimage = await API.Actions.sendPayment(
recipientPub,
amount,
memo,
feeLimit
)
this.socket.emit(Action.SEND_PAYMENT, { this.socket.emit(Action.SEND_PAYMENT, {
ok: true, ok: true,

View file

@ -902,11 +902,21 @@ const sendHRWithInitialMsg = async (
* @param {string} to * @param {string} to
* @param {number} amount * @param {number} amount
* @param {string} memo * @param {string} memo
* @param {number} feeLimit
* @param {number=} maxParts
* @param {number=} timeoutSeconds
* @throws {Error} If no response in less than 20 seconds from the recipient, or * @throws {Error} If no response in less than 20 seconds from the recipient, or
* lightning cannot find a route for the payment. * lightning cannot find a route for the payment.
* @returns {Promise<string>} The payment's preimage. * @returns {Promise<string>} The payment's preimage.
*/ */
const sendPayment = async (to, amount, memo) => { const sendPayment = async (
to,
amount,
memo,
feeLimit,
maxParts = 3,
timeoutSeconds = 5
) => {
try { try {
const SEA = require('../Mediator').mySEA const SEA = require('../Mediator').mySEA
const getUser = () => require('../Mediator').getUser() const getUser = () => require('../Mediator').getUser()
@ -1037,7 +1047,7 @@ const sendPayment = async (to, amount, memo) => {
} }
const { const {
services: { lightning } services: { router }
} = LightningServices } = LightningServices
/** /**
@ -1047,50 +1057,46 @@ const sendPayment = async (to, amount, memo) => {
/** /**
* Partial * Partial
* https://api.lightning.community/#grpc-response-sendresponse-2 * https://api.lightning.community/#sendpaymentv2
* @typedef {object} SendResponse * @typedef {object} SendResponse
* @prop {string|null} payment_error * @prop {string} failure_reason
* @prop {any[]|null} payment_route
* @prop {string} payment_preimage * @prop {string} payment_preimage
*/ */
logger.info('Will now send payment through lightning') logger.info('Will now send payment through lightning')
const sendPaymentSyncArgs = { const sendPaymentV2Args = {
/** @type {string} */ /** @type {string} */
payment_request: orderResponse.response payment_request: orderResponse.response,
max_parts: maxParts,
timeout_seconds: timeoutSeconds,
no_inflight_updates: true,
fee_limit_sat: feeLimit
} }
/** @type {string} */ const preimage = await new Promise((res, rej) => {
const preimage = await new Promise((resolve, rej) => { const sentPaymentStream = router.sendPaymentV2(sendPaymentV2Args)
lightning.sendPaymentSync(sendPaymentSyncArgs, ( /**
/** @type {SendErr=} */ err, * @param {SendResponse} response
/** @type {SendResponse} */ res */
) => { const dataCB = response => {
if (err) { logger.info('SendPayment Data:', response)
rej(new Error(err.details)) if (response.failure_reason !== 'FAILURE_REASON_NONE') {
} else if (res) { rej(new Error(response.failure_reason))
if (res.payment_error) {
rej(
new Error(
`sendPaymentSync error response: ${JSON.stringify(res)}`
)
)
} else if (!res.payment_route || !res.payment_preimage) {
rej(
new Error(
`sendPaymentSync no payment route response or preimage: ${JSON.stringify(
res
)}`
)
)
} else {
resolve(res.payment_preimage)
}
} else { } else {
rej(new Error('no error or response received from sendPaymentSync')) res(response.payment_preimage)
} }
}) }
sentPaymentStream.on('data', dataCB)
/**
*
* @param {SendErr} err
*/
const errCB = err => {
logger.error('SendPayment Error:', err)
rej(err.details)
}
sentPaymentStream.on('error', errCB)
}) })
if (Utils.successfulHandshakeAlreadyExists(to)) { if (Utils.successfulHandshakeAlreadyExists(to)) {

View file

@ -1446,7 +1446,12 @@ module.exports = async (
const { router } = LightningServices.services const { router } = LightningServices.services
// this is the recommended value from lightning labs // this is the recommended value from lightning labs
let paymentRequest = {} let paymentRequest = {}
const { keysend, maxParts = 3, timeoutSeconds = 5 } = req.body const { keysend, maxParts = 3, timeoutSeconds = 5, feeLimit } = req.body
if (!feeLimit) {
return res.status(500).json({
errorMessage: 'please provide a "feeLimit" to the send payment request'
})
}
if (keysend) { if (keysend) {
const { dest, amt, finalCltvDelta = 40 } = req.body const { dest, amt, finalCltvDelta = 40 } = req.body
if (!dest || !amt) { if (!dest || !amt) {
@ -1474,7 +1479,8 @@ module.exports = async (
payment_hash: r_hash, payment_hash: r_hash,
max_parts: maxParts, max_parts: maxParts,
timeout_seconds: timeoutSeconds, timeout_seconds: timeoutSeconds,
no_inflight_updates: true no_inflight_updates: true,
fee_limit_sat: feeLimit
} }
} else { } else {
const { payreq } = req.body const { payreq } = req.body
@ -1483,7 +1489,8 @@ module.exports = async (
payment_request: payreq, payment_request: payreq,
max_parts: maxParts, max_parts: maxParts,
timeout_seconds: timeoutSeconds, timeout_seconds: timeoutSeconds,
no_inflight_updates: true no_inflight_updates: true,
fee_limit_sat: feeLimit
} }
if (req.body.amt) { if (req.body.amt) {
@ -1493,25 +1500,14 @@ module.exports = async (
logger.info('Sending payment', paymentRequest) logger.info('Sending payment', paymentRequest)
const sentPayment = router.sendPaymentV2(paymentRequest) const sentPayment = router.sendPaymentV2(paymentRequest)
let finalEvent = null //Object to send to the socket, depends on final event from the stream
sentPayment.on('data', response => { sentPayment.on('data', response => {
if (res.headersSent) { logger.info('SendPayment Data:', response)
//if res was already sent if (response.failure_reason !== 'FAILURE_REASON_NONE') {
if (response.status !== 'SUCCEEDED') { res.status(500).json({
//if the operation failed errorMessage: response.failure_reason
logger.error('Sen payment failure', response.details) })
} else {
finalEvent = { status: response.status }
}
} else { } else {
if (response.status !== 'SUCCEEDED') { res.json(response)
logger.error('Sen payment failure', response.details)
return res.status(500).json({
errorMessage: sanitizeLNDError(response.details)
})
}
logger.info('SendPayment Data:', response)
return res.json(response)
} }
}) })
@ -1521,26 +1517,17 @@ module.exports = async (
sentPayment.on('error', async err => { sentPayment.on('error', async err => {
logger.error('SendPayment Error:', err) logger.error('SendPayment Error:', err)
if (res.headersSent) { const health = await checkHealth()
logger.error('Sen payment failure', err) if (health.LNDStatus.success) {
res.status(500).json({
errorMessage: sanitizeLNDError(err.details)
})
} else { } else {
const health = await checkHealth() res.status(500)
if (health.LNDStatus.success) { res.json({ errorMessage: 'LND is down' })
res.status(500).json({
errorMessage: sanitizeLNDError(err)
})
} else {
res.status(500)
res.json({ errorMessage: 'LND is down' })
}
}
})
sentPayment.on('end', () => {
if (finalEvent !== null) {
//send the last event got from the stream
//TO DO send finalEvent on socket
} }
}) })
//sentPayment.on('end', () => {})
}) })
app.post('/api/lnd/trackpayment', (req, res) => { app.post('/api/lnd/trackpayment', (req, res) => {
@ -2026,6 +2013,53 @@ module.exports = async (
}) })
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
app.post(`/api/gun/sendpayment`, async (req, res) => {
const {
recipientPub,
amount,
memo,
maxParts,
timeoutSeconds,
feeLimit,
sessionUuid
} = req.body
logger.info('handling spont pay')
if (!feeLimit) {
logger.error(
'please provide a "feeLimit" to the send spont payment request'
)
return res.status(500).json({
errorMessage:
'please provide a "feeLimit" to the send spont payment request'
})
}
if (!recipientPub || !amount) {
logger.info(
'please provide a "recipientPub" and "amount" to the send spont payment request'
)
return res.status(500).json({
errorMessage:
'please provide a "recipientPub" and "amount" to the send spont payment request'
})
}
try {
const preimage = await GunActions.sendPayment(
recipientPub,
amount,
memo,
feeLimit,
maxParts,
timeoutSeconds
)
res.json({ preimage, sessionUuid })
} catch (err) {
logger.info('spont pay err:', err)
return res.status(500).json({
errorMessage: err.message
})
}
})
app.get(`/api/gun/wall/:publicKey?`, async (req, res) => { app.get(`/api/gun/wall/:publicKey?`, async (req, res) => {
try { try {
const { page } = req.query const { page } = req.query