Skip to content

Commit

Permalink
implement context maze
Browse files Browse the repository at this point in the history
  • Loading branch information
riccardobl committed Jan 7, 2025
1 parent 5cd447b commit ac791ac
Show file tree
Hide file tree
Showing 23 changed files with 125 additions and 120 deletions.
8 changes: 8 additions & 0 deletions api/lib/bolt/bolt11.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable camelcase */
import { payViaPaymentRequest, parsePaymentRequest } from 'ln-service'
import { isBolt11 } from '@/lib/bolt/bolt11-tags'
import { estimateRouteFee } from '@/api/lnd'
export { isBolt11 }

export async function parseBolt11 ({ request }) {
Expand All @@ -9,6 +10,7 @@ export async function parseBolt11 ({ request }) {
}

export async function payBolt11 ({ lnd, request, max_fee, max_fee_mtokens, ...args }) {
if (!lnd) throw new Error('lnd required') // check if forgot to pass lnd
if (!isBolt11(request)) throw new Error('not a bolt11 invoice')
return payViaPaymentRequest({
lnd,
Expand All @@ -18,3 +20,9 @@ export async function payBolt11 ({ lnd, request, max_fee, max_fee_mtokens, ...ar
...args
})
}

export async function estimateBolt11RouteFee ({ lnd, destination, tokens, mtokens, request, timeout }) {
if (!lnd) throw new Error('lnd required') // check if forgot to pass lnd
if (request && !isBolt11(request)) throw new Error('not a bolt11 request')
return await estimateRouteFee({ lnd, destination, tokens, mtokens, request, timeout })
}
28 changes: 24 additions & 4 deletions api/lib/bolt/bolt12.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,39 @@
import { payViaBolt12PaymentRequest, decodeBolt12Invoice } from '@/api/lib/lndk'
import { isBolt12Invoice, isBolt12Offer, isBolt12 } from '@/lib/bolt/bolt12-info'
import { toPositiveNumber } from '@/lib/format'
import { estimateRouteFee } from '@/api/lnd'
export { isBolt12Invoice, isBolt12Offer, isBolt12 }

export async function payBolt12 ({ lnd, request: invoice, max_fee, max_fee_mtokens }) {
export async function payBolt12 ({ lndk, request: invoice, max_fee, max_fee_mtokens }) {
if (!lndk) throw new Error('lndk required') // check if forgot to pass lndk
if (!isBolt12Invoice(invoice)) throw new Error('not a bolt12 invoice')
return await payViaBolt12PaymentRequest({ lnd, request: invoice, max_fee, max_fee_mtokens })
return await payViaBolt12PaymentRequest({ lndk, request: invoice, max_fee, max_fee_mtokens })
}

export async function parseBolt12 ({ lnd, request: invoice }) {
export async function parseBolt12 ({ lndk, request: invoice }) {
if (!lndk) throw new Error('lndk required') // check if forgot to pass lndk
if (!isBolt12Invoice(invoice)) throw new Error('not a bolt12 request')
const decodedInvoice = await decodeBolt12Invoice({ lnd, request: invoice })
const decodedInvoice = await decodeBolt12Invoice({ lndk, request: invoice })
return convertBolt12RequestToLNRequest(decodedInvoice)
}

export async function estimateBolt12RouteFee ({ lnd, lndk, destination, tokens, mtokens, request, timeout }) {
if (!lndk) throw new Error('lndk required') // check if forgot to pass lndk
if (!lnd) throw new Error('lnd required') // check if forgot to pass lnd
if (request && !isBolt12Invoice(request)) throw new Error('not a bolt12 request')

const { amount_msats, node_id } = request ? await decodeBolt12Invoice({ lndk, request }) : {}

// extract mtokens and destination from invoice if they are not provided
if (!tokens && !mtokens) mtokens = toPositiveNumber(amount_msats)
destination ??= Buffer.from(node_id.key).toString('hex')

if (!destination) throw new Error('no destination provided')
if (!tokens && !mtokens) throw new Error('no tokens amount provided')

return await estimateRouteFee({ lnd, destination, tokens, mtokens, timeout })
}

const featureBitTypes = {
0: 'DATALOSS_PROTECT_REQ',
1: 'DATALOSS_PROTECT_OPT',
Expand Down
17 changes: 8 additions & 9 deletions api/lib/bolt/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
/* eslint-disable camelcase */
import { payBolt12, parseBolt12, isBolt12Invoice, isBolt12Offer } from '@/api/lib/bolt/bolt12'
import { payBolt11, parseBolt11, isBolt11 } from '@/api/lib/bolt/bolt11'
import { payBolt11, parseBolt11, isBolt11, estimateBolt11RouteFee } from '@/api/lib/bolt/bolt11'
import { estimateBolt12RouteFee } from '@/api/lib/lndk'
import { estimateRouteFee } from '@/api/lnd'

export async function payInvoice ({ lnd, request: invoice, max_fee, max_fee_mtokens, ...args }) {
export async function payInvoice ({ lnd, lndk, request: invoice, max_fee, max_fee_mtokens, ...args }) {
if (isBolt12Invoice(invoice)) {
return await payBolt12({ lnd, request: invoice, max_fee, max_fee_mtokens, ...args })
return await payBolt12({ lndk, request: invoice, max_fee, max_fee_mtokens, ...args })
} else if (isBolt11(invoice)) {
return await payBolt11({ lnd, request: invoice, max_fee, max_fee_mtokens, ...args })
} else if (isBolt12Offer(invoice)) {
Expand All @@ -16,9 +15,9 @@ export async function payInvoice ({ lnd, request: invoice, max_fee, max_fee_mtok
}
}

export async function parseInvoice ({ lnd, request }) {
export async function parseInvoice ({ lndk, request }) {
if (isBolt12Invoice(request)) {
return await parseBolt12({ lnd, request })
return await parseBolt12({ lndk, request })
} else if (isBolt11(request)) {
return await parseBolt11({ request })
} else if (isBolt12Offer(request)) {
Expand All @@ -28,11 +27,11 @@ export async function parseInvoice ({ lnd, request }) {
}
}

export async function estimateFees ({ lnd, destination, tokens, mtokens, request, timeout }) {
export async function estimateFees ({ lnd, lndk, destination, tokens, mtokens, request, timeout }) {
if (isBolt12Invoice(request)) {
return await estimateBolt12RouteFee({ lnd, destination, tokens, mtokens, request, timeout })
return await estimateBolt12RouteFee({ lnd, lndk, destination, tokens, mtokens, request, timeout })
} else if (isBolt11(request)) {
return await estimateRouteFee({ lnd, destination, tokens, request, mtokens, timeout })
return await estimateBolt11RouteFee({ lnd, destination, tokens, request, mtokens, timeout })
} else if (isBolt12Offer(request)) {
throw new Error('bolt12 offer instead of invoice, please fetch a bolt12 invoice from the offer first')
} else {
Expand Down
47 changes: 7 additions & 40 deletions api/lib/lndk.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,12 @@ import protobuf from 'protobufjs'
import grpcCredentials from 'lightning/lnd_grpc/grpc_credentials'
import { grpcSslCipherSuites } from 'lightning/grpc/index'
import { fromJSON } from '@grpc/proto-loader'
import { estimateRouteFee } from '@/api/lnd'
import * as bech32b12 from '@/lib/bech32b12'

/* eslint-disable camelcase */
const { GRPC_SSL_CIPHER_SUITES } = process.env

const lndkInstances = new WeakMap()

export function enableLNDK (lnd, { cert, macaroon, socket: lndkSocket }, withProxy) {
// already installed
if (lndkInstances.has(lnd)) return
console.log('enabling lndk', lndkSocket, 'withProxy', withProxy)

export function authenticatedLndkGrpc ({ cert, macaroon, socket: lndkSocket }, withProxy) {
// workaround to load from string
const protoArgs = { keepCase: true, longs: Number, defaults: true, oneofs: true }
const proto = protobuf.parse(LNDK_RPC_PROTO, protoArgs).root
Expand All @@ -38,22 +31,13 @@ export function enableLNDK (lnd, { cert, macaroon, socket: lndkSocket }, withPro
}

const client = new OffersService(lndkSocket, credentials, params)
lndkInstances.set(lnd, client)
}

export function getLNDK (lnd) {
if (!lndkInstances.has(lnd)) {
throw new Error('lndk not available, please use enableLNDK first')
}
return lndkInstances.get(lnd)
return client
}

export async function decodeBolt12Invoice ({
lnd,
lndk,
request
}) {
const lndk = getLNDK(lnd)

// decode bech32 bolt12 invoice to hex string
if (!request.startsWith('lni1')) throw new Error('not a valid bech32 encoded bolt12 invoice')
const invoice_hex_str = bech32b12.decode(request.slice(4)).toString('hex')
Expand All @@ -70,9 +54,7 @@ export async function decodeBolt12Invoice ({
return { ...decodedRequest, invoice_hex_str }
}

export async function fetchBolt12InvoiceFromOffer ({ lnd, offer, msats, description, timeout = 10_000 }) {
const lndk = getLNDK(lnd)

export async function fetchBolt12InvoiceFromOffer ({ lndk, offer, msats, description, timeout = 10_000 }) {
return new Promise((resolve, reject) => {
lndk.GetInvoice({
offer,
Expand All @@ -87,7 +69,7 @@ export async function fetchBolt12InvoiceFromOffer ({ lnd, offer, msats, descript
const bech32invoice = 'lni1' + bech32b12.encode(Buffer.from(response.invoice_hex_str, 'hex'))

// sanity check
const { amount_msats } = await decodeBolt12Invoice({ lnd, request: bech32invoice })
const { amount_msats } = await decodeBolt12Invoice({ lndk, request: bech32invoice })
if (toPositiveNumber(amount_msats) !== toPositiveNumber(msats)) {
return reject(new Error('invalid invoice response'))
}
Expand All @@ -101,14 +83,12 @@ export async function fetchBolt12InvoiceFromOffer ({ lnd, offer, msats, descript
}

export async function payViaBolt12PaymentRequest ({
lnd,
lndk,
request: invoiceBech32,
max_fee,
max_fee_mtokens
}) {
const lndk = getLNDK(lnd)

const { amount_msats, invoice_hex_str } = await decodeBolt12Invoice({ lnd, request: invoiceBech32 })
const { amount_msats, invoice_hex_str } = await decodeBolt12Invoice({ lndk, request: invoiceBech32 })

if (!max_fee_mtokens && max_fee) {
max_fee_mtokens = toPositiveNumber(satsToMsats(max_fee))
Expand All @@ -130,16 +110,3 @@ export async function payViaBolt12PaymentRequest ({
})
})
}

export async function estimateBolt12RouteFee ({ lnd, destination, tokens, mtokens, request, timeout }) {
const { amount_msats, node_id } = request ? await decodeBolt12Invoice({ lnd, request }) : {}

// extract mtokens and destination from invoice if they are not provided
if (!tokens && !mtokens) mtokens = toPositiveNumber(amount_msats)
destination ??= Buffer.from(node_id.key).toString('hex')

if (!destination) throw new Error('no destination provided')
if (!tokens && !mtokens) throw new Error('no tokens amount provided')

return await estimateRouteFee({ lnd, destination, tokens, mtokens, timeout })
}
4 changes: 2 additions & 2 deletions api/lnd/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { cachedFetcher } from '@/lib/fetch'
import { toPositiveNumber } from '@/lib/format'
import { authenticatedLndGrpc } from '@/lib/lnd'
import { enableLNDK } from '@/api/lib/lndk'
import { authenticatedLndkGrpc } from '@/api/lib/lndk'
import { getIdentity, getHeight, getWalletInfo, getNode, getPayment } from 'ln-service'
import { datePivot } from '@/lib/time'
import { LND_PATHFINDING_TIMEOUT_MS } from '@/lib/constants'
Expand All @@ -11,7 +11,7 @@ const lnd = global.lnd || authenticatedLndGrpc({
macaroon: process.env.LND_MACAROON,
socket: process.env.LND_SOCKET
}).lnd
enableLNDK(lnd, {
export const lndk = authenticatedLndkGrpc({
cert: process.env.LNDK_CERT,
macaroon: process.env.LNDK_MACAROON,
socket: process.env.LNDK_SOCKET
Expand Down
1 change: 1 addition & 0 deletions api/paidAction/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ All functions have the following signature: `function(args: Object, context: Obj
- `tx`: the current transaction (for anything that needs to be done atomically with the payment)
- `models`: the current prisma client (for anything that doesn't need to be done atomically with the payment)
- `lnd`: the current lnd client
- `lndk`: the current lndk client

## Recording Cowboy Credits

Expand Down
8 changes: 4 additions & 4 deletions api/paidAction/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ async function beginPessimisticAction (actionType, args, context) {
async function performP2PAction (actionType, args, incomingContext) {
// if the action has an invoiceable peer, we'll create a peer invoice
// wrap it, and return the wrapped invoice
const { cost, sybilFeePercent, models, lnd, me } = incomingContext
const { cost, sybilFeePercent, models, lnd, lndk, me } = incomingContext
if (!sybilFeePercent) {
throw new Error('sybil fee percent is not set for an invoiceable peer action')
}
Expand All @@ -233,7 +233,7 @@ async function performP2PAction (actionType, args, incomingContext) {
feePercent: sybilFeePercent,
description,
expiry: INVOICE_EXPIRE_SECS
}, { models, me, lnd })
}, { models, me, lnd, lndk })

context = {
...incomingContext,
Expand All @@ -257,7 +257,7 @@ async function performP2PAction (actionType, args, incomingContext) {
// we don't need to use the module for perform-ing outside actions
// because we can't track the state of outside invoices we aren't paid/paying
async function performDirectAction (actionType, args, incomingContext) {
const { models, lnd, cost } = incomingContext
const { models, lnd, cost, lndk } = incomingContext
const { comment, lud18Data, noteStr, description: actionDescription } = args

const userId = await paidActions[actionType]?.getInvoiceablePeer?.(args, incomingContext)
Expand All @@ -276,7 +276,7 @@ async function performDirectAction (actionType, args, incomingContext) {
description,
expiry: INVOICE_EXPIRE_SECS,
supportBolt12: false // direct payment is not supported to bolt12 for compatibility reasons
}, { models, lnd })
}, { models, lnd, lndk })
} catch (e) {
console.error('failed to create outside invoice', e)
throw new NonInvoiceablePeerError()
Expand Down
5 changes: 3 additions & 2 deletions api/payingAction/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import { payInvoice, parseInvoice } from '@/api/lib/bolt'
// paying actions are completely distinct from paid actions
// and there's only one paying action: send
// ... still we want the api to at least be similar
export default async function performPayingAction ({ bolt11, maxFee, walletId }, { me, models, lnd }) {
export default async function performPayingAction ({ bolt11, maxFee, walletId }, { me, models, lnd, lndk }) {
try {
console.group('performPayingAction', `${bolt11.slice(0, 10)}...`, maxFee, walletId)

if (!me) {
throw new Error('You must be logged in to perform this action')
}

const decoded = await parseInvoice({ request: bolt11, lnd })
const decoded = await parseInvoice({ request: bolt11, lnd, lndk })
const cost = toPositiveBigInt(toPositiveBigInt(decoded.mtokens) + satsToMsats(maxFee))

console.log('cost', cost)
Expand Down Expand Up @@ -42,6 +42,7 @@ export default async function performPayingAction ({ bolt11, maxFee, walletId },

payInvoice({
lnd,
lndk,
request: withdrawal.bolt11,
max_fee: msatsToSats(withdrawal.msatsFeePaying),
pathfinding_timeout: LND_PATHFINDING_TIMEOUT_MS,
Expand Down
8 changes: 4 additions & 4 deletions api/resolvers/item.js
Original file line number Diff line number Diff line change
Expand Up @@ -945,7 +945,7 @@ export default {

return await performPaidAction('POLL_VOTE', { id }, { me, models, lnd })
},
act: async (parent, { id, sats, act = 'TIP', hasSendWallet }, { me, models, lnd, headers }) => {
act: async (parent, { id, sats, act = 'TIP', hasSendWallet }, { me, models, lnd, lndk, headers }) => {
assertApiKeyNotPermitted({ me })
await validateSchema(actSchema, { sats, act })
await assertGofacYourself({ models, headers })
Expand Down Expand Up @@ -979,11 +979,11 @@ export default {
}

if (act === 'TIP') {
return await performPaidAction('ZAP', { id, sats, hasSendWallet }, { me, models, lnd })
return await performPaidAction('ZAP', { id, sats, hasSendWallet }, { me, models, lnd, lndk })
} else if (act === 'DONT_LIKE_THIS') {
return await performPaidAction('DOWN_ZAP', { id, sats }, { me, models, lnd })
return await performPaidAction('DOWN_ZAP', { id, sats }, { me, models, lnd, lndk })
} else if (act === 'BOOST') {
return await performPaidAction('BOOST', { id, sats }, { me, models, lnd })
return await performPaidAction('BOOST', { id, sats }, { me, models, lnd, lndk })
} else {
throw new GqlInputError('unknown act')
}
Expand Down
4 changes: 2 additions & 2 deletions api/resolvers/paidAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default {
}
},
Mutation: {
retryPaidAction: async (parent, { invoiceId }, { models, me, lnd }) => {
retryPaidAction: async (parent, { invoiceId }, { models, me, lnd, lndk }) => {
if (!me) {
throw new Error('You must be logged in')
}
Expand All @@ -67,7 +67,7 @@ export default {
throw new Error(`Invoice is not in failed state: ${invoice.actionState}`)
}

const result = await retryPaidAction(invoice.actionType, { invoice }, { models, me, lnd })
const result = await retryPaidAction(invoice.actionType, { invoice }, { models, me, lnd, lndk })

return {
...result,
Expand Down
Loading

0 comments on commit ac791ac

Please sign in to comment.