WIP: Bisq DAO support. Transactions list and details.

This commit is contained in:
softsimon
2020-07-03 23:45:19 +07:00
parent 94ccd98d0a
commit d39b4a5c92
59 changed files with 926 additions and 38 deletions

1
backend/.gitignore vendored
View File

@@ -43,3 +43,4 @@ testem.log
Thumbs.db
cache.json
blocks.json

View File

@@ -13,6 +13,7 @@
"INITIAL_BLOCK_AMOUNT": 8,
"TX_PER_SECOND_SPAN_SECONDS": 150,
"ELECTRS_API_URL": "https://www.blockstream.info/testnet/api",
"BISQ_ENABLED": false,
"SSL": false,
"SSL_CERT_FILE_PATH": "/etc/letsencrypt/live/mysite/fullchain.pem",
"SSL_KEY_FILE_PATH": "/etc/letsencrypt/live/mysite/privkey.pem"

78
backend/src/api/bisq.ts Normal file
View File

@@ -0,0 +1,78 @@
import * as fs from 'fs';
import { BisqBlocks, BisqBlock, BisqTransaction } from '../interfaces';
class Bisq {
static FILE_NAME = './blocks.json';
private latestBlockHeight = 0;
private blocks: BisqBlock[] = [];
private transactions: BisqTransaction[] = [];
private transactionsIndex: { [txId: string]: BisqTransaction } = {};
private blocksIndex: { [hash: string]: BisqBlock } = {};
constructor() {}
startBisqService(): void {
this.loadBisqDumpFile();
}
async loadBisqDumpFile(): Promise<void> {
await this.loadBisqBlocksDump();
this.buildIndex();
}
getTransaction(txId: string): BisqTransaction | undefined {
return this.transactionsIndex[txId];
}
getTransactions(start: number, length: number): [BisqTransaction[], number] {
return [this.transactions.slice(start, length + start), this.transactions.length];
}
getBlock(hash: string): BisqBlock | undefined {
return this.blocksIndex[hash];
}
private buildIndex() {
this.blocks.forEach((block) => {
this.blocksIndex[block.hash] = block;
block.txs.forEach((tx) => {
this.transactions.push(tx);
this.transactionsIndex[tx.id] = tx;
});
});
this.blocks.reverse();
this.transactions.reverse();
console.log('Bisq data index rebuilt');
}
private async loadBisqBlocksDump() {
const start = new Date().getTime();
const cacheData = await this.loadData();
if (cacheData) {
console.log('Parsing Bisq data from dump file');
const data: BisqBlocks = JSON.parse(cacheData);
if (data.blocks) {
this.blocks = data.blocks;
this.latestBlockHeight = data.chainHeight;
const end = new Date().getTime();
const time = end - start;
console.log('Loaded bisq dump in ' + time + ' ms');
} else {
throw new Error(`Bisq dump didn't contain any blocks`);
}
}
}
private loadData(): Promise<string> {
return new Promise((resolve, reject) => {
fs.readFile(Bisq.FILE_NAME, 'utf8', (err, data) => {
if (err) {
reject(err);
}
resolve(data);
});
});
}
}
export default new Bisq();

View File

