2022-04-29 03:57:27 +04:00
import logger from '../../logger' ;
import DB from '../../database' ;
class ChannelsApi {
2022-05-01 15:35:28 +04:00
public async $getAllChannels ( ) : Promise < any [ ] > {
try {
const query = ` SELECT * FROM channels ` ;
const [ rows ] : any = await DB . query ( query ) ;
return rows ;
} catch ( e ) {
2022-05-07 11:32:15 +04:00
logger . err ( '$getAllChannels error: ' + ( e instanceof Error ? e.message : e ) ) ;
2022-05-01 15:35:28 +04:00
throw e ;
}
}
2022-07-21 22:43:12 +02:00
public async $getAllChannelsGeo ( ) : Promise < any [ ] > {
try {
const query = ` SELECT nodes_1.public_key as node1_public_key, nodes_1.alias AS node1_alias,
nodes_1 . latitude AS node1_latitude , nodes_1 . longitude AS node1_longitude ,
nodes_2 . public_key as node2_public_key , nodes_2 . alias AS node2_alias ,
nodes_2 . latitude AS node2_latitude , nodes_2 . longitude AS node2_longitude ,
channels . capacity
FROM channels
JOIN nodes AS nodes_1 on nodes_1 . public_key = channels . node1_public_key
JOIN nodes AS nodes_2 on nodes_2 . public_key = channels . node2_public_key
WHERE nodes_1 . latitude IS NOT NULL AND nodes_1 . longitude IS NOT NULL
AND nodes_2 . latitude IS NOT NULL AND nodes_2 . longitude IS NOT NULL
` ;
const [ rows ] : any = await DB . query ( query ) ;
return rows . map ( ( row ) = > [
row . node1_public_key , row . node1_alias , row . node1_longitude , row . node1_latitude ,
row . node2_public_key , row . node2_alias , row . node2_longitude , row . node2_latitude ,
row . capacity ] ) ;
} catch ( e ) {
logger . err ( '$getAllChannels error: ' + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
2022-05-09 18:21:42 +04:00
public async $searchChannelsById ( search : string ) : Promise < any [ ] > {
try {
const searchStripped = search . replace ( '%' , '' ) + '%' ;
const query = ` SELECT id, short_id, capacity FROM channels WHERE id LIKE ? OR short_id LIKE ? LIMIT 10 ` ;
const [ rows ] : any = await DB . query ( query , [ searchStripped , searchStripped ] ) ;
return rows ;
} catch ( e ) {
logger . err ( '$searchChannelsById error: ' + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
2022-05-01 15:35:28 +04:00
public async $getChannelsByStatus ( status : number ) : Promise < any [ ] > {
try {
const query = ` SELECT * FROM channels WHERE status = ? ` ;
const [ rows ] : any = await DB . query ( query , [ status ] ) ;
return rows ;
} catch ( e ) {
2022-05-07 11:32:15 +04:00
logger . err ( '$getChannelsByStatus error: ' + ( e instanceof Error ? e.message : e ) ) ;
2022-05-01 15:35:28 +04:00
throw e ;
}
}
2022-06-29 23:06:13 +02:00
public async $getClosedChannelsWithoutReason ( ) : Promise < any [ ] > {
try {
2022-07-06 11:58:06 +02:00
const query = ` SELECT * FROM channels WHERE status = 2 AND closing_reason IS NULL AND closing_transaction_id != '' ` ;
2022-06-29 23:06:13 +02:00
const [ rows ] : any = await DB . query ( query ) ;
return rows ;
} catch ( e ) {
logger . err ( '$getClosedChannelsWithoutReason error: ' + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
2022-05-05 23:19:24 +04:00
public async $getChannelsWithoutCreatedDate ( ) : Promise < any [ ] > {
try {
const query = ` SELECT * FROM channels WHERE created IS NULL ` ;
const [ rows ] : any = await DB . query ( query ) ;
return rows ;
} catch ( e ) {
logger . err ( '$getChannelsWithoutCreatedDate error: ' + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
2022-05-26 18:23:57 +04:00
public async $getChannel ( id : string ) : Promise < any > {
2022-05-01 03:01:27 +04:00
try {
2022-05-16 00:01:53 +04:00
const query = ` SELECT n1.alias AS alias_left, n2.alias AS alias_right, channels.*, ns1.channels AS channels_left, ns1.capacity AS capacity_left, ns2.channels AS channels_right, ns2.capacity AS capacity_right FROM channels LEFT JOIN nodes AS n1 ON n1.public_key = channels.node1_public_key LEFT JOIN nodes AS n2 ON n2.public_key = channels.node2_public_key LEFT JOIN node_stats AS ns1 ON ns1.public_key = channels.node1_public_key LEFT JOIN node_stats AS ns2 ON ns2.public_key = channels.node2_public_key WHERE (ns1.id = (SELECT MAX(id) FROM node_stats WHERE public_key = channels.node1_public_key) AND ns2.id = (SELECT MAX(id) FROM node_stats WHERE public_key = channels.node2_public_key)) AND channels.id = ? ` ;
2022-05-26 18:23:57 +04:00
const [ rows ] : any = await DB . query ( query , [ id ] ) ;
2022-05-15 19:22:14 +04:00
if ( rows [ 0 ] ) {
return this . convertChannel ( rows [ 0 ] ) ;
}
2022-05-01 03:01:27 +04:00
} catch ( e ) {
logger . err ( '$getChannel error: ' + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
2022-07-06 14:56:10 +02:00
public async $getChannelsStats ( ) : Promise < any > {
try {
// Feedback from zerofeerouting:
// "I would argue > 5000ppm can be ignored. Channels charging more than .5% fee are ignored by CLN for example."
const ignoredFeeRateThreshold = 5000 ;
const ignoredBaseFeeThreshold = 5000 ;
// Capacity
let query = ` SELECT AVG(capacity) AS avgCapacity FROM channels WHERE status = 1 ORDER BY capacity ` ;
const [ avgCapacity ] : any = await DB . query ( query ) ;
query = ` SELECT capacity FROM channels WHERE status = 1 ORDER BY capacity ` ;
let [ capacity ] : any = await DB . query ( query ) ;
capacity = capacity . map ( capacity = > capacity . capacity ) ;
const medianCapacity = capacity [ Math . floor ( capacity . length / 2 ) ] ;
// Fee rates
query = ` SELECT node1_fee_rate FROM channels WHERE node1_fee_rate < ${ ignoredFeeRateThreshold } AND status = 1 ` ;
let [ feeRates1 ] : any = await DB . query ( query ) ;
feeRates1 = feeRates1 . map ( rate = > rate . node1_fee_rate ) ;
query = ` SELECT node2_fee_rate FROM channels WHERE node2_fee_rate < ${ ignoredFeeRateThreshold } AND status = 1 ` ;
let [ feeRates2 ] : any = await DB . query ( query ) ;
feeRates2 = feeRates2 . map ( rate = > rate . node2_fee_rate ) ;
let feeRates = ( feeRates1 . concat ( feeRates2 ) ) . sort ( ( a , b ) = > a - b ) ;
let avgFeeRate = 0 ;
for ( const rate of feeRates ) {
avgFeeRate += rate ;
}
avgFeeRate /= feeRates . length ;
const medianFeeRate = feeRates [ Math . floor ( feeRates . length / 2 ) ] ;
// Base fees
query = ` SELECT node1_base_fee_mtokens FROM channels WHERE node1_base_fee_mtokens < ${ ignoredBaseFeeThreshold } AND status = 1 ` ;
let [ baseFees1 ] : any = await DB . query ( query ) ;
baseFees1 = baseFees1 . map ( rate = > rate . node1_base_fee_mtokens ) ;
query = ` SELECT node2_base_fee_mtokens FROM channels WHERE node2_base_fee_mtokens < ${ ignoredBaseFeeThreshold } AND status = 1 ` ;
let [ baseFees2 ] : any = await DB . query ( query ) ;
baseFees2 = baseFees2 . map ( rate = > rate . node2_base_fee_mtokens ) ;
let baseFees = ( baseFees1 . concat ( baseFees2 ) ) . sort ( ( a , b ) = > a - b ) ;
let avgBaseFee = 0 ;
for ( const fee of baseFees ) {
avgBaseFee += fee ;
}
avgBaseFee /= baseFees . length ;
const medianBaseFee = feeRates [ Math . floor ( baseFees . length / 2 ) ] ;
return {
avgCapacity : parseInt ( avgCapacity [ 0 ] . avgCapacity , 10 ) ,
avgFeeRate : avgFeeRate ,
avgBaseFee : avgBaseFee ,
medianCapacity : medianCapacity ,
medianFeeRate : medianFeeRate ,
medianBaseFee : medianBaseFee ,
}
} catch ( e ) {
logger . err ( ` Cannot calculate channels statistics. Reason: ${ e instanceof Error ? e.message : e } ` ) ;
throw e ;
}
}
2022-05-09 18:21:42 +04:00
public async $getChannelsByTransactionId ( transactionIds : string [ ] ) : Promise < any [ ] > {
2022-05-07 11:32:15 +04:00
try {
2022-05-09 18:21:42 +04:00
transactionIds = transactionIds . map ( ( id ) = > '\'' + id + '\'' ) ;
2022-06-30 00:35:27 +02:00
const query = ` SELECT n1.alias AS alias_left, n2.alias AS alias_right, channels.* FROM channels LEFT JOIN nodes AS n1 ON n1.public_key = channels.node1_public_key LEFT JOIN nodes AS n2 ON n2.public_key = channels.node2_public_key WHERE channels.transaction_id IN ( ${ transactionIds . join ( ', ' ) } ) OR channels.closing_transaction_id IN ( ${ transactionIds . join ( ', ' ) } ) ` ;
2022-05-09 18:21:42 +04:00
const [ rows ] : any = await DB . query ( query ) ;
2022-05-15 19:22:14 +04:00
const channels = rows . map ( ( row ) = > this . convertChannel ( row ) ) ;
return channels ;
2022-05-07 11:32:15 +04:00
} catch ( e ) {
logger . err ( '$getChannelByTransactionId error: ' + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
2022-05-16 01:36:59 +04:00
public async $getChannelsForNode ( public_key : string , index : number , length : number , status : string ) : Promise < any [ ] > {
2022-04-29 03:57:27 +04:00
try {
2022-05-16 01:36:59 +04:00
// Default active and inactive channels
let statusQuery = '< 2' ;
// Closed channels only
if ( status === 'closed' ) {
statusQuery = '= 2' ;
}
const query = ` SELECT n1.alias AS alias_left, n2.alias AS alias_right, channels.*, ns1.channels AS channels_left, ns1.capacity AS capacity_left, ns2.channels AS channels_right, ns2.capacity AS capacity_right FROM channels LEFT JOIN nodes AS n1 ON n1.public_key = channels.node1_public_key LEFT JOIN nodes AS n2 ON n2.public_key = channels.node2_public_key LEFT JOIN node_stats AS ns1 ON ns1.public_key = channels.node1_public_key LEFT JOIN node_stats AS ns2 ON ns2.public_key = channels.node2_public_key WHERE (ns1.id = (SELECT MAX(id) FROM node_stats WHERE public_key = channels.node1_public_key) AND ns2.id = (SELECT MAX(id) FROM node_stats WHERE public_key = channels.node2_public_key)) AND (node1_public_key = ? OR node2_public_key = ?) AND status ${ statusQuery } ORDER BY channels.capacity DESC LIMIT ?, ? ` ;
const [ rows ] : any = await DB . query ( query , [ public_key , public_key , index , length ] ) ;
2022-05-15 19:22:14 +04:00
const channels = rows . map ( ( row ) = > this . convertChannel ( row ) ) ;
return channels ;
2022-04-29 03:57:27 +04:00
} catch ( e ) {
logger . err ( '$getChannelsForNode error: ' + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
2022-05-15 19:22:14 +04:00
2022-05-16 01:36:59 +04:00
public async $getChannelsCountForNode ( public_key : string , status : string ) : Promise < any > {
try {
// Default active and inactive channels
let statusQuery = '< 2' ;
// Closed channels only
if ( status === 'closed' ) {
statusQuery = '= 2' ;
}
const query = ` SELECT COUNT(*) AS count FROM channels WHERE (node1_public_key = ? OR node2_public_key = ?) AND status ${ statusQuery } ` ;
const [ rows ] : any = await DB . query ( query , [ public_key , public_key ] ) ;
return rows [ 0 ] [ 'count' ] ;
} catch ( e ) {
logger . err ( '$getChannelsForNode error: ' + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
2022-05-15 19:22:14 +04:00
private convertChannel ( channel : any ) : any {
return {
'id' : channel . id ,
'short_id' : channel . short_id ,
'capacity' : channel . capacity ,
'transaction_id' : channel . transaction_id ,
2022-05-16 01:36:59 +04:00
'transaction_vout' : channel . transaction_vout ,
2022-06-29 23:06:13 +02:00
'closing_transaction_id' : channel . closing_transaction_id ,
'closing_reason' : channel . closing_reason ,
2022-05-15 19:22:14 +04:00
'updated_at' : channel . updated_at ,
'created' : channel . created ,
'status' : channel . status ,
'node_left' : {
'alias' : channel . alias_left ,
'public_key' : channel . node1_public_key ,
2022-05-16 00:01:53 +04:00
'channels' : channel . channels_left ,
'capacity' : channel . capacity_left ,
2022-05-15 19:22:14 +04:00
'base_fee_mtokens' : channel . node1_base_fee_mtokens ,
'cltv_delta' : channel . node1_cltv_delta ,
'fee_rate' : channel . node1_fee_rate ,
'is_disabled' : channel . node1_is_disabled ,
'max_htlc_mtokens' : channel . node1_max_htlc_mtokens ,
'min_htlc_mtokens' : channel . node1_min_htlc_mtokens ,
'updated_at' : channel . node1_updated_at ,
} ,
'node_right' : {
'alias' : channel . alias_right ,
'public_key' : channel . node2_public_key ,
2022-05-16 00:01:53 +04:00
'channels' : channel . channels_right ,
'capacity' : channel . capacity_right ,
2022-05-15 19:22:14 +04:00
'base_fee_mtokens' : channel . node2_base_fee_mtokens ,
'cltv_delta' : channel . node2_cltv_delta ,
'fee_rate' : channel . node2_fee_rate ,
'is_disabled' : channel . node2_is_disabled ,
'max_htlc_mtokens' : channel . node2_max_htlc_mtokens ,
'min_htlc_mtokens' : channel . node2_min_htlc_mtokens ,
'updated_at' : channel . node2_updated_at ,
} ,
} ;
}
2022-04-29 03:57:27 +04:00
}
export default new ChannelsApi ( ) ;