Merge branch 'master' into frontend_runtime_config

This commit is contained in:
wiz 2022-11-21 17:46:02 +09:00 committed by GitHub
commit 235ac204b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 253 additions and 36 deletions

View File

@ -2,6 +2,7 @@
"MEMPOOL": { "MEMPOOL": {
"NETWORK": "mainnet", "NETWORK": "mainnet",
"BACKEND": "electrum", "BACKEND": "electrum",
"ENABLED": true,
"HTTP_PORT": 8999, "HTTP_PORT": 8999,
"SPAWN_CLUSTER_PROCS": 0, "SPAWN_CLUSTER_PROCS": 0,
"API_URL_PREFIX": "/api/v1/", "API_URL_PREFIX": "/api/v1/",

View File

@ -1,7 +1,9 @@
{ {
"MEMPOOL": { "MEMPOOL": {
"ENABLED": true,
"NETWORK": "__MEMPOOL_NETWORK__", "NETWORK": "__MEMPOOL_NETWORK__",
"BACKEND": "__MEMPOOL_BACKEND__", "BACKEND": "__MEMPOOL_BACKEND__",
"ENABLED": true,
"BLOCKS_SUMMARIES_INDEXING": true, "BLOCKS_SUMMARIES_INDEXING": true,
"HTTP_PORT": 1, "HTTP_PORT": 1,
"SPAWN_CLUSTER_PROCS": 2, "SPAWN_CLUSTER_PROCS": 2,

View File

@ -13,6 +13,7 @@ describe('Mempool Backend Config', () => {
const config = jest.requireActual('../config').default; const config = jest.requireActual('../config').default;
expect(config.MEMPOOL).toStrictEqual({ expect(config.MEMPOOL).toStrictEqual({
ENABLED: true,
NETWORK: 'mainnet', NETWORK: 'mainnet',
BACKEND: 'none', BACKEND: 'none',
BLOCKS_SUMMARIES_INDEXING: false, BLOCKS_SUMMARIES_INDEXING: false,

View File

@ -103,12 +103,11 @@ class Mempool {
return txTimes; return txTimes;
} }
public async $updateMempool() { public async $updateMempool(): Promise<void> {
logger.debug('Updating mempool'); logger.debug(`Updating mempool...`);
const start = new Date().getTime(); const start = new Date().getTime();
let hasChange: boolean = false; let hasChange: boolean = false;
const currentMempoolSize = Object.keys(this.mempoolCache).length; const currentMempoolSize = Object.keys(this.mempoolCache).length;
let txCount = 0;
const transactions = await bitcoinApi.$getRawMempool(); const transactions = await bitcoinApi.$getRawMempool();
const diff = transactions.length - currentMempoolSize; const diff = transactions.length - currentMempoolSize;
const newTransactions: TransactionExtended[] = []; const newTransactions: TransactionExtended[] = [];
@ -124,7 +123,6 @@ class Mempool {
try { try {
const transaction = await transactionUtils.$getTransactionExtended(txid); const transaction = await transactionUtils.$getTransactionExtended(txid);
this.mempoolCache[txid] = transaction; this.mempoolCache[txid] = transaction;
txCount++;
if (this.inSync) { if (this.inSync) {
this.txPerSecondArray.push(new Date().getTime()); this.txPerSecondArray.push(new Date().getTime());
this.vBytesPerSecondArray.push({ this.vBytesPerSecondArray.push({
@ -133,14 +131,9 @@ class Mempool {
}); });
} }
hasChange = true; hasChange = true;
if (diff > 0) {
logger.debug('Fetched transaction ' + txCount + ' / ' + diff);
} else {
logger.debug('Fetched transaction ' + txCount);
}
newTransactions.push(transaction); newTransactions.push(transaction);
} catch (e) { } catch (e) {
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e)); logger.debug(`Error finding transaction '${txid}' in the mempool: ` + (e instanceof Error ? e.message : e));
} }
} }
@ -197,8 +190,7 @@ class Mempool {
const end = new Date().getTime(); const end = new Date().getTime();
const time = end - start; const time = end - start;
logger.debug(`New mempool size: ${Object.keys(this.mempoolCache).length} Change: ${diff}`); logger.debug(`Mempool updated in ${time / 1000} seconds. New size: ${Object.keys(this.mempoolCache).length} (${diff > 0 ? '+' + diff : diff})`);
logger.debug('Mempool updated in ' + time / 1000 + ' seconds');
} }
public handleRbfTransactions(rbfTransactions: { [txid: string]: TransactionExtended; }) { public handleRbfTransactions(rbfTransactions: { [txid: string]: TransactionExtended; }) {

View File

@ -27,6 +27,7 @@ class MiningRoutes {
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustments/:interval', this.$getDifficultyAdjustments) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustments/:interval', this.$getDifficultyAdjustments)
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/predictions/:interval', this.$getHistoricalBlockPrediction) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/predictions/:interval', this.$getHistoricalBlockPrediction)
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/:hash', this.$getBlockAudit) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/:hash', this.$getBlockAudit)
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/timestamp/:timestamp', this.$getHeightFromTimestamp)
; ;
} }
@ -252,6 +253,29 @@ class MiningRoutes {
res.status(500).send(e instanceof Error ? e.message : e); res.status(500).send(e instanceof Error ? e.message : e);
} }
} }
private async $getHeightFromTimestamp(req: Request, res: Response) {
try {
const timestamp = parseInt(req.params.timestamp, 10);
// This will prevent people from entering milliseconds etc.
// Block timestamps are allowed to be up to 2 hours off, so 24 hours
// will never put the maximum value before the most recent block
const nowPlus1day = Math.floor(Date.now() / 1000) + 60 * 60 * 24;
// Prevent non-integers that are not seconds
if (!/^[1-9][0-9]*$/.test(req.params.timestamp) || timestamp > nowPlus1day) {
throw new Error(`Invalid timestamp, value must be Unix seconds`);
}
const result = await BlocksRepository.$getBlockHeightFromTimestamp(
timestamp,
);
res.header('Pragma', 'public');
res.header('Cache-control', 'public');
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
res.json(result);
} catch (e) {
res.status(500).send(e instanceof Error ? e.message : e);
}
}
} }
export default new MiningRoutes(); export default new MiningRoutes();

View File

@ -4,6 +4,7 @@ const configFromFile = require(
interface IConfig { interface IConfig {
MEMPOOL: { MEMPOOL: {
ENABLED: boolean;
NETWORK: 'mainnet' | 'testnet' | 'signet' | 'liquid' | 'liquidtestnet'; NETWORK: 'mainnet' | 'testnet' | 'signet' | 'liquid' | 'liquidtestnet';
BACKEND: 'esplora' | 'electrum' | 'none'; BACKEND: 'esplora' | 'electrum' | 'none';
HTTP_PORT: number; HTTP_PORT: number;
@ -119,6 +120,7 @@ interface IConfig {
const defaults: IConfig = { const defaults: IConfig = {
'MEMPOOL': { 'MEMPOOL': {
'ENABLED': true,
'NETWORK': 'mainnet', 'NETWORK': 'mainnet',
'BACKEND': 'none', 'BACKEND': 'none',
'HTTP_PORT': 8999, 'HTTP_PORT': 8999,
@ -224,11 +226,11 @@ const defaults: IConfig = {
'BISQ_URL': 'https://bisq.markets/api', 'BISQ_URL': 'https://bisq.markets/api',
'BISQ_ONION': 'http://bisqmktse2cabavbr2xjq7xw3h6g5ottemo5rolfcwt6aly6tp5fdryd.onion/api' 'BISQ_ONION': 'http://bisqmktse2cabavbr2xjq7xw3h6g5ottemo5rolfcwt6aly6tp5fdryd.onion/api'
}, },
"MAXMIND": { 'MAXMIND': {
'ENABLED': false, 'ENABLED': false,
"GEOLITE2_CITY": "/usr/local/share/GeoIP/GeoLite2-City.mmdb", 'GEOLITE2_CITY': '/usr/local/share/GeoIP/GeoLite2-City.mmdb',
"GEOLITE2_ASN": "/usr/local/share/GeoIP/GeoLite2-ASN.mmdb", 'GEOLITE2_ASN': '/usr/local/share/GeoIP/GeoLite2-ASN.mmdb',
"GEOIP2_ISP": "/usr/local/share/GeoIP/GeoIP2-ISP.mmdb" 'GEOIP2_ISP': '/usr/local/share/GeoIP/GeoIP2-ISP.mmdb'
}, },
}; };

View File

@ -1,4 +1,4 @@
import express from "express"; import express from 'express';
import { Application, Request, Response, NextFunction } from 'express'; import { Application, Request, Response, NextFunction } from 'express';
import * as http from 'http'; import * as http from 'http';
import * as WebSocket from 'ws'; import * as WebSocket from 'ws';
@ -34,7 +34,7 @@ import miningRoutes from './api/mining/mining-routes';
import bisqRoutes from './api/bisq/bisq.routes'; import bisqRoutes from './api/bisq/bisq.routes';
import liquidRoutes from './api/liquid/liquid.routes'; import liquidRoutes from './api/liquid/liquid.routes';
import bitcoinRoutes from './api/bitcoin/bitcoin.routes'; import bitcoinRoutes from './api/bitcoin/bitcoin.routes';
import fundingTxFetcher from "./tasks/lightning/sync-tasks/funding-tx-fetcher"; import fundingTxFetcher from './tasks/lightning/sync-tasks/funding-tx-fetcher';
class Server { class Server {
private wss: WebSocket.Server | undefined; private wss: WebSocket.Server | undefined;
@ -74,7 +74,7 @@ class Server {
} }
} }
async startServer(worker = false) { async startServer(worker = false): Promise<void> {
logger.notice(`Starting Mempool Server${worker ? ' (worker)' : ''}... (${backendInfo.getShortCommitHash()})`); logger.notice(`Starting Mempool Server${worker ? ' (worker)' : ''}... (${backendInfo.getShortCommitHash()})`);
this.app this.app
@ -92,7 +92,9 @@ class Server {
this.setUpWebsocketHandling(); this.setUpWebsocketHandling();
await syncAssets.syncAssets$(); await syncAssets.syncAssets$();
diskCache.loadMempoolCache(); if (config.MEMPOOL.ENABLED) {
diskCache.loadMempoolCache();
}
if (config.DATABASE.ENABLED) { if (config.DATABASE.ENABLED) {
await DB.checkDbConnection(); await DB.checkDbConnection();
@ -127,7 +129,10 @@ class Server {
fiatConversion.startService(); fiatConversion.startService();
this.setUpHttpApiRoutes(); this.setUpHttpApiRoutes();
this.runMainUpdateLoop();
if (config.MEMPOOL.ENABLED) {
this.runMainUpdateLoop();
}
if (config.BISQ.ENABLED) { if (config.BISQ.ENABLED) {
bisq.startBisqService(); bisq.startBisqService();
@ -149,7 +154,7 @@ class Server {
}); });
} }
async runMainUpdateLoop() { async runMainUpdateLoop(): Promise<void> {
try { try {
try { try {
await memPool.$updateMemPoolInfo(); await memPool.$updateMemPoolInfo();
@ -183,7 +188,7 @@ class Server {
} }
} }
async $runLightningBackend() { async $runLightningBackend(): Promise<void> {
try { try {
await fundingTxFetcher.$init(); await fundingTxFetcher.$init();
await networkSyncService.$startService(); await networkSyncService.$startService();
@ -195,7 +200,7 @@ class Server {
}; };
} }
setUpWebsocketHandling() { setUpWebsocketHandling(): void {
if (this.wss) { if (this.wss) {
websocketHandler.setWebsocketServer(this.wss); websocketHandler.setWebsocketServer(this.wss);
} }
@ -209,19 +214,21 @@ class Server {
}); });
} }
websocketHandler.setupConnectionHandling(); websocketHandler.setupConnectionHandling();
statistics.setNewStatisticsEntryCallback(websocketHandler.handleNewStatistic.bind(websocketHandler)); if (config.MEMPOOL.ENABLED) {
blocks.setNewBlockCallback(websocketHandler.handleNewBlock.bind(websocketHandler)); statistics.setNewStatisticsEntryCallback(websocketHandler.handleNewStatistic.bind(websocketHandler));
memPool.setMempoolChangedCallback(websocketHandler.handleMempoolChange.bind(websocketHandler)); blocks.setNewBlockCallback(websocketHandler.handleNewBlock.bind(websocketHandler));
memPool.setMempoolChangedCallback(websocketHandler.handleMempoolChange.bind(websocketHandler));
}
fiatConversion.setProgressChangedCallback(websocketHandler.handleNewConversionRates.bind(websocketHandler)); fiatConversion.setProgressChangedCallback(websocketHandler.handleNewConversionRates.bind(websocketHandler));
loadingIndicators.setProgressChangedCallback(websocketHandler.handleLoadingChanged.bind(websocketHandler)); loadingIndicators.setProgressChangedCallback(websocketHandler.handleLoadingChanged.bind(websocketHandler));
} }
setUpHttpApiRoutes() { setUpHttpApiRoutes(): void {
bitcoinRoutes.initRoutes(this.app); bitcoinRoutes.initRoutes(this.app);
if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED) { if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED && config.MEMPOOL.ENABLED) {
statisticsRoutes.initRoutes(this.app); statisticsRoutes.initRoutes(this.app);
} }
if (Common.indexingEnabled()) { if (Common.indexingEnabled() && config.MEMPOOL.ENABLED) {
miningRoutes.initRoutes(this.app); miningRoutes.initRoutes(this.app);
} }
if (config.BISQ.ENABLED) { if (config.BISQ.ENABLED) {
@ -238,4 +245,4 @@ class Server {
} }
} }
const server = new Server(); ((): Server => new Server())();

View File

@ -392,6 +392,36 @@ class BlocksRepository {
} }
} }
/**
* Get the first block at or directly after a given timestamp
* @param timestamp number unix time in seconds
* @returns The height and timestamp of a block (timestamp might vary from given timestamp)
*/
public async $getBlockHeightFromTimestamp(
timestamp: number,
): Promise<{ height: number; hash: string; timestamp: number }> {
try {
// Get first block at or after the given timestamp
const query = `SELECT height, hash, blockTimestamp as timestamp FROM blocks
WHERE blockTimestamp <= FROM_UNIXTIME(?)
ORDER BY blockTimestamp DESC
LIMIT 1`;
const params = [timestamp];
const [rows]: any[][] = await DB.query(query, params);
if (rows.length === 0) {
throw new Error(`No block was found before timestamp ${timestamp}`);
}
return rows[0];
} catch (e) {
logger.err(
'Cannot get block height from timestamp from the db. Reason: ' +
(e instanceof Error ? e.message : e),
);
throw e;
}
}
/** /**
* Return blocks height * Return blocks height
*/ */

View File

@ -89,6 +89,7 @@ Below we list all settings from `mempool-config.json` and the corresponding over
"MEMPOOL": { "MEMPOOL": {
"NETWORK": "mainnet", "NETWORK": "mainnet",
"BACKEND": "electrum", "BACKEND": "electrum",
"ENABLED": true,
"HTTP_PORT": 8999, "HTTP_PORT": 8999,
"SPAWN_CLUSTER_PROCS": 0, "SPAWN_CLUSTER_PROCS": 0,
"API_URL_PREFIX": "/api/v1/", "API_URL_PREFIX": "/api/v1/",

View File

@ -2,6 +2,7 @@
"MEMPOOL": { "MEMPOOL": {
"NETWORK": "__MEMPOOL_NETWORK__", "NETWORK": "__MEMPOOL_NETWORK__",
"BACKEND": "__MEMPOOL_BACKEND__", "BACKEND": "__MEMPOOL_BACKEND__",
"ENABLED": __MEMPOOL_ENABLED__,
"HTTP_PORT": __MEMPOOL_HTTP_PORT__, "HTTP_PORT": __MEMPOOL_HTTP_PORT__,
"SPAWN_CLUSTER_PROCS": __MEMPOOL_SPAWN_CLUSTER_PROCS__, "SPAWN_CLUSTER_PROCS": __MEMPOOL_SPAWN_CLUSTER_PROCS__,
"API_URL_PREFIX": "__MEMPOOL_API_URL_PREFIX__", "API_URL_PREFIX": "__MEMPOOL_API_URL_PREFIX__",

View File

@ -3,6 +3,7 @@
# MEMPOOL # MEMPOOL
__MEMPOOL_NETWORK__=${MEMPOOL_NETWORK:=mainnet} __MEMPOOL_NETWORK__=${MEMPOOL_NETWORK:=mainnet}
__MEMPOOL_BACKEND__=${MEMPOOL_BACKEND:=electrum} __MEMPOOL_BACKEND__=${MEMPOOL_BACKEND:=electrum}
__MEMPOOL_ENABLED__=${MEMPOOL_ENABLED:=true}
__MEMPOOL_HTTP_PORT__=${BACKEND_HTTP_PORT:=8999} __MEMPOOL_HTTP_PORT__=${BACKEND_HTTP_PORT:=8999}
__MEMPOOL_SPAWN_CLUSTER_PROCS__=${MEMPOOL_SPAWN_CLUSTER_PROCS:=0} __MEMPOOL_SPAWN_CLUSTER_PROCS__=${MEMPOOL_SPAWN_CLUSTER_PROCS:=0}
__MEMPOOL_API_URL_PREFIX__=${MEMPOOL_API_URL_PREFIX:=/api/v1/} __MEMPOOL_API_URL_PREFIX__=${MEMPOOL_API_URL_PREFIX:=/api/v1/}
@ -111,6 +112,7 @@ mkdir -p "${__MEMPOOL_CACHE_DIR__}"
sed -i "s/__MEMPOOL_NETWORK__/${__MEMPOOL_NETWORK__}/g" mempool-config.json sed -i "s/__MEMPOOL_NETWORK__/${__MEMPOOL_NETWORK__}/g" mempool-config.json
sed -i "s/__MEMPOOL_BACKEND__/${__MEMPOOL_BACKEND__}/g" mempool-config.json sed -i "s/__MEMPOOL_BACKEND__/${__MEMPOOL_BACKEND__}/g" mempool-config.json
sed -i "s/__MEMPOOL_ENABLED__/${__MEMPOOL_ENABLED__}/g" mempool-config.json
sed -i "s/__MEMPOOL_HTTP_PORT__/${__MEMPOOL_HTTP_PORT__}/g" mempool-config.json sed -i "s/__MEMPOOL_HTTP_PORT__/${__MEMPOOL_HTTP_PORT__}/g" mempool-config.json
sed -i "s/__MEMPOOL_SPAWN_CLUSTER_PROCS__/${__MEMPOOL_SPAWN_CLUSTER_PROCS__}/g" mempool-config.json sed -i "s/__MEMPOOL_SPAWN_CLUSTER_PROCS__/${__MEMPOOL_SPAWN_CLUSTER_PROCS__}/g" mempool-config.json
sed -i "s!__MEMPOOL_API_URL_PREFIX__!${__MEMPOOL_API_URL_PREFIX__}!g" mempool-config.json sed -i "s!__MEMPOOL_API_URL_PREFIX__!${__MEMPOOL_API_URL_PREFIX__}!g" mempool-config.json

View File

@ -221,6 +221,10 @@
"proxyConfig": "proxy.conf.local.js", "proxyConfig": "proxy.conf.local.js",
"verbose": true "verbose": true
}, },
"local-esplora": {
"proxyConfig": "proxy.conf.local-esplora.js",
"verbose": true
},
"mixed": { "mixed": {
"proxyConfig": "proxy.conf.mixed.js", "proxyConfig": "proxy.conf.mixed.js",
"verbose": true "verbose": true

View File

@ -29,6 +29,7 @@
"serve:local-prod": "npm run generate-config && npm run ng -- serve -c local-prod", "serve:local-prod": "npm run generate-config && npm run ng -- serve -c local-prod",
"serve:local-staging": "npm run generate-config && npm run ng -- serve -c local-staging", "serve:local-staging": "npm run generate-config && npm run ng -- serve -c local-staging",
"start": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local", "start": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local",
"start:local-esplora": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-esplora",
"start:stg": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c staging", "start:stg": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c staging",
"start:local-prod": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-prod", "start:local-prod": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-prod",
"start:local-staging": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-staging", "start:local-staging": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-staging",

View File

@ -0,0 +1,137 @@
const fs = require('fs');
const FRONTEND_CONFIG_FILE_NAME = 'mempool-frontend-config.json';
let configContent;
// Read frontend config
try {
const rawConfig = fs.readFileSync(FRONTEND_CONFIG_FILE_NAME);
configContent = JSON.parse(rawConfig);
console.log(`${FRONTEND_CONFIG_FILE_NAME} file found, using provided config`);
} catch (e) {
console.log(e);
if (e.code !== 'ENOENT') {
throw new Error(e);
} else {
console.log(`${FRONTEND_CONFIG_FILE_NAME} file not found, using default config`);
}
}
let PROXY_CONFIG = [];
if (configContent && configContent.BASE_MODULE === 'liquid') {
PROXY_CONFIG.push(...[
{
context: ['/liquid/api/v1/**'],
target: `http://127.0.0.1:8999`,
secure: false,
ws: true,
changeOrigin: true,
proxyTimeout: 30000,
pathRewrite: {
"^/liquid": ""
},
},
{
context: ['/liquid/api/**'],
target: `http://127.0.0.1:3000`,
secure: false,
changeOrigin: true,
proxyTimeout: 30000,
pathRewrite: {
"^/liquid/api/": ""
},
},
{
context: ['/liquidtestnet/api/v1/**'],
target: `http://127.0.0.1:8999`,
secure: false,
ws: true,
changeOrigin: true,
proxyTimeout: 30000,
pathRewrite: {
"^/liquidtestnet": ""
},
},
{
context: ['/liquidtestnet/api/**'],
target: `http://127.0.0.1:3000`,
secure: false,
changeOrigin: true,
proxyTimeout: 30000,
pathRewrite: {
"^/liquidtestnet/api/": "/"
},
},
]);
}
if (configContent && configContent.BASE_MODULE === 'bisq') {
PROXY_CONFIG.push(...[
{
context: ['/bisq/api/v1/ws'],
target: `http://127.0.0.1:8999`,
secure: false,
ws: true,
changeOrigin: true,
proxyTimeout: 30000,
pathRewrite: {
"^/bisq": ""
},
},
{
context: ['/bisq/api/v1/**'],
target: `http://127.0.0.1:8999`,
secure: false,
changeOrigin: true,
proxyTimeout: 30000,
},
{
context: ['/bisq/api/**'],
target: `http://127.0.0.1:8999`,
secure: false,
changeOrigin: true,
proxyTimeout: 30000,
pathRewrite: {
"^/bisq/api/": "/api/v1/bisq/"
},
}
]);
}
PROXY_CONFIG.push(...[
{
context: ['/testnet/api/v1/lightning/**'],
target: `http://127.0.0.1:8999`,
secure: false,
changeOrigin: true,
proxyTimeout: 30000,
pathRewrite: {
"^/testnet": ""
},
},
{
context: ['/api/v1/**'],
target: `http://127.0.0.1:8999`,
secure: false,
ws: true,
changeOrigin: true,
proxyTimeout: 30000,
},
{
context: ['/api/**'],
target: `http://127.0.0.1:3000`,
secure: false,
changeOrigin: true,
proxyTimeout: 30000,
pathRewrite: {
"^/api": ""
},
}
]);
console.log(PROXY_CONFIG);
module.exports = PROXY_CONFIG;

View File

@ -2,9 +2,7 @@
<div class="d-flex"> <div class="d-flex">
<div class="search-box-container mr-2"> <div class="search-box-container mr-2">
<input (focus)="focus$.next($any($event).target.value)" (click)="click$.next($any($event).target.value)" formControlName="searchText" type="text" class="form-control" i18n-placeholder="search-form.searchbar-placeholder" placeholder="Explore the full Bitcoin ecosystem"> <input (focus)="focus$.next($any($event).target.value)" (click)="click$.next($any($event).target.value)" formControlName="searchText" type="text" class="form-control" i18n-placeholder="search-form.searchbar-placeholder" placeholder="Explore the full Bitcoin ecosystem">
<app-search-results #searchResults [hidden]="dropdownHidden" [results]="typeAhead$ | async" (selectedResult)="selectedResult($event)"></app-search-results>
<app-search-results #searchResults [results]="typeAhead$ | async" (selectedResult)="selectedResult($event)"></app-search-results>
</div> </div>
<div> <div>
<button [disabled]="isSearching" type="submit" class="btn btn-block btn-primary"> <button [disabled]="isSearching" type="submit" class="btn btn-block btn-primary">

View File

@ -1,4 +1,4 @@
import { Component, OnInit, ChangeDetectionStrategy, EventEmitter, Output, ViewChild, HostListener } from '@angular/core'; import { Component, OnInit, ChangeDetectionStrategy, EventEmitter, Output, ViewChild, HostListener, ElementRef } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { AssetsService } from '../../services/assets.service'; import { AssetsService } from '../../services/assets.service';
@ -23,6 +23,16 @@ export class SearchFormComponent implements OnInit {
isTypeaheading$ = new BehaviorSubject<boolean>(false); isTypeaheading$ = new BehaviorSubject<boolean>(false);
typeAhead$: Observable<any>; typeAhead$: Observable<any>;
searchForm: FormGroup; searchForm: FormGroup;
dropdownHidden = false;
@HostListener('document:click', ['$event'])
onDocumentClick(event) {
if (this.elementRef.nativeElement.contains(event.target)) {
this.dropdownHidden = false;
} else {
this.dropdownHidden = true;
}
}
regexAddress = /^([a-km-zA-HJ-NP-Z1-9]{26,35}|[a-km-zA-HJ-NP-Z1-9]{80}|[A-z]{2,5}1[a-zA-HJ-NP-Z0-9]{39,59})$/; regexAddress = /^([a-km-zA-HJ-NP-Z1-9]{26,35}|[a-km-zA-HJ-NP-Z1-9]{80}|[A-z]{2,5}1[a-zA-HJ-NP-Z0-9]{39,59})$/;
regexBlockhash = /^[0]{8}[a-fA-F0-9]{56}$/; regexBlockhash = /^[0]{8}[a-fA-F0-9]{56}$/;
@ -45,6 +55,7 @@ export class SearchFormComponent implements OnInit {
private electrsApiService: ElectrsApiService, private electrsApiService: ElectrsApiService,
private apiService: ApiService, private apiService: ApiService,
private relativeUrlPipe: RelativeUrlPipe, private relativeUrlPipe: RelativeUrlPipe,
private elementRef: ElementRef,
) { } ) { }
ngOnInit(): void { ngOnInit(): void {

View File

@ -1,5 +1,6 @@
{ {
"MEMPOOL": { "MEMPOOL": {
"ENABLED": false,
"NETWORK": "mainnet", "NETWORK": "mainnet",
"BACKEND": "esplora", "BACKEND": "esplora",
"HTTP_PORT": 8993, "HTTP_PORT": 8993,

View File

@ -1,5 +1,6 @@
{ {
"MEMPOOL": { "MEMPOOL": {
"ENABLED": false,
"NETWORK": "signet", "NETWORK": "signet",
"BACKEND": "esplora", "BACKEND": "esplora",
"HTTP_PORT": 8991, "HTTP_PORT": 8991,

View File

@ -1,5 +1,6 @@
{ {
"MEMPOOL": { "MEMPOOL": {
"ENABLED": false,
"NETWORK": "testnet", "NETWORK": "testnet",
"BACKEND": "esplora", "BACKEND": "esplora",
"HTTP_PORT": 8992, "HTTP_PORT": 8992,