Merge pull request #666 from shocknet/moar-tests

better settings, 3rd node
This commit is contained in:
Justin (shocknet) 2024-04-05 13:50:27 -04:00 committed by GitHub
commit f8e05be6de
19 changed files with 152 additions and 102 deletions

View file

@ -4,14 +4,14 @@
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": " tsc && node build/src/tests/testRunner.js", "clean": "rimraf build",
"start": "tsc && node build/src/index.js", "test": "npm run clean && tsc && node build/src/tests/testRunner.js",
"start": "npm run clean && tsc && node build/src/index.js",
"start:ci": "git reset --hard && git pull && npm run start", "start:ci": "git reset --hard && git pull && npm run start",
"build_autogenerated": "cd proto && rimraf autogenerated && protoc -I ./service --pub_out=. service/*", "build_autogenerated": "cd proto && rimraf autogenerated && protoc -I ./service --pub_out=. service/*",
"build_lnd_client_1": "cd proto && protoc -I ./others --plugin=.\\node_modules\\.bin\\protoc-gen-ts_proto.cmd --ts_proto_out=./lnd --ts_proto_opt=esModuleInterop=true others/* ", "build_lnd_client_1": "cd proto && protoc -I ./others --plugin=.\\node_modules\\.bin\\protoc-gen-ts_proto.cmd --ts_proto_out=./lnd --ts_proto_opt=esModuleInterop=true others/* ",
"build_lnd_client": "cd proto && rimraf lnd/* && npx protoc --ts_out ./lnd --ts_opt long_type_string --proto_path others others/* ", "build_lnd_client": "cd proto && rimraf lnd/* && npx protoc --ts_out ./lnd --ts_opt long_type_string --proto_path others others/* ",
"typeorm": "typeorm-ts-node-commonjs", "typeorm": "typeorm-ts-node-commonjs"
"aa": "tsc && cd build && node src/services/nostr/index.js"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View file

@ -31,45 +31,3 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
}) })
return { Stop: () => nostr.Stop, Send: (...args) => nostr.Send(...args) } return { Stop: () => nostr.Stop, Send: (...args) => nostr.Send(...args) }
} }
/*
export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSettings: NostrSettings): Nostr => {
// TODO: - move to codegen
const nostr = new Nostr(nostrSettings,
async (event) => {
if (!nostrSettings.allowedPubs.includes(event.pub)) {
console.log("nostr pub not allowed")
return
}
let nostrUser = await mainHandler.storage.FindNostrUser(event.pub)
if (!nostrUser) {
nostrUser = await mainHandler.storage.AddNostrUser(event.pub)
}
let j: EventRequest
try {
j = JSON.parse(event.content)
} catch {
console.error("invalid json event received", event.content)
return
}
if (handledRequests.includes(j.requestId)) {
console.log("request already handled")
return
}
handledRequests.push(j.requestId)
switch (j.method) {
case '/api/user/chain/new':
const error = Types.NewAddressRequestValidate(j.body)
if (error !== null) {
console.error("invalid request from", event.pub, j)// TODO: dont dox
return // TODO: respond
}
if (!serverMethods.NewAddress) {
throw new Error("unimplemented NewInvoice")
}
const res = await serverMethods.NewAddress({ user_id: nostrUser.user.user_id }, j.body)
nostr.Send(event.pub, JSON.stringify({ ...res, requestId: j.requestId }))
}
})
return nostr
}*/

View file

