119 lines
4.1 KiB
TypeScript
119 lines
4.1 KiB
TypeScript
import { existsSync, promises } from 'fs';
|
|
import bitcoinClient from '../../../api/bitcoin/bitcoin-client';
|
|
import { Common } from '../../../api/common';
|
|
import config from '../../../config';
|
|
import logger from '../../../logger';
|
|
|
|
const fsPromises = promises;
|
|
|
|
const BLOCKS_CACHE_MAX_SIZE = 100;
|
|
const CACHE_FILE_NAME = config.MEMPOOL.CACHE_DIR + '/ln-funding-txs-cache.json';
|
|
|
|
class FundingTxFetcher {
|
|
private running = false;
|
|
private blocksCache = {};
|
|
private channelNewlyProcessed = 0;
|
|
public fundingTxCache = {};
|
|
|
|
async $init(): Promise<void> {
|
|
// Load funding tx disk cache
|
|
if (Object.keys(this.fundingTxCache).length === 0 && existsSync(CACHE_FILE_NAME)) {
|
|
try {
|
|
this.fundingTxCache = JSON.parse(await fsPromises.readFile(CACHE_FILE_NAME, 'utf-8'));
|
|
} catch (e) {
|
|
logger.err(`Unable to parse channels funding txs disk cache. Starting from scratch`);
|
|
this.fundingTxCache = {};
|
|
}
|
|
logger.debug(`Imported ${Object.keys(this.fundingTxCache).length} funding tx amount from the disk cache`);
|
|
}
|
|
}
|
|
|
|
async $fetchChannelsFundingTxs(channelIds: string[]): Promise<void> {
|
|
if (this.running) {
|
|
return;
|
|
}
|
|
this.running = true;
|
|
|
|
const globalTimer = new Date().getTime() / 1000;
|
|
let cacheTimer = new Date().getTime() / 1000;
|
|
let loggerTimer = new Date().getTime() / 1000;
|
|
let channelProcessed = 0;
|
|
this.channelNewlyProcessed = 0;
|
|
for (const channelId of channelIds) {
|
|
await this.$fetchChannelOpenTx(channelId);
|
|
++channelProcessed;
|
|
|
|
let elapsedSeconds = Math.round((new Date().getTime() / 1000) - loggerTimer);
|
|
if (elapsedSeconds > 10) {
|
|
elapsedSeconds = Math.round((new Date().getTime() / 1000) - globalTimer);
|
|
logger.info(`Indexing channels funding tx ${channelProcessed + 1} of ${channelIds.length} ` +
|
|
`(${Math.floor(channelProcessed / channelIds.length * 10000) / 100}%) | ` +
|
|
`elapsed: ${elapsedSeconds} seconds`
|
|
);
|
|
loggerTimer = new Date().getTime() / 1000;
|
|
}
|
|
|
|
elapsedSeconds = Math.round((new Date().getTime() / 1000) - cacheTimer);
|
|
if (elapsedSeconds > 60) {
|
|
logger.debug(`Saving ${Object.keys(this.fundingTxCache).length} funding txs cache into disk`);
|
|
fsPromises.writeFile(CACHE_FILE_NAME, JSON.stringify(this.fundingTxCache));
|
|
cacheTimer = new Date().getTime() / 1000;
|
|
}
|
|
}
|
|
|
|
if (this.channelNewlyProcessed > 0) {
|
|
logger.info(`Indexed ${this.channelNewlyProcessed} additional channels funding tx`);
|
|
logger.debug(`Saving ${Object.keys(this.fundingTxCache).length} funding txs cache into disk`);
|
|
fsPromises.writeFile(CACHE_FILE_NAME, JSON.stringify(this.fundingTxCache));
|
|
}
|
|
|
|
this.running = false;
|
|
}
|
|
|
|
public async $fetchChannelOpenTx(channelId: string): Promise<{timestamp: number, txid: string, value: number}> {
|
|
if (channelId.indexOf('x') === -1) {
|
|
channelId = Common.channelIntegerIdToShortId(channelId);
|
|
}
|
|
|
|
if (this.fundingTxCache[channelId]) {
|
|
return this.fundingTxCache[channelId];
|
|
}
|
|
|
|
const parts = channelId.split('x');
|
|
const blockHeight = parts[0];
|
|
const txIdx = parts[1];
|
|
const outputIdx = parts[2];
|
|
|
|
let block = this.blocksCache[blockHeight];
|
|
// Fetch it from core
|
|
if (!block) {
|
|
const blockHash = await bitcoinClient.getBlockHash(parseInt(blockHeight, 10));
|
|
block = await bitcoinClient.getBlock(blockHash, 1);
|
|
}
|
|
this.blocksCache[block.height] = block;
|
|
|
|
const blocksCacheHashes = Object.keys(this.blocksCache).sort((a, b) => parseInt(b) - parseInt(a)).reverse();
|
|
if (blocksCacheHashes.length > BLOCKS_CACHE_MAX_SIZE) {
|
|
for (let i = 0; i < 10; ++i) {
|
|
delete this.blocksCache[blocksCacheHashes[i]];
|
|
}
|
|
}
|
|
|
|
const txid = block.tx[txIdx];
|
|
const rawTx = await bitcoinClient.getRawTransaction(txid);
|
|
const tx = await bitcoinClient.decodeRawTransaction(rawTx);
|
|
|
|
this.fundingTxCache[channelId] = {
|
|
timestamp: block.time,
|
|
txid: txid,
|
|
value: tx.vout[outputIdx].value,
|
|
};
|
|
|
|
++this.channelNewlyProcessed;
|
|
|
|
return this.fundingTxCache[channelId];
|
|
}
|
|
}
|
|
|
|
export default new FundingTxFetcher;
|