last seen inactive chan
This commit is contained in:
parent
9f9a66a513
commit
67896c81ec
17 changed files with 162 additions and 27 deletions
|
|
@ -3,6 +3,7 @@ import { BalanceEvent } from "./build/src/services/storage/entity/BalanceEvent.j
|
||||||
import { ChannelBalanceEvent } from "./build/src/services/storage/entity/ChannelsBalanceEvent.js"
|
import { ChannelBalanceEvent } from "./build/src/services/storage/entity/ChannelsBalanceEvent.js"
|
||||||
import { ChannelRouting } from "./build/src/services/storage/entity/ChannelRouting.js"
|
import { ChannelRouting } from "./build/src/services/storage/entity/ChannelRouting.js"
|
||||||
import { RootOperation } from "./build/src/services/storage/entity/RootOperation.js"
|
import { RootOperation } from "./build/src/services/storage/entity/RootOperation.js"
|
||||||
|
import { ChannelEvent } from "./build/src/services/storage/entity/ChannelEvent.js"
|
||||||
import { LndMetrics1703170330183 } from './build/src/services/storage/migrations/1703170330183-lnd_metrics.js'
|
import { LndMetrics1703170330183 } from './build/src/services/storage/migrations/1703170330183-lnd_metrics.js'
|
||||||
import { ChannelRouting1709316653538 } from './build/src/services/storage/migrations/1709316653538-channel_routing.js'
|
import { ChannelRouting1709316653538 } from './build/src/services/storage/migrations/1709316653538-channel_routing.js'
|
||||||
import { HtlcCount1724266887195 } from './build/src/services/storage/migrations/1724266887195-htlc_count.js'
|
import { HtlcCount1724266887195 } from './build/src/services/storage/migrations/1724266887195-htlc_count.js'
|
||||||
|
|
@ -11,8 +12,8 @@ import { BalanceEvents1724860966825 } from './build/src/services/storage/migrati
|
||||||
export default new DataSource({
|
export default new DataSource({
|
||||||
type: "sqlite",
|
type: "sqlite",
|
||||||
database: "metrics.sqlite",
|
database: "metrics.sqlite",
|
||||||
entities: [BalanceEvent, ChannelBalanceEvent, ChannelRouting, RootOperation],
|
entities: [BalanceEvent, ChannelBalanceEvent, ChannelRouting, RootOperation, ChannelEvent],
|
||||||
migrations: [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825]
|
migrations: [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825]
|
||||||
});
|
});
|
||||||
|
|
||||||
//npx typeorm migration:generate ./src/services/storage/migrations/root_ops_time -d ./metricsDatasource.js
|
//npx typeorm migration:generate ./src/services/storage/migrations/channel_events -d ./metricsDatasource.js
|
||||||
|
|
@ -1340,6 +1340,7 @@ The nostr server will send back a message response, and inside the body there wi
|
||||||
- __capacity__: _number_
|
- __capacity__: _number_
|
||||||
- __channel_id__: _string_
|
- __channel_id__: _string_
|
||||||
- __channel_point__: _string_
|
- __channel_point__: _string_
|
||||||
|
- __inactive_since_unix__: _number_
|
||||||
- __label__: _string_
|
- __label__: _string_
|
||||||
- __lifetime__: _number_
|
- __lifetime__: _number_
|
||||||
- __local_balance__: _number_
|
- __local_balance__: _number_
|
||||||
|
|
|
||||||
|
|
@ -475,15 +475,16 @@ type OfferInvoices struct {
|
||||||
Invoices []OfferInvoice `json:"invoices"`
|
Invoices []OfferInvoice `json:"invoices"`
|
||||||
}
|
}
|
||||||
type OpenChannel struct {
|
type OpenChannel struct {
|
||||||
Active bool `json:"active"`
|
Active bool `json:"active"`
|
||||||
Capacity int64 `json:"capacity"`
|
Capacity int64 `json:"capacity"`
|
||||||
Channel_id string `json:"channel_id"`
|
Channel_id string `json:"channel_id"`
|
||||||
Channel_point string `json:"channel_point"`
|
Channel_point string `json:"channel_point"`
|
||||||
Label string `json:"label"`
|
Inactive_since_unix int64 `json:"inactive_since_unix"`
|
||||||
Lifetime int64 `json:"lifetime"`
|
Label string `json:"label"`
|
||||||
Local_balance int64 `json:"local_balance"`
|
Lifetime int64 `json:"lifetime"`
|
||||||
Policy *ChannelPolicy `json:"policy"`
|
Local_balance int64 `json:"local_balance"`
|
||||||
Remote_balance int64 `json:"remote_balance"`
|
Policy *ChannelPolicy `json:"policy"`
|
||||||
|
Remote_balance int64 `json:"remote_balance"`
|
||||||
}
|
}
|
||||||
type OpenChannelRequest struct {
|
type OpenChannelRequest struct {
|
||||||
Close_address string `json:"close_address"`
|
Close_address string `json:"close_address"`
|
||||||
|
|
|
||||||
|
|
@ -2751,6 +2751,7 @@ export type OpenChannel = {
|
||||||
capacity: number
|
capacity: number
|
||||||
channel_id: string
|
channel_id: string
|
||||||
channel_point: string
|
channel_point: string
|
||||||
|
inactive_since_unix: number
|
||||||
label: string
|
label: string
|
||||||
lifetime: number
|
lifetime: number
|
||||||
local_balance: number
|
local_balance: number
|
||||||
|
|
@ -2765,6 +2766,7 @@ export type OpenChannelOptions = OptionsBaseMessage & {
|
||||||
capacity_CustomCheck?: (v: number) => boolean
|
capacity_CustomCheck?: (v: number) => boolean
|
||||||
channel_id_CustomCheck?: (v: string) => boolean
|
channel_id_CustomCheck?: (v: string) => boolean
|
||||||
channel_point_CustomCheck?: (v: string) => boolean
|
channel_point_CustomCheck?: (v: string) => boolean
|
||||||
|
inactive_since_unix_CustomCheck?: (v: number) => boolean
|
||||||
label_CustomCheck?: (v: string) => boolean
|
label_CustomCheck?: (v: string) => boolean
|
||||||
lifetime_CustomCheck?: (v: number) => boolean
|
lifetime_CustomCheck?: (v: number) => boolean
|
||||||
local_balance_CustomCheck?: (v: number) => boolean
|
local_balance_CustomCheck?: (v: number) => boolean
|
||||||
|
|
@ -2787,6 +2789,9 @@ export const OpenChannelValidate = (o?: OpenChannel, opts: OpenChannelOptions =
|
||||||
if (typeof o.channel_point !== 'string') return new Error(`${path}.channel_point: is not a string`)
|
if (typeof o.channel_point !== 'string') return new Error(`${path}.channel_point: is not a string`)
|
||||||
if (opts.channel_point_CustomCheck && !opts.channel_point_CustomCheck(o.channel_point)) return new Error(`${path}.channel_point: custom check failed`)
|
if (opts.channel_point_CustomCheck && !opts.channel_point_CustomCheck(o.channel_point)) return new Error(`${path}.channel_point: custom check failed`)
|
||||||
|
|
||||||
|
if (typeof o.inactive_since_unix !== 'number') return new Error(`${path}.inactive_since_unix: is not a number`)
|
||||||
|
if (opts.inactive_since_unix_CustomCheck && !opts.inactive_since_unix_CustomCheck(o.inactive_since_unix)) return new Error(`${path}.inactive_since_unix: custom check failed`)
|
||||||
|
|
||||||
if (typeof o.label !== 'string') return new Error(`${path}.label: is not a string`)
|
if (typeof o.label !== 'string') return new Error(`${path}.label: is not a string`)
|
||||||
if (opts.label_CustomCheck && !opts.label_CustomCheck(o.label)) return new Error(`${path}.label: custom check failed`)
|
if (opts.label_CustomCheck && !opts.label_CustomCheck(o.label)) return new Error(`${path}.label: custom check failed`)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -184,12 +184,13 @@ message OpenChannel {
|
||||||
string channel_id = 1;
|
string channel_id = 1;
|
||||||
int64 capacity = 2;
|
int64 capacity = 2;
|
||||||
bool active = 3;
|
bool active = 3;
|
||||||
int64 lifetime =4 ;
|
int64 lifetime =4;
|
||||||
int64 local_balance=5;
|
int64 local_balance=5;
|
||||||
int64 remote_balance = 6;
|
int64 remote_balance = 6;
|
||||||
string label = 7;
|
string label = 7;
|
||||||
string channel_point = 8;
|
string channel_point = 8;
|
||||||
optional ChannelPolicy policy = 9;
|
optional ChannelPolicy policy = 9;
|
||||||
|
int64 inactive_since_unix = 10;
|
||||||
}
|
}
|
||||||
message ClosedChannel {
|
message ClosedChannel {
|
||||||
string channel_id = 1;
|
string channel_id = 1;
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,12 @@ import { LightningClient } from '../../../proto/lnd/lightning.client.js'
|
||||||
import { InvoicesClient } from '../../../proto/lnd/invoices.client.js'
|
import { InvoicesClient } from '../../../proto/lnd/invoices.client.js'
|
||||||
import { RouterClient } from '../../../proto/lnd/router.client.js'
|
import { RouterClient } from '../../../proto/lnd/router.client.js'
|
||||||
import { ChainNotifierClient } from '../../../proto/lnd/chainnotifier.client.js'
|
import { ChainNotifierClient } from '../../../proto/lnd/chainnotifier.client.js'
|
||||||
import { GetInfoResponse, AddressType, NewAddressResponse, AddInvoiceResponse, Invoice_InvoiceState, PayReq, Payment_PaymentStatus, Payment, PaymentFailureReason, SendCoinsResponse, EstimateFeeResponse, ChannelBalanceResponse, TransactionDetails, ListChannelsResponse, ClosedChannelsResponse, PendingChannelsResponse, ForwardingHistoryResponse, CoinSelectionStrategy, OpenStatusUpdate, CloseStatusUpdate, PendingUpdate } from '../../../proto/lnd/lightning.js'
|
import { GetInfoResponse, AddressType, NewAddressResponse, AddInvoiceResponse, Invoice_InvoiceState, PayReq, Payment_PaymentStatus, Payment, PaymentFailureReason, SendCoinsResponse, EstimateFeeResponse, ChannelBalanceResponse, TransactionDetails, ListChannelsResponse, ClosedChannelsResponse, PendingChannelsResponse, ForwardingHistoryResponse, CoinSelectionStrategy, OpenStatusUpdate, CloseStatusUpdate, PendingUpdate, ChannelEventUpdate_UpdateType } from '../../../proto/lnd/lightning.js'
|
||||||
import { OpenChannelReq } from './openChannelReq.js';
|
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, HtlcCb, BalanceInfo } from './settings.js';
|
import { LndSettings, AddressPaidCb, InvoicePaidCb, NodeInfo, Invoice, DecodedInvoice, PaidInvoice, NewBlockCb, HtlcCb, BalanceInfo, ChannelEventCb } from './settings.js';
|
||||||
import { ERROR, getLogger } from '../helpers/logger.js';
|
import { ERROR, getLogger } from '../helpers/logger.js';
|
||||||
import { HtlcEvent_EventType } from '../../../proto/lnd/router.js';
|
import { HtlcEvent_EventType } from '../../../proto/lnd/router.js';
|
||||||
import { LiquidityProvider, LiquidityRequest } from '../main/liquidityProvider.js';
|
import { LiquidityProvider, LiquidityRequest } from '../main/liquidityProvider.js';
|
||||||
|
|
@ -38,17 +38,19 @@ export default class {
|
||||||
invoicePaidCb: InvoicePaidCb
|
invoicePaidCb: InvoicePaidCb
|
||||||
newBlockCb: NewBlockCb
|
newBlockCb: NewBlockCb
|
||||||
htlcCb: HtlcCb
|
htlcCb: HtlcCb
|
||||||
|
channelEventCb: ChannelEventCb
|
||||||
log = getLogger({ component: 'lndManager' })
|
log = getLogger({ component: 'lndManager' })
|
||||||
outgoingOpsLocked = false
|
outgoingOpsLocked = false
|
||||||
liquidProvider: LiquidityProvider
|
liquidProvider: LiquidityProvider
|
||||||
utils: Utils
|
utils: Utils
|
||||||
constructor(settings: LndSettings, liquidProvider: LiquidityProvider, utils: Utils, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb, newBlockCb: NewBlockCb, htlcCb: HtlcCb) {
|
constructor(settings: LndSettings, liquidProvider: LiquidityProvider, utils: Utils, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb, newBlockCb: NewBlockCb, htlcCb: HtlcCb, channelEventCb: ChannelEventCb) {
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.utils = utils
|
this.utils = utils
|
||||||
this.addressPaidCb = addressPaidCb
|
this.addressPaidCb = addressPaidCb
|
||||||
this.invoicePaidCb = invoicePaidCb
|
this.invoicePaidCb = invoicePaidCb
|
||||||
this.newBlockCb = newBlockCb
|
this.newBlockCb = newBlockCb
|
||||||
this.htlcCb = htlcCb
|
this.htlcCb = htlcCb
|
||||||
|
this.channelEventCb = channelEventCb
|
||||||
const { lndAddr, lndCertPath, lndMacaroonPath } = this.settings.mainNode
|
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');
|
||||||
|
|
@ -97,6 +99,7 @@ export default class {
|
||||||
this.SubscribeInvoicePaid()
|
this.SubscribeInvoicePaid()
|
||||||
this.SubscribeNewBlock()
|
this.SubscribeNewBlock()
|
||||||
this.SubscribeHtlcEvents()
|
this.SubscribeHtlcEvents()
|
||||||
|
this.SubscribeChannelEvents()
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
return new Promise<void>((res, rej) => {
|
return new Promise<void>((res, rej) => {
|
||||||
const interval = setInterval(async () => {
|
const interval = setInterval(async () => {
|
||||||
|
|
@ -168,6 +171,20 @@ export default class {
|
||||||
}, deadLndRetrySeconds * 1000)
|
}, deadLndRetrySeconds * 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async SubscribeChannelEvents() {
|
||||||
|
const stream = this.lightning.subscribeChannelEvents({}, { abort: this.abortController.signal })
|
||||||
|
stream.responses.onMessage(async channel => {
|
||||||
|
const channels = await this.ListChannels()
|
||||||
|
this.channelEventCb(channel, channels.channels)
|
||||||
|
})
|
||||||
|
stream.responses.onError(error => {
|
||||||
|
this.log("Error with subscribeChannelEvents stream")
|
||||||
|
})
|
||||||
|
stream.responses.onComplete(() => {
|
||||||
|
this.log("subscribeChannelEvents stream closed")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async SubscribeHtlcEvents() {
|
async SubscribeHtlcEvents() {
|
||||||
const stream = this.router.subscribeHtlcEvents({}, { abort: this.abortController.signal })
|
const stream = this.router.subscribeHtlcEvents({}, { abort: this.abortController.signal })
|
||||||
stream.responses.onMessage(htlc => {
|
stream.responses.onMessage(htlc => {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Channel, ChannelEventUpdate } from "../../../proto/lnd/lightning"
|
||||||
import { HtlcEvent } from "../../../proto/lnd/router"
|
import { HtlcEvent } from "../../../proto/lnd/router"
|
||||||
export type NodeSettings = {
|
export type NodeSettings = {
|
||||||
lndAddr: string
|
lndAddr: string
|
||||||
|
|
@ -35,6 +36,7 @@ export type AddressPaidCb = (txOutput: TxOutput, address: string, amount: number
|
||||||
export type InvoicePaidCb = (paymentRequest: string, amount: number, used: 'lnd' | 'provider' | 'internal') => Promise<void>
|
export type InvoicePaidCb = (paymentRequest: string, amount: number, used: 'lnd' | 'provider' | 'internal') => Promise<void>
|
||||||
export type NewBlockCb = (height: number) => void
|
export type NewBlockCb = (height: number) => void
|
||||||
export type HtlcCb = (event: HtlcEvent) => void
|
export type HtlcCb = (event: HtlcEvent) => void
|
||||||
|
export type ChannelEventCb = (event: ChannelEventUpdate, channels: Channel[]) => void
|
||||||
|
|
||||||
export type NodeInfo = {
|
export type NodeInfo = {
|
||||||
alias: string
|
alias: string
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,7 @@ export class AdminManager {
|
||||||
ListChannels = async (): Promise<Types.LndChannels> => {
|
ListChannels = async (): Promise<Types.LndChannels> => {
|
||||||
const { channels } = await this.lnd.ListChannels(true)
|
const { channels } = await this.lnd.ListChannels(true)
|
||||||
const { identityPubkey } = await this.lnd.GetInfo()
|
const { identityPubkey } = await this.lnd.GetInfo()
|
||||||
|
const activity = await this.storage.metricsStorage.GetChannelsActivity()
|
||||||
const openChannels = await Promise.all(channels.map(async c => {
|
const openChannels = await Promise.all(channels.map(async c => {
|
||||||
const info = await this.lnd.GetChannelInfo(c.chanId)
|
const info = await this.lnd.GetChannelInfo(c.chanId)
|
||||||
const policies = [{ pub: info.node1Pub, policy: info.node1Policy }, { pub: info.node2Pub, policy: info.node2Policy }]
|
const policies = [{ pub: info.node1Pub, policy: info.node1Policy }, { pub: info.node2Pub, policy: info.node2Policy }]
|
||||||
|
|
@ -182,6 +183,7 @@ export class AdminManager {
|
||||||
label: c.peerAlias || c.remotePubkey,
|
label: c.peerAlias || c.remotePubkey,
|
||||||
lifetime: Number(c.lifetime),
|
lifetime: Number(c.lifetime),
|
||||||
policy,
|
policy,
|
||||||
|
inactive_since_unix: activity[c.chanId] || 0
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import ApplicationManager from './applicationManager.js'
|
||||||
import PaymentManager, { PendingTx } from './paymentManager.js'
|
import PaymentManager, { PendingTx } from './paymentManager.js'
|
||||||
import { MainSettings } from './settings.js'
|
import { MainSettings } from './settings.js'
|
||||||
import LND from "../lnd/lnd.js"
|
import LND from "../lnd/lnd.js"
|
||||||
import { AddressPaidCb, HtlcCb, InvoicePaidCb, NewBlockCb } from "../lnd/settings.js"
|
import { AddressPaidCb, ChannelEventCb, HtlcCb, InvoicePaidCb, NewBlockCb } from "../lnd/settings.js"
|
||||||
import { ERROR, getLogger, PubLogger } from "../helpers/logger.js"
|
import { ERROR, getLogger, PubLogger } from "../helpers/logger.js"
|
||||||
import AppUserManager from "./appUserManager.js"
|
import AppUserManager from "./appUserManager.js"
|
||||||
import { Application } from '../storage/entity/Application.js'
|
import { Application } from '../storage/entity/Application.js'
|
||||||
|
|
@ -66,7 +66,7 @@ export default class {
|
||||||
const updateProviderBalance = (b: number) => this.storage.liquidityStorage.IncrementTrackedProviderBalance('lnPub', settings.liquiditySettings.liquidityProviderPub, b)
|
const updateProviderBalance = (b: number) => this.storage.liquidityStorage.IncrementTrackedProviderBalance('lnPub', settings.liquiditySettings.liquidityProviderPub, b)
|
||||||
this.liquidityProvider = new LiquidityProvider(settings.liquiditySettings.liquidityProviderPub, this.utils, this.invoicePaidCb, updateProviderBalance)
|
this.liquidityProvider = new LiquidityProvider(settings.liquiditySettings.liquidityProviderPub, this.utils, this.invoicePaidCb, updateProviderBalance)
|
||||||
this.rugPullTracker = new RugPullTracker(this.storage, this.liquidityProvider)
|
this.rugPullTracker = new RugPullTracker(this.storage, this.liquidityProvider)
|
||||||
this.lnd = new LND(settings.lndSettings, this.liquidityProvider, this.utils, this.addressPaidCb, this.invoicePaidCb, this.newBlockCb, this.htlcCb)
|
this.lnd = new LND(settings.lndSettings, this.liquidityProvider, this.utils, this.addressPaidCb, this.invoicePaidCb, this.newBlockCb, this.htlcCb, this.channelEventCb)
|
||||||
this.liquidityManager = new LiquidityManager(this.settings.liquiditySettings, this.storage, this.utils, this.liquidityProvider, this.lnd, this.rugPullTracker)
|
this.liquidityManager = new LiquidityManager(this.settings.liquiditySettings, this.storage, this.utils, this.liquidityProvider, this.lnd, this.rugPullTracker)
|
||||||
this.metricsManager = new MetricsManager(this.storage, this.lnd)
|
this.metricsManager = new MetricsManager(this.storage, this.lnd)
|
||||||
|
|
||||||
|
|
@ -133,6 +133,10 @@ export default class {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
channelEventCb: ChannelEventCb = (e, channels) => {
|
||||||
|
this.metricsManager.ChannelEventCb(e, channels)
|
||||||
|
}
|
||||||
|
|
||||||
htlcCb: HtlcCb = (e) => {
|
htlcCb: HtlcCb = (e) => {
|
||||||
this.metricsManager.HtlcCb(e)
|
this.metricsManager.HtlcCb(e)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ 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 { HtlcEvent, HtlcEvent_EventType } from '../../../proto/lnd/router.js'
|
||||||
|
import { Channel, ChannelEventUpdate, ChannelPoint } from '../../../proto/lnd/lightning.js'
|
||||||
import { BalanceInfo } from '../lnd/settings.js'
|
import { BalanceInfo } from '../lnd/settings.js'
|
||||||
import { BalanceEvent } from '../storage/entity/BalanceEvent.js'
|
import { BalanceEvent } from '../storage/entity/BalanceEvent.js'
|
||||||
import { ChannelBalanceEvent } from '../storage/entity/ChannelsBalanceEvent.js'
|
import { ChannelBalanceEvent } from '../storage/entity/ChannelsBalanceEvent.js'
|
||||||
|
|
@ -13,7 +14,6 @@ import { getLogger } from '../helpers/logger.js'
|
||||||
import { encodeTLV, usageMetricsToTlv } from '../helpers/tlv.js'
|
import { encodeTLV, usageMetricsToTlv } from '../helpers/tlv.js'
|
||||||
import { ChannelCloseSummary_ClosureType } from '../../../proto/lnd/lightning.js'
|
import { ChannelCloseSummary_ClosureType } from '../../../proto/lnd/lightning.js'
|
||||||
|
|
||||||
|
|
||||||
export default class Handler {
|
export default class Handler {
|
||||||
|
|
||||||
storage: Storage
|
storage: Storage
|
||||||
|
|
@ -46,6 +46,29 @@ export default class Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async ChannelEventCb(event: ChannelEventUpdate, channels: Channel[]) {
|
||||||
|
if (event.channel.oneofKind === 'inactiveChannel') {
|
||||||
|
const channel = this.getRelevantChannel(event.channel.inactiveChannel, channels)
|
||||||
|
if (!channel) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await this.storage.metricsStorage.FlagInactiveChannel(channel.chanId)
|
||||||
|
return
|
||||||
|
} else if (event.channel.oneofKind === 'activeChannel') {
|
||||||
|
const channel = this.getRelevantChannel(event.channel.activeChannel, channels)
|
||||||
|
if (!channel) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await this.storage.metricsStorage.FlagActiveChannel(channel.chanId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getRelevantChannel(c: ChannelPoint, channels: Channel[]) {
|
||||||
|
const point = `${c.fundingTxid}:${c.outputIndex}`
|
||||||
|
return channels.find(c => c.channelPoint === point)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async HtlcCb(htlc: HtlcEvent) {
|
async HtlcCb(htlc: HtlcEvent) {
|
||||||
|
|
@ -326,12 +349,13 @@ export default class Handler {
|
||||||
|
|
||||||
|
|
||||||
async GetLndMetrics(req: Types.LndMetricsRequest): Promise<Types.LndMetrics> {
|
async GetLndMetrics(req: Types.LndMetricsRequest): Promise<Types.LndMetrics> {
|
||||||
const [chansInfo, pendingChansInfo, closedChansInfo, routing, rootOps] = await Promise.all([
|
const [chansInfo, pendingChansInfo, closedChansInfo, routing, rootOps, channelsActivity] = await Promise.all([
|
||||||
this.GetChannelsInfo(),
|
this.GetChannelsInfo(),
|
||||||
this.GetPendingChannelsInfo(),
|
this.GetPendingChannelsInfo(),
|
||||||
this.lnd.ListClosedChannels(),
|
this.lnd.ListClosedChannels(),
|
||||||
this.storage.metricsStorage.GetChannelRouting({ from: req.from_unix, to: req.to_unix }),
|
this.storage.metricsStorage.GetChannelRouting({ from: req.from_unix, to: req.to_unix }),
|
||||||
this.storage.metricsStorage.GetRootOperations({ from: req.from_unix, to: req.to_unix })
|
this.storage.metricsStorage.GetRootOperations({ from: req.from_unix, to: req.to_unix }),
|
||||||
|
this.storage.metricsStorage.GetChannelsActivity()
|
||||||
])
|
])
|
||||||
const { openChannels, totalActive, totalInactive } = chansInfo
|
const { openChannels, totalActive, totalInactive } = chansInfo
|
||||||
const { totalPendingOpen, totalPendingClose } = pendingChansInfo
|
const { totalPendingOpen, totalPendingClose } = pendingChansInfo
|
||||||
|
|
@ -379,7 +403,7 @@ export default class Handler {
|
||||||
offline_channels: totalInactive,
|
offline_channels: totalInactive,
|
||||||
online_channels: totalActive,
|
online_channels: totalActive,
|
||||||
closed_channels: closed,
|
closed_channels: closed,
|
||||||
open_channels: openChannels.map(c => ({ channel_point: c.channelPoint, active: c.active, capacity: Number(c.capacity), channel_id: c.chanId, lifetime: Number(c.lifetime), local_balance: Number(c.localBalance), remote_balance: Number(c.remoteBalance), label: c.peerAlias })),
|
open_channels: openChannels.map(c => ({ channel_point: c.channelPoint, active: c.active, capacity: Number(c.capacity), channel_id: c.chanId, lifetime: Number(c.lifetime), local_balance: Number(c.localBalance), remote_balance: Number(c.remoteBalance), label: c.peerAlias, inactive_since_unix: channelsActivity[c.chanId] || 0 })),
|
||||||
forwarding_events: totalEvents,
|
forwarding_events: totalEvents,
|
||||||
forwarding_fees: totalFees,
|
forwarding_fees: totalFees,
|
||||||
root_ops: rootOps.map(r => ({ amount: r.operation_amount, created_at_unix: r.at_unix || 0, op_id: r.operation_identifier, op_type: mapRootOpType(r.operation_type) })),
|
root_ops: rootOps.map(r => ({ amount: r.operation_amount, created_at_unix: r.at_unix || 0, op_id: r.operation_identifier, op_type: mapRootOpType(r.operation_type) })),
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import { InviteToken } from "../entity/InviteToken.js"
|
||||||
import { DebitAccess } from "../entity/DebitAccess.js"
|
import { DebitAccess } from "../entity/DebitAccess.js"
|
||||||
import { RootOperation } from "../entity/RootOperation.js"
|
import { RootOperation } from "../entity/RootOperation.js"
|
||||||
import { UserOffer } from "../entity/UserOffer.js"
|
import { UserOffer } from "../entity/UserOffer.js"
|
||||||
|
import { ChannelEvent } from "../entity/ChannelEvent.js"
|
||||||
|
|
||||||
|
|
||||||
export type DbSettings = {
|
export type DbSettings = {
|
||||||
|
|
@ -74,7 +75,8 @@ export const MetricsDbEntities = {
|
||||||
'BalanceEvent': BalanceEvent,
|
'BalanceEvent': BalanceEvent,
|
||||||
'ChannelBalanceEvent': ChannelBalanceEvent,
|
'ChannelBalanceEvent': ChannelBalanceEvent,
|
||||||
'ChannelRouting': ChannelRouting,
|
'ChannelRouting': ChannelRouting,
|
||||||
'RootOperation': RootOperation
|
'RootOperation': RootOperation,
|
||||||
|
'ChannelEvent': ChannelEvent
|
||||||
}
|
}
|
||||||
export type MetricsDbNames = keyof typeof MetricsDbEntities
|
export type MetricsDbNames = keyof typeof MetricsDbEntities
|
||||||
export const MetricsDbEntitiesNames = Object.keys(MetricsDbEntities)
|
export const MetricsDbEntitiesNames = Object.keys(MetricsDbEntities)
|
||||||
|
|
|
||||||
22
src/services/storage/entity/ChannelEvent.ts
Normal file
22
src/services/storage/entity/ChannelEvent.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class ChannelEvent {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
serial_id: number
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
channel_id: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
event_type: 'activity'
|
||||||
|
|
||||||
|
@Column({ default: 0 })
|
||||||
|
inactive_since_unix: number
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
created_at: Date
|
||||||
|
|
||||||
|
@UpdateDateColumn()
|
||||||
|
updated_at: Date
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,8 @@ import { ChannelRouting } from "./entity/ChannelRouting.js";
|
||||||
import { RootOperation } from "./entity/RootOperation.js";
|
import { RootOperation } from "./entity/RootOperation.js";
|
||||||
import { StorageInterface } from "./db/storageInterface.js";
|
import { StorageInterface } from "./db/storageInterface.js";
|
||||||
import { Utils } from "../helpers/utilsWrapper.js";
|
import { Utils } from "../helpers/utilsWrapper.js";
|
||||||
|
import { Channel, ChannelEventUpdate } from "../../../proto/lnd/lightning.js";
|
||||||
|
import { ChannelEvent } from "./entity/ChannelEvent.js";
|
||||||
export default class {
|
export default class {
|
||||||
//DB: DataSource | EntityManager
|
//DB: DataSource | EntityManager
|
||||||
settings: StorageSettings
|
settings: StorageSettings
|
||||||
|
|
@ -27,6 +29,42 @@ export default class {
|
||||||
//return executedMigrations;
|
//return executedMigrations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async FlagActiveChannel(chanId: string) {
|
||||||
|
const existing = await this.dbs.FindOne<ChannelEvent>('ChannelEvent', { where: { channel_id: chanId, event_type: 'activity' } })
|
||||||
|
if (!existing) {
|
||||||
|
await this.dbs.CreateAndSave<ChannelEvent>('ChannelEvent', { channel_id: chanId, event_type: 'activity', inactive_since_unix: 0 })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing.inactive_since_unix > 0) {
|
||||||
|
await this.dbs.Update<ChannelEvent>('ChannelEvent', existing.serial_id, { inactive_since_unix: 0 })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
async FlagInactiveChannel(chanId: string) {
|
||||||
|
const existing = await this.dbs.FindOne<ChannelEvent>('ChannelEvent', { where: { channel_id: chanId, event_type: 'activity' } })
|
||||||
|
if (!existing) {
|
||||||
|
await this.dbs.CreateAndSave<ChannelEvent>('ChannelEvent', { channel_id: chanId, event_type: 'activity', inactive_since_unix: Math.floor(Date.now() / 1000) })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (existing.inactive_since_unix > 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await this.dbs.Update<ChannelEvent>('ChannelEvent', existing.serial_id, { inactive_since_unix: Math.floor(Date.now() / 1000) })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetChannelsActivity(): Promise<Record<string, number>> {
|
||||||
|
const events = await this.dbs.Find<ChannelEvent>('ChannelEvent', { where: { event_type: 'activity' } })
|
||||||
|
const activityMap: Record<string, number> = {}
|
||||||
|
events.forEach(e => {
|
||||||
|
activityMap[e.channel_id] = e.inactive_since_unix
|
||||||
|
})
|
||||||
|
return activityMap
|
||||||
|
}
|
||||||
|
|
||||||
async SaveBalanceEvents(balanceEvent: Partial<BalanceEvent>, channelBalanceEvents: Partial<ChannelBalanceEvent>[]) {
|
async SaveBalanceEvents(balanceEvent: Partial<BalanceEvent>, channelBalanceEvents: Partial<ChannelBalanceEvent>[]) {
|
||||||
//const blanceEventEntry = this.DB.getRepository(BalanceEvent).create(balanceEvent)
|
//const blanceEventEntry = this.DB.getRepository(BalanceEvent).create(balanceEvent)
|
||||||
//const balanceEntry = await this.txQueue.PushToQueue<BalanceEvent>({ exec: async db => db.getRepository(BalanceEvent).save(blanceEventEntry), dbTx: false })
|
//const balanceEntry = await this.txQueue.PushToQueue<BalanceEvent>({ exec: async db => db.getRepository(BalanceEvent).save(blanceEventEntry), dbTx: false })
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class ChannelEvents1750777346411 implements MigrationInterface {
|
||||||
|
name = 'ChannelEvents1750777346411'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`CREATE TABLE "channel_event" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "channel_id" varchar NOT NULL, "event_type" varchar NOT NULL, "inactive_since_unix" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP TABLE "channel_event"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -16,8 +16,9 @@ import { UserCbUrl1727112281043 } from './1727112281043-user_cb_url.js'
|
||||||
import { RootOps1732566440447 } from './1732566440447-root_ops.js'
|
import { RootOps1732566440447 } from './1732566440447-root_ops.js'
|
||||||
import { UserOffer1733502626042 } from './1733502626042-user_offer.js'
|
import { UserOffer1733502626042 } from './1733502626042-user_offer.js'
|
||||||
import { RootOpsTime1745428134124 } from './1745428134124-root_ops_time.js'
|
import { RootOpsTime1745428134124 } from './1745428134124-root_ops_time.js'
|
||||||
|
import { ChannelEvents1750777346411 } from './1750777346411-channel_events.js'
|
||||||
export const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, TrackedProvider1720814323679, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042]
|
export const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, TrackedProvider1720814323679, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042]
|
||||||
export const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825, RootOps1732566440447, RootOpsTime1745428134124]
|
export const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825, RootOps1732566440447, RootOpsTime1745428134124, ChannelEvents1750777346411]
|
||||||
/* export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise<boolean> => {
|
/* export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise<boolean> => {
|
||||||
await connectAndMigrate(log, storageManager, allMigrations, allMetricsMigrations)
|
await connectAndMigrate(log, storageManager, allMigrations, allMetricsMigrations)
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ export const setupNetwork = async (): Promise<ChainTools> => {
|
||||||
await core.InitAddress()
|
await core.InitAddress()
|
||||||
await core.Mine(1)
|
await core.Mine(1)
|
||||||
const setupUtils = new Utils({ dataDir: settings.storageSettings.dataDir, allowResetMetricsStorages: settings.allowResetMetricsStorages })
|
const setupUtils = new Utils({ dataDir: settings.storageSettings.dataDir, allowResetMetricsStorages: settings.allowResetMetricsStorages })
|
||||||
const alice = new LND(settings.lndSettings, new LiquidityProvider("", setupUtils, async () => { }, async () => { }), setupUtils, async () => { }, async () => { }, () => { }, () => { })
|
const alice = new LND(settings.lndSettings, new LiquidityProvider("", setupUtils, async () => { }, async () => { }), setupUtils, async () => { }, async () => { }, () => { }, () => { }, () => { })
|
||||||
const bob = new LND({ ...settings.lndSettings, mainNode: settings.lndSettings.otherNode }, new LiquidityProvider("", setupUtils, async () => { }, async () => { }), setupUtils, async () => { }, async () => { }, () => { }, () => { })
|
const bob = new LND({ ...settings.lndSettings, mainNode: settings.lndSettings.otherNode }, new LiquidityProvider("", setupUtils, async () => { }, async () => { }), setupUtils, async () => { }, async () => { }, () => { }, () => { }, () => { })
|
||||||
await tryUntil<void>(async i => {
|
await tryUntil<void>(async i => {
|
||||||
const peers = await alice.ListPeers()
|
const peers = await alice.ListPeers()
|
||||||
if (peers.peers.length > 0) {
|
if (peers.peers.length > 0) {
|
||||||
|
|
|
||||||
|
|
@ -78,11 +78,11 @@ export const SetupTest = async (d: Describe, chainTools: ChainTools): Promise<Te
|
||||||
await externalAccessToMainLnd.Warmup() */
|
await externalAccessToMainLnd.Warmup() */
|
||||||
|
|
||||||
const otherLndSetting = { ...settings.lndSettings, mainNode: settings.lndSettings.otherNode }
|
const otherLndSetting = { ...settings.lndSettings, mainNode: settings.lndSettings.otherNode }
|
||||||
const externalAccessToOtherLnd = new LND(otherLndSetting, new LiquidityProvider("", extermnalUtils, async () => { }, async () => { }), extermnalUtils, async () => { }, async () => { }, () => { }, () => { })
|
const externalAccessToOtherLnd = new LND(otherLndSetting, new LiquidityProvider("", extermnalUtils, async () => { }, async () => { }), extermnalUtils, async () => { }, async () => { }, () => { }, () => { }, () => { })
|
||||||
await externalAccessToOtherLnd.Warmup()
|
await externalAccessToOtherLnd.Warmup()
|
||||||
|
|
||||||
const thirdLndSetting = { ...settings.lndSettings, mainNode: settings.lndSettings.thirdNode }
|
const thirdLndSetting = { ...settings.lndSettings, mainNode: settings.lndSettings.thirdNode }
|
||||||
const externalAccessToThirdLnd = new LND(thirdLndSetting, new LiquidityProvider("", extermnalUtils, async () => { }, async () => { }), extermnalUtils, async () => { }, async () => { }, () => { }, () => { })
|
const externalAccessToThirdLnd = new LND(thirdLndSetting, new LiquidityProvider("", extermnalUtils, async () => { }, async () => { }), extermnalUtils, async () => { }, async () => { }, () => { }, () => { }, () => { })
|
||||||
await externalAccessToThirdLnd.Warmup()
|
await externalAccessToThirdLnd.Warmup()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue