diff --git a/README.md b/README.md index db921d86d..ae0e2191e 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,7 @@ It is an open-source project developed and operated for the benefit of the Bitco Mempool can be self-hosted on a wide variety of your own hardware, ranging from a simple one-click installation on a Raspberry Pi full-node distro all the way to a robust production instance on a powerful FreeBSD server. -We support the following installation methods, ranked in order from simple to advanced: - -1) [One-click installation on full-node distros](#one-click-installation) -2) [Docker installation on Linux using docker-compose](./docker) -3) [Manual installation on Linux or FreeBSD](#manual-installation) -4) [Production installation on a powerful FreeBSD server](./production) - -This doc offers install notes on the one-click method and manual install method. Follow the links above for install notes on Docker and production installations. +**Most people should use a one-click install method.** Other install methods are meant for developers and others with experience managing servers. ## One-Click Installation @@ -29,167 +22,12 @@ Mempool can be conveniently installed on the following full-node distros: - [myNode](https://github.com/mynodebtc/mynode) - [Start9](https://github.com/Start9Labs/embassy-os) - -## Manual Installation +**We highly recommend you deploy your own Mempool instance this way.** No matter which option you pick, you'll be able to get your own fully-sovereign instance of Mempool up quickly without needing to fiddle with any settings. -The following instructions are for a manual installation on Linux or FreeBSD. You may need to change file and directory paths to match your OS. +## Advanced Installation Methods -You will need [Bitcoin Core](https://github.com/bitcoin/bitcoin), [Electrum Server](https://github.com/romanz/electrs), [Node.js](https://github.com/nodejs/node), [MariaDB](https://github.com/mariadb/server), and [Nginx](https://github.com/nginx/nginx). Below, we walk through how to configure each of these. +Mempool can be installed in other ways too, but we only recommend doing so if you're a developer, have experience managing servers, or otherwise know what you're doing. -### 1. Get Latest Mempool Release - -Clone the Mempool repo, and checkout the latest release tag: - -```bash -git clone https://github.com/mempool/mempool -cd mempool -latestrelease=$(curl -s https://api.github.com/repos/mempool/mempool/releases/latest|grep tag_name|head -1|cut -d '"' -f4) -git checkout $latestrelease -``` - -### 2. Configure Bitcoin Core - -Enable RPC and txindex in `bitcoin.conf`: - -```bash -rpcuser=mempool -rpcpassword=mempool -txindex=1 -``` - -### 3. Get & Configure MySQL - -Install MariaDB from your OS package manager: - -```bash -# Debian, Ubuntu, etc. -apt-get install mariadb-server mariadb-client - -# macOS -brew install mariadb -mysql.server start -``` - -Create a database and grant privileges: - -```bash -MariaDB [(none)]> drop database mempool; -Query OK, 0 rows affected (0.00 sec) - -MariaDB [(none)]> create database mempool; -Query OK, 1 row affected (0.00 sec) - -MariaDB [(none)]> grant all privileges on mempool.* to 'mempool'@'%' identified by 'mempool'; -Query OK, 0 rows affected (0.00 sec) -``` - -### 4. Build Mempool Backend - -Install Mempool dependencies with npm and build the backend: - -```bash -cd backend -npm install --prod -npm run build -``` - -In the `backend` folder, make a copy of the sample config: - -```bash -cp mempool-config.sample.json mempool-config.json -``` - -Edit `mempool-config.json` with your Bitcoin Core node RPC credentials: - -```bash -{ - "MEMPOOL": { - "NETWORK": "mainnet", - "BACKEND": "electrum", - "HTTP_PORT": 8999 - }, - "CORE_RPC": { - "HOST": "127.0.0.1", - "PORT": 8332, - "USERNAME": "mempool", - "PASSWORD": "mempool" - }, - "ELECTRUM": { - "HOST": "127.0.0.1", - "PORT": 50002, - "TLS_ENABLED": true - }, - "DATABASE": { - "ENABLED": true, - "HOST": "127.0.0.1", - "PORT": 3306, - "USERNAME": "mempool", - "PASSWORD": "mempool", - "DATABASE": "mempool" - } -} -``` - -Start the backend: - -```bash -npm run start -``` - -When it's running, you should see output like this: - -```bash -Mempool updated in 0.189 seconds -Updating mempool -Mempool updated in 0.096 seconds -Updating mempool -Mempool updated in 0.099 seconds -Updating mempool -Calculated fee for transaction 1 / 10 -Calculated fee for transaction 2 / 10 -Calculated fee for transaction 3 / 10 -Calculated fee for transaction 4 / 10 -Calculated fee for transaction 5 / 10 -Calculated fee for transaction 6 / 10 -Calculated fee for transaction 7 / 10 -Calculated fee for transaction 8 / 10 -Calculated fee for transaction 9 / 10 -Calculated fee for transaction 10 / 10 -Mempool updated in 0.243 seconds -Updating mempool -``` - -### 5. Build Mempool Frontend - -Install the Mempool dependencies with npm and build the frontend: - -```bash -cd frontend -npm install --prod -npm run build -``` - -Install the output into the nginx webroot folder: - -```bash -sudo rsync -av --delete dist/ /var/www/ -``` - -### 6. `nginx` + `certbot` - -Install the supplied `nginx.conf` and `nginx-mempool.conf` in `/etc/nginx`: - -```bash -# install nginx and certbot -apt-get install -y nginx python3-certbot-nginx - -# install the mempool configuration for nginx -cp nginx.conf nginx-mempool.conf /etc/nginx/ - -# replace example.com with your domain name -certbot --nginx -d example.com -``` - -If everything went well, you should see the beautiful mempool :grin: - -If you get stuck on "loading blocks", this means the websocket can't connect. Check your nginx proxy setup, firewalls, etc. and open an issue if you need help. +- See the [`docker/`](./docker/) directory for instructions on deploying Mempool with Docker. +- See the [`backend/`](./backend/) and [`frontend/`](./frontend/) directories for manual install instructions oriented for developers and small-scale deployments. +- See the [`production/`](./production/) directory for guidance on setting up a more serious Mempool instance designed for high performance at scale. \ No newline at end of file diff --git a/backend/README.md b/backend/README.md index e65205d67..cbda2478b 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,22 +1,161 @@ -# Setup backend watchers +# Mempool Backend -The backend is static. Typescript scripts are compiled into the `dist` folder and served through a node web server. +These instructions are mostly intended for developers, but can be used as a basis for personal or small-scale production setups. -You can avoid the manual shutdown/recompile/restart command line cycle by using a watcher. +If you choose to use these instructions for a production setup, be aware that you will still probably need to do additional configuration for your specific OS, environment, use-case, etc. We do our best here to provide a good starting point, but only proceed if you know what you're doing. Mempool does not provide support for custom setups. -Make sure you are in the `backend` directory `cd backend`. +See other ways to set up Mempool on [the main README](/../../#installation-methods). -1. Install nodemon and ts-node +Jump to a section in this doc: +- [Set Up the Backend](#setup) +- [Development Tips](#development-tips) + +## Setup + +### 1. Clone Mempool Repository + +Get the latest Mempool code: ``` -sudo npm install -g ts-node nodemon +git clone https://github.com/mempool/mempool +cd mempool ``` -2. Run the watcher +Check out the latest release: -> Note: You can find your npm global binary folder using `npm -g bin`, where nodemon will be installed. +``` +latestrelease=$(curl -s https://api.github.com/repos/mempool/mempool/releases/latest|grep tag_name|head -1|cut -d '"' -f4) +git checkout $latestrelease +``` + +### 2. Configure Bitcoin Core + +Turn on `txindex`, enable RPC, and set RPC credentials in `bitcoin.conf`: + +``` +txindex=1 +server=1 +rpcuser=mempool +rpcpassword=mempool +``` + +### 3. Configure Electrum Server + +[Pick an Electrum Server implementation](https://mempool.space/docs/faq#address-lookup-issues), configure it, and make sure it's synced. + +**This step is optional.** You can run Mempool without configuring an Electrum Server for it, but address lookups will be disabled. + +### 4. Configure MariaDB + +_Mempool needs MariaDB v10.5 or later. If you already have MySQL installed, make sure to migrate any existing databases **before** installing MariaDB._ + +Get MariaDB from your operating system's package manager: + +``` +# Debian, Ubuntu, etc. +apt-get install mariadb-server mariadb-client + +# macOS +brew install mariadb +mysql.server start +``` + +Create a database and grant privileges: + +``` +MariaDB [(none)]> drop database mempool; +Query OK, 0 rows affected (0.00 sec) + +MariaDB [(none)]> create database mempool; +Query OK, 1 row affected (0.00 sec) + +MariaDB [(none)]> grant all privileges on mempool.* to 'mempool'@'%' identified by 'mempool'; +Query OK, 0 rows affected (0.00 sec) +``` + +### 5. Prepare Mempool Backend + +#### Build + +_Node.js 16 and npm 7 are recommended._ + +Install dependencies with `npm` and build the backend: + +``` +cd backend +npm install # add --prod for production +npm run build +``` + +#### Configure + +In the backend folder, make a copy of the sample config file: + +``` +cp mempool-config.sample.json mempool-config.json +``` + +Edit `mempool-config.json` as needed. + +In particular, make sure: +- the correct Bitcoin Core RPC credentials are specified in `CORE_RPC` +- the correct `BACKEND` is specified in `MEMPOOL`: + - "electrum" if you're using [romanz/electrs](https://github.com/romanz/electrs) or [cculianu/Fulcrum](https://github.com/cculianu/Fulcrum) + - "esplora" if you're using [Blockstream/electrs](https://github.com/Blockstream/electrs) + - "none" if you're not using any Electrum Server + +### 6. Run Mempool Backend + +Run the Mempool backend: + +``` +npm run start +``` + +When it's running, you should see output like this: + +``` +Mempool updated in 0.189 seconds +Updating mempool +Mempool updated in 0.096 seconds +Updating mempool +Mempool updated in 0.099 seconds +Updating mempool +Calculated fee for transaction 1 / 10 +Calculated fee for transaction 2 / 10 +Calculated fee for transaction 3 / 10 +Calculated fee for transaction 4 / 10 +Calculated fee for transaction 5 / 10 +Calculated fee for transaction 6 / 10 +Calculated fee for transaction 7 / 10 +Calculated fee for transaction 8 / 10 +Calculated fee for transaction 9 / 10 +Calculated fee for transaction 10 / 10 +Mempool updated in 0.243 seconds +Updating mempool +``` + +### 7. Set Up Mempool Frontend +With the backend configured and running, proceed to set up the [Mempool frontend](../frontend#manual-setup). + +## Development Tips + +### Set Up Backend Watchers + +The Mempool backend is static. TypeScript scripts are compiled into the `dist` folder and served through a Node.js web server. + +As a result, for development purposes, you may find it helpful to set up backend watchers to avoid the manual shutdown/recompile/restart command-line cycle. + +First, install `nodemon` and `ts-node`: + +``` +npm install -g ts-node nodemon +``` + +Then, run the watcher: ``` nodemon src/index.ts --ignore cache/ --ignore pools.json ``` +`nodemon` should be in npm's global binary folder. If needed, you can determine where that is with `npm -g bin`. diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index ba6fdff22..b9b7b3aff 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -417,10 +417,7 @@ class Blocks { return blockExtended; } - public async $getBlocksExtras(fromHeight?: number, limit: number = 15): Promise { - // Note - This API is breaking if indexing is not available. For now it is okay because we only - // use it for the mining pages, and mining pages should not be available if indexing is turned off. - // I'll need to fix it before we refactor the block(s) related pages + public async $getBlocks(fromHeight?: number, limit: number = 15): Promise { try { let currentHeight = fromHeight !== undefined ? fromHeight : this.getCurrentBlockHeight(); const returnBlocks: BlockExtended[] = []; diff --git a/backend/src/api/mining.ts b/backend/src/api/mining.ts index 8d11abfa7..7e7008351 100644 --- a/backend/src/api/mining.ts +++ b/backend/src/api/mining.ts @@ -89,7 +89,6 @@ class Mining { }); poolsStatistics['pools'] = poolsStats; - poolsStatistics['oldestIndexedBlockTimestamp'] = await BlocksRepository.$oldestBlockTimestamp(); const blockCount: number = await BlocksRepository.$blockCount(null, interval); poolsStatistics['blockCount'] = blockCount; diff --git a/backend/src/index.ts b/backend/src/index.ts index 4b7881730..30c5ecf37 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -27,7 +27,6 @@ import icons from './api/liquid/icons'; import { Common } from './api/common'; import mining from './api/mining'; import HashratesRepository from './repositories/HashratesRepository'; -import BlocksRepository from './repositories/BlocksRepository'; import poolsUpdater from './tasks/pools-updater'; import indexer from './indexer'; @@ -315,8 +314,8 @@ class Server { } this.app - .get(config.MEMPOOL.API_URL_PREFIX + 'blocks-extras', routes.getBlocksExtras) - .get(config.MEMPOOL.API_URL_PREFIX + 'blocks-extras/:height', routes.getBlocksExtras) + .get(config.MEMPOOL.API_URL_PREFIX + 'blocks', routes.getBlocks.bind(routes)) + .get(config.MEMPOOL.API_URL_PREFIX + 'blocks/:height', routes.getBlocks.bind(routes)) .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash', routes.getBlock); if (config.MEMPOOL.BACKEND !== 'esplora') { @@ -330,8 +329,6 @@ class Server { .get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/status', routes.getTransactionStatus) .get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/outspends', routes.getTransactionOutspends) .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/header', routes.getBlockHeader) - .get(config.MEMPOOL.API_URL_PREFIX + 'blocks', routes.getBlocks) - .get(config.MEMPOOL.API_URL_PREFIX + 'blocks/:height', routes.getBlocks) .get(config.MEMPOOL.API_URL_PREFIX + 'blocks/tip/height', routes.getBlockTipHeight) .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/txs', routes.getBlockTransactions) .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/txs/:index', routes.getBlockTransactions) diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 0081bd34f..60b07da1b 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -81,7 +81,7 @@ export interface TransactionStripped { export interface BlockExtension { totalFees?: number; - medianFee?: number; // Actually the median fee rate that we compute ourself + medianFee?: number; feeRange?: number[]; reward?: number; coinbaseTx?: TransactionMinerInfo; diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index a34f32e83..dcd5b48d1 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -475,9 +475,9 @@ class BlocksRepository { public async $getHistoricalBlockFees(div: number, interval: string | null): Promise { try { let query = `SELECT - CAST(AVG(height) as INT) as avg_height, + CAST(AVG(height) as INT) as avgHeight, CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp, - CAST(AVG(fees) as INT) as avg_fees + CAST(AVG(fees) as INT) as avgFees FROM blocks`; if (interval !== null) { @@ -500,9 +500,9 @@ class BlocksRepository { public async $getHistoricalBlockRewards(div: number, interval: string | null): Promise { try { let query = `SELECT - CAST(AVG(height) as INT) as avg_height, + CAST(AVG(height) as INT) as avgHeight, CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp, - CAST(AVG(reward) as INT) as avg_rewards + CAST(AVG(reward) as INT) as avgRewards FROM blocks`; if (interval !== null) { @@ -525,15 +525,15 @@ class BlocksRepository { public async $getHistoricalBlockFeeRates(div: number, interval: string | null): Promise { try { let query = `SELECT - CAST(AVG(height) as INT) as avg_height, + CAST(AVG(height) as INT) as avgHeight, CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp, - CAST(AVG(JSON_EXTRACT(fee_span, '$[0]')) as INT) as avg_fee_0, - CAST(AVG(JSON_EXTRACT(fee_span, '$[1]')) as INT) as avg_fee_10, - CAST(AVG(JSON_EXTRACT(fee_span, '$[2]')) as INT) as avg_fee_25, - CAST(AVG(JSON_EXTRACT(fee_span, '$[3]')) as INT) as avg_fee_50, - CAST(AVG(JSON_EXTRACT(fee_span, '$[4]')) as INT) as avg_fee_75, - CAST(AVG(JSON_EXTRACT(fee_span, '$[5]')) as INT) as avg_fee_90, - CAST(AVG(JSON_EXTRACT(fee_span, '$[6]')) as INT) as avg_fee_100 + CAST(AVG(JSON_EXTRACT(fee_span, '$[0]')) as INT) as avgFee_0, + CAST(AVG(JSON_EXTRACT(fee_span, '$[1]')) as INT) as avgFee_10, + CAST(AVG(JSON_EXTRACT(fee_span, '$[2]')) as INT) as avgFee_25, + CAST(AVG(JSON_EXTRACT(fee_span, '$[3]')) as INT) as avgFee_50, + CAST(AVG(JSON_EXTRACT(fee_span, '$[4]')) as INT) as avgFee_75, + CAST(AVG(JSON_EXTRACT(fee_span, '$[5]')) as INT) as avgFee_90, + CAST(AVG(JSON_EXTRACT(fee_span, '$[6]')) as INT) as avgFee_100 FROM blocks`; if (interval !== null) { @@ -556,9 +556,9 @@ class BlocksRepository { public async $getHistoricalBlockSizes(div: number, interval: string | null): Promise { try { let query = `SELECT - CAST(AVG(height) as INT) as avg_height, + CAST(AVG(height) as INT) as avgHeight, CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp, - CAST(AVG(size) as INT) as avg_size + CAST(AVG(size) as INT) as avgSize FROM blocks`; if (interval !== null) { @@ -581,9 +581,9 @@ class BlocksRepository { public async $getHistoricalBlockWeights(div: number, interval: string | null): Promise { try { let query = `SELECT - CAST(AVG(height) as INT) as avg_height, + CAST(AVG(height) as INT) as avgHeight, CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp, - CAST(AVG(weight) as INT) as avg_weight + CAST(AVG(weight) as INT) as avgWeight FROM blocks`; if (interval !== null) { diff --git a/backend/src/routes.ts b/backend/src/routes.ts index cf28dd71d..ef8dd47e5 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -669,14 +669,12 @@ class Routes { public async $getHistoricalBlockFeeRates(req: Request, res: Response) { try { const blockFeeRates = await mining.$getHistoricalBlockFeeRates(req.params.interval); - const oldestIndexedBlockTimestamp = await BlocksRepository.$oldestBlockTimestamp(); + const blockCount = await BlocksRepository.$blockCount(null, null); res.header('Pragma', 'public'); res.header('Cache-control', 'public'); + res.header('X-total-count', blockCount.toString()); res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); - res.json({ - oldestIndexedBlockTimestamp: oldestIndexedBlockTimestamp, - blockFeeRates: blockFeeRates, - }); + res.json(blockFeeRates); } catch (e) { res.status(500).send(e instanceof Error ? e.message : e); } @@ -720,20 +718,22 @@ class Routes { } } - public async getBlocksExtras(req: Request, res: Response) { + public async getBlocks(req: Request, res: Response) { try { - const height = req.params.height === undefined ? undefined : parseInt(req.params.height, 10); - res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); - res.json(await blocks.$getBlocksExtras(height, 15)); + if (['mainnet', 'testnet', 'signet', 'regtest'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin + const height = req.params.height === undefined ? undefined : parseInt(req.params.height, 10); + res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); + res.json(await blocks.$getBlocks(height, 15)); + } else { // Liquid, Bisq + return await this.getLegacyBlocks(req, res); + } } catch (e) { res.status(500).send(e instanceof Error ? e.message : e); } } - public async getBlocks(req: Request, res: Response) { + public async getLegacyBlocks(req: Request, res: Response) { try { - loadingIndicators.setProgress('blocks', 0); - const returnBlocks: IEsploraApi.Block[] = []; const fromHeight = parseInt(req.params.height, 10) || blocks.getCurrentBlockHeight(); @@ -757,16 +757,15 @@ class Routes { returnBlocks.push(block); nextHash = block.previousblockhash; } - loadingIndicators.setProgress('blocks', i / 10 * 100); } + res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); res.json(returnBlocks); } catch (e) { - loadingIndicators.setProgress('blocks', 100); res.status(500).send(e instanceof Error ? e.message : e); } } - + public async getBlockTransactions(req: Request, res: Response) { try { loadingIndicators.setProgress('blocktxs-' + req.params.hash, 0); diff --git a/backend/src/utils/blocks-utils.ts b/backend/src/utils/blocks-utils.ts index 8760a08c0..937a37448 100644 --- a/backend/src/utils/blocks-utils.ts +++ b/backend/src/utils/blocks-utils.ts @@ -15,7 +15,7 @@ export function prepareBlock(block: any): BlockExtended { weight: block.weight, previousblockhash: block.previousblockhash, extras: { - coinbaseRaw: block.coinbase_raw ?? block.extras.coinbaseRaw, + coinbaseRaw: block.coinbase_raw ?? block.extras?.coinbaseRaw, medianFee: block.medianFee ?? block.median_fee ?? block.extras?.medianFee, feeRange: block.feeRange ?? block.fee_span, reward: block.reward ?? block?.extras?.reward, diff --git a/docker/README.md b/docker/README.md index 37a2cc079..45bdff615 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,18 +1,23 @@ # Docker Installation -This directory contains the Dockerfiles used to build and release the official images and a `docker-compose.yml` for end users to run a Mempool instance with minimal effort. +This directory contains the Dockerfiles used to build and release the official images, as well as a `docker-compose.yml` to configure environment variables and other settings. -You can choose to configure Mempool to run with a basic backend powered by just `bitcoind`, or with `bitcoind` along with an Electrum-compatible server for full functionality. +If you are looking to use these Docker images to deploy your own instance of Mempool, note that they only containerize Mempool's frontend and backend. You will still need to deploy and configure Bitcoin Core and an Electrum Server separately, along with any other utilities specific to your use case (e.g., a reverse proxy, etc). Such configuration is mostly beyond the scope of the Mempool project, so please only proceed if you know what you're doing. -## `bitcoind`-only Configuration +Jump to a section in this doc: +- [Configure with Bitcoin Core Only](#configure-with-bitcoin-core-only) +- [Configure with Bitcoin Core + Electrum Server](#configure-with-bitcoin-core--electrum-server) +- [Further Configuration](#further-configuration) -_Note: address lookups require an Electrum server and will not work with this configuration._ +## Configure with Bitcoin Core Only -Make sure `bitcoind` is running and synced. +_Note: address lookups require an Electrum Server and will not work with this configuration. [Add an Electrum Server](#configure-with-bitcoin-core--electrum-server) to your backend for full functionality._ -The default Docker configuration assumes you have added RPC credentials for a `mempool` user with a `mempool` password in your `bitcoin.conf` file, like so: +The default Docker configuration assumes you have the following configuration in your `bitcoin.conf` file: ``` +txindex=1 +server=1 rpcuser=mempool rpcpassword=mempool ``` @@ -31,6 +36,8 @@ If you want to use different credentials, specify them in the `docker-compose.ym The IP address in the example above refers to Docker's default gateway IP address so that the container can hit the `bitcoind` instance running on the host machine. If your setup is different, update it accordingly. +Make sure `bitcoind` is running and synced. + Now, run: ```bash @@ -39,11 +46,11 @@ docker-compose up Your Mempool instance should be running at http://localhost. The graphs will be populated as new transactions are detected. -## `bitcoind` + Electrum Server Configuration +## Configure with Bitcoin Core + Electrum Server -First, configure `bitcoind` as specified above, and make sure your Electrum server is running and synced. +First, configure `bitcoind` as specified above, and make sure your Electrum Server is running and synced. See [this FAQ](https://mempool.space/docs/faq#address-lookup-issues) if you need help picking an Electrum Server implementation. -Then, make sure the following variables are set in `docker-compose.yml`, as shown below, so Mempool can connect to your Electrum server: +Then, set the following variables in `docker-compose.yml` so Mempool can connect to your Electrum Server: ``` api: @@ -54,6 +61,11 @@ Then, make sure the following variables are set in `docker-compose.yml`, as show ELECTRUM_TLS_ENABLED: "false" ``` +Eligible values for `MEMPOOL_BACKEND`: + - "electrum" if you're using [romanz/electrs](https://github.com/romanz/electrs) or [cculianu/Fulcrum](https://github.com/cculianu/Fulcrum) + - "esplora" if you're using [Blockstream/electrs](https://github.com/Blockstream/electrs) + - "none" if you're not using any Electrum Server + Of course, if your Docker host IP address is different, update accordingly. With `bitcoind` and Electrum Server set up, run Mempool with: diff --git a/frontend/README.md b/frontend/README.md index f7fb08552..e223f17c2 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,8 +1,30 @@ -# mempool-frontend +# Mempool Frontend -## Contributing +You can build and run the Mempool frontend and proxy to the production Mempool backend (for easier frontend development), or you can connect it to your own backend for a full Mempool development instance, custom deployment, etc. -This package is used for the https://mempool.space, https://liquid.network and https://bisq.markets websites - there are npm scripts to setup all three, which effectively change how BASE_MODULE is configured: +Jump to a section in this doc: +- [Quick Setup for Frontend Development](#quick-setup-for-frontend-development) +- [Manual Frontend Setup](#manual-setup) +- [Translations](#translations-transifex-project) + +## Quick Setup for Frontend Development + +If you want to quickly improve the UI, fix typos, or make other updates that don't require any backend changes, you don't need to set up an entire backend—you can simply run the Mempool frontend locally and proxy to the mempool.space backend. + +### 1. Clone Mempool Repository + +Get the latest Mempool code: + +``` +git clone https://github.com/mempool/mempool +cd mempool +``` + +### 2. Specify Website + +The same frontend codebase is used for https://mempool.space, https://liquid.network and https://bisq.markets. + +Configure the frontend for the site you want by running the corresponding command: ``` $ npm run config:defaults:mempool @@ -10,18 +32,22 @@ $ npm run config:defaults:liquid $ npm run config:defaults:bisq ``` -Changes that affect the frontend codebase only can be done using the production backend so you don't need to spin up the entire Mempool infrastructure. This is very convenient in case you want to quickly improve the UI, fix typos or implement new features that don't require any backend changes. +### 3. Run the Frontend -Make your changes, install the project dependencies and run the frontend server as follows: +_Node.js 16 and npm 7 are recommended._ + +Install project dependencies and run the frontend server: ``` $ npm install $ npm run serve:local-prod ``` -The frontend will be available at http://localhost:4200/ and all API requests will be proxied to the production server at https://mempool.space +The frontend will be available at http://localhost:4200/ and all API requests will be proxied to the production server at https://mempool.space. -After making your changes, you can run our end-to-end automation suite and check for possible regressions: +### 4. Test + +After making your changes, you can run our end-to-end automation suite and check for possible regressions. Headless: @@ -37,11 +63,43 @@ $ npm run config:defaults:mempool && npm run cypress:open This will open the Cypress test runner, where you can select any of the test files to run. -If all tests are green, submit your PR and it will be reviewed by someone on the team as soon as possible. +If all tests are green, submit your PR, and it will be reviewed by someone on the team as soon as possible. + +## Manual Setup + +Set up the [Mempool backend](../backend/) first, if you haven't already. + +### 1. Build the Frontend + +_Node.js 16 and npm 7 are recommended._ + +Build the frontend: + +``` +cd frontend +npm install # add --prod for production +npm run build +``` + +### 2. Run the Frontend + +#### Development + +To run your local Mempool frontend with your local Mempool backend: + +``` +npm run serve +``` + +#### Production + +The `npm run build` command from step 1 above should have generated a `dist` directory. Put the contents of `dist/` onto your web server. + +You will probably want to set up a reverse proxy, TLS, etc. There are sample nginx configuration files in the top level of the repository for reference, but note that support for such tasks is outside the scope of this project. ## Translations: Transifex Project -The mempool frontend strings are localized into 20+ locales: +The Mempool frontend strings are localized into 20+ locales: https://www.transifex.com/mempool/mempool/dashboard/ ### Translators diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6996073e5..dc5f7a01a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -3900,9 +3900,9 @@ } }, "node_modules/@socket.io/component-emitter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.0.0.tgz", - "integrity": "sha512-2pTGuibAXJswAPJjaKisthqS/NOK5ypG4LYT6tEAV0S/mxW0zOIvYvGK0V8w8+SHxAm6vRMSjqSalFXeBAqs+Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", "dev": true }, "node_modules/@tootallnate/once": { @@ -4755,15 +4755,17 @@ } }, "node_modules/async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dependencies": { + "lodash": "^4.17.14" + } }, "node_modules/async-each-series": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/async-each-series/-/async-each-series-0.1.1.tgz", - "integrity": "sha1-dhfBkXQB/Yykooqtzj266Yr+tDI=", + "integrity": "sha512-p4jj6Fws4Iy2m0iCmI2am2ZNZCgbdgE+P8F/8csmn2vx7ixXrO2zGcuNsD46X5uZSVecmkEy/M06X2vG8KD6dQ==", "dev": true, "engines": { "node": ">=0.8.0" @@ -4996,12 +4998,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "dev": true - }, "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -5254,13 +5250,13 @@ } }, "node_modules/browser-sync": { - "version": "2.27.9", - "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.27.9.tgz", - "integrity": "sha512-3zBtggcaZIeU9so4ja9yxk7/CZu9B3DOL6zkxFpzHCHsQmkGBPVXg61jItbeoa+WXgNLnr1sYES/2yQwyEZ2+w==", + "version": "2.27.10", + "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.27.10.tgz", + "integrity": "sha512-xKm+6KJmJu6RuMWWbFkKwOCSqQOxYe3nOrFkKI5Tr/ZzjPxyU3pFShKK3tWnazBo/3lYQzN7fzjixG8fwJh1Xw==", "dev": true, "dependencies": { - "browser-sync-client": "^2.27.9", - "browser-sync-ui": "^2.27.9", + "browser-sync-client": "^2.27.10", + "browser-sync-ui": "^2.27.10", "bs-recipes": "1.3.4", "bs-snippet-injector": "^2.0.1", "chokidar": "^3.5.1", @@ -5277,7 +5273,7 @@ "localtunnel": "^2.0.1", "micromatch": "^4.0.2", "opn": "5.3.0", - "portscanner": "2.1.1", + "portscanner": "2.2.0", "qs": "6.2.3", "raw-body": "^2.3.2", "resp-modifier": "6.0.2", @@ -5298,15 +5294,16 @@ } }, "node_modules/browser-sync-client": { - "version": "2.27.9", - "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-2.27.9.tgz", - "integrity": "sha512-FHW8kydp7FXo6jnX3gXJCpHAHtWNLK0nx839nnK+boMfMI1n4KZd0+DmTxHBsHsF3OHud4V4jwoN8U5HExMIdQ==", + "version": "2.27.10", + "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-2.27.10.tgz", + "integrity": "sha512-KCFKA1YDj6cNul0VsA28apohtBsdk5Wv8T82ClOZPZMZWxPj4Ny5AUbrj9UlAb/k6pdxE5HABrWDhP9+cjt4HQ==", "dev": true, "dependencies": { "etag": "1.8.1", "fresh": "0.5.2", "mitt": "^1.1.3", - "rxjs": "^5.5.6" + "rxjs": "^5.5.6", + "typescript": "^4.6.2" }, "engines": { "node": ">=8.0.0" @@ -5333,10 +5330,23 @@ "node": ">=0.10.0" } }, + "node_modules/browser-sync-client/node_modules/typescript": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", + "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/browser-sync-ui": { - "version": "2.27.9", - "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-2.27.9.tgz", - "integrity": "sha512-rsduR2bRIwFvM8CX6iY/Nu5aWub0WB9zfSYg9Le/RV5N5DEyxJYey0VxdfWCnzDOoelassTDzYQo+r0iJno3qw==", + "version": "2.27.10", + "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-2.27.10.tgz", + "integrity": "sha512-elbJILq4Uo6OQv6gsvS3Y9vRAJlWu+h8j0JDkF0X/ua+3S6SVbbiWnZc8sNOFlG7yvVGIwBED3eaYQ0iBo1Dtw==", "dev": true, "dependencies": { "async-each-series": "0.1.1", @@ -7891,20 +7901,16 @@ } }, "node_modules/engine.io-client": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.1.1.tgz", - "integrity": "sha512-V05mmDo4gjimYW+FGujoGmmmxRaDsrVr7AXA3ZIfa04MWM1jOfZfUwou0oNqhNwy/votUDvGDt4JA4QF4e0b4g==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.2.2.tgz", + "integrity": "sha512-8ZQmx0LQGRTYkHuogVZuGSpDqYZtCM/nv8zQ68VZ+JkOpazJ7ICdsSpaO6iXwvaU30oFg5QJOJWj8zWqhbKjkQ==", "dev": true, "dependencies": { - "@socket.io/component-emitter": "~3.0.0", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", - "engine.io-parser": "~5.0.0", - "has-cors": "1.1.0", - "parseqs": "0.0.6", - "parseuri": "0.0.6", + "engine.io-parser": "~5.0.3", "ws": "~8.2.3", - "xmlhttprequest-ssl": "~2.0.0", - "yeast": "0.1.2" + "xmlhttprequest-ssl": "~2.0.0" } }, "node_modules/engine.io-client/node_modules/ws": { @@ -9571,12 +9577,6 @@ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" }, - "node_modules/has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", - "dev": true - }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -13181,18 +13181,6 @@ "parse5": "^6.0.1" } }, - "node_modules/parseqs": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", - "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==", - "dev": true - }, - "node_modules/parseuri": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", - "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==", - "dev": true - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -13366,14 +13354,6 @@ "node": ">= 0.12.0" } }, - "node_modules/portfinder/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dependencies": { - "lodash": "^4.17.14" - } - }, "node_modules/portfinder/node_modules/debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -13383,12 +13363,12 @@ } }, "node_modules/portscanner": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.1.1.tgz", - "integrity": "sha1-6rtAnk3iSVD1oqUW01rnaTQ/u5Y=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", + "integrity": "sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==", "dev": true, "dependencies": { - "async": "1.5.2", + "async": "^2.6.0", "is-number-like": "^1.0.3" }, "engines": { @@ -15098,29 +15078,27 @@ "devOptional": true }, "node_modules/socket.io-client": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.4.1.tgz", - "integrity": "sha512-N5C/L5fLNha5Ojd7Yeb/puKcPWWcoB/A09fEjjNsg91EDVr5twk/OEyO6VT9dlLSUNY85NpW6KBhVMvaLKQ3vQ==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.5.1.tgz", + "integrity": "sha512-e6nLVgiRYatS+AHXnOnGi4ocOpubvOUCGhyWw8v+/FxW8saHkinG6Dfhi9TU0Kt/8mwJIAASxvw6eujQmjdZVA==", "dev": true, "dependencies": { - "@socket.io/component-emitter": "~3.0.0", - "backo2": "~1.0.2", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", - "engine.io-client": "~6.1.1", - "parseuri": "0.0.6", - "socket.io-parser": "~4.1.1" + "engine.io-client": "~6.2.1", + "socket.io-parser": "~4.2.0" }, "engines": { "node": ">=10.0.0" } }, "node_modules/socket.io-client/node_modules/socket.io-parser": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.1.2.tgz", - "integrity": "sha512-j3kk71QLJuyQ/hh5F/L2t1goqzdTL0gvDzuhTuNSwihfuFUrcSji0qFZmJJPtG6Rmug153eOPsUizeirf1IIog==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.0.tgz", + "integrity": "sha512-tLfmEwcEwnlQTxFB7jibL/q2+q8dlVQzj4JdRLJ/W/G1+Fu9VSxCx1Lo+n1HvXxKnM//dUuD0xgiA7tQf57Vng==", "dev": true, "dependencies": { - "@socket.io/component-emitter": "~3.0.0", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" }, "engines": { @@ -17264,12 +17242,6 @@ "fd-slicer": "~1.1.0" } }, - "node_modules/yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", - "dev": true - }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -20020,9 +19992,9 @@ "devOptional": true }, "@socket.io/component-emitter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.0.0.tgz", - "integrity": "sha512-2pTGuibAXJswAPJjaKisthqS/NOK5ypG4LYT6tEAV0S/mxW0zOIvYvGK0V8w8+SHxAm6vRMSjqSalFXeBAqs+Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", "dev": true }, "@tootallnate/once": { @@ -20794,15 +20766,17 @@ "optional": true }, "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "requires": { + "lodash": "^4.17.14" + } }, "async-each-series": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/async-each-series/-/async-each-series-0.1.1.tgz", - "integrity": "sha1-dhfBkXQB/Yykooqtzj266Yr+tDI=", + "integrity": "sha512-p4jj6Fws4Iy2m0iCmI2am2ZNZCgbdgE+P8F/8csmn2vx7ixXrO2zGcuNsD46X5uZSVecmkEy/M06X2vG8KD6dQ==", "dev": true }, "asynckit": { @@ -20976,12 +20950,6 @@ "@babel/helper-define-polyfill-provider": "^0.3.1" } }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "dev": true - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -21210,13 +21178,13 @@ } }, "browser-sync": { - "version": "2.27.9", - "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.27.9.tgz", - "integrity": "sha512-3zBtggcaZIeU9so4ja9yxk7/CZu9B3DOL6zkxFpzHCHsQmkGBPVXg61jItbeoa+WXgNLnr1sYES/2yQwyEZ2+w==", + "version": "2.27.10", + "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.27.10.tgz", + "integrity": "sha512-xKm+6KJmJu6RuMWWbFkKwOCSqQOxYe3nOrFkKI5Tr/ZzjPxyU3pFShKK3tWnazBo/3lYQzN7fzjixG8fwJh1Xw==", "dev": true, "requires": { - "browser-sync-client": "^2.27.9", - "browser-sync-ui": "^2.27.9", + "browser-sync-client": "^2.27.10", + "browser-sync-ui": "^2.27.10", "bs-recipes": "1.3.4", "bs-snippet-injector": "^2.0.1", "chokidar": "^3.5.1", @@ -21233,7 +21201,7 @@ "localtunnel": "^2.0.1", "micromatch": "^4.0.2", "opn": "5.3.0", - "portscanner": "2.1.1", + "portscanner": "2.2.0", "qs": "6.2.3", "raw-body": "^2.3.2", "resp-modifier": "6.0.2", @@ -21343,15 +21311,16 @@ } }, "browser-sync-client": { - "version": "2.27.9", - "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-2.27.9.tgz", - "integrity": "sha512-FHW8kydp7FXo6jnX3gXJCpHAHtWNLK0nx839nnK+boMfMI1n4KZd0+DmTxHBsHsF3OHud4V4jwoN8U5HExMIdQ==", + "version": "2.27.10", + "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-2.27.10.tgz", + "integrity": "sha512-KCFKA1YDj6cNul0VsA28apohtBsdk5Wv8T82ClOZPZMZWxPj4Ny5AUbrj9UlAb/k6pdxE5HABrWDhP9+cjt4HQ==", "dev": true, "requires": { "etag": "1.8.1", "fresh": "0.5.2", "mitt": "^1.1.3", - "rxjs": "^5.5.6" + "rxjs": "^5.5.6", + "typescript": "^4.6.2" }, "dependencies": { "rxjs": { @@ -21368,13 +21337,19 @@ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", "dev": true + }, + "typescript": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", + "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", + "dev": true } } }, "browser-sync-ui": { - "version": "2.27.9", - "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-2.27.9.tgz", - "integrity": "sha512-rsduR2bRIwFvM8CX6iY/Nu5aWub0WB9zfSYg9Le/RV5N5DEyxJYey0VxdfWCnzDOoelassTDzYQo+r0iJno3qw==", + "version": "2.27.10", + "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-2.27.10.tgz", + "integrity": "sha512-elbJILq4Uo6OQv6gsvS3Y9vRAJlWu+h8j0JDkF0X/ua+3S6SVbbiWnZc8sNOFlG7yvVGIwBED3eaYQ0iBo1Dtw==", "dev": true, "requires": { "async-each-series": "0.1.1", @@ -23395,20 +23370,16 @@ } }, "engine.io-client": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.1.1.tgz", - "integrity": "sha512-V05mmDo4gjimYW+FGujoGmmmxRaDsrVr7AXA3ZIfa04MWM1jOfZfUwou0oNqhNwy/votUDvGDt4JA4QF4e0b4g==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.2.2.tgz", + "integrity": "sha512-8ZQmx0LQGRTYkHuogVZuGSpDqYZtCM/nv8zQ68VZ+JkOpazJ7ICdsSpaO6iXwvaU30oFg5QJOJWj8zWqhbKjkQ==", "dev": true, "requires": { - "@socket.io/component-emitter": "~3.0.0", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", - "engine.io-parser": "~5.0.0", - "has-cors": "1.1.0", - "parseqs": "0.0.6", - "parseuri": "0.0.6", + "engine.io-parser": "~5.0.3", "ws": "~8.2.3", - "xmlhttprequest-ssl": "~2.0.0", - "yeast": "0.1.2" + "xmlhttprequest-ssl": "~2.0.0" }, "dependencies": { "ws": { @@ -24622,12 +24593,6 @@ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" }, - "has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", - "dev": true - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -27435,18 +27400,6 @@ "parse5": "^6.0.1" } }, - "parseqs": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", - "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==", - "dev": true - }, - "parseuri": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", - "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==", - "dev": true - }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -27576,14 +27529,6 @@ "mkdirp": "^0.5.5" }, "dependencies": { - "async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "requires": { - "lodash": "^4.17.14" - } - }, "debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -27595,12 +27540,12 @@ } }, "portscanner": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.1.1.tgz", - "integrity": "sha1-6rtAnk3iSVD1oqUW01rnaTQ/u5Y=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", + "integrity": "sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==", "dev": true, "requires": { - "async": "1.5.2", + "async": "^2.6.0", "is-number-like": "^1.0.3" } }, @@ -28879,26 +28824,24 @@ "devOptional": true }, "socket.io-client": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.4.1.tgz", - "integrity": "sha512-N5C/L5fLNha5Ojd7Yeb/puKcPWWcoB/A09fEjjNsg91EDVr5twk/OEyO6VT9dlLSUNY85NpW6KBhVMvaLKQ3vQ==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.5.1.tgz", + "integrity": "sha512-e6nLVgiRYatS+AHXnOnGi4ocOpubvOUCGhyWw8v+/FxW8saHkinG6Dfhi9TU0Kt/8mwJIAASxvw6eujQmjdZVA==", "dev": true, "requires": { - "@socket.io/component-emitter": "~3.0.0", - "backo2": "~1.0.2", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", - "engine.io-client": "~6.1.1", - "parseuri": "0.0.6", - "socket.io-parser": "~4.1.1" + "engine.io-client": "~6.2.1", + "socket.io-parser": "~4.2.0" }, "dependencies": { "socket.io-parser": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.1.2.tgz", - "integrity": "sha512-j3kk71QLJuyQ/hh5F/L2t1goqzdTL0gvDzuhTuNSwihfuFUrcSji0qFZmJJPtG6Rmug153eOPsUizeirf1IIog==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.0.tgz", + "integrity": "sha512-tLfmEwcEwnlQTxFB7jibL/q2+q8dlVQzj4JdRLJ/W/G1+Fu9VSxCx1Lo+n1HvXxKnM//dUuD0xgiA7tQf57Vng==", "dev": true, "requires": { - "@socket.io/component-emitter": "~3.0.0", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" } } @@ -30543,12 +30486,6 @@ "fd-slicer": "~1.1.0" } }, - "yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", - "dev": true - }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 672d68686..6951accb2 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -7,7 +7,6 @@ import { AddressComponent } from './components/address/address.component'; import { MasterPageComponent } from './components/master-page/master-page.component'; import { AboutComponent } from './components/about/about.component'; import { StatusViewComponent } from './components/status-view/status-view.component'; -import { LatestBlocksComponent } from './components/latest-blocks/latest-blocks.component'; import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component'; import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-policy.component'; import { TrademarkPolicyComponent } from './components/trademark-policy/trademark-policy.component'; @@ -36,19 +35,20 @@ let routes: Routes = [ component: MasterPageComponent, children: [ { - path: 'tx/push', - component: PushTransactionComponent, + path: 'mining/blocks', + redirectTo: 'blocks', + pathMatch: 'full' }, { - path: 'blocks', - component: LatestBlocksComponent, + path: 'tx/push', + component: PushTransactionComponent, }, { path: 'about', component: AboutComponent, }, { - path: 'mining/blocks', + path: 'blocks', component: BlocksList, }, { @@ -115,6 +115,11 @@ let routes: Routes = [ { path: 'signet', children: [ + { + path: 'mining/blocks', + redirectTo: 'blocks', + pathMatch: 'full' + }, { path: '', pathMatch: 'full', @@ -128,16 +133,12 @@ let routes: Routes = [ path: 'tx/push', component: PushTransactionComponent, }, - { - path: 'blocks', - component: LatestBlocksComponent, - }, { path: 'about', component: AboutComponent, }, { - path: 'mining/blocks', + path: 'blocks', component: BlocksList, }, { @@ -211,19 +212,20 @@ let routes: Routes = [ component: MasterPageComponent, children: [ { - path: 'tx/push', - component: PushTransactionComponent, + path: 'mining/blocks', + redirectTo: 'blocks', + pathMatch: 'full' }, { - path: 'blocks', - component: LatestBlocksComponent, + path: 'tx/push', + component: PushTransactionComponent, }, { path: 'about', component: AboutComponent, }, { - path: 'mining/blocks', + path: 'blocks', component: BlocksList, }, { @@ -321,16 +323,12 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { path: 'tx/push', component: PushTransactionComponent, }, - { - path: 'blocks', - component: LatestBlocksComponent, - }, { path: 'about', component: AboutComponent, }, { - path: 'mining/blocks', + path: 'blocks', component: BlocksList, }, { @@ -429,16 +427,12 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { path: 'tx/push', component: PushTransactionComponent, }, - { - path: 'blocks', - component: LatestBlocksComponent, - }, { path: 'about', component: AboutComponent, }, { - path: 'mining/blocks', + path: 'blocks', component: BlocksList, }, { diff --git a/frontend/src/app/bisq/bisq.module.ts b/frontend/src/app/bisq/bisq.module.ts index 34c09f971..ebb9c3ee6 100644 --- a/frontend/src/app/bisq/bisq.module.ts +++ b/frontend/src/app/bisq/bisq.module.ts @@ -7,7 +7,6 @@ import { LightweightChartsComponent } from './lightweight-charts/lightweight-cha import { LightweightChartsAreaComponent } from './lightweight-charts-area/lightweight-charts-area.component'; import { BisqMarketComponent } from './bisq-market/bisq-market.component'; import { BisqTransactionsComponent } from './bisq-transactions/bisq-transactions.component'; -import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap'; import { BisqTransactionComponent } from './bisq-transaction/bisq-transaction.component'; import { BisqBlockComponent } from './bisq-block/bisq-block.component'; import { BisqDashboardComponent } from './bisq-dashboard/bisq-dashboard.component'; @@ -50,7 +49,6 @@ import { CommonModule } from '@angular/common'; CommonModule, BisqRoutingModule, SharedModule, - NgbPaginationModule, FontAwesomeModule, NgxBootstrapMultiselectModule, ], diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html index 27892626c..47489457c 100644 --- a/frontend/src/app/components/about/about.component.html +++ b/frontend/src/app/components/about/about.component.html @@ -250,6 +250,10 @@ Marina + + + Schildbach + diff --git a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.html b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.html index ec3773ca8..016c80936 100644 --- a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.html +++ b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.html @@ -8,34 +8,34 @@
-
diff --git a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts index 656a00d45..3a4d2b89d 100644 --- a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts +++ b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts @@ -76,7 +76,7 @@ export class BlockFeeRatesGraphComponent implements OnInit { this.isLoading = true; return this.apiService.getHistoricalBlockFeeRates$(timespan) .pipe( - tap((data: any) => { + tap((response) => { // Group by percentile const seriesData = { 'Min': [], @@ -87,15 +87,15 @@ export class BlockFeeRatesGraphComponent implements OnInit { '90th': [], 'Max': [] }; - for (const rate of data.blockFeeRates) { + for (const rate of response.body) { const timestamp = rate.timestamp * 1000; - seriesData['Min'].push([timestamp, rate.avg_fee_0, rate.avg_height]); - seriesData['10th'].push([timestamp, rate.avg_fee_10, rate.avg_height]); - seriesData['25th'].push([timestamp, rate.avg_fee_25, rate.avg_height]); - seriesData['Median'].push([timestamp, rate.avg_fee_50, rate.avg_height]); - seriesData['75th'].push([timestamp, rate.avg_fee_75, rate.avg_height]); - seriesData['90th'].push([timestamp, rate.avg_fee_90, rate.avg_height]); - seriesData['Max'].push([timestamp, rate.avg_fee_100, rate.avg_height]); + seriesData['Min'].push([timestamp, rate.avgFee_0, rate.avgHeight]); + seriesData['10th'].push([timestamp, rate.avgFee_10, rate.avgHeight]); + seriesData['25th'].push([timestamp, rate.avgFee_25, rate.avgHeight]); + seriesData['Median'].push([timestamp, rate.avgFee_50, rate.avgHeight]); + seriesData['75th'].push([timestamp, rate.avgFee_75, rate.avgHeight]); + seriesData['90th'].push([timestamp, rate.avgFee_90, rate.avgHeight]); + seriesData['Max'].push([timestamp, rate.avgFee_100, rate.avgHeight]); } // Prepare chart @@ -130,13 +130,9 @@ export class BlockFeeRatesGraphComponent implements OnInit { }); this.isLoading = false; }), - map((data: any) => { - const availableTimespanDay = ( - (new Date().getTime() / 1000) - (data.oldestIndexedBlockTimestamp) - ) / 3600 / 24; - + map((response) => { return { - availableTimespanDay: availableTimespanDay, + blockCount: parseInt(response.headers.get('x-total-count'), 10), }; }), ); diff --git a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts index c938b351f..185938d5d 100644 --- a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts +++ b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts @@ -71,7 +71,7 @@ export class BlockFeesGraphComponent implements OnInit { .pipe( tap((response) => { this.prepareChartOptions({ - blockFees: response.body.map(val => [val.timestamp * 1000, val.avg_fees / 100000000]), + blockFees: response.body.map(val => [val.timestamp * 1000, val.avgFees / 100000000]), }); this.isLoading = false; }), diff --git a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts index 48d7ec10c..24614903c 100644 --- a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts +++ b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts @@ -69,7 +69,7 @@ export class BlockRewardsGraphComponent implements OnInit { .pipe( tap((response) => { this.prepareChartOptions({ - blockRewards: response.body.map(val => [val.timestamp * 1000, val.avg_rewards / 100000000]), + blockRewards: response.body.map(val => [val.timestamp * 1000, val.avgRewards / 100000000]), }); this.isLoading = false; }), diff --git a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts index 7c72b42ef..3255fc4af 100644 --- a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts +++ b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts @@ -83,8 +83,8 @@ export class BlockSizesWeightsGraphComponent implements OnInit { tap((response) => { const data = response.body; this.prepareChartOptions({ - sizes: data.sizes.map(val => [val.timestamp * 1000, val.avg_size / 1000000, val.avg_height]), - weights: data.weights.map(val => [val.timestamp * 1000, val.avg_weight / 1000000, val.avg_height]), + sizes: data.sizes.map(val => [val.timestamp * 1000, val.avgSize / 1000000, val.avgHeight]), + weights: data.weights.map(val => [val.timestamp * 1000, val.avgWeight / 1000000, val.avgHeight]), }); this.isLoading = false; }), diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index c0ff29889..00fc18f2a 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -114,7 +114,7 @@ - + Miner - + +
+
+ Error loading data. +

+ {{ transactionsError.status }}: {{ transactionsError.error }} +
+
+
+
+ +
@@ -271,9 +282,9 @@
- Error loading block data. + Error loading data.

- {{ error.error }} + {{ error.code }}: {{ error.error }}
diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index 57417a5c3..bd70e8628 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -38,6 +38,7 @@ export class BlockComponent implements OnInit, OnDestroy { showDetails = false; showPreviousBlocklink = true; showNextBlocklink = true; + transactionsError: any = null; subscription: Subscription; keyNavigationSubscription: Subscription; @@ -152,12 +153,13 @@ export class BlockComponent implements OnInit, OnDestroy { this.stateService.markBlock$.next({ blockHeight: this.blockHeight }); this.isLoadingTransactions = true; this.transactions = null; + this.transactionsError = null; }), debounceTime(300), switchMap((block) => this.electrsApiService.getBlockTransactions$(block.id) .pipe( catchError((err) => { - console.log(err); + this.transactionsError = err; return of([]); })) ), @@ -218,9 +220,16 @@ export class BlockComponent implements OnInit, OnDestroy { const start = (page - 1) * this.itemsPerPage; this.isLoadingTransactions = true; this.transactions = null; + this.transactionsError = null; target.scrollIntoView(); // works for chrome this.electrsApiService.getBlockTransactions$(this.block.id, start) + .pipe( + catchError((err) => { + this.transactionsError = err; + return of([]); + }) + ) .subscribe((transactions) => { this.transactions = transactions; this.isLoadingTransactions = false; diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html index 9abb0f98f..4a5694153 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.html +++ b/frontend/src/app/components/blocks-list/blocks-list.component.html @@ -1,6 +1,6 @@ -
+

Blocks

@@ -9,22 +9,22 @@
- - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.scss b/frontend/src/app/components/blocks-list/blocks-list.component.scss index 817b73192..8388ecac2 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.scss +++ b/frontend/src/app/components/blocks-list/blocks-list.component.scss @@ -12,6 +12,9 @@ padding-left: 0px; padding-bottom: 0px; } +.container-xl.legacy { + max-width: 1140px; +} .container { max-width: 100%; @@ -64,6 +67,9 @@ tr, td, th { width: 10%; } } +.height.legacy { + width: 15%; +} .timestamp { width: 18%; @@ -71,6 +77,9 @@ tr, td, th { display: none; } } +.timestamp.legacy { + width: 20%; +} .mined { width: 13%; @@ -78,6 +87,12 @@ tr, td, th { display: none; } } +.mined.legacy { + width: 15%; + @media (max-width: 576px) { + display: table-cell; + } +} .txs { padding-right: 40px; @@ -94,6 +109,10 @@ tr, td, th { display: none; } } +.txs.legacy { + padding-right: 80px; + width: 10%; +} .fees { width: 10%; @@ -132,6 +151,12 @@ tr, td, th { display: none; } } +.size.legacy { + width: 20%; + @media (max-width: 576px) { + display: table-cell; + } +} /* Tooltip text */ .tooltip-custom { diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.ts b/frontend/src/app/components/blocks-list/blocks-list.component.ts index b5b66b22b..7cca2af62 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.ts +++ b/frontend/src/app/components/blocks-list/blocks-list.component.ts @@ -17,6 +17,7 @@ export class BlocksList implements OnInit { blocks$: Observable = undefined; + indexingAvailable = false; isLoading = true; fromBlockHeight = undefined; paginationMaxSize: number; @@ -35,6 +36,9 @@ export class BlocksList implements OnInit { } ngOnInit(): void { + this.indexingAvailable = (this.stateService.env.BASE_MODULE === 'mempool' && + this.stateService.env.MINING_DASHBOARD === true); + if (!this.widget) { this.websocketService.want(['blocks']); } @@ -55,17 +59,19 @@ export class BlocksList implements OnInit { this.isLoading = false; }), map(blocks => { - for (const block of blocks) { - // @ts-ignore: Need to add an extra field for the template - block.extras.pool.logo = `./resources/mining-pools/` + - block.extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg'; + if (this.indexingAvailable) { + for (const block of blocks) { + // @ts-ignore: Need to add an extra field for the template + block.extras.pool.logo = `./resources/mining-pools/` + + block.extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg'; + } } if (this.widget) { return blocks.slice(0, 6); } return blocks; }), - retryWhen(errors => errors.pipe(delayWhen(() => timer(1000)))) + retryWhen(errors => errors.pipe(delayWhen(() => timer(10000)))) ) }) ), @@ -81,9 +87,11 @@ export class BlocksList implements OnInit { return blocks[0]; } this.blocksCount = Math.max(this.blocksCount, blocks[1][0].height) + 1; - // @ts-ignore: Need to add an extra field for the template - blocks[1][0].extras.pool.logo = `./resources/mining-pools/` + - blocks[1][0].extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg'; + if (this.stateService.env.MINING_DASHBOARD) { + // @ts-ignore: Need to add an extra field for the template + blocks[1][0].extras.pool.logo = `./resources/mining-pools/` + + blocks[1][0].extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg'; + } acc.unshift(blocks[1][0]); acc = acc.slice(0, this.widget ? 6 : 15); return acc; diff --git a/frontend/src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.component.html b/frontend/src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.component.html index 6e80d828a..5f11c2608 100644 --- a/frontend/src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.component.html +++ b/frontend/src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.component.html @@ -1,4 +1,4 @@ -
+
HeightPoolTimestampMinedRewardFeesTXsSizeHeightPoolTimestampMinedRewardFeesTXsTransactionsSize
- {{ block.height - }} + {{ block.height }} + + + + + {{ block.tx_count | number }} +
@@ -61,29 +61,29 @@
+ + + + + + + +
diff --git a/frontend/src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.components.ts b/frontend/src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.components.ts index 854c1c349..fb7d5c8f7 100644 --- a/frontend/src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.components.ts +++ b/frontend/src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.components.ts @@ -34,10 +34,6 @@ export class DifficultyAdjustmentsTable implements OnInit { .pipe( map((response) => { const data = response.body; - const availableTimespanDay = ( - (new Date().getTime() / 1000) - (data.oldestIndexedBlockTimestamp) - ) / 3600 / 24; - const tableData = []; for (let i = data.difficulty.length - 1; i > 0; --i) { const selectedPowerOfTen: any = selectPowerOfTen(data.difficulty[i].difficulty); @@ -53,7 +49,6 @@ export class DifficultyAdjustmentsTable implements OnInit { this.isLoading = false; return { - availableTimespanDay: availableTimespanDay, difficulty: tableData.slice(0, 6), }; }), diff --git a/frontend/src/app/components/graphs/graphs.component.html b/frontend/src/app/components/graphs/graphs.component.html index 6c59645ea..b02adae5e 100644 --- a/frontend/src/app/components/graphs/graphs.component.html +++ b/frontend/src/app/components/graphs/graphs.component.html @@ -1,7 +1,7 @@ -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
HeightTimestampMinedTransactionsSize
{{ block.height }}‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}{{ block.tx_count | number }} -
-
-
-
-
-
-
-
-
- - -
- Error loading blocks -
- {{ error.error }} -
-
- -
diff --git a/frontend/src/app/components/latest-blocks/latest-blocks.component.scss b/frontend/src/app/components/latest-blocks/latest-blocks.component.scss deleted file mode 100644 index 0f2246c99..000000000 --- a/frontend/src/app/components/latest-blocks/latest-blocks.component.scss +++ /dev/null @@ -1,14 +0,0 @@ -.progress { - background-color: #2d3348; -} - -@media (min-width: 768px) { - .d-md-block { - display: table-cell !important; - } -} -@media (min-width: 992px) { - .d-lg-block { - display: table-cell !important; - } -} diff --git a/frontend/src/app/components/latest-blocks/latest-blocks.component.ts b/frontend/src/app/components/latest-blocks/latest-blocks.component.ts deleted file mode 100644 index 3d4ae91ad..000000000 --- a/frontend/src/app/components/latest-blocks/latest-blocks.component.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; -import { ElectrsApiService } from '../../services/electrs-api.service'; -import { StateService } from '../../services/state.service'; -import { Block } from '../../interfaces/electrs.interface'; -import { Subscription, Observable, merge, of } from 'rxjs'; -import { SeoService } from '../../services/seo.service'; -import { WebsocketService } from 'src/app/services/websocket.service'; -import { map } from 'rxjs/operators'; - -@Component({ - selector: 'app-latest-blocks', - templateUrl: './latest-blocks.component.html', - styleUrls: ['./latest-blocks.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class LatestBlocksComponent implements OnInit, OnDestroy { - network$: Observable; - error: any; - blocks: any[] = []; - blockSubscription: Subscription; - isLoading = true; - interval: any; - blocksLoadingStatus$: Observable; - - latestBlockHeight: number; - - heightOfPageUntilBlocks = 150; - heightOfBlocksTableChunk = 470; - - constructor( - private electrsApiService: ElectrsApiService, - public stateService: StateService, - private seoService: SeoService, - private websocketService: WebsocketService, - private cd: ChangeDetectorRef, - ) { } - - ngOnInit() { - this.seoService.setTitle($localize`:@@8a7b4bd44c0ac71b2e72de0398b303257f7d2f54:Blocks`); - this.websocketService.want(['blocks']); - - this.network$ = merge(of(''), this.stateService.networkChanged$); - - this.blocksLoadingStatus$ = this.stateService.loadingIndicators$ - .pipe( - map((indicators) => indicators['blocks'] !== undefined ? indicators['blocks'] : 0) - ); - - this.blockSubscription = this.stateService.blocks$ - .subscribe(([block]) => { - if (block === null || !this.blocks.length) { - return; - } - - this.latestBlockHeight = block.height; - - if (block.height === this.blocks[0].height) { - return; - } - - // If we are out of sync, reload the blocks instead - if (block.height > this.blocks[0].height + 1) { - this.loadInitialBlocks(); - return; - } - - if (block.height <= this.blocks[0].height) { - return; - } - - this.blocks.pop(); - this.blocks.unshift(block); - this.cd.markForCheck(); - }); - - this.loadInitialBlocks(); - } - - ngOnDestroy() { - clearInterval(this.interval); - this.blockSubscription.unsubscribe(); - } - - loadInitialBlocks() { - this.electrsApiService.listBlocks$() - .subscribe((blocks) => { - this.blocks = blocks; - this.isLoading = false; - this.error = undefined; - - this.latestBlockHeight = blocks[0].height; - - const spaceForBlocks = window.innerHeight - this.heightOfPageUntilBlocks; - const chunks = Math.ceil(spaceForBlocks / this.heightOfBlocksTableChunk) - 1; - if (chunks > 0) { - this.loadMore(chunks); - } - this.cd.markForCheck(); - }, - (error) => { - console.log(error); - this.error = error; - this.isLoading = false; - this.cd.markForCheck(); - }); - } - - loadMore(chunks = 0) { - if (this.isLoading) { - return; - } - const height = this.blocks[this.blocks.length - 1].height - 1; - if (height < 0) { - return; - } - this.isLoading = true; - this.electrsApiService.listBlocks$(height) - .subscribe((blocks) => { - this.blocks = this.blocks.concat(blocks); - this.isLoading = false; - this.error = undefined; - - const chunksLeft = chunks - 1; - if (chunksLeft > 0) { - this.loadMore(chunksLeft); - } - this.cd.markForCheck(); - }, - (error) => { - console.log(error); - this.error = error; - this.isLoading = false; - this.cd.markForCheck(); - }); - } - - trackByBlock(index: number, block: Block) { - return block.height; - } -} diff --git a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html index 6a208e5c7..fe7c08ba2 100644 --- a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html +++ b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html @@ -51,7 +51,7 @@
diff --git a/frontend/src/app/dashboard/dashboard.component.html b/frontend/src/app/dashboard/dashboard.component.html index 2ab42a6a0..d26b6660a 100644 --- a/frontend/src/app/dashboard/dashboard.component.html +++ b/frontend/src/app/dashboard/dashboard.component.html @@ -136,7 +136,7 @@ - +
diff --git a/frontend/src/app/dashboard/dashboard.component.scss b/frontend/src/app/dashboard/dashboard.component.scss index 47fb5b757..e6ddb5f84 100644 --- a/frontend/src/app/dashboard/dashboard.component.scss +++ b/frontend/src/app/dashboard/dashboard.component.scss @@ -198,9 +198,7 @@ display: none; text-align: right; width: 20%; - @media (min-width: 376px) { - display: table-cell; - } + display: table-cell; } .table-cell-size { display: none; diff --git a/frontend/src/app/docs/api-docs/api-docs-data.ts b/frontend/src/app/docs/api-docs/api-docs-data.ts index a9823af5e..20108d250 100644 --- a/frontend/src/app/docs/api-docs/api-docs-data.ts +++ b/frontend/src/app/docs/api-docs/api-docs-data.ts @@ -3049,7 +3049,6 @@ export const restApiDocsData = [ "slug": "antpool" }, ... - "oldestIndexedBlockTimestamp": 1231006505, "blockCount": 1005, "lastEstimatedHashrate": 230086716765559200000 }` @@ -3080,7 +3079,6 @@ export const restApiDocsData = [ }, ... ], - "oldestIndexedBlockTimestamp": 1296688602, "blockCount": 2226180, "lastEstimatedHashrate": 602244182177430.8 }` @@ -3834,34 +3832,34 @@ export const restApiDocsData = [ curl: [`1w`], response: `[ { - "avg_height": 735644, + "avgHeight": 735644, "timestamp": 1652119111, - "avg_fees": 24212890 + "avgFees": 24212890 }, { - "avg_height": 735646, + "avgHeight": 735646, "timestamp": 1652120252, - "avg_fees": 21655996 + "avgFees": 21655996 }, { - "avg_height": 735648, + "avgHeight": 735648, "timestamp": 1652121214, - "avg_fees": 20678859 + "avgFees": 20678859 }, { - "avg_height": 735649, + "avgHeight": 735649, "timestamp": 1652121757, - "avg_fees": 21020140 + "avgFees": 21020140 }, { - "avg_height": 735650, + "avgHeight": 735650, "timestamp": 1652122367, - "avg_fees": 23064200 + "avgFees": 23064200 }, { - "avg_height": 735652, + "avgHeight": 735652, "timestamp": 1652122893, - "avg_fees": 17620723 + "avgFees": 17620723 }, ... ]` @@ -3872,14 +3870,14 @@ export const restApiDocsData = [ curl: [`1w`], response: `[ { - "avg_height": 2224253, + "avgHeight": 2224253, "timestamp": 1652346420, - "avg_fees": 211686 + "avgFees": 211686 }, { - "avg_height": 2224254, + "avgHeight": 2224254, "timestamp": 1652346850, - "avg_fees": 2565952 + "avgFees": 2565952 }, ... ]` @@ -3890,14 +3888,14 @@ export const restApiDocsData = [ curl: [`1w`], response: `[ { - "avg_height": 89978, + "avgHeight": 89978, "timestamp": 1652346573, - "avg_fees": 1071 + "avgFees": 1071 }, { - "avg_height": 89979, + "avgHeight": 89979, "timestamp": 1652346970, - "avg_fees": 1224 + "avgFees": 1224 }, ... ]` @@ -3933,29 +3931,29 @@ export const restApiDocsData = [ curl: [`1d`], response: `[ { - "avg_height": 599992, + "avgHeight": 599992, "timestamp": 1571438412, - "avg_rewards": 1260530933 + "avgRewards": 1260530933 }, { - "avg_height": 600000, + "avgHeight": 600000, "timestamp": 1571443398, - "avg_rewards": 1264314538 + "avgRewards": 1264314538 }, { - "avg_height": 725441, + "avgHeight": 725441, "timestamp": 1646139035, - "avg_rewards": 637067563 + "avgRewards": 637067563 }, { - "avg_height": 725585, + "avgHeight": 725585, "timestamp": 1646222444, - "avg_rewards": 646519104 + "avgRewards": 646519104 }, { - "avg_height": 725727, + "avgHeight": 725727, "timestamp": 1646308374, - "avg_rewards": 638709605 + "avgRewards": 638709605 }, ... ]` @@ -3966,14 +3964,14 @@ export const restApiDocsData = [ curl: [`1d`], response: `[ { - "avg_height": 12, + "avgHeight": 12, "timestamp": 1296689648, - "avg_rewards": 5000000000 + "avgRewards": 5000000000 }, { - "avg_height": 269, + "avgHeight": 269, "timestamp": 1296717674, - "avg_rewards": 5000091820 + "avgRewards": 5000091820 }, ... ]` @@ -3984,14 +3982,14 @@ export const restApiDocsData = [ curl: [`1d`], response: `[ { - "avg_height": 183, + "avgHeight": 183, "timestamp": 1598962247, - "avg_rewards": 5000000000 + "avgRewards": 5000000000 }, { - "avg_height": 576, + "avgHeight": 576, "timestamp": 1599047892, - "avg_rewards": 5000000000 + "avgRewards": 5000000000 }, ... ]` @@ -4025,111 +4023,103 @@ export const restApiDocsData = [ esModule: [], commonJS: [], curl: [`1m`], - response: `{ - "oldestIndexedBlockTimestamp": 1571434851, - "blockFeeRates": [ - { - "avg_height": 732152, - "timestamp": 1650132959, - "avg_fee_0": 1, - "avg_fee_10": 2, - "avg_fee_25": 2, - "avg_fee_50": 3, - "avg_fee_75": 4, - "avg_fee_90": 8, - "avg_fee_100": 393 - }, - { - "avg_height": 732158, - "timestamp": 1650134432, - "avg_fee_0": 1, - "avg_fee_10": 1, - "avg_fee_25": 2, - "avg_fee_50": 4, - "avg_fee_75": 6, - "avg_fee_90": 10, - "avg_fee_100": 240 - }, - { - "avg_height": 732161, - "timestamp": 1650135818, - "avg_fee_0": 1, - "avg_fee_10": 1, - "avg_fee_25": 1, - "avg_fee_50": 2, - "avg_fee_75": 5, - "avg_fee_90": 8, - "avg_fee_100": 251 - }, - ... - ] -}` + response: `[ + { + "avgHeight": 732152, + "timestamp": 1650132959, + "avgFee_0": 1, + "avgFee_10": 2, + "avgFee_25": 2, + "avgFee_50": 3, + "avgFee_75": 4, + "avgFee_90": 8, + "avgFee_100": 393 + }, + { + "avgHeight": 732158, + "timestamp": 1650134432, + "avgFee_0": 1, + "avgFee_10": 1, + "avgFee_25": 2, + "avgFee_50": 4, + "avgFee_75": 6, + "avgFee_90": 10, + "avgFee_100": 240 + }, + { + "avgHeight": 732161, + "timestamp": 1650135818, + "avgFee_0": 1, + "avgFee_10": 1, + "avgFee_25": 1, + "avgFee_50": 2, + "avgFee_75": 5, + "avgFee_90": 8, + "avgFee_100": 251 + }, + ... +]` }, codeSampleTestnet: { esModule: [], commonJS: [], curl: [`1m`], - response: `{ - "oldestIndexedBlockTimestamp": 1296688602, - "blockFeeRates": [ - { - "avg_height": 2196306, - "timestamp": 1650360168, - "avg_fee_0": 1, - "avg_fee_10": 1, - "avg_fee_25": 1, - "avg_fee_50": 1, - "avg_fee_75": 2, - "avg_fee_90": 28, - "avg_fee_100": 2644 - }, - { - "avg_height": 2196308, - "timestamp": 1650361209, - "avg_fee_0": 1, - "avg_fee_10": 1, - "avg_fee_25": 1, - "avg_fee_50": 4, - "avg_fee_75": 12, - "avg_fee_90": 65, - "avg_fee_100": 102 - }, - ... - ] -}` + response: `[ + { + "avgHeight": 2196306, + "timestamp": 1650360168, + "avgFee_0": 1, + "avgFee_10": 1, + "avgFee_25": 1, + "avgFee_50": 1, + "avgFee_75": 2, + "avgFee_90": 28, + "avgFee_100": 2644 + }, + { + "avgHeight": 2196308, + "timestamp": 1650361209, + "avgFee_0": 1, + "avgFee_10": 1, + "avgFee_25": 1, + "avgFee_50": 4, + "avgFee_75": 12, + "avgFee_90": 65, + "avgFee_100": 102 + }, + ... +]` }, codeSampleSignet: { esModule: [], commonJS: [], curl: [`1m`], response: `{ - "oldestIndexedBlockTimestamp": 1598918400, - "blockFeeRates": [ - { - "avg_height": 86620, - "timestamp": 1650360010, - "avg_fee_0": 1, - "avg_fee_10": 1, - "avg_fee_25": 1, - "avg_fee_50": 1, - "avg_fee_75": 1, - "avg_fee_90": 1, - "avg_fee_100": 1 - }, - { - "avg_height": 86623, - "timestamp": 1650361330, - "avg_fee_0": 1, - "avg_fee_10": 1, - "avg_fee_25": 1, - "avg_fee_50": 1, - "avg_fee_75": 1, - "avg_fee_90": 1, - "avg_fee_100": 1 - }, - ... - ] -}` +"blockFeeRates": [ + { + "avgHeight": 86620, + "timestamp": 1650360010, + "avgFee_0": 1, + "avgFee_10": 1, + "avgFee_25": 1, + "avgFee_50": 1, + "avgFee_75": 1, + "avgFee_90": 1, + "avgFee_100": 1 + }, + { + "avgHeight": 86623, + "timestamp": 1650361330, + "avgFee_0": 1, + "avgFee_10": 1, + "avgFee_25": 1, + "avgFee_50": 1, + "avgFee_75": 1, + "avgFee_90": 1, + "avgFee_100": 1 + }, + ... +]` }, codeSampleLiquid: emptyCodeSample, codeSampleLiquidTestnet: emptyCodeSample, @@ -4163,47 +4153,47 @@ export const restApiDocsData = [ response: `{ "sizes": [ { - "avg_height": 576650, + "avgHeight": 576650, "timestamp": 1558212081, - "avg_size": 1271404 + "avgSize": 1271404 }, { - "avg_height": 576715, + "avgHeight": 576715, "timestamp": 1558246272, - "avg_size": 1105893 + "avgSize": 1105893 }, { - "avg_height": 576797, + "avgHeight": 576797, "timestamp": 1558289379, - "avg_size": 1141071 + "avgSize": 1141071 }, { - "avg_height": 576885, + "avgHeight": 576885, "timestamp": 1558330184, - "avg_size": 1108166 + "avgSize": 1108166 }, ... ], "weights": [ { - "avg_height": 576650, + "avgHeight": 576650, "timestamp": 1558212081, - "avg_weight": 3994002 + "avgWeight": 3994002 }, { - "avg_height": 576715, + "avgHeight": 576715, "timestamp": 1558246272, - "avg_weight": 3756312 + "avgWeight": 3756312 }, { - "avg_height": 576797, + "avgHeight": 576797, "timestamp": 1558289379, - "avg_weight": 3719625 + "avgWeight": 3719625 }, { - "avg_height": 576885, + "avgHeight": 576885, "timestamp": 1558330184, - "avg_weight": 3631381 + "avgWeight": 3631381 }, ... ] @@ -4216,27 +4206,27 @@ export const restApiDocsData = [ response: `{ "sizes": [ { - "avg_height": 1517188, + "avgHeight": 1517188, "timestamp": 1558262730, - "avg_size": 25089 + "avgSize": 25089 }, { - "avg_height": 1517275, + "avgHeight": 1517275, "timestamp": 1558290933, - "avg_size": 21679 + "avgSize": 21679 }, ... ], "weights": [ { - "avg_height": 1517188, + "avgHeight": 1517188, "timestamp": 1558262730, - "avg_weight": 74921 + "avgWeight": 74921 }, { - "avg_height": 1517275, + "avgHeight": 1517275, "timestamp": 1558290933, - "avg_weight": 65164 + "avgWeight": 65164 }, ... ] @@ -4249,27 +4239,27 @@ export const restApiDocsData = [ response: `{ "sizes": [ { - "avg_height": 83, + "avgHeight": 83, "timestamp": 1598937527, - "avg_size": 329 + "avgSize": 329 }, { - "avg_height": 266, + "avgHeight": 266, "timestamp": 1598982991, - "avg_size": 330 + "avgSize": 330 }, ... ], "weights": [ { - "avg_height": 83, + "avgHeight": 83, "timestamp": 1598937527, - "avg_weight": 1209 + "avgWeight": 1209 }, { - "avg_height": 266, + "avgHeight": 266, "timestamp": 1598982991, - "avg_weight": 1212 + "avgWeight": 1212 }, ... ] diff --git a/frontend/src/app/docs/code-template/code-template.component.html b/frontend/src/app/docs/code-template/code-template.component.html index 47cea6d03..373f7daf6 100644 --- a/frontend/src/app/docs/code-template/code-template.component.html +++ b/frontend/src/app/docs/code-template/code-template.component.html @@ -1,14 +1,14 @@
-