Skip to content
This repository has been archived by the owner on Nov 17, 2023. It is now read-only.

Commit

Permalink
refactor: clean up send payment logic
Browse files Browse the repository at this point in the history
  • Loading branch information
mrfelton committed Mar 29, 2020
1 parent dc9c736 commit 5fa4ba9
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 65 deletions.
129 changes: 66 additions & 63 deletions renderer/reducers/payment/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { fetchChannels } from 'reducers/channels'
import { infoSelectors } from 'reducers/info'
import { showError } from 'reducers/notification'
import messages from './messages'
import paymentSelectors from './selectors'
import { paymentsSending } from './selectors'
import * as constants from './constants'

const {
Expand Down Expand Up @@ -126,12 +126,12 @@ export const fetchPayments = () => async dispatch => {
/**
* updatePayment - Updates specified payment request.
*
* @param {string} paymentRequest Payment request
* @param {string} paymentHash Payment hash
* @returns {Function} Thunk
*/
export const updatePayment = paymentRequest => async dispatch => {
export const updatePayment = paymentHash => async dispatch => {
const { payments } = await grpc.services.Lightning.listPayments()
const payment = payments.find(p => p.paymentRequest === paymentRequest)
const payment = payments.find(p => p.paymentHash === paymentHash)
if (payment) {
dispatch(receivePayments([payment]))
}
Expand All @@ -140,26 +140,26 @@ export const updatePayment = paymentRequest => async dispatch => {
/**
* paymentSuccessful - Success handler for payInvoice.
*
* @param {{paymentRequest}} paymentRequest Payment request
* @param {{paymentId}} paymentId Payment id (internal)
* @returns {Function} Thunk
*/
export const paymentSuccessful = ({ paymentId }) => async (dispatch, getState) => {
const paymentSending = find(paymentSelectors.paymentsSendingSelector(getState()), { paymentId })
const paymentSending = find(paymentsSending(getState()), { paymentId })

// If we found a related entry in paymentsSending, gracefully remove it and handle as success case.
if (paymentSending) {
const { creationDate, paymentRequest } = paymentSending
const { creationDate, paymentHash } = paymentSending

// Ensure payment stays in sending state for at least 2 seconds.
await delay(2000 - (Date.now() - creationDate * 1000))
// Ensure payment stays in sending state for at least 1 second.
await delay(1000 - (Date.now() - creationDate * 1000))

// Mark the payment as successful.
dispatch({ type: PAYMENT_SUCCESSFUL, paymentId })

// Wait for another second.
await delay(1500)
// Wait for another 3 seconds.
await delay(3000)

dispatch(updatePayment(paymentRequest))
dispatch(updatePayment(paymentHash))
}

// Fetch new balance.
Expand All @@ -178,7 +178,7 @@ export const paymentSuccessful = ({ paymentId }) => async (dispatch, getState) =
* @returns {Function} Thunk
*/
export const paymentFailed = (error, { paymentId }) => async (dispatch, getState) => {
const paymentSending = find(paymentSelectors.paymentsSendingSelector(getState()), { paymentId })
const paymentSending = find(paymentsSending(getState()), { paymentId })

// errors that trigger retry mechanism
const RETRIABLE_ERRORS = [
Expand All @@ -195,7 +195,7 @@ export const paymentFailed = (error, { paymentId }) => async (dispatch, getState
if (paymentSending) {
const { creationDate, paymentRequest, remainingRetries, maxRetries } = paymentSending
// if we have retries left and error is eligible for retry - rebroadcast payment
if (remainingRetries && RETRIABLE_ERRORS.includes(error)) {
if (paymentRequest && remainingRetries && RETRIABLE_ERRORS.includes(error)) {
const data = {
...paymentSending,
payReq: paymentRequest,
Expand All @@ -215,6 +215,42 @@ export const paymentFailed = (error, { paymentId }) => async (dispatch, getState
}
}

const getPaymentConfig = () => {
return {
allowSelfPayment: true,
timeoutSeconds: PAYMENT_TIMEOUT,
}
}

const prepareKeysendPayload = (pubkey, amt, feeLimit) => {
const preimage = generatePreimage()

return {
...getPaymentConfig(),
dest: Buffer.from(pubkey, 'hex'),
feeLimit: feeLimit ? { fixed: feeLimit } : null,
paymentHash: sha256digest(preimage),
amt,
finalCltvDelta: DEFAULT_CLTV_DELTA,
destCustomRecords: {
[KEYSEND_PREIMAGE_TYPE]: preimage,
},
}
}

const prepareBolt11Payload = (payReq, amt, feeLimit) => {
const invoice = decodePayReq(payReq)
const { millisatoshis } = invoice

return {
...getPaymentConfig(),
paymentRequest: invoice.paymentRequest,
paymentHash: getTag(invoice, 'payment_hash'), // hash is not needed in the payload but store for convienience.
feeLimit: feeLimit ? { fixed: feeLimit } : null,
amt: millisatoshis ? null : amt,
}
}

/**
* payInvoice - Pay a lightniung invoice.
* Controller code that wraps the send action and schedules automatic retries in the case of a failure.
Expand All @@ -223,8 +259,8 @@ export const paymentFailed = (error, { paymentId }) => async (dispatch, getState
* @param {string} options.payReq Payment request or node pubkey
* @param {number} options.amt Payment amount (in sats)
* @param {number} options.feeLimit The max fee to apply
* @param {number} options.retries Number of remaining retries
* @param {number} options.route Specific route to use.
* @param {number} options.retries Number of remaining retries
* @param {string} options.originalPaymentId Id of the original payment if (required if this is a payment retry)
* @returns {Function} Thunk
*/
Expand All @@ -238,47 +274,17 @@ export const payInvoice = ({
}) => async (dispatch, getState) => {
const paymentId = originalPaymentId || genId()
const isKeysend = isPubkey(payReq)
let pubkey
let paymentHash
let paymentRequest

let payload = {
paymentId,
feeLimit: feeLimit ? { fixed: feeLimit } : null,
allowSelfPayment: true,
}
let payload

// Keysend payment.
// Prepare payload for lnd.
if (isKeysend) {
pubkey = payReq
const preimage = generatePreimage()
paymentHash = sha256digest(preimage)

payload = {
...payload,
paymentHash,
amt,
finalCltvDelta: DEFAULT_CLTV_DELTA,
dest: Buffer.from(pubkey, 'hex'),
destCustomRecords: {
[KEYSEND_PREIMAGE_TYPE]: preimage,
},
}
payload = prepareKeysendPayload(payReq, amt, feeLimit)
} else {
payload = prepareBolt11Payload(payReq, amt, feeLimit)
}

// Bolt11 invoice payment.
else {
const invoice = decodePayReq(payReq)
const { millisatoshis } = invoice
paymentHash = getTag(invoice, 'payment_hash')
pubkey = invoice.payeeNodeKey
paymentRequest = invoice.paymentRequest // eslint-disable-line prefer-destructuring
payload = {
...payload,
amt: millisatoshis ? null : amt,
paymentRequest,
}
}
// Add id into the payload as a way to allow identification later.
payload.paymentId = paymentId

// If we already have an id then this is a retry. Decrease the retry count.
// Otherwise, add to paymentsSending in the state.
Expand All @@ -287,13 +293,16 @@ export const payInvoice = ({
} else {
dispatch(
sendPayment({
path: [pubkey],
paymentHash,
// path: [pubkey],
paymentHash:
route && route.isExact
? route.paymentHash.toString('hex')
: payload.paymentHash.toString('hex'),
paymentId,
feeLimit,
value: amt,
remainingRetries: retries,
paymentRequest,
paymentRequest: payload.paymentRequest,
maxRetries: retries,
isKeysend,
})
Expand All @@ -306,12 +315,6 @@ export const payInvoice = ({

// Use Router service if lnd version supports it.
if (infoSelectors.hasRouterSupport(getState())) {
payload = {
...payload,
feeLimit,
timeoutSeconds: PAYMENT_TIMEOUT,
}

// If we have been supplied with exact route, attempt to use that route.
if (route && route.isExact) {
let result = {}
Expand Down Expand Up @@ -354,8 +357,8 @@ export const payInvoice = ({

dispatch(paymentSuccessful(data))
} catch (e) {
const { payload: data, message: error } = e
dispatch(paymentFailed(error, data))
const { details, message } = e
dispatch(paymentFailed(message, details))
}
}

Expand Down
4 changes: 2 additions & 2 deletions services/grpc/lightning.methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ async function sendPayment(payload = {}) {
else {
grpcLog.error('PAYMENT ERROR', res)
const error = new Error(res.paymentError)
error.payload = res
error.details = res
reject(error)
}
call.end()
Expand All @@ -316,7 +316,7 @@ async function sendPayment(payload = {}) {
call.write(payload)
} catch (e) {
const error = new Error(e.message)
error.payload = res
error.details = res
throw error
}
})
Expand Down

0 comments on commit 5fa4ba9

Please sign in to comment.