@@ -73,10 +73,10 @@ class Blocks {
console.log(`${found} of ${txIds.length} found in mempool. ${notFound} not found.`);
block.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0);
block.coinbaseTx = this.stripCoinbaseTransaction(transactions[0]);
transactions.sort((a, b) => b.feePerVsize - a.feePerVsize);
block.medianFee = transactions.length > 1 ? Common.median(transactions.map((tx) => tx.feePerVsize)) : 0;
block.feeRange = transactions.length > 1 ? Common.getFeesInRange(transactions, 8, 1) : [0, 0];
block.coinbaseTx = this.stripCoinbaseTransaction(transactions[0]);
this.blocks.push(block);
if (this.blocks.length > config.KEEP_BLOCK_AMOUNT) {

View File

@@ -35,6 +35,9 @@ class Mempool {
public setMempool(mempoolData: { [txId: string]: TransactionExtended }) {
this.mempoolCache = mempoolData;
if (this.mempoolChangedCallback) {
this.mempoolChangedCallback(this.mempoolCache, [], []);
}
}
public async updateMemPoolInfo() {

View File

@@ -14,6 +14,7 @@ import diskCache from './api/disk-cache';
import statistics from './api/statistics';
import websocketHandler from './api/websocket-handler';
import fiatConversion from './api/fiat-conversion';
import bisq from './api/bisq';
class Server {
wss: WebSocket.Server;
@@ -50,6 +51,10 @@ class Server {
fiatConversion.startService();
diskCache.loadMempoolCache();
if (config.BISQ_ENABLED) {
bisq.startBisqService();
}
this.server.listen(config.HTTP_PORT, () => {
console.log(`Server started on port ${config.HTTP_PORT}`);
});
@@ -84,6 +89,14 @@ class Server {
.get(config.API_ENDPOINT + 'statistics/1y', routes.get1YStatistics.bind(routes))
.get(config.API_ENDPOINT + 'backend-info', routes.getBackendInfo)
;
if (config.BISQ_ENABLED) {
this.app
.get(config.API_ENDPOINT + 'bisq/tx/:txId', routes.getBisqTransaction)
.get(config.API_ENDPOINT + 'bisq/block/:hash', routes.getBisqBlock)
.get(config.API_ENDPOINT + 'bisq/txs/:index/:length', routes.getBisqTransactions)
;
}
}
}

View File

@@ -230,3 +230,77 @@ export interface VbytesPerSecond {
unixTime: number;
vSize: number;
}
export interface BisqBlocks {
chainHeight: number;
blocks: BisqBlock[];
}
export interface BisqBlock {
height: number;
time: number;
hash: string;
previousBlockHash: string;
txs: BisqTransaction[];
}
export interface BisqTransaction {
txVersion: string;
id: string;
blockHeight: number;
blockHash: string;
time: number;
inputs: BisqInput[];
outputs: BisqOutput[];
txType: string;
txTypeDisplayString: string;
burntFee: number;
invalidatedBsq: number;
unlockBlockHeight: number;
}
interface BisqInput {
spendingTxOutputIndex: number;
spendingTxId: string;
bsqAmount: number;
isVerified: boolean;
address: string;
time: number;
}
interface BisqOutput {
txVersion: string;
txId: string;
index: number;
bsqAmount: number;
btcAmount: number;
height: number;
isVerified: boolean;
burntFee: number;
invalidatedBsq: number;
address: string;
scriptPubKey: BisqScriptPubKey;
time: any;
txType: string;
txTypeDisplayString: string;
txOutputType: string;
txOutputTypeDisplayString: string;
lockTime: number;
isUnspent: boolean;
spentInfo: SpentInfo;
opReturn?: string;
}
interface BisqScriptPubKey {
addresses: string[];
asm: string;
hex: string;
reqSigs: number;
type: string;
}
interface SpentInfo {
height: number;
inputIndex: number;
txId: string;
}

View File

@@ -4,6 +4,7 @@ import feeApi from './api/fee-api';
import backendInfo from './api/backend-info';
import mempoolBlocks from './api/mempool-blocks';
import mempool from './api/mempool';
import bisq from './api/bisq';
class Routes {
private cache = {};
@@ -85,6 +86,36 @@ class Routes {
public getBackendInfo(req: Request, res: Response) {
res.send(backendInfo.getBackendInfo());
}
public getBisqTransaction(req: Request, res: Response) {
const result = bisq.getTransaction(req.params.txId);
if (result) {
res.send(result);
} else {
res.status(404).send('Bisq transaction not found');
}
}
public getBisqTransactions(req: Request, res: Response) {
const index = parseInt(req.params.index, 10) || 0;
const length = parseInt(req.params.length, 10) > 100 ? 100 : parseInt(req.params.length, 10) || 25;
const [transactions, count] = bisq.getTransactions(index, length);
if (transactions) {
res.header('X-Total-Count', count.toString());
res.send(transactions);
} else {
res.status(404).send('Bisq transaction not found');
}
}
public getBisqBlock(req: Request, res: Response) {
const result = bisq.getBlock(req['hash']);
if (result) {
res.send(result);
} else {
res.status(404).send('Bisq block not found');
}
}
}
export default new Routes();