@ -44,7 +44,7 @@ export const getLogger = (params: LoggerParams): PubLogger => {
if (params.userId) { if (params.userId) {
toLog.push(params.userId) toLog.push(params.userId)
} }
const parsed = message.map(m => typeof m === 'object' ? JSON.stringify(m) : m) const parsed = message.map(m => typeof m === 'object' ? JSON.stringify(m, (_, v) => typeof v === 'bigint' ? v.toString() : v) : m)
const final = `${toLog.join(" ")} >> ${parsed.join(" ")}` const final = `${toLog.join(" ")} >> ${parsed.join(" ")}`
console.log(final) console.log(final)
writers.forEach(w => w(final)) writers.forEach(w => w(final))

View file

@ -12,7 +12,7 @@ export const LoadLndSettingsFromEnv = (): LndSettings => {
const feeRateLimit = EnvMustBeInteger("OUTBOUND_MAX_FEE_BPS") / 10000 const feeRateLimit = EnvMustBeInteger("OUTBOUND_MAX_FEE_BPS") / 10000
const feeFixedLimit = EnvMustBeInteger("OUTBOUND_MAX_FEE_EXTRA_SATS") const feeFixedLimit = EnvMustBeInteger("OUTBOUND_MAX_FEE_EXTRA_SATS")
const mockLnd = EnvCanBeBoolean("MOCK_LND") const mockLnd = EnvCanBeBoolean("MOCK_LND")
return { lndAddr, lndCertPath, lndMacaroonPath, feeRateLimit, feeFixedLimit, mockLnd, otherLndAddr: "", otherLndCertPath: "", otherLndMacaroonPath: "" } return { mainNode: { lndAddr, lndCertPath, lndMacaroonPath }, feeRateLimit, feeFixedLimit, mockLnd }
} }
export interface LightningHandler { export interface LightningHandler {
Stop(): void Stop(): void

View file

@ -40,7 +40,7 @@ export default class {
this.invoicePaidCb = invoicePaidCb this.invoicePaidCb = invoicePaidCb
this.newBlockCb = newBlockCb this.newBlockCb = newBlockCb
this.htlcCb = htlcCb this.htlcCb = htlcCb
const { lndAddr, lndCertPath, lndMacaroonPath } = this.settings const { lndAddr, lndCertPath, lndMacaroonPath } = this.settings.mainNode
const lndCert = fs.readFileSync(lndCertPath); const lndCert = fs.readFileSync(lndCertPath);
const macaroon = fs.readFileSync(lndMacaroonPath).toString('hex'); const macaroon = fs.readFileSync(lndMacaroonPath).toString('hex');
const sslCreds = credentials.createSsl(lndCert); const sslCreds = credentials.createSsl(lndCert);

View file

@ -1,16 +1,17 @@
import { HtlcEvent } from "../../../proto/lnd/router" import { HtlcEvent } from "../../../proto/lnd/router"
export type NodeSettings = {
export type LndSettings = {
lndAddr: string lndAddr: string
lndCertPath: string lndCertPath: string
lndMacaroonPath: string lndMacaroonPath: string
}
export type LndSettings = {
mainNode: NodeSettings
feeRateLimit: number feeRateLimit: number
feeFixedLimit: number feeFixedLimit: number
mockLnd: boolean mockLnd: boolean
otherLndAddr: string otherNode?: NodeSettings
otherLndCertPath: string thirdNode?: NodeSettings
otherLndMacaroonPath: string
} }
type TxOutput = { type TxOutput = {
hash: string hash: string

View file

@ -189,7 +189,6 @@ export default class {
this.log("paying external invoice", invoice) this.log("paying external invoice", invoice)
const routingFeeLimit = this.lnd.GetFeeLimitAmount(payAmount) const routingFeeLimit = this.lnd.GetFeeLimitAmount(payAmount)
await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement + routingFeeLimit, invoice) await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement + routingFeeLimit, invoice)
console.log("decremented")
const pendingPayment = await this.storage.paymentStorage.AddPendingExternalPayment(userId, invoice, payAmount, linkedApplication) const pendingPayment = await this.storage.paymentStorage.AddPendingExternalPayment(userId, invoice, payAmount, linkedApplication)
try { try {
const payment = await this.lnd.PayInvoice(invoice, amountForLnd, routingFeeLimit) const payment = await this.lnd.PayInvoice(invoice, amountForLnd, routingFeeLimit)
@ -523,9 +522,8 @@ export default class {
} }
} }
async SendUserToUserPayment(fromUserId: string, toUserId: string, amount: number, linkedApplication: Application): Promise<number> { async SendUserToUserPayment(fromUserId: string, toUserId: string, amount: number, linkedApplication: Application): Promise<{ amount: number, fees: number }> {
let sentAmount = 0 const payment = await this.storage.StartTransaction(async tx => {
await this.storage.StartTransaction(async tx => {
const fromUser = await this.storage.userStorage.GetUser(fromUserId, tx) const fromUser = await this.storage.userStorage.GetUser(fromUserId, tx)
const toUser = await this.storage.userStorage.GetUser(toUserId, tx) const toUser = await this.storage.userStorage.GetUser(toUserId, tx)
if (fromUser.locked || toUser.locked) { if (fromUser.locked || toUser.locked) {
@ -536,21 +534,21 @@ export default class {
} }
const isAppUserPayment = fromUser.user_id !== linkedApplication.owner.user_id const isAppUserPayment = fromUser.user_id !== linkedApplication.owner.user_id
let fee = this.getServiceFee(Types.UserOperationType.OUTGOING_USER_TO_USER, amount, isAppUserPayment) let fee = this.getServiceFee(Types.UserOperationType.OUTGOING_USER_TO_USER, amount, isAppUserPayment)
const toIncrement = amount - fee const toDecrement = amount + fee
const paymentEntry = await this.storage.paymentStorage.CreateUserToUserPayment(fromUserId, toUserId, amount, fee, linkedApplication, tx) const paymentEntry = await this.storage.paymentStorage.AddPendingUserToUserPayment(fromUserId, toUserId, amount, fee, linkedApplication, tx)
await this.storage.userStorage.DecrementUserBalance(fromUser.user_id, amount, `${toUserId}:${paymentEntry.serial_id}`, tx) await this.storage.userStorage.DecrementUserBalance(fromUser.user_id, toDecrement, `${toUserId}:${paymentEntry.serial_id}`, tx)
await this.storage.userStorage.IncrementUserBalance(toUser.user_id, toIncrement, `${fromUserId}:${paymentEntry.serial_id}`, tx) await this.storage.userStorage.IncrementUserBalance(toUser.user_id, amount, `${fromUserId}:${paymentEntry.serial_id}`, tx)
await this.storage.paymentStorage.SaveUserToUserPayment(paymentEntry, tx) await this.storage.paymentStorage.SetPendingUserToUserPaymentAsPaid(paymentEntry.serial_id, tx)
if (isAppUserPayment && fee > 0) { if (isAppUserPayment && fee > 0) {
await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, fee, 'fees', tx) await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, fee, 'fees', tx)
} }
sentAmount = toIncrement return paymentEntry
}) })
const fromUser = await this.storage.userStorage.GetUser(fromUserId) const fromUser = await this.storage.userStorage.GetUser(fromUserId)
const toUser = await this.storage.userStorage.GetUser(toUserId) const toUser = await this.storage.userStorage.GetUser(toUserId)
this.storage.eventsLog.LogEvent({ type: 'u2u_sender', userId: fromUserId, appId: linkedApplication.app_id, appUserId: "", balance: fromUser.balance_sats, data: toUserId, amount: amount }) this.storage.eventsLog.LogEvent({ type: 'u2u_sender', userId: fromUserId, appId: linkedApplication.app_id, appUserId: "", balance: fromUser.balance_sats, data: toUserId, amount: payment.paid_amount + payment.service_fees })
this.storage.eventsLog.LogEvent({ type: 'u2u_receiver', userId: toUserId, appId: linkedApplication.app_id, appUserId: "", balance: toUser.balance_sats, data: fromUserId, amount: amount }) this.storage.eventsLog.LogEvent({ type: 'u2u_receiver', userId: toUserId, appId: linkedApplication.app_id, appUserId: "", balance: toUser.balance_sats, data: fromUserId, amount: amount })
return sentAmount return { amount: payment.paid_amount, fees: payment.service_fees }
} }
async CheckNewlyConfirmedTxs(height: number) { async CheckNewlyConfirmedTxs(height: number) {

View file

@ -2,6 +2,7 @@ import Storage from '../storage/index.js'
import { LightningHandler } from "../lnd/index.js" import { LightningHandler } from "../lnd/index.js"
import { LoggedEvent } from '../storage/eventsLog.js' import { LoggedEvent } from '../storage/eventsLog.js'
import { Invoice, Payment } from '../../../proto/lnd/lightning'; import { Invoice, Payment } from '../../../proto/lnd/lightning';
import { getLogger } from '../helpers/logger.js';
const LN_INVOICE_REGEX = /^(lightning:)?(lnbc|lntb)[0-9a-zA-Z]+$/; const LN_INVOICE_REGEX = /^(lightning:)?(lnbc|lntb)[0-9a-zA-Z]+$/;
const BITCOIN_ADDRESS_REGEX = /^(bitcoin:)?([13][a-km-zA-HJ-NP-Z1-9]{25,34}|bc1[a-zA-HJ-NP-Z0-9]{39,59})$/; const BITCOIN_ADDRESS_REGEX = /^(bitcoin:)?([13][a-km-zA-HJ-NP-Z1-9]{25,34}|bc1[a-zA-HJ-NP-Z0-9]{39,59})$/;
type UniqueDecrementReasons = 'ban' type UniqueDecrementReasons = 'ban'
@ -19,6 +20,7 @@ export default class SanityChecker {
incrementSources: Record<string, boolean> = {} incrementSources: Record<string, boolean> = {}
decrementSources: Record<string, boolean> = {} decrementSources: Record<string, boolean> = {}
decrementEvents: Record<string, { userId: string, refund: number, failure: boolean }> = {} decrementEvents: Record<string, { userId: string, refund: number, failure: boolean }> = {}
log = getLogger({ appName: "SanityChecker" })
users: Record<string, { ts: number, updatedBalance: number }> = {} users: Record<string, { ts: number, updatedBalance: number }> = {}
constructor(storage: Storage, lnd: LightningHandler) { constructor(storage: Storage, lnd: LightningHandler) {
this.storage = storage this.storage = storage
@ -201,7 +203,6 @@ export default class SanityChecker {
throw new Error("payment failled, should not refund routing fees " + invoice) throw new Error("payment failled, should not refund routing fees " + invoice)
} }
if (entry.refund !== amt) { if (entry.refund !== amt) {
console.log(entry.refund, amt)
throw new Error("refund amount mismatch for routing fee refund " + invoice) throw new Error("refund amount mismatch for routing fee refund " + invoice)
} }
} }
@ -251,9 +252,8 @@ export default class SanityChecker {
checkUserEntry(e: LoggedEvent, u: { ts: number, updatedBalance: number } | undefined) { checkUserEntry(e: LoggedEvent, u: { ts: number, updatedBalance: number } | undefined) {
const newEntry = { ts: e.timestampMs, updatedBalance: e.balance + e.amount * (e.type === 'balance_decrement' ? -1 : 1) } const newEntry = { ts: e.timestampMs, updatedBalance: e.balance + e.amount * (e.type === 'balance_decrement' ? -1 : 1) }
console.log(e)
if (!u) { if (!u) {
console.log(e.userId, "balance starts at", e.balance, "sats and moves by", e.amount * (e.type === 'balance_decrement' ? -1 : 1), "sats, resulting in", newEntry.updatedBalance, "sats") this.log(e.userId, "balance starts at", e.balance, "sats and moves by", e.amount * (e.type === 'balance_decrement' ? -1 : 1), "sats, resulting in", newEntry.updatedBalance, "sats")
return newEntry return newEntry
} }
if (e.timestampMs < u.ts) { if (e.timestampMs < u.ts) {
@ -262,7 +262,7 @@ export default class SanityChecker {
if (e.balance !== u.updatedBalance) { if (e.balance !== u.updatedBalance) {
throw new Error("inconsistent balance update got: " + e.balance + " expected " + u.updatedBalance) throw new Error("inconsistent balance update got: " + e.balance + " expected " + u.updatedBalance)
} }
console.log(e.userId, "balance updates from", e.balance, "sats and moves by", e.amount * (e.type === 'balance_decrement' ? -1 : 1), "sats, resulting in", newEntry.updatedBalance, "sats") this.log(e.userId, "balance updates from", e.balance, "sats and moves by", e.amount * (e.type === 'balance_decrement' ? -1 : 1), "sats, resulting in", newEntry.updatedBalance, "sats")
return newEntry return newEntry
} }
} }

View file

@ -1,5 +1,5 @@
import { LoadStorageSettingsFromEnv, StorageSettings } from '../storage/index.js' import { LoadStorageSettingsFromEnv, StorageSettings } from '../storage/index.js'
import { LndSettings } from '../lnd/settings.js' import { LndSettings, NodeSettings } from '../lnd/settings.js'
import { LoadWatchdogSettingsFromEnv, WatchdogSettings } from './watchdog.js' import { LoadWatchdogSettingsFromEnv, WatchdogSettings } from './watchdog.js'
import { LoadLndSettingsFromEnv } from '../lnd/index.js' import { LoadLndSettingsFromEnv } from '../lnd/index.js'
import { EnvMustBeInteger, EnvMustBeNonEmptyString } from '../helpers/envParser.js' import { EnvMustBeInteger, EnvMustBeNonEmptyString } from '../helpers/envParser.js'
@ -44,7 +44,7 @@ export const LoadMainSettingsFromEnv = (): MainSettings => {
} }
} }
export const LoadTestSettingsFromEnv = (): MainSettings => { export const LoadTestSettingsFromEnv = (): MainSettings & { lndSettings: { otherNode: NodeSettings, thirdNode: NodeSettings } } => {
const eventLogPath = `logs/eventLogV2Test${Date.now()}.csv` const eventLogPath = `logs/eventLogV2Test${Date.now()}.csv`
const settings = LoadMainSettingsFromEnv() const settings = LoadMainSettingsFromEnv()
return { return {
@ -52,9 +52,16 @@ export const LoadTestSettingsFromEnv = (): MainSettings => {
storageSettings: { dbSettings: { ...settings.storageSettings.dbSettings, databaseFile: ":memory:", metricsDatabaseFile: ":memory:" }, eventLogPath }, storageSettings: { dbSettings: { ...settings.storageSettings.dbSettings, databaseFile: ":memory:", metricsDatabaseFile: ":memory:" }, eventLogPath },
lndSettings: { lndSettings: {
...settings.lndSettings, ...settings.lndSettings,
otherLndAddr: EnvMustBeNonEmptyString("LND_OTHER_ADDR"), otherNode: {
otherLndCertPath: EnvMustBeNonEmptyString("LND_OTHER_CERT_PATH"), lndAddr: EnvMustBeNonEmptyString("LND_OTHER_ADDR"),
otherLndMacaroonPath: EnvMustBeNonEmptyString("LND_OTHER_MACAROON_PATH") lndCertPath: EnvMustBeNonEmptyString("LND_OTHER_CERT_PATH"),
lndMacaroonPath: EnvMustBeNonEmptyString("LND_OTHER_MACAROON_PATH")
},
thirdNode: {
lndAddr: EnvMustBeNonEmptyString("LND_THIRD_ADDR"),
lndCertPath: EnvMustBeNonEmptyString("LND_THIRD_CERT_PATH"),
lndMacaroonPath: EnvMustBeNonEmptyString("LND_THIRD_MACAROON_PATH")
}
}, },
skipSanityCheck: true skipSanityCheck: true
} }

View file

@ -56,13 +56,13 @@ process.on("message", (message: ChildProcessRequest) => {
sendToNostr(message.appId, message.data, message.relays) sendToNostr(message.appId, message.data, message.relays)
break break
default: default:
console.error("unknown nostr request", message) getLogger({ appName: "nostrMiddleware" })("ERROR", "unknown nostr request", message)
break break
} }
}) })
const initSubprocessHandler = (settings: NostrSettings) => { const initSubprocessHandler = (settings: NostrSettings) => {
if (subProcessHandler) { if (subProcessHandler) {
console.error("nostr settings ignored since handler already exists") getLogger({ appName: "nostrMiddleware" })("ERROR", "nostr settings ignored since handler already exists")
return return
} }
subProcessHandler = new Handler(settings, event => { subProcessHandler = new Handler(settings, event => {
@ -74,7 +74,7 @@ const initSubprocessHandler = (settings: NostrSettings) => {
} }
const sendToNostr: NostrSend = (appId, data, relays) => { const sendToNostr: NostrSend = (appId, data, relays) => {
if (!subProcessHandler) { if (!subProcessHandler) {
console.error("nostr was not initialized") getLogger({ appName: "nostrMiddleware" })("ERROR", "nostr was not initialized")
return return
} }
subProcessHandler.Send(appId, data, relays) subProcessHandler.Send(appId, data, relays)
@ -87,9 +87,10 @@ export default class Handler {
subs: Sub[] = [] subs: Sub[] = []
apps: Record<string, AppInfo> = {} apps: Record<string, AppInfo> = {}
eventCallback: (event: NostrEvent) => void eventCallback: (event: NostrEvent) => void
log = getLogger({ appName: "nostrMiddleware" })
constructor(settings: NostrSettings, eventCallback: (event: NostrEvent) => void) { constructor(settings: NostrSettings, eventCallback: (event: NostrEvent) => void) {
this.settings = settings this.settings = settings
console.log( this.log(
{ {
...settings, ...settings,
apps: settings.apps.map(app => { apps: settings.apps.map(app => {
@ -151,7 +152,7 @@ export default class Handler {
} }
const eventId = e.id const eventId = e.id
if (handledEvents.includes(eventId)) { if (handledEvents.includes(eventId)) {
console.log("event already handled") this.log("event already handled")
return return
} }
handledEvents.push(eventId) handledEvents.push(eventId)

View file

@ -41,7 +41,7 @@ export default class {
return { executedMigrations, executedMetricsMigrations }; return { executedMigrations, executedMetricsMigrations };
} }
StartTransaction(exec: TX<void>, description?: string) { StartTransaction<T>(exec: TX<T>, description?: string) {
return this.txQueue.PushToQueue({ exec, dbTx: true, description }) return this.txQueue.PushToQueue({ exec, dbTx: true, description })
} }
} }

View file

@ -283,18 +283,19 @@ export default class {
return found return found
} }
async CreateUserToUserPayment(fromUserId: string, toUserId: string, amount: number, fee: number, linkedApplication: Application, dbTx: DataSource | EntityManager) { async AddPendingUserToUserPayment(fromUserId: string, toUserId: string, amount: number, fee: number, linkedApplication: Application, dbTx: DataSource | EntityManager) {
return dbTx.getRepository(UserToUserPayment).create({ const entry = dbTx.getRepository(UserToUserPayment).create({
from_user: await this.userStorage.GetUser(fromUserId, dbTx), from_user: await this.userStorage.GetUser(fromUserId, dbTx),
to_user: await this.userStorage.GetUser(toUserId, dbTx), to_user: await this.userStorage.GetUser(toUserId, dbTx),
paid_at_unix: Math.floor(Date.now() / 1000), paid_at_unix: 0,
paid_amount: amount, paid_amount: amount,
service_fees: fee, service_fees: fee,
linkedApplication linkedApplication
}) })
return dbTx.getRepository(UserToUserPayment).save(entry)
} }
async SaveUserToUserPayment(payment: UserToUserPayment, dbTx: DataSource | EntityManager) { async SetPendingUserToUserPaymentAsPaid(serialId: number, dbTx: DataSource | EntityManager) {
return dbTx.getRepository(UserToUserPayment).save(payment) dbTx.getRepository(UserToUserPayment).update(serialId, { paid_at_unix: Math.floor(Date.now() / 1000) })
} }
GetUserToUserReceivedPayments(userId: string, fromIndex: number, take = 50, entityManager = this.DB) { GetUserToUserReceivedPayments(userId: string, fromIndex: number, take = 50, entityManager = this.DB) {

View file

@ -1,15 +1,17 @@
import { defaultInvoiceExpiry } from '../services/storage/paymentStorage.js' import { defaultInvoiceExpiry } from '../services/storage/paymentStorage.js'
import { Describe, expect, expectThrowsAsync, runSanityCheck, safelySetUserBalance, SetupTest, TestBase } from './testBase.js' import { Describe, expect, expectThrowsAsync, runSanityCheck, safelySetUserBalance, SetupTest, TestBase } from './testBase.js'
export const ignore = false export const ignore = false
export const dev = false
export default async (T: TestBase) => { export default async (T: TestBase) => {
await safelySetUserBalance(T, T.user1, 2000) await safelySetUserBalance(T, T.user1, 2000)
await testSuccessfulExternalPayment(T) await testSuccessfulExternalPayment(T)
await testFailedExternalPayment(T)
await runSanityCheck(T) await runSanityCheck(T)
} }
const testSuccessfulExternalPayment = async (T: TestBase) => { const testSuccessfulExternalPayment = async (T: TestBase) => {
T.d("starting testSuccessfulExternalPayment")
const application = await T.main.storage.applicationStorage.GetApplication(T.app.appId) const application = await T.main.storage.applicationStorage.GetApplication(T.app.appId)
const invoice = await T.externalAccessToOtherLnd.NewInvoice(500, "test", defaultInvoiceExpiry) const invoice = await T.externalAccessToOtherLnd.NewInvoice(500, "test", defaultInvoiceExpiry)
expect(invoice.payRequest).to.startWith("lnbcrt5u") expect(invoice.payRequest).to.startWith("lnbcrt5u")
@ -27,3 +29,19 @@ const testSuccessfulExternalPayment = async (T: TestBase) => {
} }
const testFailedExternalPayment = async (T: TestBase) => {
T.d("starting testFailedExternalPayment")
const application = await T.main.storage.applicationStorage.GetApplication(T.app.appId)
const invoice = await T.externalAccessToOtherLnd.NewInvoice(1500, "test", defaultInvoiceExpiry)
expect(invoice.payRequest).to.startWith("lnbcrt15u")
T.d("generated 1500 sats invoice for external node")
await expectThrowsAsync(T.main.paymentManager.PayInvoice(T.user1.userId, { invoice: invoice.payRequest, amount: 0 }, application), "not enough balance to decrement")
T.d("payment failed as expected, with the expected error message")
const u1 = await T.main.storage.userStorage.GetUser(T.user1.userId)
expect(u1.balance_sats).to.be.equal(1496)
T.d("user1 balance is still 1496")
const owner = await T.main.storage.userStorage.GetUser(application.owner.user_id)
expect(owner.balance_sats).to.be.equal(3)
T.d("app balance is still 3 sats")
}

View file

@ -10,6 +10,7 @@ export default async (T: TestBase) => {
} }
const testSuccessfulInternalPayment = async (T: TestBase) => { const testSuccessfulInternalPayment = async (T: TestBase) => {
T.d("starting testSuccessfulInternalPayment")
const application = await T.main.storage.applicationStorage.GetApplication(T.app.appId) const application = await T.main.storage.applicationStorage.GetApplication(T.app.appId)
const invoice = await T.main.paymentManager.NewInvoice(T.user2.userId, { amountSats: 1000, memo: "test" }, { linkedApplication: application, expiry: defaultInvoiceExpiry }) const invoice = await T.main.paymentManager.NewInvoice(T.user2.userId, { amountSats: 1000, memo: "test" }, { linkedApplication: application, expiry: defaultInvoiceExpiry })
expect(invoice.invoice).to.startWith("lnbcrt10u") expect(invoice.invoice).to.startWith("lnbcrt10u")
@ -29,6 +30,7 @@ const testSuccessfulInternalPayment = async (T: TestBase) => {
} }
const testFailedInternalPayment = async (T: TestBase) => { const testFailedInternalPayment = async (T: TestBase) => {
T.d("starting testFailedInternalPayment")
const application = await T.main.storage.applicationStorage.GetApplication(T.app.appId) const application = await T.main.storage.applicationStorage.GetApplication(T.app.appId)
const invoice = await T.main.paymentManager.NewInvoice(T.user2.userId, { amountSats: 1000, memo: "test" }, { linkedApplication: application, expiry: defaultInvoiceExpiry }) const invoice = await T.main.paymentManager.NewInvoice(T.user2.userId, { amountSats: 1000, memo: "test" }, { linkedApplication: application, expiry: defaultInvoiceExpiry })
expect(invoice.invoice).to.startWith("lnbcrt10u") expect(invoice.invoice).to.startWith("lnbcrt10u")

View file

@ -12,18 +12,15 @@ export default async (T: TestBase) => {
const testSpamExternalPayment = async (T: TestBase) => { const testSpamExternalPayment = async (T: TestBase) => {
T.d("starting testSpamExternalPayment")
const application = await T.main.storage.applicationStorage.GetApplication(T.app.appId) const application = await T.main.storage.applicationStorage.GetApplication(T.app.appId)
const invoices = await Promise.all(new Array(10).fill(0).map(() => T.externalAccessToOtherLnd.NewInvoice(500, "test", defaultInvoiceExpiry))) const invoices = await Promise.all(new Array(10).fill(0).map(() => T.externalAccessToOtherLnd.NewInvoice(500, "test", defaultInvoiceExpiry)))
T.d("generated 10 500 sats invoices for external node") T.d("generated 10 500 sats invoices for external node")
const res = await Promise.all(invoices.map(async (invoice, i) => { const res = await Promise.all(invoices.map(async (invoice, i) => {
try { try {
T.d("trying to pay invoice " + i)
const result = await T.main.paymentManager.PayInvoice(T.user1.userId, { invoice: invoice.payRequest, amount: 0 }, application) const result = await T.main.paymentManager.PayInvoice(T.user1.userId, { invoice: invoice.payRequest, amount: 0 }, application)
T.d("payment succeeded " + i)
return { success: true, result } return { success: true, result }
} catch (e: any) { } catch (e: any) {
T.d("payment failed " + i)
console.log(e, i)
return { success: false, err: e } return { success: false, err: e }
} }
})) }))

View file

@ -13,6 +13,7 @@ export default async (T: TestBase) => {
const testSpamExternalPayment = async (T: TestBase) => { const testSpamExternalPayment = async (T: TestBase) => {
T.d("starting testSpamExternalPayment")
const application = await T.main.storage.applicationStorage.GetApplication(T.app.appId) const application = await T.main.storage.applicationStorage.GetApplication(T.app.appId)
const invoicesForExternal = await Promise.all(new Array(5).fill(0).map(() => T.externalAccessToOtherLnd.NewInvoice(500, "test", defaultInvoiceExpiry))) const invoicesForExternal = await Promise.all(new Array(5).fill(0).map(() => T.externalAccessToOtherLnd.NewInvoice(500, "test", defaultInvoiceExpiry)))
const invoicesForUser2 = await Promise.all(new Array(5).fill(0).map(() => T.main.paymentManager.NewInvoice(T.user2.userId, { amountSats: 500, memo: "test" }, { linkedApplication: application, expiry: defaultInvoiceExpiry }))) const invoicesForUser2 = await Promise.all(new Array(5).fill(0).map(() => T.main.paymentManager.NewInvoice(T.user2.userId, { amountSats: 500, memo: "test" }, { linkedApplication: application, expiry: defaultInvoiceExpiry })))
@ -20,13 +21,9 @@ const testSpamExternalPayment = async (T: TestBase) => {
T.d("generated 10 500 sats mixed invoices between external node and user 2") T.d("generated 10 500 sats mixed invoices between external node and user 2")
const res = await Promise.all(invoices.map(async (invoice, i) => { const res = await Promise.all(invoices.map(async (invoice, i) => {
try { try {
T.d("trying to pay invoice " + i)
const result = await T.main.paymentManager.PayInvoice(T.user1.userId, { invoice: invoice, amount: 0 }, application) const result = await T.main.paymentManager.PayInvoice(T.user1.userId, { invoice: invoice, amount: 0 }, application)
T.d("payment succeeded " + i)
return { success: true, result } return { success: true, result }
} catch (e: any) { } catch (e: any) {
T.d("payment failed " + i)
console.log(e, i)
return { success: false, err: e } return { success: false, err: e }
} }
})) }))

View file

@ -27,6 +27,7 @@ export type TestBase = {
user2: TestUserData user2: TestUserData
externalAccessToMainLnd: LND externalAccessToMainLnd: LND
externalAccessToOtherLnd: LND externalAccessToOtherLnd: LND
externalAccessToThirdLnd: LND
d: Describe d: Describe
} }
@ -45,16 +46,21 @@ export const SetupTest = async (d: Describe): Promise<TestBase> => {
const externalAccessToMainLnd = new LND(settings.lndSettings, console.log, console.log, () => { }, () => { }) const externalAccessToMainLnd = new LND(settings.lndSettings, console.log, console.log, () => { }, () => { })
const otherLndSetting = { ...settings.lndSettings, lndCertPath: settings.lndSettings.otherLndCertPath, lndMacaroonPath: settings.lndSettings.otherLndMacaroonPath, lndAddr: settings.lndSettings.otherLndAddr }
const externalAccessToOtherLnd = new LND(otherLndSetting, console.log, console.log, () => { }, () => { })
await externalAccessToMainLnd.Warmup() await externalAccessToMainLnd.Warmup()
const otherLndSetting = { ...settings.lndSettings, mainNode: settings.lndSettings.otherNode }
const externalAccessToOtherLnd = new LND(otherLndSetting, console.log, console.log, () => { }, () => { })
await externalAccessToOtherLnd.Warmup() await externalAccessToOtherLnd.Warmup()
const thirdLndSetting = { ...settings.lndSettings, mainNode: settings.lndSettings.thirdNode }
const externalAccessToThirdLnd = new LND(thirdLndSetting, console.log, console.log, () => { }, () => { })
await externalAccessToThirdLnd.Warmup()
return { return {
expect, main, app, expect, main, app,
user1, user2, user1, user2,
externalAccessToMainLnd, externalAccessToOtherLnd, externalAccessToMainLnd, externalAccessToOtherLnd, externalAccessToThirdLnd,
d d
} }
} }
@ -64,6 +70,7 @@ export const teardown = async (T: TestBase) => {
T.main.lnd.Stop() T.main.lnd.Stop()
T.externalAccessToMainLnd.Stop() T.externalAccessToMainLnd.Stop()
T.externalAccessToOtherLnd.Stop() T.externalAccessToOtherLnd.Stop()
T.externalAccessToThirdLnd.Stop()
console.log("teardown") console.log("teardown")
} }

View file

@ -4,16 +4,37 @@ import { Describe, SetupTest, teardown, TestBase } from './testBase.js'
type TestModule = { type TestModule = {
ignore?: boolean ignore?: boolean
dev?: boolean
default: (T: TestBase) => Promise<void> default: (T: TestBase) => Promise<void>
} }
let failures = 0 let failures = 0
const start = async () => { const start = async () => {
const files = await globby("**/*.spec.js") const files = await globby("**/*.spec.js")
const modules: { file: string, module: TestModule }[] = []
let devModule = -1
for (const file of files) { for (const file of files) {
console.log(file)
const module = await import(`./${file.slice("build/src/tests/".length)}`) as TestModule const module = await import(`./${file.slice("build/src/tests/".length)}`) as TestModule
await runTestFile(file, module) modules.push({ module, file })
if (module.dev) {
console.log("dev module found", file)
if (devModule !== -1) {
console.error(redConsole, "there are multiple dev modules", resetConsole)
return
}
devModule = modules.length - 1
}
}
if (devModule !== -1) {
console.log("running dev module")
await runTestFile(modules[devModule].file, modules[devModule].module)
return
}
else {
console.log("running all tests")
for (const { file, module } of modules) {
await runTestFile(file, module)
}
} }
if (failures) { if (failures) {
console.error(redConsole, "there have been", `${failures}`, "failures in all tests", resetConsole) console.error(redConsole, "there have been", `${failures}`, "failures in all tests", resetConsole)
@ -24,11 +45,15 @@ const start = async () => {
} }
const runTestFile = async (fileName: string, mod: TestModule) => { const runTestFile = async (fileName: string, mod: TestModule) => {
console.log(fileName)
const d = getDescribe(fileName) const d = getDescribe(fileName)
if (mod.ignore) { if (mod.ignore) {
d("-----ignoring file-----") d("-----ignoring this file-----")
return return
} }
if (mod.dev) {
d("-----running only this file-----")
}
const T = await SetupTest(d) const T = await SetupTest(d)
try { try {
d("test starting") d("test starting")
@ -39,6 +64,9 @@ const runTestFile = async (fileName: string, mod: TestModule) => {
d(e, true) d(e, true)
await teardown(T) await teardown(T)
} }
if (mod.dev) {
d("dev mod is not allowed to in CI, failing for precaution", true)
}
} }
const getDescribe = (fileName: string): Describe => { const getDescribe = (fileName: string): Describe => {

View file

@ -0,0 +1,35 @@
import { defaultInvoiceExpiry } from '../services/storage/paymentStorage.js'
import { Describe, expect, expectThrowsAsync, runSanityCheck, safelySetUserBalance, SetupTest, TestBase } from './testBase.js'
export const ignore = false
export const dev = true
export default async (T: TestBase) => {
await safelySetUserBalance(T, T.user1, 2000)
await testSuccessfulU2UPayment(T)
await testFailedInternalPayment(T)
await runSanityCheck(T)
}
const testSuccessfulU2UPayment = async (T: TestBase) => {
T.d("starting testSuccessfulU2UPayment")
const application = await T.main.storage.applicationStorage.GetApplication(T.app.appId)
const sentAmt = await T.main.paymentManager.SendUserToUserPayment(T.user1.userId, T.user2.userId, 1000, application)
expect(sentAmt.amount).to.be.equal(1000)
T.d("paid 1000 sats u2u from user1 to user2")
const u1 = await T.main.storage.userStorage.GetUser(T.user1.userId)
const u2 = await T.main.storage.userStorage.GetUser(T.user2.userId)
const owner = await T.main.storage.userStorage.GetUser(application.owner.user_id)
expect(u2.balance_sats).to.be.equal(1000)
T.d("user2 balance is 1000")
expect(u1.balance_sats).to.be.equal(994)
T.d("user1 balance is 994 cuz he paid 6 sats fee")
expect(owner.balance_sats).to.be.equal(6)
T.d("app balance is 6 sats")
}
const testFailedInternalPayment = async (T: TestBase) => {
T.d("starting testFailedInternalPayment")
const application = await T.main.storage.applicationStorage.GetApplication(T.app.appId)
await expectThrowsAsync(T.main.paymentManager.SendUserToUserPayment(T.user1.userId, T.user2.userId, 1000, application), "not enough balance to send payment")
T.d("payment failed as expected, with the expected error message")
}