lnd metrics

This commit is contained in:
boufni95 2023-12-18 17:33:24 +01:00
parent f14657ae73
commit 8d7a0ebe6f
23 changed files with 3850 additions and 2845 deletions

View file

@ -105,9 +105,9 @@ The nostr server will send back a message response, and inside the body there wi
- __User__: - __User__:
- expected context content - expected context content
- __app_id__: _string_
- __app_user_id__: _string_ - __app_user_id__: _string_
- __user_id__: _string_ - __user_id__: _string_
- __app_id__: _string_
- __Admin__: - __Admin__:
- expected context content - expected context content
@ -155,6 +155,13 @@ The nostr server will send back a message response, and inside the body there wi
- input: [AppsMetricsRequest](#AppsMetricsRequest) - input: [AppsMetricsRequest](#AppsMetricsRequest)
- output: [AppsMetrics](#AppsMetrics) - output: [AppsMetrics](#AppsMetrics)
- GetLndMetrics
- auth type: __Admin__
- http method: __post__
- http route: __/api/admin/metrics/lnd__
- input: [LndMetricsRequest](#LndMetricsRequest)
- output: [LndMetrics](#LndMetrics)
- Health - Health
- auth type: __Guest__ - auth type: __Guest__
- http method: __get__ - http method: __get__
@ -421,42 +428,34 @@ The nostr server will send back a message response, and inside the body there wi
## Messages ## Messages
### The content of requests and response from the methods ### The content of requests and response from the methods
### PayInvoiceResponse ### AuthApp
- __preimage__: _string_ - __app__: _[Application](#Application)_
- __amount_paid__: _number_ - __auth_token__: _string_
- __operation_id__: _string_
- __service_fee__: _number_
- __network_fee__: _number_
### OpenChannelRequest ### PayAddressRequest
- __destination__: _string_ - __address__: _string_
- __fundingAmount__: _number_ - __amoutSats__: _number_
- __pushAmount__: _number_ - __satsPerVByte__: _number_
- __closeAddress__: _string_
### LndGetInfoRequest ### PayInvoiceRequest
- __nodeId__: _number_ - __invoice__: _string_
### SendAppUserToAppPaymentRequest
- __from_user_identifier__: _string_
- __amount__: _number_ - __amount__: _number_
### SetMockAppBalanceRequest ### GetUserOperationsResponse
- __amount__: _number_ - __latestOutgoingInvoiceOperations__: _[UserOperations](#UserOperations)_
- __latestIncomingInvoiceOperations__: _[UserOperations](#UserOperations)_
- __latestOutgoingTxOperations__: _[UserOperations](#UserOperations)_
- __latestIncomingTxOperations__: _[UserOperations](#UserOperations)_
- __latestOutgoingUserToUserPayemnts__: _[UserOperations](#UserOperations)_
- __latestIncomingUserToUserPayemnts__: _[UserOperations](#UserOperations)_
### UserOperations ### AddAppUserRequest
- __fromIndex__: _number_ - __identifier__: _string_
- __toIndex__: _number_ - __fail_if_exists__: _boolean_
- __operations__: ARRAY of: _[UserOperation](#UserOperation)_ - __balance__: _number_
### GetAppUserLNURLInfoRequest ### Product
- __user_identifier__: _string_ - __id__: _string_
- __base_url_override__: _string_
### DecodeInvoiceResponse
- __amount__: _number_
### AddProductRequest
- __name__: _string_ - __name__: _string_
- __price_sats__: _number_ - __price_sats__: _number_
@ -471,12 +470,131 @@ The nostr server will send back a message response, and inside the body there wi
- __nostr__: _boolean_ - __nostr__: _boolean_
- __batch_size__: _number_ - __batch_size__: _number_
### SetMockInvoiceAsPaidRequest ### LndMetricsRequest
- __from_unix__: _number_ *this field is optional
- __to_unix__: _number_ *this field is optional
### LndNodeMetrics
- __channels_balance_events__: ARRAY of: _[ChannelBalanceEvent](#ChannelBalanceEvent)_
- __chain_balance_events__: ARRAY of: _[ChainBalanceEvent](#ChainBalanceEvent)_
- __routing_events__: ARRAY of: _[RoutingEvent](#RoutingEvent)_
### UsersInfo
- __total__: _number_
- __no_balance__: _number_
- __negative_balance__: _number_
- __always_been_inactive__: _number_
- __balance_avg__: _number_
- __balance_median__: _number_
### NewInvoiceResponse
- __invoice__: _string_
### PayInvoiceResponse
- __preimage__: _string_
- __amount_paid__: _number_
- __operation_id__: _string_
- __service_fee__: _number_
- __network_fee__: _number_
### OpenChannelResponse
- __channelId__: _string_
### LnurlLinkResponse
- __lnurl__: _string_
- __k1__: _string_
### AddAppRequest
- __name__: _string_
- __allow_user_creation__: _boolean_
### Application
- __name__: _string_
- __id__: _string_
- __balance__: _number_
- __npub__: _string_
### SendAppUserToAppPaymentRequest
- __from_user_identifier__: _string_
- __amount__: _number_
### DecodeInvoiceResponse
- __amount__: _number_
### ClosureMigration
- __closes_at_unix__: _number_
### SetMockAppBalanceRequest
- __amount__: _number_
### HandleLnurlPayResponse
- __pr__: _string_
- __routes__: ARRAY of: _[Empty](#Empty)_
### AppsMetrics
- __apps__: ARRAY of: _[AppMetrics](#AppMetrics)_
### OpenChannelRequest
- __destination__: _string_
- __fundingAmount__: _number_
- __pushAmount__: _number_
- __closeAddress__: _string_
### RelaysMigration
- __relays__: ARRAY of: _string_
### GetAppUserRequest
- __user_identifier__: _string_
### PayAppUserInvoiceRequest
- __user_identifier__: _string_
- __invoice__: _string_ - __invoice__: _string_
- __amount__: _number_ - __amount__: _number_
### LndGetInfoResponse ### SendAppUserToAppUserPaymentRequest
- __alias__: _string_ - __from_user_identifier__: _string_
- __to_user_identifier__: _string_
- __amount__: _number_
### NewAddressRequest
- __addressType__: _[AddressType](#AddressType)_
### PayAddressResponse
- __txId__: _string_
- __operation_id__: _string_
- __service_fee__: _number_
- __network_fee__: _number_
### AddProductRequest
- __name__: _string_
- __price_sats__: _number_
### GetProductBuyLinkResponse
- __link__: _string_
### RoutingEvent
- __incoming_channel_id__: _number_
- __incoming_htlc_id__: _number_
- __outgoing_channel_id__: _number_
- __outgoing_htlc_id__: _number_
- __timestamp_ns__: _number_
- __event_type__: _string_
- __incoming_amt_msat__: _number_
- __outgoing_amt_msat__: _number_
- __failure_string__: _string_
- __settled__: _boolean_
- __offchain__: _boolean_
- __forward_fail_event__: _boolean_
### AppUser
- __identifier__: _string_
- __info__: _[UserInfo](#UserInfo)_
- __max_withdrawable__: _number_
### AddAppInvoiceRequest
- __payer_identifier__: _string_
- __http_callback_url__: _string_
- __invoice_req__: _[NewInvoiceRequest](#NewInvoiceRequest)_
### AddAppUserInvoiceRequest ### AddAppUserInvoiceRequest
- __receiver_identifier__: _string_ - __receiver_identifier__: _string_
@ -484,50 +602,72 @@ The nostr server will send back a message response, and inside the body there wi
- __http_callback_url__: _string_ - __http_callback_url__: _string_
- __invoice_req__: _[NewInvoiceRequest](#NewInvoiceRequest)_ - __invoice_req__: _[NewInvoiceRequest](#NewInvoiceRequest)_
### GetProductBuyLinkResponse ### DecodeInvoiceRequest
- __link__: _string_ - __invoice__: _string_
### AppsMetrics ### UserOperations
- __apps__: ARRAY of: _[AppMetrics](#AppMetrics)_ - __fromIndex__: _number_
- __toIndex__: _number_
- __operations__: ARRAY of: _[UserOperation](#UserOperation)_
### MigrationUpdate ### EncryptionExchangeRequest
- __closure__: _[ClosureMigration](#ClosureMigration)_ *this field is optional - __publicKey__: _string_
- __relays__: _[RelaysMigration](#RelaysMigration)_ *this field is optional - __deviceId__: _string_
### Empty ### GetUserOperationsRequest
- __latestIncomingInvoice__: _number_
- __latestOutgoingInvoice__: _number_
- __latestIncomingTx__: _number_
- __latestOutgoingTx__: _number_
- __latestIncomingUserToUserPayment__: _number_
- __latestOutgoingUserToUserPayment__: _number_
### AddAppUserRequest ### AppMetrics
- __identifier__: _string_
- __fail_if_exists__: _boolean_
- __balance__: _number_
### GetUserOperationsResponse
- __latestOutgoingInvoiceOperations__: _[UserOperations](#UserOperations)_
- __latestIncomingInvoiceOperations__: _[UserOperations](#UserOperations)_
- __latestOutgoingTxOperations__: _[UserOperations](#UserOperations)_
- __latestIncomingTxOperations__: _[UserOperations](#UserOperations)_
- __latestOutgoingUserToUserPayemnts__: _[UserOperations](#UserOperations)_
- __latestIncomingUserToUserPayemnts__: _[UserOperations](#UserOperations)_
### AuthApp
- __app__: _[Application](#Application)_ - __app__: _[Application](#Application)_
- __auth_token__: _string_ - __users__: _[UsersInfo](#UsersInfo)_
- __total_received__: _number_
- __total_spent__: _number_
- __total_available__: _number_
- __unpaid_invoices__: _number_
- __operations__: ARRAY of: _[UserOperation](#UserOperation)_
### AppUser ### ChainBalanceEvent
- __identifier__: _string_ - __block_height__: _number_
- __info__: _[UserInfo](#UserInfo)_ - __confirmed_balance__: _number_
- __max_withdrawable__: _number_ - __unconfirmed_balance__: _number_
- __total_balance__: _number_
### SendAppUserToAppUserPaymentRequest ### AuthAppRequest
- __from_user_identifier__: _string_ - __name__: _string_
- __to_user_identifier__: _string_ - __allow_user_creation__: _boolean_ *this field is optional
### SetMockAppUserBalanceRequest
- __user_identifier__: _string_
- __amount__: _number_ - __amount__: _number_
### NewAddressResponse ### NewAddressResponse
- __address__: _string_ - __address__: _string_
### DecodeInvoiceRequest ### LiveUserOperation
- __invoice__: _string_ - __operation__: _[UserOperation](#UserOperation)_
### Empty
### ChannelBalanceEvent
- __block_height__: _number_
- __channel_id__: _string_
- __local_balance_sats__: _number_
- __remote_balance_sats__: _number_
### LndMetrics
- __nodes__: ARRAY of: _[LndNodeMetrics](#LndNodeMetrics)_
### LndGetInfoResponse
- __alias__: _string_
### NewInvoiceRequest
- __amountSats__: _number_
- __memo__: _string_
### LnurlWithdrawInfoResponse ### LnurlWithdrawInfoResponse
- __tag__: _string_ - __tag__: _string_
@ -539,91 +679,18 @@ The nostr server will send back a message response, and inside the body there wi
- __balanceCheck__: _string_ - __balanceCheck__: _string_
- __payLink__: _string_ - __payLink__: _string_
### HandleLnurlPayResponse
- __pr__: _string_
- __routes__: ARRAY of: _[Empty](#Empty)_
### RelaysMigration
- __relays__: ARRAY of: _string_
### Product
- __id__: _string_
- __name__: _string_
- __price_sats__: _number_
### UsageMetrics ### UsageMetrics
- __metrics__: ARRAY of: _[UsageMetric](#UsageMetric)_ - __metrics__: ARRAY of: _[UsageMetric](#UsageMetric)_
### AppsMetricsRequest
- __from_unix__: _number_ *this field is optional
- __to_unix__: _number_ *this field is optional
- __big_user_sats__: _number_ *this field is optional
- __huge_user_sats__: _number_ *this field is optional
- __include_operations__: _boolean_ *this field is optional
### AppMetrics
- __app_name__: _string_
- __app_id__: _string_
- __app_npub__: _string_
- __app_balance__: _number_
- __total_received__: _number_
- __total_spent__: _number_
- __total_available__: _number_
- __total_users__: _number_
- __total_big_users__: _number_
- __total_huge_users__: _number_
- __unpaid_invoices__: _number_
- __operations__: ARRAY of: _[UserOperation](#UserOperation)_
### AddAppRequest
- __name__: _string_
- __allow_user_creation__: _boolean_
### OpenChannelResponse
- __channelId__: _string_
### PayAddressRequest
- __address__: _string_
- __amoutSats__: _number_
- __satsPerVByte__: _number_
### UserInfo ### UserInfo
- __userId__: _string_ - __userId__: _string_
- __balance__: _number_ - __balance__: _number_
- __max_withdrawable__: _number_ - __max_withdrawable__: _number_
### LiveUserOperation ### AppsMetricsRequest
- __operation__: _[UserOperation](#UserOperation)_ - __from_unix__: _number_ *this field is optional
- __to_unix__: _number_ *this field is optional
### ClosureMigration - __include_operations__: _boolean_ *this field is optional
- __closes_at_unix__: _number_
### GetAppUserRequest
- __user_identifier__: _string_
### GetUserOperationsRequest
- __latestIncomingInvoice__: _number_
- __latestOutgoingInvoice__: _number_
- __latestIncomingTx__: _number_
- __latestOutgoingTx__: _number_
- __latestIncomingUserToUserPayment__: _number_
- __latestOutgoingUserToUserPayment__: _number_
### NewInvoiceRequest
- __amountSats__: _number_
- __memo__: _string_
### AddAppInvoiceRequest
- __payer_identifier__: _string_
- __http_callback_url__: _string_
- __invoice_req__: _[NewInvoiceRequest](#NewInvoiceRequest)_
### NewInvoiceResponse
- __invoice__: _string_
### PayInvoiceRequest
- __invoice__: _string_
- __amount__: _number_
### LnurlPayInfoResponse ### LnurlPayInfoResponse
- __tag__: _string_ - __tag__: _string_
@ -634,34 +701,6 @@ The nostr server will send back a message response, and inside the body there wi
- __allowsNostr__: _boolean_ - __allowsNostr__: _boolean_
- __nostrPubkey__: _string_ - __nostrPubkey__: _string_
### LnurlLinkResponse
- __lnurl__: _string_
- __k1__: _string_
### EncryptionExchangeRequest
- __publicKey__: _string_
- __deviceId__: _string_
### AuthAppRequest
- __name__: _string_
- __allow_user_creation__: _boolean_ *this field is optional
### Application
- __name__: _string_
- __id__: _string_
- __balance__: _number_
- __npub__: _string_
### SetMockAppUserBalanceRequest
- __user_identifier__: _string_
- __amount__: _number_
### PayAddressResponse
- __txId__: _string_
- __operation_id__: _string_
- __service_fee__: _number_
- __network_fee__: _number_
### UserOperation ### UserOperation
- __paidAtUnix__: _number_ - __paidAtUnix__: _number_
- __type__: _[UserOperationType](#UserOperationType)_ - __type__: _[UserOperationType](#UserOperationType)_
@ -673,13 +712,20 @@ The nostr server will send back a message response, and inside the body there wi
- __network_fee__: _number_ - __network_fee__: _number_
- __confirmed__: _boolean_ - __confirmed__: _boolean_
### PayAppUserInvoiceRequest ### MigrationUpdate
- __user_identifier__: _string_ - __closure__: _[ClosureMigration](#ClosureMigration)_ *this field is optional
- __relays__: _[RelaysMigration](#RelaysMigration)_ *this field is optional
### LndGetInfoRequest
- __nodeId__: _number_
### SetMockInvoiceAsPaidRequest
- __invoice__: _string_ - __invoice__: _string_
- __amount__: _number_ - __amount__: _number_
### NewAddressRequest ### GetAppUserLNURLInfoRequest
- __addressType__: _[AddressType](#AddressType)_ - __user_identifier__: _string_
- __base_url_override__: _string_
## Enums ## Enums
### The enumerators used in the messages ### The enumerators used in the messages

File diff suppressed because it is too large Load diff

View file

@ -139,6 +139,28 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
opts.metricsCallback([{ ...info, ...stats, ...authContext }]) opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e } } catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
}) })
if (!opts.allowNotImplementedMethods && !methods.GetLndMetrics) throw new Error('method: GetLndMetrics is not implemented')
app.post('/api/admin/metrics/lnd', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'GetLndMetrics', batch: false, nostr: false, batchSize: 0}
const stats: Types.RequestStats = { start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
let authCtx: Types.AuthContext = {}
try {
if (!methods.GetLndMetrics) throw new Error('method: GetLndMetrics is not implemented')
const authContext = await opts.AdminAuthGuard(req.headers['authorization'])
authCtx = authContext
stats.guard = process.hrtime.bigint()
const request = req.body
const error = Types.LndMetricsRequestValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)
const query = req.query
const params = req.params
const response = await methods.GetLndMetrics({rpcName:'GetLndMetrics', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res.json({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.Health) throw new Error('method: Health is not implemented') if (!opts.allowNotImplementedMethods && !methods.Health) throw new Error('method: Health is not implemented')
app.get('/api/health', async (req, res) => { app.get('/api/health', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'Health', batch: false, nostr: false, batchSize: 0} const info: Types.RequestInfo = { rpcName: 'Health', batch: false, nostr: false, batchSize: 0}

View file

@ -85,6 +85,20 @@ export default (params: ClientParams) => ({
} }
return { status: 'ERROR', reason: 'invalid response' } return { status: 'ERROR', reason: 'invalid response' }
}, },
GetLndMetrics: async (request: Types.LndMetricsRequest): Promise<ResultError | ({ status: 'OK' }& Types.LndMetrics)> => {
const auth = await params.retrieveAdminAuth()
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
let finalRoute = '/api/admin/metrics/lnd'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.LndMetricsValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
Health: async (): Promise<ResultError | ({ status: 'OK' })> => { Health: async (): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveGuestAuth() const auth = await params.retrieveGuestAuth()
if (auth === null) throw new Error('retrieveGuestAuth() returned null') if (auth === null) throw new Error('retrieveGuestAuth() returned null')

File diff suppressed because it is too large Load diff

View file

@ -108,6 +108,12 @@ service LightningPub {
option (http_method) = "post"; option (http_method) = "post";
option (http_route) = "/api/admin/metrics/apps"; option (http_route) = "/api/admin/metrics/apps";
} }
rpc GetLndMetrics(structs.LndMetricsRequest) returns (structs.LndMetrics) {
option (auth_type) = "Admin";
option (http_method) = "post";
option (http_route) = "/api/admin/metrics/lnd";
}
// </Admin> // </Admin>
// <Guest> // <Guest>

View file

@ -31,26 +31,30 @@ message UsageMetrics {
message AppsMetricsRequest { message AppsMetricsRequest {
optional int64 from_unix = 1; optional int64 from_unix = 1;
optional int64 to_unix = 2; optional int64 to_unix = 2;
optional int64 big_user_sats = 3; optional bool include_operations = 3;
optional int64 huge_user_sats = 4; }
optional bool include_operations = 5;
message UsersInfo {
int64 total = 1;
int64 no_balance = 2;
int64 negative_balance = 3;
int64 always_been_inactive = 4;
int64 balance_avg = 5;
int64 balance_median = 6;
} }
message AppMetrics { message AppMetrics {
string app_name = 1; Application app = 1;
string app_id = 2;
string app_npub = 3; UsersInfo users = 2;
int64 app_balance = 4;
int64 total_received = 5; int64 total_received = 5;
int64 total_spent = 6; int64 total_spent = 6;
int64 total_available = 7; int64 total_available = 7;
int64 total_users = 8; int64 unpaid_invoices = 10;
int64 total_big_users = 9;
int64 total_huge_users = 10;
int64 unpaid_invoices = 11;
repeated UserOperation operations = 100; repeated UserOperation operations = 100;
} }
@ -59,6 +63,49 @@ message AppsMetrics {
repeated AppMetrics apps = 1; repeated AppMetrics apps = 1;
} }
message LndMetricsRequest {
optional int64 from_unix = 1;
optional int64 to_unix = 2;
}
message RoutingEvent {
int64 incoming_channel_id = 1;
int64 incoming_htlc_id=2;
int64 outgoing_channel_id = 3;
int64 outgoing_htlc_id =4;
int64 timestamp_ns = 5;
string event_type = 6;
int64 incoming_amt_msat = 7;
int64 outgoing_amt_msat = 8;
string failure_string = 9;
bool settled = 10;
bool offchain = 11;
bool forward_fail_event = 12;
}
message ChannelBalanceEvent {
int64 block_height = 1;
string channel_id = 2;
int64 local_balance_sats = 3;
int64 remote_balance_sats = 4;
}
message ChainBalanceEvent {
int64 block_height = 1;
int64 confirmed_balance = 2;
int64 unconfirmed_balance = 3;
int64 total_balance = 4;
}
message LndNodeMetrics {
repeated ChannelBalanceEvent channels_balance_events = 1;
repeated ChainBalanceEvent chain_balance_events = 2;
repeated RoutingEvent routing_events = 3;
}
message LndMetrics {
repeated LndNodeMetrics nodes = 1;
}
message LndGetInfoRequest { message LndGetInfoRequest {
int64 nodeId = 1; int64 nodeId = 1;
} }

View file

@ -5,7 +5,7 @@ import NewLightningHandler, { LightningHandler, LoadLndSettingsFromEnv } from '.
let lnd: LightningHandler let lnd: LightningHandler
export const ignore = true export const ignore = true
export const setup = async () => { export const setup = async () => {
lnd = NewLightningHandler(LoadLndSettingsFromEnv(true), console.log, console.log, console.log) lnd = NewLightningHandler(LoadLndSettingsFromEnv(true), console.log, console.log, console.log, console.log)
await lnd.Warmup() await lnd.Warmup()
} }
export const teardown = () => { export const teardown = () => {

View file

@ -1,7 +1,7 @@
import * as Types from '../../../proto/autogenerated/ts/types.js' import * as Types from '../../../proto/autogenerated/ts/types.js'
import { GetInfoResponse, NewAddressResponse, AddInvoiceResponse, PayReq, Payment, SendCoinsResponse, EstimateFeeResponse, TransactionDetails } from '../../../proto/lnd/lightning.js' import { GetInfoResponse, NewAddressResponse, AddInvoiceResponse, PayReq, Payment, SendCoinsResponse, EstimateFeeResponse, TransactionDetails } from '../../../proto/lnd/lightning.js'
import { EnvMustBeNonEmptyString, EnvMustBeInteger, EnvCanBeBoolean } from '../helpers/envParser.js' import { EnvMustBeNonEmptyString, EnvMustBeInteger, EnvCanBeBoolean } from '../helpers/envParser.js'
import { AddressPaidCb, DecodedInvoice, Invoice, InvoicePaidCb, LndSettings, NewBlockCb, NodeInfo, PaidInvoice } from './settings.js' import { AddressPaidCb, BalanceInfo, DecodedInvoice, HtlcCb, Invoice, InvoicePaidCb, LndSettings, NewBlockCb, NodeInfo, PaidInvoice } from './settings.js'
import LND from './lnd.js' import LND from './lnd.js'
import MockLnd from './mock.js' import MockLnd from './mock.js'
import { getLogger } from '../helpers/logger.js' import { getLogger } from '../helpers/logger.js'
@ -31,14 +31,15 @@ export interface LightningHandler {
SetMockInvoiceAsPaid(invoice: string, amount: number): Promise<void> SetMockInvoiceAsPaid(invoice: string, amount: number): Promise<void>
ChannelBalance(): Promise<{ local: number, remote: number }> ChannelBalance(): Promise<{ local: number, remote: number }>
GetTransactions(startHeight: number): Promise<TransactionDetails> GetTransactions(startHeight: number): Promise<TransactionDetails>
GetBalance(): Promise<BalanceInfo>
} }
export default (settings: LndSettings, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb, newBlockCb: NewBlockCb): LightningHandler => { export default (settings: LndSettings, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb, newBlockCb: NewBlockCb, htlcCb: HtlcCb): LightningHandler => {
if (settings.mockLnd) { if (settings.mockLnd) {
getLogger({})("registering mock lnd handler") getLogger({})("registering mock lnd handler")
return new MockLnd(settings, addressPaidCb, invoicePaidCb, newBlockCb) return new MockLnd(settings, addressPaidCb, invoicePaidCb, newBlockCb)
} else { } else {
getLogger({})("registering prod lnd handler") getLogger({})("registering prod lnd handler")
return new LND(settings, addressPaidCb, invoicePaidCb, newBlockCb) return new LND(settings, addressPaidCb, invoicePaidCb, newBlockCb, htlcCb)
} }
} }

View file

@ -13,8 +13,9 @@ import { OpenChannelReq } from './openChannelReq.js';
import { AddInvoiceReq } from './addInvoiceReq.js'; import { AddInvoiceReq } from './addInvoiceReq.js';
import { PayInvoiceReq } from './payInvoiceReq.js'; import { PayInvoiceReq } from './payInvoiceReq.js';
import { SendCoinsReq } from './sendCoinsReq.js'; import { SendCoinsReq } from './sendCoinsReq.js';
import { LndSettings, AddressPaidCb, InvoicePaidCb, NodeInfo, Invoice, DecodedInvoice, PaidInvoice, NewBlockCb } from './settings.js'; import { LndSettings, AddressPaidCb, InvoicePaidCb, NodeInfo, Invoice, DecodedInvoice, PaidInvoice, NewBlockCb, HtlcCb, BalanceInfo } from './settings.js';
import { getLogger } from '../helpers/logger.js'; import { getLogger } from '../helpers/logger.js';
import { HtlcEvent_EventType } from '../../../proto/lnd/router.js';
const DeadLineMetadata = (deadline = 10 * 1000) => ({ deadline: Date.now() + deadline }) const DeadLineMetadata = (deadline = 10 * 1000) => ({ deadline: Date.now() + deadline })
const deadLndRetrySeconds = 5 const deadLndRetrySeconds = 5
export default class { export default class {
@ -30,12 +31,14 @@ export default class {
addressPaidCb: AddressPaidCb addressPaidCb: AddressPaidCb
invoicePaidCb: InvoicePaidCb invoicePaidCb: InvoicePaidCb
newBlockCb: NewBlockCb newBlockCb: NewBlockCb
htlcCb: HtlcCb
log = getLogger({}) log = getLogger({})
constructor(settings: LndSettings, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb, newBlockCb: NewBlockCb) { constructor(settings: LndSettings, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb, newBlockCb: NewBlockCb, htlcCb: HtlcCb) {
this.settings = settings this.settings = settings
this.addressPaidCb = addressPaidCb this.addressPaidCb = addressPaidCb
this.invoicePaidCb = invoicePaidCb this.invoicePaidCb = invoicePaidCb
this.newBlockCb = newBlockCb this.newBlockCb = newBlockCb
this.htlcCb = htlcCb
const { lndAddr, lndCertPath, lndMacaroonPath } = this.settings const { lndAddr, lndCertPath, lndMacaroonPath } = this.settings
const lndCert = fs.readFileSync(lndCertPath); const lndCert = fs.readFileSync(lndCertPath);
const macaroon = fs.readFileSync(lndMacaroonPath).toString('hex'); const macaroon = fs.readFileSync(lndMacaroonPath).toString('hex');
@ -67,6 +70,7 @@ export default class {
this.SubscribeAddressPaid() this.SubscribeAddressPaid()
this.SubscribeInvoicePaid() this.SubscribeInvoicePaid()
this.SubscribeNewBlock() this.SubscribeNewBlock()
this.SubscribeHtlcEvents()
this.ready = true this.ready = true
} }
@ -102,6 +106,19 @@ export default class {
}, deadLndRetrySeconds * 1000) }, deadLndRetrySeconds * 1000)
} }
async SubscribeHtlcEvents() {
const stream = this.router.subscribeHtlcEvents({}, { abort: this.abortController.signal })
stream.responses.onMessage(htlc => {
this.htlcCb(htlc)
})
stream.responses.onError(error => {
this.log("Error with subscribeHtlcEvents stream")
})
stream.responses.onComplete(() => {
this.log("subscribeHtlcEvents stream closed")
})
}
async SubscribeNewBlock() { async SubscribeNewBlock() {
const { blockHeight } = await this.GetInfo() const { blockHeight } = await this.GetInfo()
const stream = this.chainNotifier.registerBlockEpochNtfn({ height: blockHeight, hash: Buffer.alloc(0) }, { abort: this.abortController.signal }) const stream = this.chainNotifier.registerBlockEpochNtfn({ height: blockHeight, hash: Buffer.alloc(0) }, { abort: this.abortController.signal })
@ -259,6 +276,20 @@ export default class {
return res.response return res.response
} }
async GetBalance(): Promise<BalanceInfo> {
const wRes = await this.lightning.walletBalance({}, DeadLineMetadata())
const { confirmedBalance, unconfirmedBalance, totalBalance } = wRes.response
const { response } = await this.lightning.listChannels({
activeOnly: false, inactiveOnly: false, privateOnly: false, publicOnly: false, peer: Buffer.alloc(0)
}, DeadLineMetadata())
const channelsBalance = response.channels.map(c => ({
channelId: c.chanId,
localBalanceSats: Number(c.localBalance),
remoteBalanceSats: Number(c.remoteBalance)
}))
return { confirmedBalance: Number(confirmedBalance), unconfirmedBalance: Number(unconfirmedBalance), totalBalance: Number(totalBalance), channelsBalance }
}
async OpenChannel(destination: string, closeAddress: string, fundingAmount: number, pushSats: number): Promise<string> { async OpenChannel(destination: string, closeAddress: string, fundingAmount: number, pushSats: number): Promise<string> {
await this.Health() await this.Health()

View file

@ -12,7 +12,7 @@ import { OpenChannelReq } from './openChannelReq.js';
import { AddInvoiceReq } from './addInvoiceReq.js'; import { AddInvoiceReq } from './addInvoiceReq.js';
import { PayInvoiceReq } from './payInvoiceReq.js'; import { PayInvoiceReq } from './payInvoiceReq.js';
import { SendCoinsReq } from './sendCoinsReq.js'; import { SendCoinsReq } from './sendCoinsReq.js';
import { LndSettings, AddressPaidCb, InvoicePaidCb, NodeInfo, Invoice, DecodedInvoice, PaidInvoice, NewBlockCb } from './settings.js'; import { LndSettings, AddressPaidCb, InvoicePaidCb, NodeInfo, Invoice, DecodedInvoice, PaidInvoice, NewBlockCb, BalanceInfo } from './settings.js';
import { getLogger } from '../helpers/logger.js'; import { getLogger } from '../helpers/logger.js';
export default class { export default class {
@ -115,6 +115,10 @@ export default class {
async GetTransactions(startHeight: number): Promise<TransactionDetails> { async GetTransactions(startHeight: number): Promise<TransactionDetails> {
throw new Error("GetTransactions disabled in mock mode") throw new Error("GetTransactions disabled in mock mode")
} }
GetBalance(): Promise<BalanceInfo> {
throw new Error("GetBalance disabled in mock mode")
}
} }

View file

@ -1,3 +1,5 @@
import { HtlcEvent } from "../../../proto/lnd/router"
export type LndSettings = { export type LndSettings = {
lndAddr: string lndAddr: string
lndCertPath: string lndCertPath: string
@ -10,10 +12,21 @@ type TxOutput = {
hash: string hash: string
index: number index: number
} }
export type BalanceInfo = {
confirmedBalance: number;
unconfirmedBalance: number;
totalBalance: number;
channelsBalance: {
channelId: string;
localBalanceSats: number;
remoteBalanceSats: number;
}[];
}
export type AddressPaidCb = (txOutput: TxOutput, address: string, amount: number, internal: boolean) => void export type AddressPaidCb = (txOutput: TxOutput, address: string, amount: number, internal: boolean) => void
export type InvoicePaidCb = (paymentRequest: string, amount: number, internal: boolean) => void export type InvoicePaidCb = (paymentRequest: string, amount: number, internal: boolean) => void
export type NewBlockCb = (height: number) => void export type NewBlockCb = (height: number) => void
export type HtlcCb = (event: HtlcEvent) => void
export type NodeInfo = { export type NodeInfo = {
alias: string alias: string

View file

@ -59,7 +59,7 @@ export default class {
this.settings = settings this.settings = settings
this.storage = new Storage(settings.storageSettings) this.storage = new Storage(settings.storageSettings)
this.metricsManager = new MetricsManager(this.storage) this.metricsManager = new MetricsManager(this.storage)
this.lnd = NewLightningHandler(settings.lndSettings, this.addressPaidCb, this.invoicePaidCb, this.newBlockCb) this.lnd = NewLightningHandler(settings.lndSettings, this.addressPaidCb, this.invoicePaidCb, this.newBlockCb, this.metricsManager.HtlcCb)
this.paymentManager = new PaymentManager(this.storage, this.lnd, this.settings, this.addressPaidCb, this.invoicePaidCb) this.paymentManager = new PaymentManager(this.storage, this.lnd, this.settings, this.addressPaidCb, this.invoicePaidCb)
this.productManager = new ProductManager(this.storage, this.paymentManager, this.settings) this.productManager = new ProductManager(this.storage, this.paymentManager, this.settings)
@ -78,10 +78,13 @@ export default class {
NewBlockHandler = async (height: number) => { NewBlockHandler = async (height: number) => {
let confirmed: (PendingTx & { confs: number; })[] let confirmed: (PendingTx & { confs: number; })[]
let log = getLogger({}) let log = getLogger({})
try { try {
const balanceEvents = await this.paymentManager.GetLndBalance()
await this.metricsManager.NewBlockCb(height, balanceEvents)
confirmed = await this.paymentManager.CheckPendingTransactions(height) confirmed = await this.paymentManager.CheckPendingTransactions(height)
} catch (err: any) { } catch (err: any) {
log("failed to check transactions after new block", err) log("failed to check transactions after new block", err.message || err)
return return
} }
await Promise.all(confirmed.map(async c => { await Promise.all(confirmed.map(async c => {

View file

@ -507,6 +507,10 @@ export default class {
return resolved.filter(t => t !== undefined) as (PendingTx & { confs: number })[] return resolved.filter(t => t !== undefined) as (PendingTx & { confs: number })[]
} }
async GetLndBalance() {
return this.lnd.GetBalance()
}
encodeLnurl(base: string) { encodeLnurl(base: string) {
if (!base || typeof base !== 'string') { if (!base || typeof base !== 'string') {
throw new Error("provided string for lnurl encode is not a string or is an empty string") throw new Error("provided string for lnurl encode is not a string or is an empty string")

View file

@ -1,6 +1,11 @@
import Storage from '../storage/index.js' import Storage from '../storage/index.js'
import * as Types from '../../../proto/autogenerated/ts/types.js' import * as Types from '../../../proto/autogenerated/ts/types.js'
import { Application } from '../storage/entity/Application.js' import { Application } from '../storage/entity/Application.js'
import { HtlcEvent, HtlcEvent_EventType } from '../../../proto/lnd/router.js'
import { RoutingEvent } from '../storage/entity/RoutingEvent.js'
import { BalanceInfo } from '../lnd/settings.js'
import { BalanceEvent } from '../storage/entity/BalanceEvent.js'
import { ChannelBalanceEvent } from '../storage/entity/ChannelsBalanceEvent.js'
const maxEvents = 100_000 const maxEvents = 100_000
export default class Handler { export default class Handler {
storage: Storage storage: Storage
@ -8,6 +13,50 @@ export default class Handler {
constructor(storage: Storage) { constructor(storage: Storage) {
this.storage = storage this.storage = storage
} }
async HtlcCb(htlc: HtlcEvent) {
const routingEvent: Partial<RoutingEvent> = {}
routingEvent.event_type = HtlcEvent_EventType[htlc.eventType]
routingEvent.incoming_channel_id = Number(htlc.incomingChannelId)
routingEvent.incoming_htlc_id = Number(htlc.incomingHtlcId)
routingEvent.outgoing_channel_id = Number(htlc.outgoingChannelId)
routingEvent.outgoing_htlc_id = Number(htlc.outgoingHtlcId)
routingEvent.timestamp_ns = Number(htlc.timestampNs)
if (htlc.event.oneofKind === 'finalHtlcEvent') {
routingEvent.offchain = htlc.event.finalHtlcEvent.offchain
routingEvent.settled = htlc.event.finalHtlcEvent.settled
} else if (htlc.event.oneofKind === 'forwardEvent') {
const { info } = htlc.event.forwardEvent
routingEvent.incoming_amt_msat = info ? Number(info.incomingAmtMsat) : undefined
routingEvent.outgoing_amt_msat = info ? Number(info.outgoingAmtMsat) : undefined
} else if (htlc.event.oneofKind === 'settleEvent') {
} else if (htlc.event.oneofKind === 'subscribedEvent') {
} else if (htlc.event.oneofKind === 'forwardFailEvent') {
routingEvent.forward_fail_event = true
} else if (htlc.event.oneofKind === 'linkFailEvent') {
routingEvent.failure_string = htlc.event.linkFailEvent.failureString
const { info } = htlc.event.linkFailEvent
routingEvent.incoming_amt_msat = info ? Number(info.incomingAmtMsat) : undefined
routingEvent.outgoing_amt_msat = info ? Number(info.outgoingAmtMsat) : undefined
}
await this.storage.metricsStorage.SaveRoutingEvent(routingEvent)
}
async NewBlockCb(height: number, balanceInfo: BalanceInfo) {
const balanceEvent: Partial<BalanceEvent> = {
block_height: height,
confirmed_chain_balance: balanceInfo.confirmedBalance,
unconfirmed_chain_balance: balanceInfo.unconfirmedBalance,
total_chain_balance: balanceInfo.totalBalance,
}
const channelsEvents: Partial<ChannelBalanceEvent>[] = balanceInfo.channelsBalance.map(c => ({
channel_id: c.channelId,
local_balance_sats: c.localBalanceSats,
remote_balance_sats: c.remoteBalanceSats,
}))
await this.storage.metricsStorage.SaveBalanceEvents(balanceEvent, channelsEvents)
}
AddMetrics(newMetrics: (Types.RequestMetric & { app_id?: string })[]) { AddMetrics(newMetrics: (Types.RequestMetric & { app_id?: string })[]) {
const parsed: Types.UsageMetric[] = newMetrics.map(m => ({ const parsed: Types.UsageMetric[] = newMetrics.map(m => ({
rpc_name: m.rpcName, rpc_name: m.rpcName,
@ -44,13 +93,8 @@ export default class Handler {
async GetAppMetrics(req: Types.AppsMetricsRequest, app: Application | null): Promise<Types.AppMetrics> { async GetAppMetrics(req: Types.AppsMetricsRequest, app: Application | null): Promise<Types.AppMetrics> {
const { receivingInvoices, receivingTransactions, outgoingInvoices, outgoingTransactions, receivingAddresses, userToUser } = await this.storage.paymentStorage.GetAppOperations(app, { from: req.from_unix, to: req.to_unix }) const { receivingInvoices, receivingTransactions, outgoingInvoices, outgoingTransactions, receivingAddresses, userToUser } = await this.storage.paymentStorage.GetAppOperations(app, { from: req.from_unix, to: req.to_unix })
const bigUser = req.big_user_sats ? req.big_user_sats : 10_000
const hugeUser = req.big_user_sats ? req.big_user_sats : 500_000
let totalReceived = 0 let totalReceived = 0
let totalSpent = 0 let totalSpent = 0
let totalAvailable = 0
let totalBigUsers = 0
let totalHugeUsers = 0
let unpaidInvoices = 0 let unpaidInvoices = 0
const operations: Types.UserOperation[] = [] const operations: Types.UserOperation[] = []
receivingInvoices.forEach(i => { receivingInvoices.forEach(i => {
@ -84,32 +128,93 @@ export default class Handler {
const users = await this.storage.applicationStorage.GetApplicationUsers(app, { from: req.from_unix, to: req.to_unix }) const users = await this.storage.applicationStorage.GetApplicationUsers(app, { from: req.from_unix, to: req.to_unix })
users.forEach(u => { let totalUserWithBalance = 0
totalAvailable += u.user.balance_sats let totalUserWithNoBalance = 0
if (u.user.balance_sats > bigUser) { let totalUsersWithNegativeBalance = 0
totalBigUsers++ let totalAlwaysBeenInactive = 0
let balanceSum = 0
let minBalance = Number.MAX_SAFE_INTEGER
let maxBalance = 0
await Promise.all(users.map(async u => {
if (u.user.balance_sats < 0) {
totalUsersWithNegativeBalance++
} else if (u.user.balance_sats === 0) {
const wasActive = await this.storage.paymentStorage.UserHasOutgoingOperation(u.user.user_id)
totalUserWithNoBalance++
if (!wasActive) {
totalAlwaysBeenInactive++
}
} else {
balanceSum += u.user.balance_sats
totalUserWithBalance++
if (u.user.balance_sats < minBalance) {
minBalance = u.user.balance_sats
}
if (u.user.balance_sats > maxBalance) {
maxBalance = u.user.balance_sats
}
} }
if (u.user.balance_sats > hugeUser) { }))
totalHugeUsers++
}
})
return { return {
app_name: app ? app.name : "unlinked to app", app: {
app_id: app ? app.app_id : "unlinked", name: app ? app.name : "unlinked to app",
app_npub: app ? (app.nostr_public_key || "") : "", id: app ? app.app_id : "unlinked",
app_balance: app ? app.owner.balance_sats : 0, npub: app ? (app.nostr_public_key || "") : "",
balance: app ? app.owner.balance_sats : 0,
},
users: {
total: users.length,
always_been_inactive: totalAlwaysBeenInactive,
balance_avg: Math.round(balanceSum / totalUserWithBalance),
balance_median: Math.round((maxBalance + minBalance) / 2),
no_balance: totalUserWithNoBalance,
negative_balance: totalUsersWithNegativeBalance,
},
total_received: totalReceived, total_received: totalReceived,
total_spent: totalSpent, total_spent: totalSpent,
total_available: totalAvailable, total_available: balanceSum,
total_users: users.length,
total_big_users: totalBigUsers,
total_huge_users: totalHugeUsers,
unpaid_invoices: unpaidInvoices, unpaid_invoices: unpaidInvoices,
operations operations
} }
} }
async GetLndMetrics(req: Types.LndMetricsRequest): Promise<Types.LndMetrics> {
const routingEvents = await this.storage.metricsStorage.GetRoutingEvents({ from: req.from_unix, to: req.to_unix })
const { channelsBalanceEvents, chainBalanceEvents } = await this.storage.metricsStorage.GetBalanceEvents({ from: req.from_unix, to: req.to_unix })
return {
nodes: [{
chain_balance_events: chainBalanceEvents.map(e => ({
block_height: e.block_height,
confirmed_balance: e.confirmed_chain_balance,
unconfirmed_balance: e.unconfirmed_chain_balance,
total_balance: e.total_chain_balance
})),
channels_balance_events: channelsBalanceEvents.map(e => ({
block_height: e.balance_event.block_height,
channel_id: e.channel_id,
local_balance_sats: e.local_balance_sats,
remote_balance_sats: e.remote_balance_sats
})),
routing_events: routingEvents.map(e => ({
event_type: e.event_type,
failure_string: e.failure_string || "",
forward_fail_event: e.forward_fail_event || false,
incoming_amt_msat: e.incoming_amt_msat || 0,
incoming_channel_id: e.incoming_channel_id || 0,
incoming_htlc_id: e.incoming_htlc_id || 0,
offchain: e.offchain || false,
outgoing_amt_msat: e.outgoing_amt_msat || 0,
outgoing_channel_id: e.outgoing_channel_id,
outgoing_htlc_id: e.outgoing_htlc_id,
settled: e.settled || false,
timestamp_ns: e.timestamp_ns
}))
}]
}
}
} }

View file

@ -10,6 +10,9 @@ export default (mainHandler: Main): Types.ServerMethods => {
GetAppsMetrics: async ({ ctx, req }) => { GetAppsMetrics: async ({ ctx, req }) => {
return mainHandler.metricsManager.GetAppsMetrics(req) return mainHandler.metricsManager.GetAppsMetrics(req)
}, },
GetLndMetrics: async ({ ctx, req }) => {
return mainHandler.metricsManager.GetLndMetrics(req)
},
EncryptionExchange: async () => { }, EncryptionExchange: async () => { },
Health: async () => { await mainHandler.lnd.Health() }, Health: async () => { await mainHandler.lnd.Health() },
LndGetInfo: async ({ ctx }) => { LndGetInfo: async ({ ctx }) => {

View file

@ -13,6 +13,9 @@ import { Product } from "./entity/Product.js"
import { UserToUserPayment } from "./entity/UserToUserPayment.js" import { UserToUserPayment } from "./entity/UserToUserPayment.js"
import { Application } from "./entity/Application.js" import { Application } from "./entity/Application.js"
import { ApplicationUser } from "./entity/ApplicationUser.js" import { ApplicationUser } from "./entity/ApplicationUser.js"
import { RoutingEvent } from "./entity/RoutingEvent.js"
import { BalanceEvent } from "./entity/BalanceEvent.js"
import { ChannelBalanceEvent } from "./entity/ChannelsBalanceEvent.js"
export type DbSettings = { export type DbSettings = {
databaseFile: string databaseFile: string
} }
@ -24,7 +27,8 @@ export default async (settings: DbSettings) => {
type: "sqlite", type: "sqlite",
database: settings.databaseFile, database: settings.databaseFile,
//logging: true, //logging: true,
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment, UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment], entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment,
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, RoutingEvent, BalanceEvent, ChannelBalanceEvent],
synchronize: true, synchronize: true,
}).initialize() }).initialize()
} }

View file

@ -0,0 +1,25 @@
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, CreateDateColumn, UpdateDateColumn } from "typeorm"
@Entity()
export class BalanceEvent {
@PrimaryGeneratedColumn()
serial_id: number
@Column()
block_height: number
@Column()
confirmed_chain_balance: number
@Column()
unconfirmed_chain_balance: number
@Column()
total_chain_balance: number
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -0,0 +1,27 @@
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from "typeorm"
import { BalanceEvent } from "./BalanceEvent.js"
@Entity()
export class ChannelBalanceEvent {
@PrimaryGeneratedColumn()
serial_id: number
@ManyToOne(type => BalanceEvent, { eager: true })
@JoinColumn()
balance_event: BalanceEvent
@Column()
channel_id: string
@Column()
local_balance_sats: number
@Column()
remote_balance_sats: number
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -0,0 +1,49 @@
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, CreateDateColumn, UpdateDateColumn } from "typeorm"
@Entity()
export class RoutingEvent {
@PrimaryGeneratedColumn()
serial_id: number
@Column()
incoming_channel_id: number
@Column()
incoming_htlc_id: number
@Column()
outgoing_channel_id: number
@Column()
outgoing_htlc_id: number
@Column()
timestamp_ns: number
@Column()
event_type: string
@Column({ nullable: true })
incoming_amt_msat?: number
@Column({ nullable: true })
outgoing_amt_msat?: number
@Column({ nullable: true })
failure_string?: string
@Column({ nullable: true })
settled?: boolean
@Column({ nullable: true })
offchain?: boolean
@Column({ nullable: true })
forward_fail_event?: boolean
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -4,6 +4,7 @@ import ProductStorage from './productStorage.js'
import ApplicationStorage from './applicationStorage.js' import ApplicationStorage from './applicationStorage.js'
import UserStorage from "./userStorage.js"; import UserStorage from "./userStorage.js";
import PaymentStorage from "./paymentStorage.js"; import PaymentStorage from "./paymentStorage.js";
import MetricsStorage from "./metricsStorage.js";
export type StorageSettings = { export type StorageSettings = {
dbSettings: DbSettings dbSettings: DbSettings
} }
@ -19,6 +20,7 @@ export default class {
applicationStorage: ApplicationStorage applicationStorage: ApplicationStorage
userStorage: UserStorage userStorage: UserStorage
paymentStorage: PaymentStorage paymentStorage: PaymentStorage
metricsStorage: MetricsStorage
pendingTx: boolean pendingTx: boolean
transactionsQueue: { exec: TX, res: () => void, rej: (message: string) => void }[] = [] transactionsQueue: { exec: TX, res: () => void, rej: (message: string) => void }[] = []
constructor(settings: StorageSettings) { constructor(settings: StorageSettings) {
@ -30,6 +32,7 @@ export default class {
this.productStorage = new ProductStorage(this.DB) this.productStorage = new ProductStorage(this.DB)
this.applicationStorage = new ApplicationStorage(this.DB, this.userStorage) this.applicationStorage = new ApplicationStorage(this.DB, this.userStorage)
this.paymentStorage = new PaymentStorage(this.DB, this.userStorage) this.paymentStorage = new PaymentStorage(this.DB, this.userStorage)
this.metricsStorage = new MetricsStorage(this.DB)
} }
StartTransaction(exec: TX) { StartTransaction(exec: TX) {

View file

@ -0,0 +1,51 @@
import { Between, DataSource, EntityManager, FindOperator, LessThanOrEqual, MoreThanOrEqual } from "typeorm"
import { RoutingEvent } from "./entity/RoutingEvent.js"
import { BalanceEvent } from "./entity/BalanceEvent.js"
import { ChannelBalanceEvent } from "./entity/ChannelsBalanceEvent.js"
export default class {
DB: DataSource | EntityManager
constructor(DB: DataSource | EntityManager) {
this.DB = DB
}
async SaveRoutingEvent(event: Partial<RoutingEvent>, entityManager = this.DB) {
const entry = entityManager.getRepository(RoutingEvent).create(event)
return entityManager.getRepository(RoutingEvent).save(entry)
}
async SaveBalanceEvents(balanceEvent: Partial<BalanceEvent>, channelBalanceEvents: Partial<ChannelBalanceEvent>[], entityManager = this.DB) {
const blanceEventEntry = entityManager.getRepository(BalanceEvent).create(balanceEvent)
const balanceEntry = await entityManager.getRepository(BalanceEvent).save(blanceEventEntry)
const channelsEntry = entityManager.getRepository(ChannelBalanceEvent).create(channelBalanceEvents.map(e => ({ ...e, balance_event: balanceEntry })))
const channelsEntries = await entityManager.getRepository(ChannelBalanceEvent).save(channelsEntry)
return { balanceEntry, channelsEntries }
}
async GetRoutingEvents({ from, to }: { from?: number, to?: number }, entityManager = this.DB) {
let q: { where: { created_at: FindOperator<Date> } } | {} = {}
if (!!from && !!to) {
q = { where: { created_at: Between<Date>(new Date(from * 1000), new Date(to * 1000)) } }
} else if (!!from) {
q = { where: { created_at: MoreThanOrEqual<Date>(new Date(from * 1000)) } }
} else if (!!to) {
q = { where: { created_at: LessThanOrEqual<Date>(new Date(to * 1000)) } }
}
return entityManager.getRepository(RoutingEvent).find(q)
}
async GetBalanceEvents({ from, to }: { from?: number, to?: number }, entityManager = this.DB) {
let q: { where: { created_at: FindOperator<Date> } } | {} = {}
if (!!from && !!to) {
q = { where: { created_at: Between<Date>(new Date(from * 1000), new Date(to * 1000)) } }
} else if (!!from) {
q = { where: { created_at: MoreThanOrEqual<Date>(new Date(from * 1000)) } }
} else if (!!to) {
q = { where: { created_at: LessThanOrEqual<Date>(new Date(to * 1000)) } }
}
const [chainBalanceEvents, channelsBalanceEvents] = await Promise.all([
entityManager.getRepository(BalanceEvent).find(q),
entityManager.getRepository(ChannelBalanceEvent).find(q),
])
return { chainBalanceEvents, channelsBalanceEvents }
}
}

View file

@ -289,4 +289,12 @@ export default class {
} }
} }
async UserHasOutgoingOperation(userId: string, entityManager = this.DB) {
const [i, tx, u2u] = await Promise.all([
entityManager.getRepository(UserInvoicePayment).findOne({ where: { user: { user_id: userId } } }),
entityManager.getRepository(UserTransactionPayment).findOne({ where: { user: { user_id: userId } } }),
entityManager.getRepository(UserToUserPayment).findOne({ where: { from_user: { user_id: userId } } }),
])
return !!i || !!tx || !!u2u
}
} }