change outputs
This commit is contained in:
parent
6eca15020e
commit
f2503af80a
6 changed files with 83 additions and 136 deletions
|
|
@ -23,8 +23,8 @@ const sanitizeFileName = (fileName: string): string => {
|
|||
const openWriter = (fileName: string): Writer => {
|
||||
const now = new Date()
|
||||
const date = `${now.getFullYear()}-${z(now.getMonth() + 1)}-${z(now.getDate())}`
|
||||
const sanitizedFileName = sanitizeFileName(fileName)
|
||||
const logPath = `${logsDir}/${sanitizedFileName}_${date}.log`
|
||||
// const sanitizedFileName = sanitizeFileName(fileName)
|
||||
const logPath = `${logsDir}/${fileName}_${date}.log`
|
||||
// Ensure parent directory exists
|
||||
const dirPath = logPath.substring(0, logPath.lastIndexOf('/'))
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
|
|
@ -48,13 +48,13 @@ if (!fs.existsSync(`${logsDir}/components`)) {
|
|||
export const getLogger = (params: LoggerParams): PubLogger => {
|
||||
const writers: Writer[] = []
|
||||
if (params.appName) {
|
||||
writers.push(openWriter(`apps/${params.appName}`))
|
||||
writers.push(openWriter(`apps/${sanitizeFileName(params.appName)}`))
|
||||
}
|
||||
if (params.userId) {
|
||||
writers.push(openWriter(`users/${params.userId}`))
|
||||
writers.push(openWriter(`users/${sanitizeFileName(params.userId)}`))
|
||||
}
|
||||
if (params.component) {
|
||||
writers.push(openWriter(`components/${params.component}`))
|
||||
writers.push(openWriter(`components/${sanitizeFileName(params.component)}`))
|
||||
}
|
||||
if (writers.length === 0) {
|
||||
writers.push(rootWriter)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { TxPointSettings } from '../storage/tlv/stateBundler.js';
|
|||
import { WalletKitClient } from '../../../proto/lnd/walletkit.client.js';
|
||||
import SettingsManager from '../main/settingsManager.js';
|
||||
import { LndNodeSettings, LndSettings } from '../main/settings.js';
|
||||
import { ListAddressesResponse } from '../../../proto/lnd/walletkit.js';
|
||||
|
||||
const DeadLineMetadata = (deadline = 10 * 1000) => ({ deadline: Date.now() + deadline })
|
||||
const deadLndRetrySeconds = 20
|
||||
|
|
@ -32,6 +33,7 @@ type NodeSettingsOverride = {
|
|||
lndCertPath: string
|
||||
lndMacaroonPath: string
|
||||
}
|
||||
export type LndAddress = { address: string, change: boolean }
|
||||
export default class {
|
||||
lightning: LightningClient
|
||||
invoices: InvoicesClient
|
||||
|
|
@ -331,6 +333,21 @@ export default class {
|
|||
})
|
||||
}
|
||||
|
||||
async IsChangeAddress(address: string): Promise<boolean> {
|
||||
const addresses = await this.ListAddresses()
|
||||
const addr = addresses.find(a => a.address === address)
|
||||
if (!addr) {
|
||||
throw new Error(`address ${address} not found in list of addresses`)
|
||||
}
|
||||
return addr.change
|
||||
}
|
||||
|
||||
async ListAddresses(): Promise<LndAddress[]> {
|
||||
const res = await this.walletKit.listAddresses({ accountName: "", showCustomAccounts: false }, DeadLineMetadata())
|
||||
const addresses = res.response.accountWithAddresses.map(a => a.addresses.map(a => ({ address: a.address, change: a.isInternal }))).flat()
|
||||
return addresses
|
||||
}
|
||||
|
||||
async NewAddress(addressType: Types.AddressType, { useProvider, from }: TxActionOptions): Promise<NewAddressResponse> {
|
||||
// Force use of provider when bypass is enabled (addresses not supported by provider, but we should fail gracefully)
|
||||
if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) {
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ export default class {
|
|||
this.liquidityManager = new LiquidityManager(this.settings, this.storage, this.utils, this.liquidityProvider, this.lnd, this.rugPullTracker)
|
||||
this.metricsManager = new MetricsManager(this.storage, this.lnd)
|
||||
|
||||
this.paymentManager = new PaymentManager(this.storage, this.lnd, this.settings, this.liquidityManager, this.utils, this.addressPaidCb, this.invoicePaidCb)
|
||||
this.paymentManager = new PaymentManager(this.storage, this.metricsManager, this.lnd, this.settings, this.liquidityManager, this.utils, this.addressPaidCb, this.invoicePaidCb)
|
||||
this.productManager = new ProductManager(this.storage, this.paymentManager, this.settings)
|
||||
this.applicationManager = new ApplicationManager(this.storage, this.settings, this.paymentManager)
|
||||
this.appUserManager = new AppUserManager(this.storage, this.settings, this.applicationManager)
|
||||
|
|
@ -213,6 +213,10 @@ export default class {
|
|||
const { blockHeight } = await this.lnd.GetInfo()
|
||||
const userAddress = await this.storage.paymentStorage.GetAddressOwner(address, tx)
|
||||
if (!userAddress) {
|
||||
const isChange = await this.lnd.IsChangeAddress(address)
|
||||
if (isChange) {
|
||||
return
|
||||
}
|
||||
await this.metricsManager.AddRootAddressPaid(address, txOutput, amount)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ import { Utils } from '../helpers/utilsWrapper.js'
|
|||
import { UserInvoicePayment } from '../storage/entity/UserInvoicePayment.js'
|
||||
import SettingsManager from './settingsManager.js'
|
||||
import { Swaps, TransactionSwapData } from '../lnd/swaps.js'
|
||||
import { Transaction, OutputDetail } from '../../../proto/lnd/lightning.js'
|
||||
import { LndAddress } from '../lnd/lnd.js'
|
||||
import Metrics from '../metrics/index.js'
|
||||
interface UserOperationInfo {
|
||||
serial_id: number
|
||||
paid_amount: number
|
||||
|
|
@ -57,8 +60,10 @@ export default class {
|
|||
utils: Utils
|
||||
swaps: Swaps
|
||||
invoiceLock: InvoiceLock
|
||||
constructor(storage: Storage, lnd: LND, settings: SettingsManager, liquidityManager: LiquidityManager, utils: Utils, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb) {
|
||||
metrics: Metrics
|
||||
constructor(storage: Storage, metrics: Metrics, lnd: LND, settings: SettingsManager, liquidityManager: LiquidityManager, utils: Utils, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb) {
|
||||
this.storage = storage
|
||||
this.metrics = metrics
|
||||
this.settings = settings
|
||||
this.lnd = lnd
|
||||
this.liquidityManager = liquidityManager
|
||||
|
|
@ -180,19 +185,11 @@ export default class {
|
|||
}
|
||||
|
||||
try {
|
||||
const lndInfo = await this.lnd.GetInfo()
|
||||
const lndPubkey = lndInfo.identityPubkey
|
||||
const { txs, currentHeight, lndPubkey } = await this.getLatestTransactions(log)
|
||||
|
||||
const startHeight = await this.storage.liquidityStorage.GetLatestCheckedHeight('lnd', lndPubkey)
|
||||
log(`checking for missed confirmed chain transactions from height ${startHeight}...`)
|
||||
|
||||
const { transactions } = await this.lnd.GetTransactions(startHeight)
|
||||
log(`retrieved ${transactions.length} transactions from LND`)
|
||||
|
||||
const recoveredCount = await this.processMissedChainTransactions(transactions, log)
|
||||
const recoveredCount = await this.processMissedChainTransactions(txs, log)
|
||||
|
||||
// Update latest checked height to current block height
|
||||
const currentHeight = lndInfo.blockHeight
|
||||
await this.storage.liquidityStorage.UpdateLatestCheckedHeight('lnd', lndPubkey, currentHeight)
|
||||
|
||||
if (recoveredCount > 0) {
|
||||
|
|
@ -205,24 +202,36 @@ export default class {
|
|||
}
|
||||
}
|
||||
|
||||
private async processMissedChainTransactions(transactions: any[], log: PubLogger): Promise<number> {
|
||||
let recoveredCount = 0
|
||||
private async getLatestTransactions(log: PubLogger): Promise<{ txs: Transaction[], currentHeight: number, lndPubkey: string }> {
|
||||
const lndInfo = await this.lnd.GetInfo()
|
||||
const lndPubkey = lndInfo.identityPubkey
|
||||
|
||||
const startHeight = await this.storage.liquidityStorage.GetLatestCheckedHeight('lnd', lndPubkey)
|
||||
log(`checking for missed confirmed chain transactions from height ${startHeight}...`)
|
||||
|
||||
const { transactions } = await this.lnd.GetTransactions(startHeight)
|
||||
log(`retrieved ${transactions.length} transactions from LND`)
|
||||
|
||||
return { txs: transactions, currentHeight: lndInfo.blockHeight, lndPubkey }
|
||||
}
|
||||
|
||||
private async processMissedChainTransactions(transactions: Transaction[], log: PubLogger): Promise<number> {
|
||||
let recoveredCount = 0
|
||||
const addresses = await this.lnd.ListAddresses()
|
||||
for (const tx of transactions) {
|
||||
if (tx.numConfirmations === 0 || !tx.outputDetails || tx.outputDetails.length === 0) {
|
||||
if (!tx.outputDetails || tx.outputDetails.length === 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
const outputsWithAddresses = await this.collectOutputsWithAddresses(tx.outputDetails)
|
||||
const hasUserOutputs = outputsWithAddresses.some(o => o.userAddress !== null)
|
||||
const outputsWithAddresses = await this.collectOutputsWithAddresses(tx)
|
||||
|
||||
for (const { output, userAddress } of outputsWithAddresses) {
|
||||
if (!userAddress) {
|
||||
await this.processRootAddressOutput(output, tx, hasUserOutputs, log)
|
||||
await this.processRootAddressOutput(output, tx, addresses, log)
|
||||
continue
|
||||
}
|
||||
|
||||
const processed = await this.processUserAddressOutput(output, tx, userAddress, log)
|
||||
const processed = await this.processUserAddressOutput(output, tx, log)
|
||||
if (processed) {
|
||||
recoveredCount++
|
||||
}
|
||||
|
|
@ -232,41 +241,37 @@ export default class {
|
|||
return recoveredCount
|
||||
}
|
||||
|
||||
private async collectOutputsWithAddresses(outputDetails: any[]): Promise<Array<{ output: any, userAddress: UserReceivingAddress | null }>> {
|
||||
const outputs: Array<{ output: any, userAddress: UserReceivingAddress | null }> = []
|
||||
|
||||
for (const output of outputDetails) {
|
||||
private async collectOutputsWithAddresses(tx: Transaction) {
|
||||
const outputs: { output: OutputDetail, userAddress: UserReceivingAddress | null, tx: Transaction }[] = []
|
||||
for (const output of tx.outputDetails) {
|
||||
if (!output.address || !output.isOurAddress) {
|
||||
continue
|
||||
}
|
||||
|
||||
const userAddress = await this.storage.paymentStorage.GetAddressOwner(output.address)
|
||||
outputs.push({ output, userAddress })
|
||||
outputs.push({ output, userAddress, tx })
|
||||
}
|
||||
|
||||
return outputs
|
||||
}
|
||||
|
||||
private async processRootAddressOutput(output: any, tx: any, hasUserOutputs: boolean, log: PubLogger): Promise<void> {
|
||||
// Root outputs in transactions with user outputs are change, not new funds
|
||||
if (hasUserOutputs) {
|
||||
return
|
||||
private async processRootAddressOutput(output: OutputDetail, tx: Transaction, addresses: LndAddress[], log: PubLogger): Promise<boolean> {
|
||||
const addr = addresses.find(a => a.address === output.address)
|
||||
if (!addr) {
|
||||
throw new Error(`address ${output.address} not found in list of addresses`)
|
||||
}
|
||||
if (addr.change) {
|
||||
log(`ignoring change address ${output.address}`)
|
||||
return false
|
||||
}
|
||||
|
||||
const amount = Number(output.amount)
|
||||
const outputIndex = Number(output.outputIndex)
|
||||
const rootOpId = `${output.address}:${tx.txHash}:${outputIndex}`
|
||||
|
||||
const existingRootOp = await this.storage.dbs.FindOne('RootOperation', {
|
||||
where: { operation_identifier: rootOpId, operation_type: "chain" }
|
||||
})
|
||||
|
||||
if (!existingRootOp) {
|
||||
await this.storage.metricsStorage.AddRootOperation("chain", rootOpId, amount)
|
||||
const existingRootOp = await this.metrics.GetRootAddressTransaction(output.address, tx.txHash, outputIndex)
|
||||
if (existingRootOp) {
|
||||
return false
|
||||
}
|
||||
this.addressPaidCb({ hash: tx.txHash, index: outputIndex }, output.address, Number(output.amount), 'lnd')
|
||||
return true
|
||||
}
|
||||
|
||||
private async processUserAddressOutput(output: any, tx: any, userAddress: UserReceivingAddress, log: PubLogger): Promise<boolean> {
|
||||
private async processUserAddressOutput(output: OutputDetail, tx: Transaction, log: PubLogger) {
|
||||
const existingTx = await this.storage.paymentStorage.GetAddressReceivingTransactionOwner(
|
||||
output.address,
|
||||
tx.txHash
|
||||
|
|
@ -279,80 +284,8 @@ export default class {
|
|||
const amount = Number(output.amount)
|
||||
const outputIndex = Number(output.outputIndex)
|
||||
log(`processing missed chain tx: address=${output.address}, txHash=${tx.txHash}, amount=${amount}, outputIndex=${outputIndex}`)
|
||||
|
||||
try {
|
||||
await this.recordMissedUserTransaction(output, tx, userAddress, amount, outputIndex, log)
|
||||
this.addressPaidCb({ hash: tx.txHash, index: outputIndex }, output.address, amount, 'lnd')
|
||||
return true
|
||||
} catch (err: any) {
|
||||
log(ERROR, `failed to process missed chain tx for address=${output.address}, txHash=${tx.txHash}:`, err.message || err)
|
||||
this.utils.stateBundler.AddTxPointFailed(
|
||||
'addressWasPaid',
|
||||
amount,
|
||||
{ used: 'lnd', from: 'system' },
|
||||
userAddress.linkedApplication?.app_id
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private async recordMissedUserTransaction(output: any, tx: any, userAddress: UserReceivingAddress, amount: number, outputIndex: number, log: PubLogger): Promise<void> {
|
||||
await this.storage.StartTransaction(async dbTx => {
|
||||
if (!userAddress.linkedApplication) {
|
||||
log(ERROR, "found address with no linked application during recovery:", output.address)
|
||||
return
|
||||
}
|
||||
|
||||
const isManagedUser = userAddress.user.user_id !== userAddress.linkedApplication.owner.user_id
|
||||
const fee = this.getReceiveServiceFee(Types.UserOperationType.INCOMING_TX, amount, isManagedUser)
|
||||
const blockHeight = tx.blockHeight || 0
|
||||
|
||||
const addedTx = await this.storage.paymentStorage.AddAddressReceivingTransaction(
|
||||
userAddress,
|
||||
tx.txHash,
|
||||
outputIndex,
|
||||
amount,
|
||||
fee,
|
||||
false,
|
||||
blockHeight,
|
||||
dbTx
|
||||
)
|
||||
|
||||
const addressData = `${output.address}:${tx.txHash}`
|
||||
this.storage.eventsLog.LogEvent({
|
||||
type: 'address_paid',
|
||||
userId: userAddress.user.user_id,
|
||||
appId: userAddress.linkedApplication.app_id,
|
||||
appUserId: "",
|
||||
balance: userAddress.user.balance_sats,
|
||||
data: addressData,
|
||||
amount
|
||||
})
|
||||
|
||||
await this.storage.userStorage.IncrementUserBalance(
|
||||
userAddress.user.user_id,
|
||||
amount - fee,
|
||||
addressData,
|
||||
dbTx
|
||||
)
|
||||
|
||||
if (fee > 0) {
|
||||
await this.storage.userStorage.IncrementUserBalance(
|
||||
userAddress.linkedApplication.owner.user_id,
|
||||
fee,
|
||||
'fees',
|
||||
dbTx
|
||||
)
|
||||
}
|
||||
|
||||
this.utils.stateBundler.AddTxPoint(
|
||||
'addressWasPaid',
|
||||
amount,
|
||||
{ used: 'lnd', from: 'system', timeDiscount: true },
|
||||
userAddress.linkedApplication.app_id
|
||||
)
|
||||
|
||||
log(`successfully processed missed chain tx: address=${output.address}, txHash=${tx.txHash}, amount=${amount}`)
|
||||
}, "process missed chain tx")
|
||||
}
|
||||
|
||||
getReceiveServiceFee = (action: Types.UserOperationType, amount: number, managedUser: boolean): number => {
|
||||
|
|
@ -825,21 +758,6 @@ export default class {
|
|||
|
||||
async GetLnurlWithdrawInfo(balanceCheckK1: string): Promise<Types.LnurlWithdrawInfoResponse> {
|
||||
throw new Error("LNURL withdraw currenlty not supported for non application users")
|
||||
/*const key = await this.storage.paymentStorage.UseUserEphemeralKey(balanceCheckK1, 'balanceCheck')
|
||||
const maxWithdrawable = this.GetMaxPayableInvoice(key.user.balance_sats)
|
||||
const callbackK1 = await this.storage.paymentStorage.AddUserEphemeralKey(key.user.user_id, 'withdraw')
|
||||
const newBalanceCheckK1 = await this.storage.paymentStorage.AddUserEphemeralKey(key.user.user_id, 'balanceCheck')
|
||||
const payInfoK1 = await this.storage.paymentStorage.AddUserEphemeralKey(key.user.user_id, 'pay')
|
||||
return {
|
||||
tag: "withdrawRequest",
|
||||
callback: `${this.settings.serviceUrl}/api/guest/lnurl_withdraw/handle`,
|
||||
defaultDescription: "lnurl withdraw from lightning.pub",
|
||||
k1: callbackK1.key,
|
||||
maxWithdrawable: maxWithdrawable * 1000,
|
||||
minWithdrawable: 10000,
|
||||
balanceCheck: this.balanceCheckUrl(newBalanceCheckK1.key),
|
||||
payLink: `${this.settings.serviceUrl}/api/guest/lnurl_pay/info?k1=${payInfoK1.key}`,
|
||||
}*/
|
||||
}
|
||||
|
||||
async HandleLnurlWithdraw(k1: string, invoice: string): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -414,6 +414,10 @@ export default class Handler {
|
|||
await this.storage.metricsStorage.AddRootOperation("chain", `${address}:${txOutput.hash}:${txOutput.index}`, amount)
|
||||
}
|
||||
|
||||
async GetRootAddressTransaction(address: string, txHash: string, index: number) {
|
||||
return this.storage.metricsStorage.GetRootOperation("chain", `${address}:${txHash}:${index}`)
|
||||
}
|
||||
|
||||
async AddRootInvoicePaid(paymentRequest: string, amount: number) {
|
||||
await this.storage.metricsStorage.AddRootOperation("invoice", paymentRequest, amount)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,6 +149,10 @@ export default class {
|
|||
return this.dbs.CreateAndSave<RootOperation>('RootOperation', { operation_type: opType, operation_amount: amount, operation_identifier: id, at_unix: Math.floor(Date.now() / 1000) }, txId)
|
||||
}
|
||||
|
||||
async GetRootOperation(opType: string, id: string, txId?: string) {
|
||||
return this.dbs.FindOne<RootOperation>('RootOperation', { where: { operation_type: opType, operation_identifier: id } }, txId)
|
||||
}
|
||||
|
||||
async GetRootOperations({ from, to }: { from?: number, to?: number }, txId?: string) {
|
||||
const q = getTimeQuery({ from, to })
|
||||
return this.dbs.Find<RootOperation>('RootOperation', q, txId)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue