2020-07-10 14:13:07 +07:00
|
|
|
const config = require('../../mempool-config.json');
|
2020-07-03 23:45:19 +07:00
|
|
|
import * as fs from 'fs';
|
2020-07-14 14:38:52 +07:00
|
|
|
import { BisqBlocks, BisqBlock, BisqTransaction, BisqStats } from '../interfaces';
|
2020-07-03 23:45:19 +07:00
|
|
|
|
|
|
|
class Bisq {
|
|
|
|
private blocks: BisqBlock[] = [];
|
|
|
|
private transactions: BisqTransaction[] = [];
|
2020-07-13 21:46:25 +07:00
|
|
|
private transactionIndex: { [txId: string]: BisqTransaction } = {};
|
|
|
|
private blockIndex: { [hash: string]: BisqBlock } = {};
|
|
|
|
private addressIndex: { [address: string]: BisqTransaction[] } = {};
|
2020-07-14 14:38:52 +07:00
|
|
|
private stats: BisqStats = {
|
|
|
|
minted: 0,
|
|
|
|
burnt: 0,
|
|
|
|
addresses: 0,
|
|
|
|
unspent_txos: 0,
|
|
|
|
spent_txos: 0,
|
|
|
|
};
|
2020-07-03 23:45:19 +07:00
|
|
|
|
|
|
|
constructor() {}
|
|
|
|
|
|
|
|
startBisqService(): void {
|
|
|
|
this.loadBisqDumpFile();
|
2020-07-10 14:13:07 +07:00
|
|
|
|
|
|
|
let fsWait: NodeJS.Timeout | null = null;
|
|
|
|
fs.watch(config.BSQ_BLOCKS_DATA_PATH, (event, filename) => {
|
|
|
|
if (filename) {
|
|
|
|
if (fsWait) {
|
|
|
|
clearTimeout(fsWait);
|
|
|
|
}
|
|
|
|
fsWait = setTimeout(() => {
|
2020-07-13 15:16:12 +07:00
|
|
|
console.log(`${filename} file change detected.`);
|
2020-07-10 14:13:07 +07:00
|
|
|
this.loadBisqDumpFile();
|
|
|
|
}, 1000);
|
|
|
|
}
|
|
|
|
});
|
2020-07-03 23:45:19 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
getTransaction(txId: string): BisqTransaction | undefined {
|
2020-07-13 21:46:25 +07:00
|
|
|
return this.transactionIndex[txId];
|
2020-07-03 23:45:19 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
getTransactions(start: number, length: number): [BisqTransaction[], number] {
|
|
|
|
return [this.transactions.slice(start, length + start), this.transactions.length];
|
|
|
|
}
|
|
|
|
|
|
|
|
getBlock(hash: string): BisqBlock | undefined {
|
2020-07-13 21:46:25 +07:00
|
|
|
return this.blockIndex[hash];
|
|
|
|
}
|
|
|
|
|
|
|
|
getAddress(hash: string): BisqTransaction[] {
|
|
|
|
return this.addressIndex[hash];
|
2020-07-03 23:45:19 +07:00
|
|
|
}
|
|
|
|
|
2020-07-13 15:16:12 +07:00
|
|
|
getBlocks(start: number, length: number): [BisqBlock[], number] {
|
|
|
|
return [this.blocks.slice(start, length + start), this.blocks.length];
|
|
|
|
}
|
|
|
|
|
2020-07-14 14:38:52 +07:00
|
|
|
getStats(): BisqStats {
|
|
|
|
return this.stats;
|
|
|
|
}
|
|
|
|
|
2020-07-13 15:16:12 +07:00
|
|
|
private async loadBisqDumpFile(): Promise<void> {
|
|
|
|
try {
|
|
|
|
const data = await this.loadData();
|
|
|
|
await this.loadBisqBlocksDump(data);
|
|
|
|
this.buildIndex();
|
2020-07-14 14:38:52 +07:00
|
|
|
this.calculateStats();
|
2020-07-13 15:16:12 +07:00
|
|
|
} catch (e) {
|
|
|
|
console.log('loadBisqDumpFile() error.', e.message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-03 23:45:19 +07:00
|
|
|
private buildIndex() {
|
2020-07-13 15:16:12 +07:00
|
|
|
const start = new Date().getTime();
|
|
|
|
this.transactions = [];
|
2020-07-13 21:46:25 +07:00
|
|
|
this.transactionIndex = {};
|
|
|
|
this.addressIndex = {};
|
|
|
|
|
2020-07-03 23:45:19 +07:00
|
|
|
this.blocks.forEach((block) => {
|
2020-07-13 21:46:25 +07:00
|
|
|
/* Build block index */
|
|
|
|
if (!this.blockIndex[block.hash]) {
|
|
|
|
this.blockIndex[block.hash] = block;
|
2020-07-11 00:17:13 +07:00
|
|
|
}
|
2020-07-13 21:46:25 +07:00
|
|
|
|
|
|
|
/* Build transactions index */
|
2020-07-03 23:45:19 +07:00
|
|
|
block.txs.forEach((tx) => {
|
2020-07-13 15:16:12 +07:00
|
|
|
this.transactions.push(tx);
|
2020-07-13 21:46:25 +07:00
|
|
|
this.transactionIndex[tx.id] = tx;
|
2020-07-03 23:45:19 +07:00
|
|
|
});
|
|
|
|
});
|
2020-07-13 21:46:25 +07:00
|
|
|
|
|
|
|
/* Build address index */
|
|
|
|
this.transactions.forEach((tx) => {
|
|
|
|
tx.inputs.forEach((input) => {
|
|
|
|
if (!this.addressIndex[input.address]) {
|
|
|
|
this.addressIndex[input.address] = [];
|
|
|
|
}
|
|
|
|
if (this.addressIndex[input.address].indexOf(tx) === -1) {
|
|
|
|
this.addressIndex[input.address].push(tx);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
tx.outputs.forEach((output) => {
|
|
|
|
if (!this.addressIndex[output.address]) {
|
|
|
|
this.addressIndex[output.address] = [];
|
|
|
|
}
|
|
|
|
if (this.addressIndex[output.address].indexOf(tx) === -1) {
|
|
|
|
this.addressIndex[output.address].push(tx);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2020-07-13 15:16:12 +07:00
|
|
|
const time = new Date().getTime() - start;
|
|
|
|
console.log('Bisq data index rebuilt in ' + time + ' ms');
|
2020-07-03 23:45:19 +07:00
|
|
|
}
|
|
|
|
|
2020-07-14 14:38:52 +07:00
|
|
|
private calculateStats() {
|
|
|
|
let minted = 0;
|
|
|
|
let burned = 0;
|
|
|
|
let unspent = 0;
|
|
|
|
let spent = 0;
|
|
|
|
|
|
|
|
this.transactions.forEach((tx) => {
|
|
|
|
tx.outputs.forEach((output) => {
|
|
|
|
if (output.opReturn) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (output.txOutputType === 'GENESIS_OUTPUT' || output.txOutputType === 'ISSUANCE_CANDIDATE_OUTPUT' && output.isVerified) {
|
|
|
|
minted += output.bsqAmount;
|
|
|
|
}
|
|
|
|
if (output.isUnspent) {
|
|
|
|
unspent++;
|
|
|
|
} else {
|
|
|
|
spent++;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
burned += tx['burntFee'];
|
|
|
|
});
|
|
|
|
|
|
|
|
this.stats = {
|
|
|
|
addresses: Object.keys(this.addressIndex).length,
|
|
|
|
minted: minted,
|
|
|
|
burnt: burned,
|
|
|
|
spent_txos: spent,
|
|
|
|
unspent_txos: unspent,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-07-11 00:17:13 +07:00
|
|
|
private async loadBisqBlocksDump(cacheData: string): Promise<void> {
|
2020-07-03 23:45:19 +07:00
|
|
|
const start = new Date().getTime();
|
2020-07-10 14:13:07 +07:00
|
|
|
if (cacheData && cacheData.length !== 0) {
|
2020-07-13 15:16:12 +07:00
|
|
|
console.log('Loading Bisq data from dump...');
|
2020-07-03 23:45:19 +07:00
|
|
|
const data: BisqBlocks = JSON.parse(cacheData);
|
2020-07-11 00:17:13 +07:00
|
|
|
if (data.blocks && data.blocks.length !== this.blocks.length) {
|
2020-07-13 23:22:24 +07:00
|
|
|
this.blocks = data.blocks.filter((block) => block.txs.length > 0);
|
2020-07-13 15:16:12 +07:00
|
|
|
this.blocks.reverse();
|
|
|
|
const time = new Date().getTime() - start;
|
|
|
|
console.log('Bisq dump loaded in ' + time + ' ms');
|
2020-07-03 23:45:19 +07:00
|
|
|
} else {
|
|
|
|
throw new Error(`Bisq dump didn't contain any blocks`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private loadData(): Promise<string> {
|
|
|
|
return new Promise((resolve, reject) => {
|
2020-07-10 14:13:07 +07:00
|
|
|
fs.readFile(config.BSQ_BLOCKS_DATA_PATH, 'utf8', (err, data) => {
|
2020-07-03 23:45:19 +07:00
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
}
|
|
|
|
resolve(data);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default new Bisq();
|