From 86cb987f956a25bd1c3445540f5b1f326b2c6d05 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Sun, 17 Jan 2021 19:44:24 -0400 Subject: [PATCH 1/8] upgrade shock-common --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index d3a0b1fe..28331476 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "request-promise": "^4.2.6", "response-time": "^2.3.2", "shelljs": "^0.8.2", - "shock-common": "29.1.0", + "shock-common": "30.0.0", "socket.io": "2.1.1", "text-encoding": "^0.7.0", "tingodb": "^0.6.1", diff --git a/yarn.lock b/yarn.lock index a8777e20..22c9bbc9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6282,10 +6282,10 @@ shellwords@^0.1.1: resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== -shock-common@29.1.0: - version "29.1.0" - resolved "https://registry.yarnpkg.com/shock-common/-/shock-common-29.1.0.tgz#3b6d8613fb7c73b8b76c98293a14ec168a9dc888" - integrity sha512-O2tK+TShF3ioAdP4K33MB5QUDTmMqzz+pZe/HnSbi9q1DyX/zQ2Uluzol1NDE/6Z2SSnVFA7/2vJKGaCEdMKoQ== +shock-common@30.0.0: + version "30.0.0" + resolved "https://registry.yarnpkg.com/shock-common/-/shock-common-30.0.0.tgz#86ee39d076fe48adf551265c55e012ab3201c8e9" + integrity sha512-GgXCNOyk/iu0FBbYcfqOy7obX4RdEn3Q4y8R970CQVk7PvxYt0nZeQwKWe49esKb2B3JL1LOF/yJ7jg7tGze3w== dependencies: immer "^6.0.6" lodash "^4.17.19" From 7c2366d6186436dbb0388a38a0f83023ae065954 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 18 Jan 2021 15:23:20 -0400 Subject: [PATCH 2/8] coordinates key --- services/gunDB/contact-api/key.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/gunDB/contact-api/key.js b/services/gunDB/contact-api/key.js index 837ffc7d..210fae06 100644 --- a/services/gunDB/contact-api/key.js +++ b/services/gunDB/contact-api/key.js @@ -62,3 +62,5 @@ exports.TOTAL_TIPS = 'totalTips' exports.PROFILE_BINARY = 'profileBinary' exports.POSTS_NEW = 'posts' + +exports.COORDINATES = 'coordinates' From f19f04a97cc83b76be296a968961cb2a1d8af0ef Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 18 Jan 2021 15:42:59 -0400 Subject: [PATCH 3/8] basic coordinates --- services/coordinates.js | 63 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 services/coordinates.js diff --git a/services/coordinates.js b/services/coordinates.js new file mode 100644 index 00000000..79031940 --- /dev/null +++ b/services/coordinates.js @@ -0,0 +1,63 @@ +/** + * @format + */ + +const Common = require('shock-common') +const mapValues = require('lodash/mapValues') +const pickBy = require('lodash/pickBy') +const Bluebird = require('bluebird') +const Logger = require('winston') +const Key = require('../services/gunDB/contact-api/key') + +const { getUser, getMySecret, mySEA } = require('./gunDB/Mediator') + +/** + * @param {string} coordID + * @param {Common.Coordinate} data + * @returns {Promise} + */ +export const writeCoordinate = async (coordID, data) => { + if (coordID !== data.id) { + throw new Error('CoordID must be equal to data.id') + } + + try { + const gunNode = getUser() + .get(Key.COORDINATES) + .get(coordID) + + /** + * Because there are optional properties, typescript can also allow them + * to be specified but with a value of `undefined`. Filter out these. + * @type {Record} + */ + const sanitizedData = pickBy(data, v => typeof v !== 'undefined') + + const encData = await Bluebird.props( + mapValues(sanitizedData, v => { + return mySEA.encrypt(v, getMySecret()) + }) + ) + gunNode.put(encData, ack => { + if (ack.err && typeof ack.err !== 'number') { + Logger.info( + `Error writting corrdinate, coordinate id: ${coordID}, data: ${JSON.stringify( + data, + null, + 2 + )}` + ) + Logger.error(ack.err) + } + }) + } catch (e) { + Logger.info( + `Error writing coordinate, coordinate id: ${coordID}, data: ${JSON.stringify( + data, + null, + 2 + )}` + ) + Logger.error(e.message) + } +} From d67b6ef6740f8ea1eb1ccab8827f908871da7b23 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 18 Jan 2021 15:43:12 -0400 Subject: [PATCH 4/8] use basic coordinates --- utils/lightningServices/v2.js | 88 +++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 25 deletions(-) diff --git a/utils/lightningServices/v2.js b/utils/lightningServices/v2.js index b933c63b..db9f8d6b 100644 --- a/utils/lightningServices/v2.js +++ b/utils/lightningServices/v2.js @@ -6,6 +6,8 @@ const logger = require('winston') const Common = require('shock-common') const Ramda = require('ramda') +const { writeCoordinate } = require('../../services/coordinates') + const lightningServices = require('./lightning-services') /** * @typedef {import('./types').PaymentV2} PaymentV2 @@ -213,12 +215,39 @@ const isValidSendPaymentInvoiceParams = sendPaymentInvoiceParams => { return true } +/** + * @param {string} payReq + * @returns {Promise} + */ +const decodePayReq = payReq => + Common.Utils.makePromise((res, rej) => { + lightningServices.lightning.decodePayReq( + { pay_req: payReq }, + /** + * @param {{ message: any; }} err + * @param {any} paymentRequest + */ + (err, paymentRequest) => { + if (err) { + rej(new Error(err.message)) + } else { + res(paymentRequest) + } + } + ) + }) + +/** + * @returns {Promise} + */ +const myLNDPub = () => Promise.resolve('afjsjkhasdjkhajksd') + /** * aklssjdklasd * @param {SendPaymentV2Request} sendPaymentRequest * @returns {Promise} */ -const sendPaymentV2 = sendPaymentRequest => { +const sendPaymentV2 = async sendPaymentRequest => { const { services: { router } } = lightningServices @@ -229,7 +258,10 @@ const sendPaymentV2 = sendPaymentRequest => { ) } - return new Promise((res, rej) => { + /** + * @type {import("./types").PaymentV2} + */ + const paymentV2 = await Common.makePromise((res, rej) => { const stream = router.sendPaymentV2(sendPaymentRequest) stream.on( @@ -268,6 +300,33 @@ const sendPaymentV2 = sendPaymentRequest => { } ) }) + + /** @type {Common.Coordinate} */ + const coord = { + amount: Number(paymentV2.value_sat), + id: paymentV2.payment_hash, + inbound: false, + timestamp: Date.now(), + toLndPub: await myLNDPub(), + fromLndPub: undefined, + invoiceMemo: undefined, + type: 'payment' + } + + if (sendPaymentRequest.payment_request) { + const invoice = await decodePayReq(sendPaymentRequest.payment_request) + + coord.invoiceMemo = invoice.description + coord.toLndPub = invoice.destination + } + + if (sendPaymentRequest.dest) { + coord.toLndPub = sendPaymentRequest.dest.toString('base64') + } + + await writeCoordinate(paymentV2.payment_hash, coord) + + return paymentV2 } /** @@ -380,28 +439,6 @@ const listPayments = req => { }) } -/** - * @param {string} payReq - * @returns {Promise} - */ -const decodePayReq = payReq => - Common.Utils.makePromise((res, rej) => { - lightningServices.lightning.decodePayReq( - { pay_req: payReq }, - /** - * @param {{ message: any; }} err - * @param {any} paymentRequest - */ - (err, paymentRequest) => { - if (err) { - rej(new Error(err.message)) - } else { - res(paymentRequest) - } - } - ) - }) - /** * @param {0|1} type * @returns {Promise} @@ -582,5 +619,6 @@ module.exports = { getChanInfo, listPeers, pendingChannels, - addInvoice + addInvoice, + myLNDPub } From 16e90ebe4eee2efd22f96931235609480d5417cd Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 18 Jan 2021 15:44:06 -0400 Subject: [PATCH 5/8] use basic coordinates --- services/gunDB/contact-api/actions.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index e747deda..2d93fc29 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -11,7 +11,8 @@ const { ErrorCode } = Constants const { sendPaymentV2Invoice, - decodePayReq + decodePayReq, + myLNDPub } = require('../../../utils/lightningServices/v2') /** @@ -21,6 +22,7 @@ const { const Getters = require('./getters') const Key = require('./key') const Utils = require('./utils') +const { writeCoordinate } = require('../../coordinates') /** * @typedef {import('./SimpleGUN').GUNNode} GUNNode @@ -1074,6 +1076,26 @@ const sendSpontaneousPayment = async ( payment_request: orderResponse.response }) + await writeCoordinate(payment.payment_hash, { + id: payment.payment_hash, + type: (() => { + if (opts.type === 'post') { + return 'tip' + } else if (opts.type === 'user') { + return 'spontaneousPayment' + } + // ensures we handle all possible types + /** @type {never} */ + const assertNever = opts.type + + return assertNever && opts.type // please TS + })(), + amount: Number(payment.value_sat), + inbound: false, + timestamp: Date.now(), + toLndPub: await myLNDPub() + }) + return payment } catch (e) { logger.error('Error inside sendPayment()') From 9c1e980bd35acb7cc28ea86017de48c9df6bc08d Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 18 Jan 2021 15:44:33 -0400 Subject: [PATCH 6/8] use basic coordinates --- services/gunDB/contact-api/jobs/onOrders.js | 157 ++++++++------------ 1 file changed, 64 insertions(+), 93 deletions(-) diff --git a/services/gunDB/contact-api/jobs/onOrders.js b/services/gunDB/contact-api/jobs/onOrders.js index 5cc5e124..430c6e5f 100644 --- a/services/gunDB/contact-api/jobs/onOrders.js +++ b/services/gunDB/contact-api/jobs/onOrders.js @@ -2,7 +2,6 @@ * @format */ // @ts-check -const { performance } = require('perf_hooks') const logger = require('winston') const isFinite = require('lodash/isFinite') const isNumber = require('lodash/isNumber') @@ -14,7 +13,11 @@ const { } = Common const LightningServices = require('../../../../utils/lightningServices') - +const { + addInvoice, + myLNDPub +} = require('../../../../utils/lightningServices/v2') +const { writeCoordinate } = require('../../../coordinates') const Key = require('../key') const Utils = require('../utils') @@ -56,28 +59,6 @@ const ordersProcessed = new Set() let currentOrderAddr = '' -/** - * @param {InvoiceRequest} invoiceReq - * @returns {Promise} - */ -const _addInvoice = invoiceReq => - new Promise((resolve, rej) => { - const { - services: { lightning } - } = LightningServices - - lightning.addInvoice(invoiceReq, ( - /** @type {any} */ error, - /** @type {InvoiceResponse} */ response - ) => { - if (error) { - rej(error) - } else { - resolve(response) - } - }) - }) - /** * @param {string} addr * @param {ISEA} SEA @@ -104,8 +85,6 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => { return } - const listenerStartTime = performance.now() - ordersProcessed.add(orderID) logger.info( @@ -114,8 +93,6 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => { )} -- addr: ${addr}` ) - const orderAnswerStartTime = performance.now() - const alreadyAnswered = await getUser() .get(Key.ORDER_TO_RESPONSE) .get(orderID) @@ -126,12 +103,6 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => { return } - const orderAnswerEndTime = performance.now() - orderAnswerStartTime - - logger.info(`[PERF] Order Already Answered: ${orderAnswerEndTime}ms`) - - const decryptStartTime = performance.now() - const senderEpub = await Utils.pubToEpub(order.from) const secret = await SEA.secret(senderEpub, getUser()._.sea) @@ -140,10 +111,6 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => { SEA.decrypt(order.memo, secret) ]) - const decryptEndTime = performance.now() - decryptStartTime - - logger.info(`[PERF] Decrypt invoice info: ${decryptEndTime}ms`) - const amount = Number(decryptedAmount) if (!isNumber(amount)) { @@ -175,26 +142,19 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => { `onOrders() -> Will now create an invoice : ${JSON.stringify(invoiceReq)}` ) - const invoiceStartTime = performance.now() - - const invoice = await _addInvoice(invoiceReq) - - const invoiceEndTime = performance.now() - invoiceStartTime - - logger.info(`[PERF] LND Invoice created in ${invoiceEndTime}ms`) + const invoice = await addInvoice( + invoiceReq.value, + invoiceReq.memo, + true, + invoiceReq.expiry + ) logger.info( 'onOrders() -> Successfully created the invoice, will now encrypt it' ) - const invoiceEncryptStartTime = performance.now() - const encInvoice = await SEA.encrypt(invoice.payment_request, secret) - const invoiceEncryptEndTime = performance.now() - invoiceEncryptStartTime - - logger.info(`[PERF] Invoice encrypted in ${invoiceEncryptEndTime}ms`) - logger.info( `onOrders() -> Will now place the encrypted invoice in order to response usergraph: ${addr}` ) @@ -205,9 +165,7 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => { type: 'invoice' } - const invoicePutStartTime = performance.now() - - await new Promise((res, rej) => { + await /** @type {Promise} */ (new Promise((res, rej) => { getUser() .get(Key.ORDER_TO_RESPONSE) .get(orderID) @@ -223,9 +181,7 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => { res() } }) - }) - - const invoicePutEndTime = performance.now() - invoicePutStartTime + })) // invoices should be settled right away so we can rely on this single // subscription instead of life-long all invoices subscription @@ -234,46 +190,61 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => { if (!Common.isPopulatedString(postID)) { throw new TypeError(`postID not a a populated string`) } - - const { r_hash } = invoice - - // A post tip order lifecycle is short enough that we can do it like this. - const stream = LightningServices.invoices.subscribeSingleInvoice({ - r_hash - }) - - /** - * @param {Common.InvoiceWhenListed} invoice - */ - const onData = invoice => { - if (invoice.settled) { - getUser() - .get('postToTipCount') - .get(postID) - .set(null) // each item in the set is a tip - - stream.off() - } - } - - stream.on('data', onData) - - stream.on('status', (/** @type {any} */ status) => { - logger.info(`Post tip, post: ${postID}, invoice status:`, status) - }) - stream.on('end', () => { - logger.warn(`Post tip, post: ${postID}, invoice stream ended`) - }) - stream.on('error', (/** @type {any} */ e) => { - logger.warn(`Post tip, post: ${postID}, error:`, e) - }) } - logger.info(`[PERF] Added invoice to GunDB in ${invoicePutEndTime}ms`) + // A post tip order lifecycle is short enough that we can do it like this. + const stream = LightningServices.invoices.subscribeSingleInvoice({ + r_hash: invoice.r_hash + }) - const listenerEndTime = performance.now() - listenerStartTime + /** @type {Common.Coordinate} */ + const coord = { + amount, + id: invoice.r_hash.toString(), + inbound: true, + timestamp: Date.now(), + type: 'invoice', + invoiceMemo: memo, + fromGunPub: order.from, + toGunPub: getUser()._.sea.pub, + toLndPub: await myLNDPub() + } - logger.info(`[PERF] Invoice generation completed in ${listenerEndTime}ms`) + if (order.targetType === 'post') { + coord.type = 'tip' + } else { + coord.type = 'spontaneousPayment' + } + + /** + * @param {Common.InvoiceWhenListed} invoice + */ + const onData = invoice => { + if (invoice.settled) { + if (order.targetType === 'post') { + getUser() + .get('postToTipCount') + // CAST: Checked above. + .get(/** @type {string} */ (order.postID)) + .set(null) // each item in the set is a tip + } + + writeCoordinate(invoice.r_hash.toString(), coord) + stream.off() + } + } + + stream.on('data', onData) + + stream.on('status', (/** @type {any} */ status) => { + logger.info(`Post tip, post: ${order.postID}, invoice status:`, status) + }) + stream.on('end', () => { + logger.warn(`Post tip, post: ${order.postID}, invoice stream ended`) + }) + stream.on('error', (/** @type {any} */ e) => { + logger.warn(`Post tip, post: ${order.postID}, error:`, e) + }) } catch (err) { logger.error( `error inside onOrders, orderAddr: ${addr}, orderID: ${orderID}, order: ${JSON.stringify( From b4a24ce7fc7b2685c9022cb579f3aa4c8289bd4e Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 18 Jan 2021 16:37:18 -0400 Subject: [PATCH 7/8] implement lnd pub getter method --- utils/lightningServices/v2.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/utils/lightningServices/v2.js b/utils/lightningServices/v2.js index db9f8d6b..45a03a52 100644 --- a/utils/lightningServices/v2.js +++ b/utils/lightningServices/v2.js @@ -240,7 +240,18 @@ const decodePayReq = payReq => /** * @returns {Promise} */ -const myLNDPub = () => Promise.resolve('afjsjkhasdjkhajksd') +const myLNDPub = () => + Common.makePromise((res, rej) => { + const { lightning } = lightningServices.getServices() + + lightning.getInfo({}, (err, data) => { + if (err) { + rej(new Error(err.message)) + } else { + res(data.identity_pubkey) + } + }) + }) /** * aklssjdklasd From 61952b17f4b87dc5094dc0042774dae798da8b48 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 22 Feb 2021 08:57:10 -0400 Subject: [PATCH 8/8] simplify --- services/coordinates.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/services/coordinates.js b/services/coordinates.js index 79031940..ac212256 100644 --- a/services/coordinates.js +++ b/services/coordinates.js @@ -22,10 +22,6 @@ export const writeCoordinate = async (coordID, data) => { } try { - const gunNode = getUser() - .get(Key.COORDINATES) - .get(coordID) - /** * Because there are optional properties, typescript can also allow them * to be specified but with a value of `undefined`. Filter out these. @@ -38,18 +34,22 @@ export const writeCoordinate = async (coordID, data) => { return mySEA.encrypt(v, getMySecret()) }) ) - gunNode.put(encData, ack => { - if (ack.err && typeof ack.err !== 'number') { - Logger.info( - `Error writting corrdinate, coordinate id: ${coordID}, data: ${JSON.stringify( - data, - null, - 2 - )}` - ) - Logger.error(ack.err) - } - }) + + getUser() + .get(Key.COORDINATES) + .get(coordID) + .put(encData, ack => { + if (ack.err && typeof ack.err !== 'number') { + Logger.info( + `Error writting corrdinate, coordinate id: ${coordID}, data: ${JSON.stringify( + data, + null, + 2 + )}` + ) + Logger.error(ack.err) + } + }) } catch (e) { Logger.info( `Error writing coordinate, coordinate id: ${coordID}, data: ${JSON.stringify(