Compare commits
71 Commits
mononaut/g
...
mononaut/m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2a5b90b38 | ||
|
|
ccc127c84a | ||
|
|
e4d92c8fe0 | ||
|
|
b4906a5e8a | ||
|
|
2637636f27 | ||
|
|
3ac70b3860 | ||
|
|
c9c477c88f | ||
|
|
8806b8332b | ||
|
|
c7a38c78d2 | ||
|
|
2d189ca3f6 | ||
|
|
4b798730b1 | ||
|
|
bbb0aea0d1 | ||
|
|
57c7df4f1c | ||
|
|
87910a6eb7 | ||
|
|
09e38ac177 | ||
|
|
4e5ebabc54 | ||
|
|
a79e1aec1c | ||
|
|
057abbb6c1 | ||
|
|
520e79aec4 | ||
|
|
ee6422c982 | ||
|
|
e8ca8e7ec1 | ||
|
|
b110f86b48 | ||
|
|
5a7a78cddc | ||
|
|
c7862e19c2 | ||
|
|
3610908171 | ||
|
|
87c336734d | ||
|
|
bad3992397 | ||
|
|
edb4c8d9ad | ||
|
|
e81d1e273a | ||
|
|
dbe7ebe530 | ||
|
|
4e007acd84 | ||
|
|
4fe745817c | ||
|
|
09ce1bd458 | ||
|
|
09a727c00d | ||
|
|
f7f1eb067b | ||
|
|
74d94579f9 | ||
|
|
885331fe4a | ||
|
|
8ca2b2b5c0 | ||
|
|
58f143f867 | ||
|
|
a19ffb884f | ||
|
|
2faca121d3 | ||
|
|
8f99d2ba58 | ||
|
|
585732e438 | ||
|
|
0cddfcfa64 | ||
|
|
aa0948a154 | ||
|
|
8f7895cb2e | ||
|
|
77a526b91c | ||
|
|
ccd9642a01 | ||
|
|
8642956dc2 | ||
|
|
57c3861ca6 | ||
|
|
de7db08ae3 | ||
|
|
431f60d9c0 | ||
|
|
da545dbcf1 | ||
|
|
c570cc1e73 | ||
|
|
6339cc8d59 | ||
|
|
fe079a72be | ||
|
|
c789797d2d | ||
|
|
479007da55 | ||
|
|
f955c1b0f4 | ||
|
|
99f7201ec5 | ||
|
|
5ecdf1988e | ||
|
|
00613fe613 | ||
|
|
0663cc2cfa | ||
|
|
a89af41c48 | ||
|
|
c751e10da8 | ||
|
|
2750f1789c | ||
|
|
aaf378ed42 | ||
|
|
bc5e03deb5 | ||
|
|
8759d49daf | ||
|
|
a0d0650e2a | ||
|
|
0ac0a56ae5 |
221
.github/workflows/ci.yml
vendored
221
.github/workflows/ci.yml
vendored
@@ -63,7 +63,96 @@ jobs:
|
||||
run: npm run build
|
||||
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/backend
|
||||
|
||||
|
||||
cache:
|
||||
name: "Cache assets for builds"
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: assets
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- name: Install (Prod dependencies only)
|
||||
run: npm ci --omit=dev --omit=optional
|
||||
working-directory: assets/frontend
|
||||
|
||||
- name: Restore cached mining pool assets
|
||||
continue-on-error: true
|
||||
id: cache-mining-pool-restore
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
mining-pool-assets.zip
|
||||
key: mining-pool-assets-cache
|
||||
|
||||
- name: Restore promo video assets
|
||||
continue-on-error: true
|
||||
id: cache-promo-video-restore
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
promo-video-assets.zip
|
||||
key: promo-video-assets-cache
|
||||
|
||||
- name: Unzip assets before building (src/resources)
|
||||
continue-on-error: true
|
||||
run: unzip -o mining-pool-assets.zip -d assets/frontend/src/resources/mining-pools
|
||||
|
||||
- name: Unzip assets before building (src/resources)
|
||||
continue-on-error: true
|
||||
run: unzip -o promo-video-assets.zip -d assets/frontend/src/resources/promo-video
|
||||
|
||||
# - name: Unzip assets before building (dist)
|
||||
# continue-on-error: true
|
||||
# run: unzip assets.zip -d assets/frontend/dist/mempool/browser/resources
|
||||
|
||||
- name: Sync-assets
|
||||
run: npm run sync-assets-dev
|
||||
working-directory: assets/frontend
|
||||
|
||||
- name: Zip mining-pool assets
|
||||
run: zip -jrq mining-pool-assets.zip assets/frontend/src/resources/mining-pools/*
|
||||
|
||||
- name: Zip promo-video assets
|
||||
run: zip -jrq promo-video-assets.zip assets/frontend/src/resources/promo-video/*
|
||||
|
||||
- name: Upload mining pool assets as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: mining-pool-assets
|
||||
path: mining-pool-assets.zip
|
||||
|
||||
- name: Upload promo video assets as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: promo-video-assets
|
||||
path: promo-video-assets.zip
|
||||
|
||||
- name: Save mining pool assets cache
|
||||
id: cache-mining-pool-save
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
mining-pool-assets.zip
|
||||
key: mining-pool-assets-cache
|
||||
|
||||
- name: Save promo video assets cache
|
||||
id: cache-promo-video-save
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
promo-video-assets.zip
|
||||
key: promo-video-assets-cache
|
||||
|
||||
frontend:
|
||||
needs: cache
|
||||
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -103,9 +192,141 @@ jobs:
|
||||
# - name: Test
|
||||
# run: npm run test
|
||||
|
||||
- name: Restore cached mining pool assets
|
||||
continue-on-error: true
|
||||
id: cache-mining-pool-restore
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
mining-pool-assets.zip
|
||||
key: mining-pool-assets-cache
|
||||
|
||||
- name: Restore promo video assets
|
||||
continue-on-error: true
|
||||
id: cache-promo-video-restore
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
promo-video-assets.zip
|
||||
key: promo-video-assets-cache
|
||||
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: mining-pool-assets
|
||||
|
||||
- name: Unzip assets before building (src/resources)
|
||||
run: unzip -o mining-pool-assets.zip -d ${{ matrix.node }}/${{ matrix.flavor }}/frontend/src/resources/mining-pools
|
||||
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: promo-video-assets
|
||||
|
||||
- name: Unzip assets before building (src/resources)
|
||||
run: unzip -o promo-video-assets.zip -d ${{ matrix.node }}/${{ matrix.flavor }}/frontend/src/resources/promo-video
|
||||
|
||||
# - name: Unzip assets before building (dist)
|
||||
# run: unzip assets.zip -d ${{ matrix.node }}/${{ matrix.flavor }}/frontend/dist/mempool/browser/resources
|
||||
|
||||
- name: Display resulting source tree
|
||||
run: ls -R
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/frontend
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
e2e:
|
||||
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
||||
runs-on: "ubuntu-latest"
|
||||
needs: frontend
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# module: ["mempool", "liquid", "bisq"] Disabling bisq support for now
|
||||
module: ["mempool", "liquid"]
|
||||
include:
|
||||
- module: "mempool"
|
||||
spec: |
|
||||
cypress/e2e/mainnet/*.spec.ts
|
||||
cypress/e2e/signet/*.spec.ts
|
||||
cypress/e2e/testnet/*.spec.ts
|
||||
- module: "liquid"
|
||||
spec: |
|
||||
cypress/e2e/liquid/liquid.spec.ts
|
||||
cypress/e2e/liquidtestnet/liquidtestnet.spec.ts
|
||||
# - module: "bisq"
|
||||
# spec: |
|
||||
# cypress/e2e/bisq/bisq.spec.ts
|
||||
|
||||
name: E2E tests for ${{ matrix.module }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: ${{ matrix.module }}
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "npm"
|
||||
cache-dependency-path: ${{ matrix.module }}/frontend/package-lock.json
|
||||
|
||||
- name: Restore cached mining pool assets
|
||||
continue-on-error: true
|
||||
id: cache-mining-pool-restore
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
mining-pool-assets.zip
|
||||
key: mining-pool-assets-cache
|
||||
|
||||
- name: Restore cached promo video assets
|
||||
continue-on-error: true
|
||||
id: cache-promo-video-restore
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
promo-video-assets.zip
|
||||
key: promo-video-assets-cache
|
||||
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: mining-pool-assets
|
||||
|
||||
- name: Unzip assets before building (src/resources)
|
||||
run: unzip -o mining-pool-assets.zip -d ${{ matrix.module }}/frontend/src/resources/mining-pools
|
||||
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: promo-video-assets
|
||||
|
||||
- name: Unzip assets before building (src/resources)
|
||||
run: unzip -o promo-video-assets.zip -d ${{ matrix.module }}/frontend/src/resources/promo-video
|
||||
|
||||
- name: Chrome browser tests (${{ matrix.module }})
|
||||
uses: cypress-io/github-action@v5
|
||||
with:
|
||||
tag: ${{ github.event_name }}
|
||||
working-directory: ${{ matrix.module }}/frontend
|
||||
build: npm run config:defaults:${{ matrix.module }}
|
||||
start: npm run start:local-staging
|
||||
wait-on: "http://localhost:4200"
|
||||
wait-on-timeout: 120
|
||||
record: true
|
||||
parallel: true
|
||||
spec: ${{ matrix.spec }}
|
||||
group: Tests on Chrome (${{ matrix.module }})
|
||||
browser: "chrome"
|
||||
ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}"
|
||||
env:
|
||||
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }}
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
|
||||
|
||||
64
.github/workflows/cypress.yml
vendored
64
.github/workflows/cypress.yml
vendored
@@ -1,64 +0,0 @@
|
||||
name: Cypress Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
|
||||
jobs:
|
||||
cypress:
|
||||
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
||||
runs-on: "ubuntu-latest"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
module: ["mempool", "liquid", "bisq"]
|
||||
include:
|
||||
- module: "mempool"
|
||||
spec: |
|
||||
cypress/e2e/mainnet/*.spec.ts
|
||||
cypress/e2e/signet/*.spec.ts
|
||||
cypress/e2e/testnet/*.spec.ts
|
||||
- module: "liquid"
|
||||
spec: |
|
||||
cypress/e2e/liquid/liquid.spec.ts
|
||||
cypress/e2e/liquidtestnet/liquidtestnet.spec.ts
|
||||
- module: "bisq"
|
||||
spec: |
|
||||
cypress/e2e/bisq/bisq.spec.ts
|
||||
|
||||
name: E2E tests for ${{ matrix.module }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: ${{ matrix.module }}
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "npm"
|
||||
cache-dependency-path: ${{ matrix.module }}/frontend/package-lock.json
|
||||
|
||||
- name: Chrome browser tests (${{ matrix.module }})
|
||||
uses: cypress-io/github-action@v5
|
||||
with:
|
||||
tag: ${{ github.event_name }}
|
||||
working-directory: ${{ matrix.module }}/frontend
|
||||
build: npm run config:defaults:${{ matrix.module }}
|
||||
start: npm run start:local-staging
|
||||
wait-on: "http://localhost:4200"
|
||||
wait-on-timeout: 120
|
||||
record: true
|
||||
parallel: true
|
||||
spec: ${{ matrix.spec }}
|
||||
group: Tests on Chrome (${{ matrix.module }})
|
||||
browser: "chrome"
|
||||
ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}"
|
||||
env:
|
||||
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }}
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -62,7 +62,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gbt"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"bytes",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"MEMPOOL_BLOCKS_AMOUNT": 8,
|
||||
"INDEXING_BLOCKS_AMOUNT": 11000,
|
||||
"BLOCKS_SUMMARIES_INDEXING": false,
|
||||
"GOGGLES_INDEXING": false,
|
||||
"USE_SECOND_NODE_FOR_MINFEE": false,
|
||||
"EXTERNAL_ASSETS": [],
|
||||
"EXTERNAL_MAX_RETRY": 1,
|
||||
@@ -33,7 +34,8 @@
|
||||
"DISK_CACHE_BLOCK_INTERVAL": 6,
|
||||
"MAX_PUSH_TX_SIZE_WEIGHT": 4000000,
|
||||
"ALLOW_UNREACHABLE": true,
|
||||
"PRICE_UPDATES_PER_HOUR": 1
|
||||
"PRICE_UPDATES_PER_HOUR": 1,
|
||||
"MAX_TRACKED_ADDRESSES": 100
|
||||
},
|
||||
"CORE_RPC": {
|
||||
"HOST": "127.0.0.1",
|
||||
|
||||
6
backend/package-lock.json
generated
6
backend/package-lock.json
generated
@@ -7667,10 +7667,10 @@
|
||||
},
|
||||
"rust-gbt": {
|
||||
"name": "gbt",
|
||||
"version": "3.0.0-dev",
|
||||
"version": "3.0.1",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@napi-rs/cli": "^2.16.1"
|
||||
"@napi-rs/cli": "2.16.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
@@ -12703,7 +12703,7 @@
|
||||
"rust-gbt": {
|
||||
"version": "file:rust-gbt",
|
||||
"requires": {
|
||||
"@napi-rs/cli": "^2.16.1"
|
||||
"@napi-rs/cli": "2.16.1"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "gbt"
|
||||
version = "0.1.0"
|
||||
description = "An inefficient re-implementation of the getBlockTemplate algorithm in Rust"
|
||||
version = "1.0.0"
|
||||
description = "An efficient re-implementation of the getBlockTemplate algorithm in Rust"
|
||||
authors = ["mononaut"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
6
backend/rust-gbt/package-lock.json
generated
6
backend/rust-gbt/package-lock.json
generated
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "gbt",
|
||||
"version": "3.0.0-dev",
|
||||
"version": "3.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "gbt",
|
||||
"version": "3.0.0-dev",
|
||||
"version": "3.0.1",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@napi-rs/cli": "^2.16.1"
|
||||
"@napi-rs/cli": "2.16.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "gbt",
|
||||
"version": "3.0.0-dev",
|
||||
"description": "An inefficient re-implementation of the getBlockTemplate algorithm in Rust",
|
||||
"version": "3.0.1",
|
||||
"description": "An efficient re-implementation of the getBlockTemplate algorithm in Rust",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"scripts": {
|
||||
@@ -25,7 +25,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@napi-rs/cli": "^2.16.1"
|
||||
"@napi-rs/cli": "2.16.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"NETWORK": "__MEMPOOL_NETWORK__",
|
||||
"BACKEND": "__MEMPOOL_BACKEND__",
|
||||
"BLOCKS_SUMMARIES_INDEXING": true,
|
||||
"GOGGLES_INDEXING": false,
|
||||
"HTTP_PORT": 1,
|
||||
"SPAWN_CLUSTER_PROCS": 2,
|
||||
"API_URL_PREFIX": "__MEMPOOL_API_URL_PREFIX__",
|
||||
@@ -34,7 +35,8 @@
|
||||
"DISK_CACHE_BLOCK_INTERVAL": 999,
|
||||
"MAX_PUSH_TX_SIZE_WEIGHT": 4000000,
|
||||
"ALLOW_UNREACHABLE": true,
|
||||
"PRICE_UPDATES_PER_HOUR": 1
|
||||
"PRICE_UPDATES_PER_HOUR": 1,
|
||||
"MAX_TRACKED_ADDRESSES": 1
|
||||
},
|
||||
"CORE_RPC": {
|
||||
"HOST": "__CORE_RPC_HOST__",
|
||||
|
||||
@@ -17,6 +17,7 @@ describe('Mempool Backend Config', () => {
|
||||
NETWORK: 'mainnet',
|
||||
BACKEND: 'none',
|
||||
BLOCKS_SUMMARIES_INDEXING: false,
|
||||
GOGGLES_INDEXING: false,
|
||||
HTTP_PORT: 8999,
|
||||
SPAWN_CLUSTER_PROCS: 0,
|
||||
API_URL_PREFIX: '/api/v1/',
|
||||
@@ -48,6 +49,7 @@ describe('Mempool Backend Config', () => {
|
||||
MAX_PUSH_TX_SIZE_WEIGHT: 400000,
|
||||
ALLOW_UNREACHABLE: true,
|
||||
PRICE_UPDATES_PER_HOUR: 1,
|
||||
MAX_TRACKED_ADDRESSES: 1,
|
||||
});
|
||||
|
||||
expect(config.ELECTRUM).toStrictEqual({ HOST: '127.0.0.1', PORT: 3306, TLS_ENABLED: true });
|
||||
|
||||
@@ -566,7 +566,7 @@ class Blocks {
|
||||
*/
|
||||
public async $classifyBlocks(): Promise<void> {
|
||||
// classification requires an esplora backend
|
||||
if (!Common.blocksSummariesIndexingEnabled() || config.MEMPOOL.BACKEND !== 'esplora') {
|
||||
if (!Common.gogglesIndexingEnabled() || config.MEMPOOL.BACKEND !== 'esplora') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -611,18 +611,19 @@ class Blocks {
|
||||
if (unclassifiedBlocks[height]) {
|
||||
const blockHash = unclassifiedBlocks[height];
|
||||
// fetch transactions
|
||||
txs = (await bitcoinApi.$getTxsForBlock(blockHash)).map(tx => transactionUtils.extendTransaction(tx));
|
||||
txs = (await bitcoinApi.$getTxsForBlock(blockHash)).map(tx => transactionUtils.extendTransaction(tx)) || [];
|
||||
// add CPFP
|
||||
const cpfpSummary = Common.calculateCpfp(height, txs, true);
|
||||
// classify
|
||||
const { transactions: classifiedTxs } = this.summarizeBlockTransactions(blockHash, cpfpSummary.transactions);
|
||||
await BlocksSummariesRepository.$saveTransactions(height, blockHash, classifiedTxs, 1);
|
||||
await Common.sleep$(250);
|
||||
}
|
||||
if (unclassifiedTemplates[height]) {
|
||||
// classify template
|
||||
const blockHash = unclassifiedTemplates[height];
|
||||
const template = await BlocksSummariesRepository.$getTemplate(blockHash);
|
||||
const alreadyClassified = template?.transactions.reduce((classified, tx) => (classified || tx.flags > 0), false);
|
||||
const alreadyClassified = template?.transactions?.reduce((classified, tx) => (classified || tx.flags > 0), false);
|
||||
let classifiedTemplate = template?.transactions || [];
|
||||
if (!alreadyClassified) {
|
||||
const templateTxs: (TransactionExtended | TransactionClassified)[] = [];
|
||||
@@ -641,7 +642,7 @@ class Blocks {
|
||||
}
|
||||
templateTxs.push(tx || templateTx);
|
||||
}
|
||||
const cpfpSummary = Common.calculateCpfp(height, txs?.filter(tx => tx.effectiveFeePerVsize != null) as TransactionExtended[], true);
|
||||
const cpfpSummary = Common.calculateCpfp(height, templateTxs?.filter(tx => tx['effectiveFeePerVsize'] != null) as TransactionExtended[], true);
|
||||
// classify
|
||||
const { transactions: classifiedTxs } = this.summarizeBlockTransactions(blockHash, cpfpSummary.transactions);
|
||||
const classifiedTxMap: { [txid: string]: TransactionClassified } = {};
|
||||
@@ -656,14 +657,17 @@ class Blocks {
|
||||
});
|
||||
}
|
||||
await BlocksSummariesRepository.$saveTemplate({ height, template: { id: blockHash, transactions: classifiedTemplate }, version: 1 });
|
||||
await Common.sleep$(250);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn(`Failed to classify template or block summary at ${height}`, logger.tags.goggles);
|
||||
}
|
||||
|
||||
// timing & logging
|
||||
indexedThisRun++;
|
||||
indexedTotal++;
|
||||
if (unclassifiedBlocks[height] || unclassifiedTemplates[height]) {
|
||||
indexedThisRun++;
|
||||
indexedTotal++;
|
||||
}
|
||||
const elapsedSeconds = (Date.now() - timer) / 1000;
|
||||
if (elapsedSeconds > 5) {
|
||||
const perSecond = indexedThisRun / elapsedSeconds;
|
||||
|
||||
@@ -508,6 +508,13 @@ export class Common {
|
||||
);
|
||||
}
|
||||
|
||||
static gogglesIndexingEnabled(): boolean {
|
||||
return (
|
||||
Common.blocksSummariesIndexingEnabled() &&
|
||||
config.MEMPOOL.GOGGLES_INDEXING === true
|
||||
);
|
||||
}
|
||||
|
||||
static cpfpIndexingEnabled(): boolean {
|
||||
return (
|
||||
Common.indexingEnabled() &&
|
||||
|
||||
@@ -81,7 +81,7 @@ class ChannelsApi {
|
||||
public async $searchChannelsById(search: string): Promise<any[]> {
|
||||
try {
|
||||
// restrict search to valid id/short_id prefix formats
|
||||
let searchStripped = search.match(/[0-9]+[0-9x]*/)?.[0] || '';
|
||||
let searchStripped = search.match(/^[0-9]+[0-9x]*$/)?.[0] || '';
|
||||
if (!searchStripped.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import config from '../config';
|
||||
import { Worker } from 'worker_threads';
|
||||
import path from 'path';
|
||||
import mempool from './mempool';
|
||||
import { Acceleration } from './services/acceleration';
|
||||
import PoolsRepository from '../repositories/PoolsRepository';
|
||||
|
||||
const MAX_UINT32 = Math.pow(2, 32) - 1;
|
||||
|
||||
@@ -19,6 +21,17 @@ class MempoolBlocks {
|
||||
private nextUid: number = 1;
|
||||
private uidMap: Map<number, string> = new Map(); // map short numerical uids to full txids
|
||||
|
||||
private pools: { [id: number]: PoolTag } = {};
|
||||
|
||||
constructor() {
|
||||
PoolsRepository.$getPools().then(allPools => {
|
||||
this.pools = {};
|
||||
for (const pool of allPools) {
|
||||
this.pools[pool.uniqueId] = pool;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getMempoolBlocks(): MempoolBlock[] {
|
||||
return this.mempoolBlocks.map((block) => {
|
||||
return {
|
||||
@@ -452,7 +465,7 @@ class MempoolBlocks {
|
||||
}
|
||||
}
|
||||
|
||||
private processBlockTemplates(mempool: { [txid: string]: MempoolTransactionExtended }, blocks: string[][], blockWeights: number[] | null, rates: [string, number][], clusters: string[][], accelerations, accelerationPool, saveResults): MempoolBlockWithTransactions[] {
|
||||
private processBlockTemplates(mempool: { [txid: string]: MempoolTransactionExtended }, blocks: string[][], blockWeights: number[] | null, rates: [string, number][], clusters: string[][], accelerations: { [txid: string]: Acceleration }, accelerationPool, saveResults): MempoolBlockWithTransactions[] {
|
||||
for (const [txid, rate] of rates) {
|
||||
if (txid in mempool) {
|
||||
mempool[txid].cpfpDirty = (rate !== mempool[txid].effectiveFeePerVsize);
|
||||
@@ -586,7 +599,7 @@ class MempoolBlocks {
|
||||
const deltas = this.calculateMempoolDeltas(this.mempoolBlocks, mempoolBlocks);
|
||||
this.mempoolBlocks = mempoolBlocks;
|
||||
this.mempoolBlockDeltas = deltas;
|
||||
|
||||
this.updateAccelerationPositions(mempool, accelerations, mempoolBlocks);
|
||||
}
|
||||
|
||||
return mempoolBlocks;
|
||||
@@ -691,6 +704,124 @@ class MempoolBlocks {
|
||||
});
|
||||
return { blocks: convertedBlocks, blockWeights, rates: convertedRates, clusters: convertedClusters, overflow: convertedOverflow };
|
||||
}
|
||||
|
||||
// estimates and saves positions of accelerations in mining partner mempools
|
||||
private updateAccelerationPositions(mempoolCache: { [txid: string]: MempoolTransactionExtended }, accelerations: { [txid: string]: Acceleration }, mempoolBlocks: MempoolBlockWithTransactions[]): void {
|
||||
const accelerationPositions: { [txid: string]: { poolId: number, pool: string, block: number, vsize: number }[] } = {};
|
||||
// keep track of simulated mempool blocks for each active pool
|
||||
const pools: {
|
||||
[pool: string]: { name: string, block: number, vsize: number, accelerations: string[], complete: boolean };
|
||||
} = {};
|
||||
// prepare a list of accelerations in ascending order (we'll pop items off the end of the list)
|
||||
const accQueue: { acceleration: Acceleration, rate: number, vsize: number }[] = Object.values(accelerations).map(acc => {
|
||||
let vsize = mempoolCache[acc.txid].vsize;
|
||||
for (const ancestor of mempoolCache[acc.txid].ancestors || []) {
|
||||
vsize += (ancestor.weight / 4);
|
||||
}
|
||||
return {
|
||||
acceleration: acc,
|
||||
rate: mempoolCache[acc.txid].effectiveFeePerVsize,
|
||||
vsize
|
||||
};
|
||||
}).sort((a, b) => a.rate - b.rate);
|
||||
// initialize the pool tracker
|
||||
for (const { acceleration } of accQueue) {
|
||||
accelerationPositions[acceleration.txid] = [];
|
||||
for (const pool of acceleration.pools) {
|
||||
if (!pools[pool]) {
|
||||
pools[pool] = {
|
||||
name: this.pools[pool]?.name || 'unknown',
|
||||
block: 0,
|
||||
vsize: 0,
|
||||
accelerations: [],
|
||||
complete: false,
|
||||
};
|
||||
}
|
||||
pools[pool].accelerations.push(acceleration.txid);
|
||||
}
|
||||
for (const ancestor of mempoolCache[acceleration.txid].ancestors || []) {
|
||||
accelerationPositions[ancestor.txid] = [];
|
||||
}
|
||||
}
|
||||
|
||||
for (const pool of Object.keys(pools)) {
|
||||
// if any pools accepted *every* acceleration, we can just use the GBT result positions directly
|
||||
if (pools[pool].accelerations.length === Object.keys(accelerations).length) {
|
||||
pools[pool].complete = true;
|
||||
}
|
||||
}
|
||||
|
||||
let block = 0;
|
||||
let index = 0;
|
||||
let next = accQueue.pop();
|
||||
// build simulated blocks for each pool by taking the best option from
|
||||
// either the mempool or the list of accelerations.
|
||||
while (next && block < mempoolBlocks.length) {
|
||||
while (next && index < mempoolBlocks[block].transactions.length) {
|
||||
const nextTx = mempoolBlocks[block].transactions[index];
|
||||
if (next.rate >= (nextTx.rate || (nextTx.fee / nextTx.vsize))) {
|
||||
for (const pool of next.acceleration.pools) {
|
||||
if (pools[pool].vsize + next.vsize <= 999_000) {
|
||||
pools[pool].vsize += next.vsize;
|
||||
} else {
|
||||
pools[pool].block++;
|
||||
pools[pool].vsize = next.vsize;
|
||||
}
|
||||
// insert the acceleration into matching pool's blocks
|
||||
if (pools[pool].complete && mempoolCache[next.acceleration.txid]?.position !== undefined) {
|
||||
accelerationPositions[next.acceleration.txid].push({
|
||||
...mempoolCache[next.acceleration.txid].position as { block: number, vsize: number },
|
||||
poolId: pool,
|
||||
pool: pools[pool].name
|
||||
});
|
||||
} else {
|
||||
accelerationPositions[next.acceleration.txid].push({
|
||||
poolId: pool,
|
||||
pool: pools[pool].name,
|
||||
block: pools[pool].block,
|
||||
vsize: pools[pool].vsize - (next.vsize / 2),
|
||||
});
|
||||
}
|
||||
// and any accelerated ancestors
|
||||
for (const ancestor of mempoolCache[next.acceleration.txid].ancestors || []) {
|
||||
if (pools[pool].complete && mempoolCache[ancestor.txid]?.position !== undefined) {
|
||||
accelerationPositions[ancestor.txid].push({
|
||||
...mempoolCache[ancestor.txid].position as { block: number, vsize: number },
|
||||
poolId: pool,
|
||||
pool: pools[pool].name,
|
||||
});
|
||||
} else {
|
||||
accelerationPositions[ancestor.txid].push({
|
||||
poolId: pool,
|
||||
pool: pools[pool].name,
|
||||
block: pools[pool].block,
|
||||
vsize: pools[pool].vsize - (next.vsize / 2),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
next = accQueue.pop();
|
||||
} else {
|
||||
// skip accelerated transactions and their CPFP ancestors
|
||||
if (accelerationPositions[nextTx.txid] == null) {
|
||||
// insert into all pools' blocks
|
||||
for (const pool of Object.keys(pools)) {
|
||||
if (pools[pool].vsize + nextTx.vsize <= 999_000) {
|
||||
pools[pool].vsize += nextTx.vsize;
|
||||
} else {
|
||||
pools[pool].block++;
|
||||
pools[pool].vsize = nextTx.vsize;
|
||||
}
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
block++;
|
||||
index = 0;
|
||||
}
|
||||
mempool.setAccelerationPositions(accelerationPositions);
|
||||
}
|
||||
}
|
||||
|
||||
export default new MempoolBlocks();
|
||||
|
||||
@@ -25,6 +25,7 @@ class Mempool {
|
||||
deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[]) => Promise<void>) | undefined;
|
||||
|
||||
private accelerations: { [txId: string]: Acceleration } = {};
|
||||
private accelerationPositions: { [txid: string]: { poolId: number, pool: string, block: number, vsize: number }[] } = {};
|
||||
|
||||
private txPerSecondArray: number[] = [];
|
||||
private txPerSecond: number = 0;
|
||||
@@ -431,6 +432,14 @@ class Mempool {
|
||||
}
|
||||
}
|
||||
|
||||
setAccelerationPositions(positions: { [txid: string]: { poolId: number, pool: string, block: number, vsize: number }[] }): void {
|
||||
this.accelerationPositions = positions;
|
||||
}
|
||||
|
||||
getAccelerationPositions(txid: string): { [pool: number]: { poolId: number, pool: string, block: number, vsize: number } } | undefined {
|
||||
return this.accelerationPositions[txid];
|
||||
}
|
||||
|
||||
private startTimer() {
|
||||
const state: any = {
|
||||
start: Date.now(),
|
||||
|
||||
@@ -7,6 +7,14 @@ export interface Acceleration {
|
||||
txid: string,
|
||||
feeDelta: number,
|
||||
pools: number[],
|
||||
effectiveFee: number;
|
||||
effectiveVsize: number;
|
||||
positions?: {
|
||||
[pool: number]: {
|
||||
block: number,
|
||||
vbytes: number,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
class AccelerationApi {
|
||||
|
||||
@@ -24,6 +24,12 @@ import { ApiPrice } from '../repositories/PricesRepository';
|
||||
import accelerationApi from './services/acceleration';
|
||||
import mempool from './mempool';
|
||||
|
||||
interface AddressTransactions {
|
||||
mempool: MempoolTransactionExtended[],
|
||||
confirmed: MempoolTransactionExtended[],
|
||||
removed: MempoolTransactionExtended[],
|
||||
}
|
||||
|
||||
// valid 'want' subscriptions
|
||||
const wantable = [
|
||||
'blocks',
|
||||
@@ -186,7 +192,8 @@ class WebsocketHandler {
|
||||
}
|
||||
response['txPosition'] = JSON.stringify({
|
||||
txid: trackTxid,
|
||||
position
|
||||
position,
|
||||
accelerationPositions: memPool.getAccelerationPositions(tx.txid),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -195,24 +202,49 @@ class WebsocketHandler {
|
||||
}
|
||||
|
||||
if (parsedMessage && parsedMessage['track-address']) {
|
||||
if (/^([a-km-zA-HJ-NP-Z1-9]{26,35}|[a-km-zA-HJ-NP-Z1-9]{80}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,100}|[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100}|04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64})$/
|
||||
.test(parsedMessage['track-address'])) {
|
||||
let matchedAddress = parsedMessage['track-address'];
|
||||
if (/^[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100}$/.test(parsedMessage['track-address'])) {
|
||||
matchedAddress = matchedAddress.toLowerCase();
|
||||
}
|
||||
if (/^04[a-fA-F0-9]{128}$/.test(parsedMessage['track-address'])) {
|
||||
client['track-address'] = '41' + matchedAddress + 'ac';
|
||||
} else if (/^(02|03)[a-fA-F0-9]{64}$/.test(parsedMessage['track-address'])) {
|
||||
client['track-address'] = '21' + matchedAddress + 'ac';
|
||||
} else {
|
||||
client['track-address'] = matchedAddress;
|
||||
}
|
||||
const validAddress = this.testAddress(parsedMessage['track-address']);
|
||||
if (validAddress) {
|
||||
client['track-address'] = validAddress;
|
||||
} else {
|
||||
client['track-address'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (parsedMessage && parsedMessage['track-addresses'] && Array.isArray(parsedMessage['track-addresses'])) {
|
||||
const addressMap: { [address: string]: string } = {};
|
||||
for (const address of parsedMessage['track-addresses']) {
|
||||
const validAddress = this.testAddress(address);
|
||||
if (validAddress) {
|
||||
addressMap[address] = validAddress;
|
||||
}
|
||||
}
|
||||
if (Object.keys(addressMap).length > config.MEMPOOL.MAX_TRACKED_ADDRESSES) {
|
||||
response['track-addresses-error'] = `"too many addresses requested, this connection supports tracking a maximum of ${config.MEMPOOL.MAX_TRACKED_ADDRESSES} addresses"`;
|
||||
client['track-addresses'] = null;
|
||||
} else if (Object.keys(addressMap).length > 0) {
|
||||
client['track-addresses'] = addressMap;
|
||||
} else {
|
||||
client['track-addresses'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (parsedMessage && parsedMessage['track-scriptpubkeys'] && Array.isArray(parsedMessage['track-scriptpubkeys'])) {
|
||||
const spks: string[] = [];
|
||||
for (const spk of parsedMessage['track-scriptpubkeys']) {
|
||||
if (/^[a-fA-F0-9]+$/.test(spk)) {
|
||||
spks.push(spk.toLowerCase());
|
||||
}
|
||||
}
|
||||
if (spks.length > config.MEMPOOL.MAX_TRACKED_ADDRESSES) {
|
||||
response['track-scriptpubkeys-error'] = `"too many scriptpubkeys requested, this connection supports tracking a maximum of ${config.MEMPOOL.MAX_TRACKED_ADDRESSES} scriptpubkeys"`;
|
||||
client['track-scriptpubkeys'] = null;
|
||||
} else if (spks.length) {
|
||||
client['track-scriptpubkeys'] = spks;
|
||||
} else {
|
||||
client['track-scriptpubkeys'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (parsedMessage && parsedMessage['track-asset']) {
|
||||
if (/^[a-fA-F0-9]{64}$/.test(parsedMessage['track-asset'])) {
|
||||
client['track-asset'] = parsedMessage['track-asset'];
|
||||
@@ -544,6 +576,50 @@ class WebsocketHandler {
|
||||
}
|
||||
}
|
||||
|
||||
if (client['track-addresses']) {
|
||||
const addressMap: { [address: string]: AddressTransactions } = {};
|
||||
for (const [address, key] of Object.entries(client['track-addresses'] || {})) {
|
||||
const newTransactions = Array.from(addressCache[key as string]?.values() || []);
|
||||
const removedTransactions = Array.from(removedAddressCache[key as string]?.values() || []);
|
||||
// txs may be missing prevouts in non-esplora backends
|
||||
// so fetch the full transactions now
|
||||
const fullTransactions = (config.MEMPOOL.BACKEND !== 'esplora') ? await this.getFullTransactions(newTransactions) : newTransactions;
|
||||
if (fullTransactions?.length) {
|
||||
addressMap[address] = {
|
||||
mempool: fullTransactions,
|
||||
confirmed: [],
|
||||
removed: removedTransactions,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(addressMap).length > 0) {
|
||||
response['multi-address-transactions'] = JSON.stringify(addressMap);
|
||||
}
|
||||
}
|
||||
|
||||
if (client['track-scriptpubkeys']) {
|
||||
const spkMap: { [spk: string]: AddressTransactions } = {};
|
||||
for (const spk of client['track-scriptpubkeys'] || []) {
|
||||
const newTransactions = Array.from(addressCache[spk as string]?.values() || []);
|
||||
const removedTransactions = Array.from(removedAddressCache[spk as string]?.values() || []);
|
||||
// txs may be missing prevouts in non-esplora backends
|
||||
// so fetch the full transactions now
|
||||
const fullTransactions = (config.MEMPOOL.BACKEND !== 'esplora') ? await this.getFullTransactions(newTransactions) : newTransactions;
|
||||
if (fullTransactions?.length) {
|
||||
spkMap[spk] = {
|
||||
mempool: fullTransactions,
|
||||
confirmed: [],
|
||||
removed: removedTransactions,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(spkMap).length > 0) {
|
||||
response['multi-scriptpubkey-transactions'] = JSON.stringify(spkMap);
|
||||
}
|
||||
}
|
||||
|
||||
if (client['track-asset']) {
|
||||
const foundTransactions: TransactionExtended[] = [];
|
||||
|
||||
@@ -599,7 +675,8 @@ class WebsocketHandler {
|
||||
position: {
|
||||
...mempoolTx.position,
|
||||
accelerated: mempoolTx.acceleration || undefined,
|
||||
}
|
||||
},
|
||||
accelerationPositions: memPool.getAccelerationPositions(mempoolTx.txid),
|
||||
};
|
||||
if (mempoolTx.cpfpDirty) {
|
||||
positionData['cpfp'] = {
|
||||
@@ -609,7 +686,7 @@ class WebsocketHandler {
|
||||
effectiveFeePerVsize: mempoolTx.effectiveFeePerVsize || null,
|
||||
sigops: mempoolTx.sigops,
|
||||
adjustedVsize: mempoolTx.adjustedVsize,
|
||||
acceleration: mempoolTx.acceleration
|
||||
acceleration: mempoolTx.acceleration,
|
||||
};
|
||||
}
|
||||
response['txPosition'] = JSON.stringify(positionData);
|
||||
@@ -821,7 +898,8 @@ class WebsocketHandler {
|
||||
position: {
|
||||
...mempoolTx.position,
|
||||
accelerated: mempoolTx.acceleration || undefined,
|
||||
}
|
||||
},
|
||||
accelerationPositions: memPool.getAccelerationPositions(mempoolTx.txid),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -844,6 +922,42 @@ class WebsocketHandler {
|
||||
}
|
||||
}
|
||||
|
||||
if (client['track-addresses']) {
|
||||
const addressMap: { [address: string]: AddressTransactions } = {};
|
||||
for (const [address, key] of Object.entries(client['track-addresses'] || {})) {
|
||||
const fullTransactions = Array.from(addressCache[key as string]?.values() || []);
|
||||
if (fullTransactions?.length) {
|
||||
addressMap[address] = {
|
||||
mempool: [],
|
||||
confirmed: fullTransactions,
|
||||
removed: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(addressMap).length > 0) {
|
||||
response['multi-address-transactions'] = JSON.stringify(addressMap);
|
||||
}
|
||||
}
|
||||
|
||||
if (client['track-scriptpubkeys']) {
|
||||
const spkMap: { [spk: string]: AddressTransactions } = {};
|
||||
for (const spk of client['track-scriptpubkeys'] || []) {
|
||||
const fullTransactions = Array.from(addressCache[spk as string]?.values() || []);
|
||||
if (fullTransactions?.length) {
|
||||
spkMap[spk] = {
|
||||
mempool: [],
|
||||
confirmed: fullTransactions,
|
||||
removed: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(spkMap).length > 0) {
|
||||
response['multi-scriptpubkey-transactions'] = JSON.stringify(spkMap);
|
||||
}
|
||||
}
|
||||
|
||||
if (client['track-asset']) {
|
||||
const foundTransactions: TransactionExtended[] = [];
|
||||
|
||||
@@ -913,6 +1027,28 @@ class WebsocketHandler {
|
||||
+ '}';
|
||||
}
|
||||
|
||||
// checks if an address conforms to a valid format
|
||||
// returns the canonical form:
|
||||
// - lowercase for bech32(m)
|
||||
// - lowercase scriptpubkey for P2PK
|
||||
// or false if invalid
|
||||
private testAddress(address): string | false {
|
||||
if (/^([a-km-zA-HJ-NP-Z1-9]{26,35}|[a-km-zA-HJ-NP-Z1-9]{80}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,100}|[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100}|04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64})$/.test(address)) {
|
||||
if (/^[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100}|04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64}$/.test(address)) {
|
||||
address = address.toLowerCase();
|
||||
}
|
||||
if (/^04[a-fA-F0-9]{128}$/.test(address)) {
|
||||
return '41' + address + 'ac';
|
||||
} else if (/^(02|03)[a-fA-F0-9]{64}$/.test(address)) {
|
||||
return '21' + address + 'ac';
|
||||
} else {
|
||||
return address;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private makeAddressCache(transactions: MempoolTransactionExtended[]): { [address: string]: Set<MempoolTransactionExtended> } {
|
||||
const addressCache: { [address: string]: Set<MempoolTransactionExtended> } = {};
|
||||
for (const tx of transactions) {
|
||||
|
||||
@@ -20,6 +20,7 @@ interface IConfig {
|
||||
MEMPOOL_BLOCKS_AMOUNT: number;
|
||||
INDEXING_BLOCKS_AMOUNT: number;
|
||||
BLOCKS_SUMMARIES_INDEXING: boolean;
|
||||
GOGGLES_INDEXING: boolean;
|
||||
USE_SECOND_NODE_FOR_MINFEE: boolean;
|
||||
EXTERNAL_ASSETS: string[];
|
||||
EXTERNAL_MAX_RETRY: number;
|
||||
@@ -39,6 +40,7 @@ interface IConfig {
|
||||
MAX_PUSH_TX_SIZE_WEIGHT: number;
|
||||
ALLOW_UNREACHABLE: boolean;
|
||||
PRICE_UPDATES_PER_HOUR: number;
|
||||
MAX_TRACKED_ADDRESSES: number;
|
||||
};
|
||||
ESPLORA: {
|
||||
REST_API_URL: string;
|
||||
@@ -174,6 +176,7 @@ const defaults: IConfig = {
|
||||
'MEMPOOL_BLOCKS_AMOUNT': 8,
|
||||
'INDEXING_BLOCKS_AMOUNT': 11000, // 0 = disable indexing, -1 = index all blocks
|
||||
'BLOCKS_SUMMARIES_INDEXING': false,
|
||||
'GOGGLES_INDEXING': false,
|
||||
'USE_SECOND_NODE_FOR_MINFEE': false,
|
||||
'EXTERNAL_ASSETS': [],
|
||||
'EXTERNAL_MAX_RETRY': 1,
|
||||
@@ -193,6 +196,7 @@ const defaults: IConfig = {
|
||||
'MAX_PUSH_TX_SIZE_WEIGHT': 400000,
|
||||
'ALLOW_UNREACHABLE': true,
|
||||
'PRICE_UPDATES_PER_HOUR': 1,
|
||||
'MAX_TRACKED_ADDRESSES': 1,
|
||||
},
|
||||
'ESPLORA': {
|
||||
'REST_API_URL': 'http://127.0.0.1:3000',
|
||||
|
||||
3
contributors/afahrer.txt
Normal file
3
contributors/afahrer.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of January 23, 2024.
|
||||
|
||||
Signed: afahrer
|
||||
@@ -22,6 +22,7 @@
|
||||
"STDOUT_LOG_MIN_PRIORITY": "__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__",
|
||||
"INDEXING_BLOCKS_AMOUNT": __MEMPOOL_INDEXING_BLOCKS_AMOUNT__,
|
||||
"BLOCKS_SUMMARIES_INDEXING": __MEMPOOL_BLOCKS_SUMMARIES_INDEXING__,
|
||||
"GOGGLES_INDEXING": __MEMPOOL_GOGGLES_INDEXING__,
|
||||
"AUTOMATIC_BLOCK_REINDEXING": __MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__,
|
||||
"AUDIT": __MEMPOOL_AUDIT__,
|
||||
"ADVANCED_GBT_AUDIT": __MEMPOOL_ADVANCED_GBT_AUDIT__,
|
||||
@@ -35,6 +36,7 @@
|
||||
"POOLS_JSON_TREE_URL": "__MEMPOOL_POOLS_JSON_TREE_URL__",
|
||||
"POOLS_JSON_URL": "__MEMPOOL_POOLS_JSON_URL__",
|
||||
"PRICE_UPDATES_PER_HOUR": __MEMPOOL_PRICE_UPDATES_PER_HOUR__
|
||||
"MAX_TRACKED_ADDRESSES": __MEMPOOL_MAX_TRACKED_ADDRESSES__
|
||||
},
|
||||
"CORE_RPC": {
|
||||
"HOST": "__CORE_RPC_HOST__",
|
||||
|
||||
@@ -17,6 +17,7 @@ __MEMPOOL_INITIAL_BLOCKS_AMOUNT__=${MEMPOOL_INITIAL_BLOCKS_AMOUNT:=8}
|
||||
__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__=${MEMPOOL_MEMPOOL_BLOCKS_AMOUNT:=8}
|
||||
__MEMPOOL_INDEXING_BLOCKS_AMOUNT__=${MEMPOOL_INDEXING_BLOCKS_AMOUNT:=11000}
|
||||
__MEMPOOL_BLOCKS_SUMMARIES_INDEXING__=${MEMPOOL_BLOCKS_SUMMARIES_INDEXING:=false}
|
||||
__MEMPOOL_GOGGLES_INDEXING__=${MEMPOOL_GOGGLES_INDEXING:=false}
|
||||
__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__=${MEMPOOL_USE_SECOND_NODE_FOR_MINFEE:=false}
|
||||
__MEMPOOL_EXTERNAL_ASSETS__=${MEMPOOL_EXTERNAL_ASSETS:=[]}
|
||||
__MEMPOOL_EXTERNAL_MAX_RETRY__=${MEMPOOL_EXTERNAL_MAX_RETRY:=1}
|
||||
@@ -36,6 +37,7 @@ __MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__=${MEMPOOL_DISK_CACHE_BLOCK_INTERVAL:=6}
|
||||
__MEMPOOL_MAX_PUSH_TX_SIZE_WEIGHT__=${MEMPOOL_MAX_PUSH_TX_SIZE_WEIGHT:=4000000}
|
||||
__MEMPOOL_ALLOW_UNREACHABLE__=${MEMPOOL_ALLOW_UNREACHABLE:=true}
|
||||
__MEMPOOL_PRICE_UPDATES_PER_HOUR__=${MEMPOOL_PRICE_UPDATES_PER_HOUR:=1}
|
||||
__MEMPOOL_MAX_TRACKED_ADDRESSES__=${MEMPOOL_MAX_TRACKED_ADDRESSES:=1}
|
||||
|
||||
# CORE_RPC
|
||||
__CORE_RPC_HOST__=${CORE_RPC_HOST:=127.0.0.1}
|
||||
@@ -169,6 +171,7 @@ sed -i "s!__MEMPOOL_INITIAL_BLOCKS_AMOUNT__!${__MEMPOOL_INITIAL_BLOCKS_AMOUNT__}
|
||||
sed -i "s!__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__!${__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__}!g" mempool-config.json
|
||||
sed -i "s!__MEMPOOL_INDEXING_BLOCKS_AMOUNT__!${__MEMPOOL_INDEXING_BLOCKS_AMOUNT__}!g" mempool-config.json
|
||||
sed -i "s!__MEMPOOL_BLOCKS_SUMMARIES_INDEXING__!${__MEMPOOL_BLOCKS_SUMMARIES_INDEXING__}!g" mempool-config.json
|
||||
sed -i "s!__MEMPOOL_GOGGLES_INDEXING__!${__MEMPOOL_GOGGLES_INDEXING__}!g" mempool-config.json
|
||||
sed -i "s!__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__!${__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__}!g" mempool-config.json
|
||||
sed -i "s!__MEMPOOL_EXTERNAL_ASSETS__!${__MEMPOOL_EXTERNAL_ASSETS__}!g" mempool-config.json
|
||||
sed -i "s!__MEMPOOL_EXTERNAL_MAX_RETRY__!${__MEMPOOL_EXTERNAL_MAX_RETRY__}!g" mempool-config.json
|
||||
@@ -188,6 +191,7 @@ sed -i "s!__MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__!${__MEMPOOL_DISK_CACHE_BLOCK_INT
|
||||
sed -i "s!__MEMPOOL_MAX_PUSH_TX_SIZE_WEIGHT__!${__MEMPOOL_MAX_PUSH_TX_SIZE_WEIGHT__}!g" mempool-config.json
|
||||
sed -i "s!__MEMPOOL_ALLOW_UNREACHABLE__!${__MEMPOOL_ALLOW_UNREACHABLE__}!g" mempool-config.json
|
||||
sed -i "s!__MEMPOOL_PRICE_UPDATES_PER_HOUR__!${__MEMPOOL_PRICE_UPDATES_PER_HOUR__}!g" mempool-config.json
|
||||
sed -i "s!__MEMPOOL_MAX_TRACKED_ADDRESSES__!${__MEMPOOL_MAX_TRACKED_ADDRESSES__}!g" mempool-config.json
|
||||
|
||||
sed -i "s!__CORE_RPC_HOST__!${__CORE_RPC_HOST__}!g" mempool-config.json
|
||||
sed -i "s!__CORE_RPC_PORT__!${__CORE_RPC_PORT__}!g" mempool-config.json
|
||||
|
||||
@@ -30,7 +30,7 @@ export class BisqDashboardComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.seoService.setTitle($localize`:@@meta.title.bisq.markets:Markets`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.bisq.markets:Explore the full Bitcoin ecosystem with The Mempool Open Source Project™. See Bisq market prices, trading activity, and more.`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.bisq.markets:Explore the full Bitcoin ecosystem with The Mempool Open Source Project®. See Bisq market prices, trading activity, and more.`);
|
||||
this.websocketService.want(['blocks']);
|
||||
|
||||
this.volumes$ = this.bisqApiService.getAllVolumesDay$()
|
||||
|
||||
@@ -422,7 +422,7 @@
|
||||
Trademark Notice<br>
|
||||
</div>
|
||||
<p>
|
||||
The Mempool Open Source Project®, Mempool Accelerator™, Mempool Enterprise®, Mempool Liquidity™, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem™, Mempool Goggles™, the mempool logo, the mempool Square logo, the mempool Blocks logo, the mempool Blocks 3 | 2 logo, the mempool.space Vertical Logo, and the mempool.space Horizontal logo are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries.
|
||||
The Mempool Open Source Project®, Mempool Accelerator™, Mempool Enterprise®, Mempool Liquidity™, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem®, Mempool Goggles™, the mempool logo, the mempool Square logo, the mempool Blocks logo, the mempool Blocks 3 | 2 logo, the mempool.space Vertical Logo, and the mempool.space Horizontal logo are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries.
|
||||
</p>
|
||||
<p>
|
||||
While our software is available under an open source software license, the copyright license does not include an implied right or license to use our trademarks. See our <a href="https://mempool.space/trademark-policy">Trademark Policy and Guidelines</a> for more details, published on <https://mempool.space/trademark-policy>.
|
||||
|
||||
@@ -66,7 +66,6 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.seoService.setTitle($localize`:@@bcf34abc2d9ed8f45a2f65dd464c46694e9a181e:Acceleration Fees`);
|
||||
this.isLoading = true;
|
||||
if (this.widget) {
|
||||
this.miningWindowPreference = '1m';
|
||||
@@ -86,6 +85,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
this.seoService.setTitle($localize`:@@bcf34abc2d9ed8f45a2f65dd464c46694e9a181e:Acceleration Fees`);
|
||||
this.miningWindowPreference = this.miningService.getDefaultTimespan('1w');
|
||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
||||
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
||||
|
||||
@@ -63,66 +63,82 @@ tr, td, th {
|
||||
}
|
||||
|
||||
.txid {
|
||||
width: 25%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 30%;
|
||||
@media (max-width: 1060px) and (min-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fee-rate {
|
||||
width: 20%;
|
||||
@media (max-width: 1060px) and (min-width: 768px) {
|
||||
text-align: start !important;
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
text-align: start !important;
|
||||
}
|
||||
@media (max-width: 840px) and (min-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: 410px) {
|
||||
display: none;
|
||||
.fee, .block, .status {
|
||||
width: 15%;
|
||||
|
||||
@media (max-width: 720px) {
|
||||
width: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
.bid {
|
||||
width: 30%;
|
||||
min-width: 150px;
|
||||
@media (max-width: 840px) and (min-width: 768px) {
|
||||
text-align: start !important;
|
||||
.widget {
|
||||
.txid {
|
||||
width: 30%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 30%;
|
||||
@media (max-width: 1060px) and (min-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media (max-width: 410px) {
|
||||
text-align: start !important;
|
||||
|
||||
.fee-rate {
|
||||
width: 20%;
|
||||
@media (max-width: 1060px) and (min-width: 768px) {
|
||||
text-align: start !important;
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
text-align: start !important;
|
||||
}
|
||||
@media (max-width: 840px) and (min-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: 410px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.time {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.fee {
|
||||
width: 35%;
|
||||
@media (max-width: 1060px) and (min-width: 768px) {
|
||||
text-align: start !important;
|
||||
.bid {
|
||||
width: 30%;
|
||||
min-width: 150px;
|
||||
@media (max-width: 840px) and (min-width: 768px) {
|
||||
text-align: start !important;
|
||||
}
|
||||
@media (max-width: 410px) {
|
||||
text-align: start !important;
|
||||
}
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
text-align: start !important;
|
||||
|
||||
.time {
|
||||
width: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
.block {
|
||||
width: 20%;
|
||||
}
|
||||
.fee {
|
||||
width: 30%;
|
||||
@media (max-width: 1060px) and (min-width: 768px) {
|
||||
text-align: start !important;
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
text-align: start !important;
|
||||
}
|
||||
}
|
||||
|
||||
.status {
|
||||
width: 20%
|
||||
.block {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.status {
|
||||
width: 20%
|
||||
}
|
||||
}
|
||||
|
||||
/* Tooltip text */
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
<div class="col">
|
||||
<div class="card list-card">
|
||||
<div class="card-body">
|
||||
<a class="title-link" href="" [routerLink]="['/acceleration-list' | relativeUrl]">
|
||||
<a class="title-link" href="" [routerLink]="['/acceleration/list' | relativeUrl]">
|
||||
<h5 class="card-title d-inline" i18n="dashboard.recent-accelerations">Recent Accelerations</h5>
|
||||
<span> </span>
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: #4a68b9"></fa-icon>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<td [innerHTML]="'‎' + (block.weight | wuBytes: 2)"></td>
|
||||
</tr>
|
||||
<tr *ngIf="auditAvailable">
|
||||
<td><ng-container i18n="latest-blocks.health">Health</ng-container> <a class="info-link" [routerLink]="['/docs/faq' | relativeUrl ]" fragment="what-is-block-health"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></a></td>
|
||||
<td><ng-container i18n="latest-blocks.health">Health</ng-container><a class="info-link" [routerLink]="['/docs/faq' | relativeUrl ]" fragment="what-is-block-health"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></a></td>
|
||||
<td>
|
||||
<span
|
||||
class="health-badge badge"
|
||||
@@ -242,7 +242,7 @@
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="col-sm" *ngIf="!isMobile">
|
||||
<h3 class="block-subtitle actual" *ngIf="!isMobile"><ng-container i18n="block.actual-block">Actual Block</ng-container> <a class="info-link" [routerLink]="['/docs/faq' | relativeUrl ]" fragment="how-do-block-audits-work"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></a></h3>
|
||||
<h3 class="block-subtitle actual" *ngIf="!isMobile"><ng-container i18n="block.actual-block">Actual Block</ng-container><a class="info-link" [routerLink]="['/docs/faq' | relativeUrl ]" fragment="how-do-block-audits-work"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></a></h3>
|
||||
<div class="block-graph-wrapper">
|
||||
<app-block-overview-graph #blockGraphActual [isLoading]="isLoadingOverview" [resolution]="86"
|
||||
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" mode="mined" [auditHighlighting]="showAudit"
|
||||
|
||||
@@ -47,20 +47,30 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="item" *ngIf="showHalving">
|
||||
<h5 class="card-title" i18n="difficulty-box.next-halving" i18n-ngbTooltip="difficulty-box.next-halving"
|
||||
ngbTooltip="Next Halving" placement="bottom" #averagefee [disableTooltip]="!isEllipsisActive(averagefee)">Next Halving</h5>
|
||||
<div class="card-text">
|
||||
<ng-container *ngTemplateOutlet="epochData.blocksUntilHalving === 1 ? blocksSingular : blocksPlural; context: {$implicit: epochData.blocksUntilHalving }"></ng-container>
|
||||
<ng-template #blocksPlural let-i i18n="shared.blocks">{{ i }} <span class="shared-block">blocks</span></ng-template>
|
||||
<ng-template #blocksSingular let-i i18n="shared.block">{{ i }} <span class="shared-block">block</span></ng-template>
|
||||
<h5 class="card-title" i18n="difficulty-box.next-halving">Next Halving</h5>
|
||||
<div class="card-text" i18n-ngbTooltip="mining.average-fee" [ngbTooltip]="halvingBlocksLeft" [tooltipContext]="{ epochData: epochData }" placement="bottom">
|
||||
<span>{{ timeUntilHalving | date }}</span>
|
||||
<div class="symbol" *ngIf="blocksUntilHalving === 1; else approxTime">
|
||||
<app-time kind="until" [time]="epochData.timeAvg + now" [fastRender]="false" [fixedRender]="true" [precision]="1" minUnit="minute"></app-time>
|
||||
</div>
|
||||
<ng-template #approxTime>
|
||||
<div class="symbol">
|
||||
<app-time kind="until" [time]="timeUntilHalving" [fastRender]="false" [fixedRender]="true" [precision]="0" [numUnits]="2" [units]="['year', 'day', 'hour', 'minute']"></app-time>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="symbol"><app-time kind="until" [time]="epochData.timeUntilHalving" [fastRender]="true" [precision]="1"></app-time></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #halvingBlocksLeft let-epochData="epochData">
|
||||
<ng-container *ngTemplateOutlet="epochData.blocksUntilHalving === 1 ? blocksSingular : blocksPlural; context: {$implicit: epochData.blocksUntilHalving }"></ng-container>
|
||||
<ng-template #blocksPlural let-i i18n="shared.blocks">{{ i }} <span class="shared-block">blocks</span></ng-template>
|
||||
<ng-template #blocksSingular let-i i18n="shared.block">{{ i }} <span class="shared-block">block</span></ng-template>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #loadingDifficulty>
|
||||
<div class="difficulty-skeleton loading-container">
|
||||
<div class="item">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||
import { combineLatest, Observable, timer } from 'rxjs';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { combineLatest, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { StateService } from '../../services/state.service';
|
||||
|
||||
interface EpochProgress {
|
||||
@@ -15,6 +15,7 @@ interface EpochProgress {
|
||||
previousRetarget: number;
|
||||
blocksUntilHalving: number;
|
||||
timeUntilHalving: number;
|
||||
timeAvg: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -26,6 +27,9 @@ interface EpochProgress {
|
||||
export class DifficultyMiningComponent implements OnInit {
|
||||
isLoadingWebSocket$: Observable<boolean>;
|
||||
difficultyEpoch$: Observable<EpochProgress>;
|
||||
blocksUntilHalving: number | null = null;
|
||||
timeUntilHalving = 0;
|
||||
now = new Date().getTime();
|
||||
|
||||
@Input() showProgress = true;
|
||||
@Input() showHalving = false;
|
||||
@@ -64,8 +68,9 @@ export class DifficultyMiningComponent implements OnInit {
|
||||
colorPreviousAdjustments = '#ffffff66';
|
||||
}
|
||||
|
||||
const blocksUntilHalving = 210000 - (maxHeight % 210000);
|
||||
const timeUntilHalving = new Date().getTime() + (blocksUntilHalving * 600000);
|
||||
this.blocksUntilHalving = 210000 - (maxHeight % 210000);
|
||||
this.timeUntilHalving = new Date().getTime() + (this.blocksUntilHalving * 600000);
|
||||
this.now = new Date().getTime();
|
||||
|
||||
const data = {
|
||||
base: `${da.progressPercent.toFixed(2)}%`,
|
||||
@@ -77,8 +82,9 @@ export class DifficultyMiningComponent implements OnInit {
|
||||
newDifficultyHeight: da.nextRetargetHeight,
|
||||
estimatedRetargetDate: da.estimatedRetargetDate,
|
||||
previousRetarget: da.previousRetarget,
|
||||
blocksUntilHalving,
|
||||
timeUntilHalving,
|
||||
blocksUntilHalving: this.blocksUntilHalving,
|
||||
timeUntilHalving: this.timeUntilHalving,
|
||||
timeAvg: da.timeAvg,
|
||||
};
|
||||
return data;
|
||||
})
|
||||
|
||||
@@ -11,6 +11,13 @@ import { MiningService } from '../../services/mining.service';
|
||||
import { download } from '../../shared/graphs.utils';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
interface Hashrate {
|
||||
timestamp: number;
|
||||
avgHashRate: number;
|
||||
share: number;
|
||||
poolName: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-hashrate-chart-pools',
|
||||
templateUrl: './hashrate-chart-pools.component.html',
|
||||
@@ -32,6 +39,7 @@ export class HashrateChartPoolsComponent implements OnInit {
|
||||
miningWindowPreference: string;
|
||||
radioGroupForm: UntypedFormGroup;
|
||||
|
||||
hashrates: Hashrate[];
|
||||
chartOptions: EChartsOption = {};
|
||||
chartInitOptions = {
|
||||
renderer: 'svg',
|
||||
@@ -87,56 +95,9 @@ export class HashrateChartPoolsComponent implements OnInit {
|
||||
return this.apiService.getHistoricalPoolsHashrate$(timespan)
|
||||
.pipe(
|
||||
tap((response) => {
|
||||
const hashrates = response.body;
|
||||
this.hashrates = response.body;
|
||||
// Prepare series (group all hashrates data point by pool)
|
||||
const grouped = {};
|
||||
for (const hashrate of hashrates) {
|
||||
if (!grouped.hasOwnProperty(hashrate.poolName)) {
|
||||
grouped[hashrate.poolName] = [];
|
||||
}
|
||||
grouped[hashrate.poolName].push(hashrate);
|
||||
}
|
||||
|
||||
const series = [];
|
||||
const legends = [];
|
||||
for (const name in grouped) {
|
||||
series.push({
|
||||
zlevel: 0,
|
||||
stack: 'Total',
|
||||
name: name,
|
||||
showSymbol: false,
|
||||
symbol: 'none',
|
||||
data: grouped[name].map((val) => [val.timestamp * 1000, val.share * 100]),
|
||||
type: 'line',
|
||||
lineStyle: { width: 0 },
|
||||
areaStyle: { opacity: 1 },
|
||||
smooth: true,
|
||||
color: poolsColor[name.replace(/[^a-zA-Z0-9]/g, '').toLowerCase()],
|
||||
emphasis: {
|
||||
disabled: true,
|
||||
scale: false,
|
||||
},
|
||||
});
|
||||
|
||||
legends.push({
|
||||
name: name,
|
||||
inactiveColor: 'rgb(110, 112, 121)',
|
||||
textStyle: {
|
||||
color: 'white',
|
||||
},
|
||||
icon: 'roundRect',
|
||||
itemStyle: {
|
||||
color: poolsColor[name.replace(/[^a-zA-Z0-9]/g, "").toLowerCase()],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this.prepareChartOptions({
|
||||
legends: legends,
|
||||
series: series,
|
||||
});
|
||||
this.isLoading = false;
|
||||
|
||||
const series = this.applyHashrates();
|
||||
if (series.length === 0) {
|
||||
this.cd.markForCheck();
|
||||
throw new Error();
|
||||
@@ -156,6 +117,77 @@ export class HashrateChartPoolsComponent implements OnInit {
|
||||
);
|
||||
}
|
||||
|
||||
applyHashrates(): any[] {
|
||||
const times: { [time: number]: { hashrates: { [pool: string]: Hashrate } } } = {};
|
||||
const pools = {};
|
||||
for (const hashrate of this.hashrates) {
|
||||
if (!times[hashrate.timestamp]) {
|
||||
times[hashrate.timestamp] = { hashrates: {} };
|
||||
}
|
||||
times[hashrate.timestamp].hashrates[hashrate.poolName] = hashrate;
|
||||
if (!pools[hashrate.poolName]) {
|
||||
pools[hashrate.poolName] = true;
|
||||
}
|
||||
}
|
||||
|
||||
const sortedTimes = Object.keys(times).sort((a,b) => parseInt(a) - parseInt(b)).map(time => ({ time: parseInt(time), hashrates: times[time].hashrates }));
|
||||
const lastHashrates = sortedTimes[sortedTimes.length - 1].hashrates;
|
||||
const sortedPools = Object.keys(pools).sort((a,b) => {
|
||||
if (lastHashrates[b]?.share ?? lastHashrates[a]?.share ?? false) {
|
||||
// sort by descending share of hashrate in latest period
|
||||
return (lastHashrates[b]?.share || 0) - (lastHashrates[a]?.share || 0);
|
||||
} else {
|
||||
// tiebreak by pool name
|
||||
b < a;
|
||||
}
|
||||
});
|
||||
|
||||
const series = [];
|
||||
const legends = [];
|
||||
for (const name of sortedPools) {
|
||||
const data = sortedTimes.map(({ time, hashrates }) => {
|
||||
return [time * 1000, (hashrates[name]?.share || 0) * 100];
|
||||
});
|
||||
series.push({
|
||||
zlevel: 0,
|
||||
stack: 'Total',
|
||||
name: name,
|
||||
showSymbol: false,
|
||||
symbol: 'none',
|
||||
data,
|
||||
type: 'line',
|
||||
lineStyle: { width: 0 },
|
||||
areaStyle: { opacity: 1 },
|
||||
smooth: true,
|
||||
color: poolsColor[name.replace(/[^a-zA-Z0-9]/g, '').toLowerCase()],
|
||||
emphasis: {
|
||||
disabled: true,
|
||||
scale: false,
|
||||
},
|
||||
});
|
||||
|
||||
legends.push({
|
||||
name: name,
|
||||
inactiveColor: 'rgb(110, 112, 121)',
|
||||
textStyle: {
|
||||
color: 'white',
|
||||
},
|
||||
icon: 'roundRect',
|
||||
itemStyle: {
|
||||
color: poolsColor[name.replace(/[^a-zA-Z0-9]/g, "").toLowerCase()],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this.prepareChartOptions({
|
||||
legends: legends,
|
||||
series: series,
|
||||
});
|
||||
this.isLoading = false;
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
prepareChartOptions(data) {
|
||||
let title: object;
|
||||
if (data.series.length === 0) {
|
||||
@@ -256,6 +288,7 @@ export class HashrateChartPoolsComponent implements OnInit {
|
||||
},
|
||||
}],
|
||||
};
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
||||
onChartInit(ec) {
|
||||
|
||||
@@ -53,7 +53,10 @@
|
||||
<a class="nav-link" [routerLink]="['/' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'tachometer-alt']" [fixedWidth]="true" i18n-title="master-page.dashboard" title="Dashboard"></fa-icon></a>
|
||||
</li>
|
||||
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-home" *ngIf="stateService.env.ACCELERATOR">
|
||||
<a class="nav-link" [routerLink]="['/acceleration' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'rocket']" [fixedWidth]="true" i18n-title="master-page.accelerator-dashboard" title="Accelerator Dashboard"></fa-icon></a>
|
||||
<a class="nav-link" [routerLink]="['/acceleration' | relativeUrl]" (click)="collapse()">
|
||||
<fa-icon [icon]="['fas', 'rocket']" [fixedWidth]="true" i18n-title="master-page.accelerator-dashboard" title="Accelerator Dashboard"></fa-icon>
|
||||
<span class="badge badge-pill badge-warning beta" i18n="beta">beta</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-pools" *ngIf="stateService.env.MINING_DASHBOARD">
|
||||
<a class="nav-link" [routerLink]="['/mining' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'hammer']" [fixedWidth]="true" i18n-title="mining.mining-dashboard" title="Mining Dashboard"></fa-icon></a>
|
||||
|
||||
@@ -211,7 +211,15 @@ nav {
|
||||
margin: 24px 0px 0px -15px;
|
||||
font-size: 8px;
|
||||
@media (max-width: 767.98px) {
|
||||
margin: 33px 0px 0px -19px;
|
||||
margin: 30px 0px 0px -19px;
|
||||
font-size: 7px;
|
||||
}
|
||||
@media (max-width: 3429px) {
|
||||
margin: 25px 0px 0px -19px;
|
||||
font-size: 7px;
|
||||
}
|
||||
@media (max-width: 369px) {
|
||||
margin: 20px 0px 0px -19px;
|
||||
font-size: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,10 @@
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
<div *ngIf="arrowVisible" id="arrow-up" [ngStyle]="{'right': rightPosition + 75 + 'px', transition: transition }" [class.blink]="txPosition?.accelerated"></div>
|
||||
<div *ngIf="arrowVisible" id="arrow-up" [ngStyle]="{'right': rightPosition + 75 + 'px', transition: transition }"></div>
|
||||
<ng-container *ngFor="let pool of accelerationPositions; trackBy: accTrackByFn">
|
||||
<div class="acceleration-arrow blink" [ngStyle]="{'right': pool.offset + 75 + 'px', transition: 'background 2s, right 2s, transform 1s' }"></div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
|
||||
@@ -122,6 +122,17 @@
|
||||
border-bottom: 35px solid #FFF;
|
||||
}
|
||||
|
||||
.acceleration-arrow {
|
||||
position: relative;
|
||||
right: 75px;
|
||||
top: 105px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 35px solid transparent;
|
||||
border-right: 35px solid transparent;
|
||||
border-bottom: 35px solid #FFF;
|
||||
}
|
||||
|
||||
.blockLink {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -154,7 +165,7 @@
|
||||
}
|
||||
|
||||
:host-context(.rtl-layout) {
|
||||
#arrow-up {
|
||||
#arrow-up, .acceleration-arrow {
|
||||
transform: translateX(70px);
|
||||
}
|
||||
}
|
||||
@@ -172,8 +183,6 @@
|
||||
}
|
||||
|
||||
.blink{
|
||||
width:400px;
|
||||
height:400px;
|
||||
border-bottom: 35px solid #FFF;
|
||||
animation: blink 0.2s infinite;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { feeLevels, mempoolFeeColors } from '../../app.constants';
|
||||
import { specialBlocks } from '../../app.constants';
|
||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||
import { Location } from '@angular/common';
|
||||
import { DifficultyAdjustment, MempoolPosition } from '../../interfaces/node-api.interface';
|
||||
import { AccelerationPosition, DifficultyAdjustment, MempoolPosition } from '../../interfaces/node-api.interface';
|
||||
import { animate, style, transition, trigger } from '@angular/animations';
|
||||
|
||||
@Component({
|
||||
@@ -66,13 +66,19 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
blockPadding: number = 30;
|
||||
containerOffset: number = 40;
|
||||
arrowVisible = false;
|
||||
accelerationArrow = true;
|
||||
tabHidden = false;
|
||||
feeRounding = '1.0-0';
|
||||
|
||||
rightPosition = 0;
|
||||
transition = 'background 2s, right 2s, transform 1s';
|
||||
accelerationPositions: AccelerationPosition[] = [];
|
||||
accTransition = 'background 2s, right 2s, transform 1s';
|
||||
animatingAcceleration: boolean = false;
|
||||
|
||||
markIndex: number;
|
||||
markedTxid: string;
|
||||
|
||||
txPosition: MempoolPosition;
|
||||
txFeePerVSize: number;
|
||||
|
||||
@@ -160,7 +166,6 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.now = Date.now();
|
||||
|
||||
this.updateMempoolBlockStyles();
|
||||
this.calculateTransactionPosition();
|
||||
|
||||
return this.mempoolBlocks;
|
||||
}),
|
||||
@@ -188,6 +193,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.markIndex = undefined;
|
||||
this.txPosition = undefined;
|
||||
this.txFeePerVSize = undefined;
|
||||
this.accelerationPositions = [];
|
||||
if (state.mempoolBlockIndex !== undefined) {
|
||||
this.markIndex = state.mempoolBlockIndex;
|
||||
}
|
||||
@@ -197,7 +203,20 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
if (state.txFeePerVSize) {
|
||||
this.txFeePerVSize = state.txFeePerVSize;
|
||||
}
|
||||
this.calculateTransactionPosition();
|
||||
if (state.accelerationPositions) {
|
||||
this.accelerationPositions = state.accelerationPositions;
|
||||
}
|
||||
if (this.txPosition && this.txPosition.accelerated) {
|
||||
const newlyAccelerated = (!this.accelerationArrow && state.txid === this.markedTxid);
|
||||
this.calculateTransactionPosition(true);
|
||||
if (newlyAccelerated || !this.animatingAcceleration) {
|
||||
this.calculateAccelerationPositions(newlyAccelerated);
|
||||
}
|
||||
} else {
|
||||
this.accelerationArrow = false;
|
||||
this.calculateTransactionPosition();
|
||||
}
|
||||
this.markedTxid = state.txid;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
|
||||
@@ -289,6 +308,10 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
return (block.isStack) ? `stack-${block.index}` : block.index;
|
||||
}
|
||||
|
||||
accTrackByFn(index: number, pool: AccelerationPosition) {
|
||||
return pool.pool;
|
||||
}
|
||||
|
||||
reduceEmptyBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
|
||||
const innerWidth = this.containerWidth || (this.stateService.env.BASE_MODULE !== 'liquid' && window.innerWidth <= 767.98 ? window.innerWidth : window.innerWidth / 2);
|
||||
let blocksAmount = this.stateService.env.MEMPOOL_BLOCKS_AMOUNT;
|
||||
@@ -389,7 +412,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
};
|
||||
}
|
||||
|
||||
calculateTransactionPosition() {
|
||||
calculateTransactionPosition(fromFee: boolean = false) {
|
||||
if ((!this.txPosition && !this.txFeePerVSize && (this.markIndex === undefined || this.markIndex === -1)) || !this.mempoolBlocks) {
|
||||
this.arrowVisible = false;
|
||||
return;
|
||||
@@ -408,7 +431,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
this.arrowVisible = true;
|
||||
|
||||
if (this.txPosition) {
|
||||
if (this.txPosition && !fromFee) {
|
||||
if (this.txPosition.block >= this.mempoolBlocks.length) {
|
||||
this.rightPosition = ((this.mempoolBlocks.length - 1) * (this.blockWidth + this.blockPadding)) + this.blockWidth;
|
||||
} else {
|
||||
@@ -418,9 +441,9 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
} else {
|
||||
let found = false;
|
||||
for (let txInBlockIndex = 0; txInBlockIndex < this.mempoolBlocks.length && !found; txInBlockIndex++) {
|
||||
for (let txInBlockIndex = this.mempoolBlocks.length - 1; txInBlockIndex >= 0 && !found; txInBlockIndex--) {
|
||||
const block = this.mempoolBlocks[txInBlockIndex];
|
||||
for (let i = 0; i < block.feeRange.length - 1 && !found; i++) {
|
||||
for (let i = block.feeRange.length - 2; i >= 0 && !found; i--) {
|
||||
if (this.txFeePerVSize < block.feeRange[i + 1] && this.txFeePerVSize >= block.feeRange[i]) {
|
||||
const feeRangeIndex = i;
|
||||
const feeRangeChunkSize = 1 / (block.feeRange.length - 1);
|
||||
@@ -448,6 +471,39 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
calculateAccelerationPositions(animate: boolean = false) {
|
||||
if (!this.accelerationPositions || !this.mempoolBlocks) {
|
||||
this.accelerationArrow = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.accelerationArrow = true;
|
||||
|
||||
const applyPositions = () => {
|
||||
for (const accelerationPosition of this.accelerationPositions) {
|
||||
if (accelerationPosition.block >= this.mempoolBlocks.length) {
|
||||
accelerationPosition.offset = ((this.mempoolBlocks.length - 1) * (this.blockWidth + this.blockPadding)) + this.blockWidth;
|
||||
} else {
|
||||
const positionInBlock = Math.min(1, this.txPosition.vsize / this.stateService.blockVSize) * this.blockWidth;
|
||||
const positionOfBlock = accelerationPosition.block * (this.blockWidth + this.blockPadding);
|
||||
accelerationPosition.offset = positionOfBlock + positionInBlock;
|
||||
}
|
||||
}
|
||||
};
|
||||
if (animate) {
|
||||
this.animatingAcceleration = true;
|
||||
for (const accelerationPosition of this.accelerationPositions) {
|
||||
accelerationPosition.offset = this.rightPosition;
|
||||
}
|
||||
setTimeout(applyPositions, 100);
|
||||
setTimeout(() => {
|
||||
this.animatingAcceleration = false;
|
||||
}, 200);
|
||||
} else {
|
||||
applyPositions();
|
||||
}
|
||||
}
|
||||
|
||||
mountEmptyBlocks() {
|
||||
const emptyBlocks = [];
|
||||
const numberOfBlocks = this.stateService.env.MEMPOOL_BLOCKS_AMOUNT;
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
<th class="" i18n="mining.pool-name">Pool</th>
|
||||
<th class="" *ngIf="this.miningWindowPreference === '24h'" i18n="mining.hashrate">Hashrate</th>
|
||||
<th class="" i18n="master-page.blocks">Blocks</th>
|
||||
<th *ngIf="auditAvailable" class="health text-right widget" i18n="latest-blocks.avg_health"
|
||||
<th *ngIf="auditAvailable" class="health text-right widget" [ngClass]="{'health-column': this.miningWindowPreference === '24h'}" i18n="latest-blocks.avg_health"
|
||||
i18n-ngbTooltip="latest-blocks.avg_health" ngbTooltip="Avg Health" placement="bottom" #health [disableTooltip]="!isEllipsisActive(health)">Avg Health</th>
|
||||
<th *ngIf="auditAvailable" class="d-none d-sm-table-cell" i18n="mining.fees-per-block">Avg Block Fees</th>
|
||||
<th class="d-none d-lg-table-cell" i18n="mining.empty-blocks">Empty Blocks</th>
|
||||
@@ -104,13 +104,13 @@
|
||||
<td class="text-right">
|
||||
<img width="25" height="25" src="{{ pool.logo }}" [alt]="pool.name + ' mining pool logo'" onError="this.src = '/resources/mining-pools/default.svg'">
|
||||
</td>
|
||||
<td class=""><a [routerLink]="[('/mining/pool/' + pool.slug) | relativeUrl]">{{ pool.name }}</a></td>
|
||||
<td class="pool-name"><a [routerLink]="[('/mining/pool/' + pool.slug) | relativeUrl]">{{ pool.name }}</a></td>
|
||||
<td class="" *ngIf="this.miningWindowPreference === '24h'">{{ pool.lastEstimatedHashrate }} {{
|
||||
miningStats.miningUnits.hashrateUnit }}</td>
|
||||
<td class="d-flex justify-content-center">
|
||||
{{ pool.blockCount }}<span class="d-none d-md-table-cell"> ({{ pool.share }}%)</span>
|
||||
</td>
|
||||
<td *ngIf="auditAvailable" class="health text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||
<td *ngIf="auditAvailable" class="health text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable, 'health-column': this.miningWindowPreference === '24h'}">
|
||||
<a
|
||||
class="health-badge badge"
|
||||
[class.badge-success]="pool.avgMatchRate >= 99"
|
||||
|
||||
@@ -41,6 +41,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 430px) {
|
||||
.pool-name {
|
||||
max-width: 110px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.health-column {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.loadingGraphs {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
:host ::ng-deep {
|
||||
.dropdown-item {
|
||||
white-space: nowrap;
|
||||
width: calc(100% - 34px);
|
||||
}
|
||||
.dropdown-menu {
|
||||
width: calc(100% - 34px);
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.dropdown-item {
|
||||
width: 410px;
|
||||
}
|
||||
.dropdown-menu {
|
||||
width: 410px;
|
||||
}
|
||||
|
||||
@@ -170,6 +170,7 @@ export class SearchFormComponent implements OnInit {
|
||||
addresses: [],
|
||||
nodes: [],
|
||||
channels: [],
|
||||
liquidAsset: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -187,6 +188,7 @@ export class SearchFormComponent implements OnInit {
|
||||
const matchesBlockHash = this.regexBlockhash.test(searchText);
|
||||
let matchesAddress = !matchesTxId && this.regexAddress.test(searchText);
|
||||
const otherNetworks = findOtherNetworks(searchText, this.network as any || 'mainnet', this.env);
|
||||
const liquidAsset = this.assets ? (this.assets[searchText] || []) : [];
|
||||
|
||||
// Add B prefix to addresses in Bisq network
|
||||
if (!matchesAddress && this.network === 'bisq' && getRegex('address', 'mainnet').test(searchText)) {
|
||||
@@ -211,6 +213,7 @@ export class SearchFormComponent implements OnInit {
|
||||
otherNetworks: otherNetworks,
|
||||
nodes: lightningResults.nodes,
|
||||
channels: lightningResults.channels,
|
||||
liquidAsset: liquidAsset,
|
||||
};
|
||||
})
|
||||
);
|
||||
@@ -259,16 +262,16 @@ export class SearchFormComponent implements OnInit {
|
||||
} else if (this.regexTransaction.test(searchText)) {
|
||||
const matches = this.regexTransaction.exec(searchText);
|
||||
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
|
||||
if (this.assets[matches[1]]) {
|
||||
this.navigate('/assets/asset/', matches[1]);
|
||||
if (this.assets[matches[0]]) {
|
||||
this.navigate('/assets/asset/', matches[0]);
|
||||
}
|
||||
this.electrsApiService.getAsset$(matches[1])
|
||||
this.electrsApiService.getAsset$(matches[0])
|
||||
.subscribe(
|
||||
() => { this.navigate('/assets/asset/', matches[1]); },
|
||||
() => { this.navigate('/assets/asset/', matches[0]); },
|
||||
() => {
|
||||
this.electrsApiService.getBlock$(matches[1])
|
||||
this.electrsApiService.getBlock$(matches[0])
|
||||
.subscribe(
|
||||
(block) => { this.navigate('/block/', matches[1], { state: { data: { block } } }); },
|
||||
(block) => { this.navigate('/block/', matches[0], { state: { data: { block } } }); },
|
||||
() => { this.navigate('/tx/', matches[0]); });
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="dropdown-menu show" *ngIf="results" [hidden]="!results.hashQuickMatch && !results.otherNetworks.length && !results.addresses.length && !results.nodes.length && !results.channels.length">
|
||||
<div class="dropdown-menu show" *ngIf="results" [hidden]="!results.hashQuickMatch && !results.otherNetworks.length && !results.addresses.length && !results.nodes.length && !results.channels.length && !results.liquidAsset.length">
|
||||
<ng-template [ngIf]="results.blockHeight">
|
||||
<div class="card-title" i18n="search.bitcoin-block-height">Bitcoin Block Height</div>
|
||||
<div class="card-title" i18n="search.bitcoin-block-height">{{ networkName }} Block Height</div>
|
||||
<button (click)="clickItem(0)" [class.active]="0 === activeIdx" type="button" role="option" class="dropdown-item">
|
||||
<ng-container *ngTemplateOutlet="goTo; context: { $implicit: results.searchText }"></ng-container>
|
||||
</button>
|
||||
@@ -17,20 +17,20 @@
|
||||
<ng-container *ngTemplateOutlet="goTo; context: { $implicit: results.searchText }"></ng-container>
|
||||
</button>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="results.txId">
|
||||
<div class="card-title" i18n="search.bitcoin-transaction">Bitcoin Transaction</div>
|
||||
<ng-template [ngIf]="results.txId && !results.liquidAsset.length">
|
||||
<div class="card-title" i18n="search.bitcoin-transaction">{{ networkName }} Transaction</div>
|
||||
<button (click)="clickItem(0)" [class.active]="0 === activeIdx" type="button" role="option" class="dropdown-item">
|
||||
<ng-container *ngTemplateOutlet="goTo; context: { $implicit: results.searchText | shortenString : 13 }"></ng-container>
|
||||
</button>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="results.address">
|
||||
<div class="card-title" i18n="search.bitcoin-address">Bitcoin Address</div>
|
||||
<div class="card-title" i18n="search.bitcoin-address">{{ networkName }} Address</div>
|
||||
<button (click)="clickItem(0)" [class.active]="0 === activeIdx" type="button" role="option" class="dropdown-item">
|
||||
<ng-container *ngTemplateOutlet="goTo; context: { $implicit: results.searchText | shortenString : isMobile ? 20 : 30 }"></ng-container>
|
||||
<ng-container *ngTemplateOutlet="goTo; context: { $implicit: results.searchText | shortenString : isMobile ? 17 : 30 }"></ng-container>
|
||||
</button>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="results.blockHash">
|
||||
<div class="card-title" i18n="search.bitcoin-block">Bitcoin Block</div>
|
||||
<div class="card-title" i18n="search.bitcoin-block">{{ networkName }} Block</div>
|
||||
<button (click)="clickItem(0)" [class.active]="0 === activeIdx" type="button" role="option" class="dropdown-item">
|
||||
<ng-container *ngTemplateOutlet="goTo; context: { $implicit: results.searchText | shortenString : 13 }"></ng-container>
|
||||
</button>
|
||||
@@ -39,12 +39,12 @@
|
||||
<div class="card-title danger" i18n="search.other-networks">Other Network Address</div>
|
||||
<ng-template ngFor [ngForOf]="results.otherNetworks" let-otherNetwork let-i="index">
|
||||
<button (click)="clickItem(results.hashQuickMatch + i)" [class.active]="(results.hashQuickMatch + i) === activeIdx" [class.inactive]="!otherNetwork.isNetworkAvailable" type="button" role="option" class="dropdown-item">
|
||||
<ng-container *ngTemplateOutlet="goTo; context: { $implicit: otherNetwork.address| shortenString : isMobile ? 20 : 25 }"></ng-container> <b>({{ otherNetwork.network.charAt(0).toUpperCase() + otherNetwork.network.slice(1) }})</b>
|
||||
<ng-container *ngTemplateOutlet="goTo; context: { $implicit: otherNetwork.address| shortenString : isMobile ? 12 : 20 }"></ng-container> <b>({{ otherNetwork.network.charAt(0).toUpperCase() + otherNetwork.network.slice(1) }})</b>
|
||||
</button>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="results.addresses.length">
|
||||
<div class="card-title" i18n="search.bitcoin-addresses">Bitcoin Addresses</div>
|
||||
<div class="card-title" i18n="search.bitcoin-addresses">{{ networkName }} Addresses</div>
|
||||
<ng-template ngFor [ngForOf]="results.addresses" let-address let-i="index">
|
||||
<button (click)="clickItem(results.hashQuickMatch + results.otherNetworks.length + i)" [class.active]="(results.hashQuickMatch + results.otherNetworks.length + i) === activeIdx" type="button" role="option" class="dropdown-item">
|
||||
<ngb-highlight [result]="address | shortenString : isMobile ? 25 : 36" [term]="results.searchText"></ngb-highlight>
|
||||
@@ -67,6 +67,12 @@
|
||||
</button>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="results.liquidAsset.length">
|
||||
<div class="card-title" i18n="search.liquid-asset">Liquid Asset</div>
|
||||
<button (click)="clickItem(0)" [class.active]="0 === activeIdx" type="button" role="option" class="dropdown-item">
|
||||
<ng-container *ngTemplateOutlet="goTo; context: { $implicit: results.searchText | shortenString : 11 }"></ng-container> <b>({{ results.liquidAsset[1] }})</b>
|
||||
</button>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<ng-template #goTo let-x i18n="search.go-to">Go to "{{ x }}"</ng-template>
|
||||
|
||||
@@ -10,15 +10,20 @@ export class SearchResultsComponent implements OnChanges {
|
||||
@Input() results: any = {};
|
||||
@Output() selectedResult = new EventEmitter();
|
||||
|
||||
isMobile = (window.innerWidth <= 767.98);
|
||||
isMobile = (window.innerWidth <= 1150);
|
||||
resultsFlattened = [];
|
||||
activeIdx = 0;
|
||||
focusFirst = true;
|
||||
networkName = '';
|
||||
|
||||
constructor(
|
||||
public stateService: StateService,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.networkName = this.stateService.network.charAt(0).toUpperCase() + this.stateService.network.slice(1);
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
this.activeIdx = 0;
|
||||
if (this.results) {
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
<ng-container *ngIf="specialEvent">
|
||||
<div class="pyro">
|
||||
<div class="before"></div>
|
||||
<div class="after"></div>
|
||||
<div class="inner a"></div>
|
||||
<div class="inner b"></div>
|
||||
<div class="inner c"></div>
|
||||
</div>
|
||||
<div class="warning-label">{{ eventName }}</div>
|
||||
</ng-container>
|
||||
|
||||
@@ -144,52 +144,86 @@ body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pyro > .before, .pyro > .after {
|
||||
.pyro > .inner {
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
@include animation((1s bang ease-out infinite backwards, 1s gravity ease-in infinite backwards, 5s position linear infinite backwards));
|
||||
@include animation((1.25s ease-out infinite bang, 1.25s ease-in infinite gravity, 10s linear infinite position));
|
||||
|
||||
&.b {
|
||||
@include animation-delay((-4.35s, -4.35s, -4.35s));
|
||||
@include animation-duration((1.45s, 1.45s, 11.6s));
|
||||
}
|
||||
&.c {
|
||||
@include animation-delay((-3.2s, -3.2s, -3.2s));
|
||||
@include animation-duration((1.6s, 1.6s, 12.8s));
|
||||
}
|
||||
}
|
||||
|
||||
.pyro > .after {
|
||||
@include animation-delay((1.25s, 1.25s, 1.25s));
|
||||
@include animation-duration((1.25s, 1.25s, 6.25s));
|
||||
}
|
||||
|
||||
@keyframes bang{
|
||||
to{
|
||||
box-shadow:-314.6666666667px -362.6666666667px red,-51.6666666667px 32.3333333333px #ff3700,-354.6666666667px -264.6666666667px #7b00ff,-319.6666666667px -73.6666666667px #00f7ff,-135.6666666667px -154.6666666667px #00ff48,57.3333333333px -402.6666666667px #0d00ff,-126.6666666667px -121.6666666667px #00ff7b,-335.6666666667px -5.6666666667px #00fff2,-291.6666666667px -.6666666667px #4f0,-126.6666666667px -187.6666666667px #7f0,-413.6666666667px -224.6666666667px #00ffbf,-283.6666666667px -391.6666666667px #00ff3c,-340.6666666667px -345.6666666667px #02f,-168.6666666667px -179.6666666667px #eaff00,7.3333333333px -153.6666666667px #26ff00,-175.6666666667px -234.6666666667px #8400ff,-324.6666666667px -254.6666666667px #0048ff,-335.6666666667px -9.6666666667px #00ff59,-304.6666666667px -8.6666666667px #001eff,-331.6666666667px -44.6666666667px #3f0,.3333333333px -49.6666666667px #0fc,-370.6666666667px -60.6666666667px #0015ff,29.3333333333px -13.6666666667px #8cff00,-168.6666666667px -281.6666666667px #f80,-48.6666666667px -61.6666666667px #f0b,33.3333333333px -113.6666666667px #ff00e1,-193.6666666667px -196.6666666667px #ff7b00,-14.6666666667px -24.6666666667px #ff0037,-149.6666666667px -273.6666666667px #0fa,-19.6666666667px -63.6666666667px #ff0004,13.3333333333px -227.6666666667px #7f0,-265.6666666667px -43.6666666667px #ff4800,-121.6666666667px -95.6666666667px #bfff00,-241.6666666667px -90.6666666667px #6200ff,-307.6666666667px -231.6666666667px #ff0062,78.3333333333px -128.6666666667px #ffbf00,27.3333333333px 44.3333333333px #95ff00,-81.6666666667px 6.3333333333px #ffc800,-343.6666666667px -247.6666666667px #2f0,-225.6666666667px -250.6666666667px #08f,-9.6666666667px -243.6666666667px #ff1a00,83.3333333333px -409.6666666667px #04f,-380.6666666667px -331.6666666667px #84ff00,-103.6666666667px -51.6666666667px #f02,-174.6666666667px -169.6666666667px #ffc800,20.3333333333px -191.6666666667px #ff0059,-40.6666666667px -55.6666666667px #0400ff,-199.6666666667px -66.6666666667px #ffd500,-358.6666666667px -5.6666666667px #0051ff,-84.6666666667px -289.6666666667px #f7ff00,-193.6666666667px -184.6666666667px #80f
|
||||
@keyframes bang {
|
||||
0%, 15% {
|
||||
box-sizing: none;
|
||||
}
|
||||
84% {
|
||||
box-shadow: -30.55vw -35.21vw red,-5.02vw 3.14vw #ff3700,-34.43vw -25.70vw #7b00ff,-31.04vw -7.15vw #00f7ff,-13.17vw -15.02vw #00ff48,5.57vw -39.09vw #0d00ff,-12.30vw -11.81vw #00ff7b,-32.59vw -0.55vw #00fff2,-28.32vw -.6666666667px #4f0,-12.30vw -18.22vw #7f0,-40.16vw -21.81vw #00ffbf,-27.54vw -38.03vw #00ff3c,-33.07vw -33.56vw #02f,-16.38vw -17.44vw #eaff00,0.71vw -14.92vw #26ff00,-17.06vw -22.78vw #8400ff,-31.52vw -24.72vw #0048ff,-32.59vw -0.94vw #00ff59,-29.58vw -0.84vw #001eff,-32.20vw -4.34vw #3f0,.3333333333px -4.82vw #0fc,-35.99vw -5.89vw #0015ff,2.85vw -1.33vw #8cff00,-16.38vw -27.35vw #f80,-4.72vw -5.99vw #f0b,3.24vw -11.04vw #ff00e1,-18.80vw -19.09vw #ff7b00,-1.42vw -2.39vw #ff0037,-14.53vw -26.57vw #0fa,-1.91vw -6.18vw #ff0004,1.29vw -22.10vw #7f0,-25.79vw -4.24vw #ff4800,-11.81vw -9.29vw #bfff00,-23.46vw -8.80vw #6200ff,-29.87vw -22.49vw #ff0062,7.61vw -12.49vw #ffbf00,2.65vw 4.30vw #95ff00,-7.93vw 0.61vw #ffc800,-33.37vw -24.05vw #2f0,-21.91vw -24.34vw #08f,-0.94vw -23.66vw #ff1a00,8.09vw -39.77vw #04f,-36.96vw -32.20vw #84ff00,-10.06vw -5.02vw #f02,-16.96vw -16.47vw #ffc800,1.97vw -18.61vw #ff0059,-3.95vw -5.40vw #0400ff,-19.39vw -6.47vw #ffd500,-34.82vw -0.55vw #0051ff,-8.22vw -28.12vw #f7ff00,-18.80vw -17.93vw #80f;
|
||||
}
|
||||
85%, 100% {
|
||||
box-sizing: none;
|
||||
}
|
||||
}
|
||||
|
||||
@include keyframes(gravity) {
|
||||
to {
|
||||
0% {
|
||||
@include transform(translateY(0px));
|
||||
opacity: 0;
|
||||
}
|
||||
15% {
|
||||
@include transform(translateY(0px));
|
||||
opacity: 1;
|
||||
}
|
||||
84% {
|
||||
@include transform(translateY(400px));
|
||||
opacity: 0;
|
||||
}
|
||||
85%, 100% {
|
||||
@include transform(translateY(200px));
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@include keyframes(position) {
|
||||
0%, 19.9% {
|
||||
margin-top: 10%;
|
||||
margin-left: 40%;
|
||||
0%, 12.4% {
|
||||
margin-top: 10vw;
|
||||
margin-left: 50%;
|
||||
}
|
||||
20%, 39.9% {
|
||||
margin-top: 40%;
|
||||
margin-left: 30%;
|
||||
12.5%, 24.9% {
|
||||
margin-top: 22vw;
|
||||
margin-left: 35%;
|
||||
}
|
||||
40%, 59.9% {
|
||||
margin-top: 20%;
|
||||
margin-left: 70%
|
||||
25%, 37.4% {
|
||||
margin-top: 15vw;
|
||||
margin-left: 80%
|
||||
}
|
||||
60%, 79.9% {
|
||||
margin-top: 30%;
|
||||
margin-left: 20%;
|
||||
37.5%, 49.9% {
|
||||
margin-top: 28vw;
|
||||
margin-left: 72%;
|
||||
}
|
||||
80%, 99.9% {
|
||||
margin-top: 30%;
|
||||
margin-left: 80%;
|
||||
50%, 62.4% {
|
||||
margin-top: 22vw;
|
||||
margin-left: 37%;
|
||||
}
|
||||
62.5%, 74.9% {
|
||||
margin-top: 10vw;
|
||||
margin-left: 66%;
|
||||
}
|
||||
75%, 87.4% {
|
||||
margin-top: 25vw;
|
||||
margin-left: 49%;
|
||||
}
|
||||
87.5%, 99.9% {
|
||||
margin-top: 18vw;
|
||||
margin-left: 58%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,12 +147,15 @@ export class StartComponent implements OnInit, AfterViewChecked, OnDestroy {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (specialBlocks[block.height] && specialBlocks[block.height].networks.includes(this.stateService.network || 'mainnet')) {
|
||||
this.specialEvent = true;
|
||||
this.eventName = specialBlocks[block.height].labelEventCompleted;
|
||||
setTimeout(() => {
|
||||
for (const block of blocks) {
|
||||
if (specialBlocks[block.height] && specialBlocks[block.height].networks.includes(this.stateService.network || 'mainnet')) {
|
||||
this.specialEvent = true;
|
||||
this.eventName = specialBlocks[block.height].labelEventCompleted;
|
||||
}
|
||||
if (specialBlocks[block.height - 8] && specialBlocks[block.height - 8].networks.includes(this.stateService.network || 'mainnet')) {
|
||||
this.specialEvent = false;
|
||||
}, 60 * 60 * 1000);
|
||||
this.eventName = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
this.resetScrollSubscription = this.stateService.resetScroll$.subscribe(reset => {
|
||||
|
||||
@@ -10,7 +10,6 @@ import { dates } from '../../shared/i18n/dates';
|
||||
export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
||||
interval: number;
|
||||
text: string;
|
||||
units: string[] = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'];
|
||||
precisionThresholds = {
|
||||
year: 100,
|
||||
month: 18,
|
||||
@@ -29,6 +28,8 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
||||
@Input() fixedRender = false;
|
||||
@Input() relative = false;
|
||||
@Input() precision: number = 0;
|
||||
@Input() numUnits: number = 1;
|
||||
@Input() units: string[] = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'];
|
||||
@Input() minUnit: 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second' = 'second';
|
||||
@Input() fractionDigits: number = 0;
|
||||
|
||||
@@ -94,6 +95,8 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
|
||||
let counter: number;
|
||||
const result = [];
|
||||
let usedUnits = 0;
|
||||
for (const [index, unit] of this.units.entries()) {
|
||||
let precisionUnit = this.units[Math.min(this.units.length - 1, index + this.precision)];
|
||||
counter = Math.floor(seconds / this.intervals[unit]);
|
||||
@@ -105,107 +108,126 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
||||
counter = Math.max(1, counter);
|
||||
}
|
||||
if (counter > 0) {
|
||||
let rounded = Math.round(seconds / this.intervals[precisionUnit]);
|
||||
if (this.fractionDigits) {
|
||||
const roundFactor = Math.pow(10,this.fractionDigits);
|
||||
let rounded;
|
||||
const roundFactor = Math.pow(10,this.fractionDigits || 0);
|
||||
if (this.kind === 'until' && usedUnits < this.numUnits) {
|
||||
rounded = Math.floor((seconds / this.intervals[precisionUnit]) * roundFactor) / roundFactor;
|
||||
} else {
|
||||
rounded = Math.round((seconds / this.intervals[precisionUnit]) * roundFactor) / roundFactor;
|
||||
}
|
||||
const dateStrings = dates(rounded);
|
||||
switch (this.kind) {
|
||||
case 'since':
|
||||
if (rounded === 1) {
|
||||
switch (precisionUnit) { // singular (1 day)
|
||||
case 'year': return $localize`:@@time-since:${dateStrings.i18nYear}:DATE: ago`; break;
|
||||
case 'month': return $localize`:@@time-since:${dateStrings.i18nMonth}:DATE: ago`; break;
|
||||
case 'week': return $localize`:@@time-since:${dateStrings.i18nWeek}:DATE: ago`; break;
|
||||
case 'day': return $localize`:@@time-since:${dateStrings.i18nDay}:DATE: ago`; break;
|
||||
case 'hour': return $localize`:@@time-since:${dateStrings.i18nHour}:DATE: ago`; break;
|
||||
case 'minute': return $localize`:@@time-since:${dateStrings.i18nMinute}:DATE: ago`; break;
|
||||
case 'second': return $localize`:@@time-since:${dateStrings.i18nSecond}:DATE: ago`; break;
|
||||
}
|
||||
} else {
|
||||
switch (precisionUnit) { // plural (2 days)
|
||||
case 'year': return $localize`:@@time-since:${dateStrings.i18nYears}:DATE: ago`; break;
|
||||
case 'month': return $localize`:@@time-since:${dateStrings.i18nMonths}:DATE: ago`; break;
|
||||
case 'week': return $localize`:@@time-since:${dateStrings.i18nWeeks}:DATE: ago`; break;
|
||||
case 'day': return $localize`:@@time-since:${dateStrings.i18nDays}:DATE: ago`; break;
|
||||
case 'hour': return $localize`:@@time-since:${dateStrings.i18nHours}:DATE: ago`; break;
|
||||
case 'minute': return $localize`:@@time-since:${dateStrings.i18nMinutes}:DATE: ago`; break;
|
||||
case 'second': return $localize`:@@time-since:${dateStrings.i18nSeconds}:DATE: ago`; break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'until':
|
||||
if (rounded === 1) {
|
||||
switch (precisionUnit) { // singular (In ~1 day)
|
||||
case 'year': return $localize`:@@time-until:In ~${dateStrings.i18nYear}:DATE:`; break;
|
||||
case 'month': return $localize`:@@time-until:In ~${dateStrings.i18nMonth}:DATE:`; break;
|
||||
case 'week': return $localize`:@@time-until:In ~${dateStrings.i18nWeek}:DATE:`; break;
|
||||
case 'day': return $localize`:@@time-until:In ~${dateStrings.i18nDay}:DATE:`; break;
|
||||
case 'hour': return $localize`:@@time-until:In ~${dateStrings.i18nHour}:DATE:`; break;
|
||||
case 'minute': return $localize`:@@time-until:In ~${dateStrings.i18nMinute}:DATE:`;
|
||||
case 'second': return $localize`:@@time-until:In ~${dateStrings.i18nSecond}:DATE:`;
|
||||
}
|
||||
} else {
|
||||
switch (precisionUnit) { // plural (In ~2 days)
|
||||
case 'year': return $localize`:@@time-until:In ~${dateStrings.i18nYears}:DATE:`; break;
|
||||
case 'month': return $localize`:@@time-until:In ~${dateStrings.i18nMonths}:DATE:`; break;
|
||||
case 'week': return $localize`:@@time-until:In ~${dateStrings.i18nWeeks}:DATE:`; break;
|
||||
case 'day': return $localize`:@@time-until:In ~${dateStrings.i18nDays}:DATE:`; break;
|
||||
case 'hour': return $localize`:@@time-until:In ~${dateStrings.i18nHours}:DATE:`; break;
|
||||
case 'minute': return $localize`:@@time-until:In ~${dateStrings.i18nMinutes}:DATE:`; break;
|
||||
case 'second': return $localize`:@@time-until:In ~${dateStrings.i18nSeconds}:DATE:`; break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'span':
|
||||
if (rounded === 1) {
|
||||
switch (precisionUnit) { // singular (1 day)
|
||||
case 'year': return $localize`:@@time-span:After ${dateStrings.i18nYear}:DATE:`; break;
|
||||
case 'month': return $localize`:@@time-span:After ${dateStrings.i18nMonth}:DATE:`; break;
|
||||
case 'week': return $localize`:@@time-span:After ${dateStrings.i18nWeek}:DATE:`; break;
|
||||
case 'day': return $localize`:@@time-span:After ${dateStrings.i18nDay}:DATE:`; break;
|
||||
case 'hour': return $localize`:@@time-span:After ${dateStrings.i18nHour}:DATE:`; break;
|
||||
case 'minute': return $localize`:@@time-span:After ${dateStrings.i18nMinute}:DATE:`; break;
|
||||
case 'second': return $localize`:@@time-span:After ${dateStrings.i18nSecond}:DATE:`; break;
|
||||
}
|
||||
} else {
|
||||
switch (precisionUnit) { // plural (2 days)
|
||||
case 'year': return $localize`:@@time-span:After ${dateStrings.i18nYears}:DATE:`; break;
|
||||
case 'month': return $localize`:@@time-span:After ${dateStrings.i18nMonths}:DATE:`; break;
|
||||
case 'week': return $localize`:@@time-span:After ${dateStrings.i18nWeeks}:DATE:`; break;
|
||||
case 'day': return $localize`:@@time-span:After ${dateStrings.i18nDays}:DATE:`; break;
|
||||
case 'hour': return $localize`:@@time-span:After ${dateStrings.i18nHours}:DATE:`; break;
|
||||
case 'minute': return $localize`:@@time-span:After ${dateStrings.i18nMinutes}:DATE:`; break;
|
||||
case 'second': return $localize`:@@time-span:After ${dateStrings.i18nSeconds}:DATE:`; break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (rounded === 1) {
|
||||
switch (precisionUnit) { // singular (1 day)
|
||||
case 'year': return dateStrings.i18nYear; break;
|
||||
case 'month': return dateStrings.i18nMonth; break;
|
||||
case 'week': return dateStrings.i18nWeek; break;
|
||||
case 'day': return dateStrings.i18nDay; break;
|
||||
case 'hour': return dateStrings.i18nHour; break;
|
||||
case 'minute': return dateStrings.i18nMinute; break;
|
||||
case 'second': return dateStrings.i18nSecond; break;
|
||||
}
|
||||
} else {
|
||||
switch (precisionUnit) { // plural (2 days)
|
||||
case 'year': return dateStrings.i18nYears; break;
|
||||
case 'month': return dateStrings.i18nMonths; break;
|
||||
case 'week': return dateStrings.i18nWeeks; break;
|
||||
case 'day': return dateStrings.i18nDays; break;
|
||||
case 'hour': return dateStrings.i18nHours; break;
|
||||
case 'minute': return dateStrings.i18nMinutes; break;
|
||||
case 'second': return dateStrings.i18nSeconds; break;
|
||||
}
|
||||
}
|
||||
if (this.kind !== 'until' || this.numUnits === 1) {
|
||||
return this.formatTime(this.kind, precisionUnit, rounded);
|
||||
} else {
|
||||
if (!usedUnits) {
|
||||
result.push(this.formatTime(this.kind, precisionUnit, rounded));
|
||||
} else {
|
||||
result.push(this.formatTime('', precisionUnit, rounded));
|
||||
}
|
||||
seconds -= (rounded * this.intervals[precisionUnit]);
|
||||
usedUnits++;
|
||||
if (usedUnits >= this.numUnits) {
|
||||
return result.join(', ');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.join(', ');
|
||||
}
|
||||
|
||||
private formatTime(kind, unit, number): string {
|
||||
const dateStrings = dates(number);
|
||||
switch (kind) {
|
||||
case 'since':
|
||||
if (number === 1) {
|
||||
switch (unit) { // singular (1 day)
|
||||
case 'year': return $localize`:@@time-since:${dateStrings.i18nYear}:DATE: ago`; break;
|
||||
case 'month': return $localize`:@@time-since:${dateStrings.i18nMonth}:DATE: ago`; break;
|
||||
case 'week': return $localize`:@@time-since:${dateStrings.i18nWeek}:DATE: ago`; break;
|
||||
case 'day': return $localize`:@@time-since:${dateStrings.i18nDay}:DATE: ago`; break;
|
||||
case 'hour': return $localize`:@@time-since:${dateStrings.i18nHour}:DATE: ago`; break;
|
||||
case 'minute': return $localize`:@@time-since:${dateStrings.i18nMinute}:DATE: ago`; break;
|
||||
case 'second': return $localize`:@@time-since:${dateStrings.i18nSecond}:DATE: ago`; break;
|
||||
}
|
||||
} else {
|
||||
switch (unit) { // plural (2 days)
|
||||
case 'year': return $localize`:@@time-since:${dateStrings.i18nYears}:DATE: ago`; break;
|
||||
case 'month': return $localize`:@@time-since:${dateStrings.i18nMonths}:DATE: ago`; break;
|
||||
case 'week': return $localize`:@@time-since:${dateStrings.i18nWeeks}:DATE: ago`; break;
|
||||
case 'day': return $localize`:@@time-since:${dateStrings.i18nDays}:DATE: ago`; break;
|
||||
case 'hour': return $localize`:@@time-since:${dateStrings.i18nHours}:DATE: ago`; break;
|
||||
case 'minute': return $localize`:@@time-since:${dateStrings.i18nMinutes}:DATE: ago`; break;
|
||||
case 'second': return $localize`:@@time-since:${dateStrings.i18nSeconds}:DATE: ago`; break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'until':
|
||||
if (number === 1) {
|
||||
switch (unit) { // singular (In ~1 day)
|
||||
case 'year': return $localize`:@@time-until:In ~${dateStrings.i18nYear}:DATE:`; break;
|
||||
case 'month': return $localize`:@@time-until:In ~${dateStrings.i18nMonth}:DATE:`; break;
|
||||
case 'week': return $localize`:@@time-until:In ~${dateStrings.i18nWeek}:DATE:`; break;
|
||||
case 'day': return $localize`:@@time-until:In ~${dateStrings.i18nDay}:DATE:`; break;
|
||||
case 'hour': return $localize`:@@time-until:In ~${dateStrings.i18nHour}:DATE:`; break;
|
||||
case 'minute': return $localize`:@@time-until:In ~${dateStrings.i18nMinute}:DATE:`;
|
||||
case 'second': return $localize`:@@time-until:In ~${dateStrings.i18nSecond}:DATE:`;
|
||||
}
|
||||
} else {
|
||||
switch (unit) { // plural (In ~2 days)
|
||||
case 'year': return $localize`:@@time-until:In ~${dateStrings.i18nYears}:DATE:`; break;
|
||||
case 'month': return $localize`:@@time-until:In ~${dateStrings.i18nMonths}:DATE:`; break;
|
||||
case 'week': return $localize`:@@time-until:In ~${dateStrings.i18nWeeks}:DATE:`; break;
|
||||
case 'day': return $localize`:@@time-until:In ~${dateStrings.i18nDays}:DATE:`; break;
|
||||
case 'hour': return $localize`:@@time-until:In ~${dateStrings.i18nHours}:DATE:`; break;
|
||||
case 'minute': return $localize`:@@time-until:In ~${dateStrings.i18nMinutes}:DATE:`; break;
|
||||
case 'second': return $localize`:@@time-until:In ~${dateStrings.i18nSeconds}:DATE:`; break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'span':
|
||||
if (number === 1) {
|
||||
switch (unit) { // singular (1 day)
|
||||
case 'year': return $localize`:@@time-span:After ${dateStrings.i18nYear}:DATE:`; break;
|
||||
case 'month': return $localize`:@@time-span:After ${dateStrings.i18nMonth}:DATE:`; break;
|
||||
case 'week': return $localize`:@@time-span:After ${dateStrings.i18nWeek}:DATE:`; break;
|
||||
case 'day': return $localize`:@@time-span:After ${dateStrings.i18nDay}:DATE:`; break;
|
||||
case 'hour': return $localize`:@@time-span:After ${dateStrings.i18nHour}:DATE:`; break;
|
||||
case 'minute': return $localize`:@@time-span:After ${dateStrings.i18nMinute}:DATE:`; break;
|
||||
case 'second': return $localize`:@@time-span:After ${dateStrings.i18nSecond}:DATE:`; break;
|
||||
}
|
||||
} else {
|
||||
switch (unit) { // plural (2 days)
|
||||
case 'year': return $localize`:@@time-span:After ${dateStrings.i18nYears}:DATE:`; break;
|
||||
case 'month': return $localize`:@@time-span:After ${dateStrings.i18nMonths}:DATE:`; break;
|
||||
case 'week': return $localize`:@@time-span:After ${dateStrings.i18nWeeks}:DATE:`; break;
|
||||
case 'day': return $localize`:@@time-span:After ${dateStrings.i18nDays}:DATE:`; break;
|
||||
case 'hour': return $localize`:@@time-span:After ${dateStrings.i18nHours}:DATE:`; break;
|
||||
case 'minute': return $localize`:@@time-span:After ${dateStrings.i18nMinutes}:DATE:`; break;
|
||||
case 'second': return $localize`:@@time-span:After ${dateStrings.i18nSeconds}:DATE:`; break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (number === 1) {
|
||||
switch (unit) { // singular (1 day)
|
||||
case 'year': return dateStrings.i18nYear; break;
|
||||
case 'month': return dateStrings.i18nMonth; break;
|
||||
case 'week': return dateStrings.i18nWeek; break;
|
||||
case 'day': return dateStrings.i18nDay; break;
|
||||
case 'hour': return dateStrings.i18nHour; break;
|
||||
case 'minute': return dateStrings.i18nMinute; break;
|
||||
case 'second': return dateStrings.i18nSecond; break;
|
||||
}
|
||||
} else {
|
||||
switch (unit) { // plural (2 days)
|
||||
case 'year': return dateStrings.i18nYears; break;
|
||||
case 'month': return dateStrings.i18nMonths; break;
|
||||
case 'week': return dateStrings.i18nWeeks; break;
|
||||
case 'day': return dateStrings.i18nDays; break;
|
||||
case 'hour': return dateStrings.i18nHours; break;
|
||||
case 'minute': return dateStrings.i18nMinutes; break;
|
||||
case 'second': return dateStrings.i18nSeconds; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@
|
||||
|
||||
<p>Also, if you are using our Marks in a way described in the sections "Uses for Which We Are Granting a License," you must include the following trademark attribution at the foot of the webpage where you have used the Mark (or, if in a book, on the credits page), on any packaging or labeling, and on advertising or marketing materials:</p>
|
||||
|
||||
<p>"The Mempool Open Source Project®, Mempool Accelerator™, Mempool Enterprise®, Mempool Liquidity™, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem™, Mempool Goggles™, the mempool logo;, the mempool Square logo;, the mempool Blocks logo;, the mempool Blocks 3 | 2 logo;, the mempool.space Vertical Logo;, and the mempool.space Horizontal logo are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries, and are used with permission. Mempool Space K.K. has no affiliation with and does not sponsor or endorse the information provided herein."</p>
|
||||
<p>"The Mempool Open Source Project®, Mempool Accelerator™, Mempool Enterprise®, Mempool Liquidity™, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem®, Mempool Goggles™, the mempool logo;, the mempool Square logo;, the mempool Blocks logo;, the mempool Blocks 3 | 2 logo;, the mempool.space Vertical Logo;, and the mempool.space Horizontal logo are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries, and are used with permission. Mempool Space K.K. has no affiliation with and does not sponsor or endorse the information provided herein."</p>
|
||||
<li>What to Do When You See Abuse</li>
|
||||
|
||||
<br>
|
||||
|
||||
@@ -299,7 +299,7 @@
|
||||
<td [innerHTML]="'‎' + (tx.weight / 4 | vbytes: 2)"></td>
|
||||
</tr>
|
||||
<tr *ngIf="adjustedVsize != null">
|
||||
<td><ng-template i18n="transaction.adjusted-vsize|Transaction Adjusted VSize">Adjusted vsize</ng-template>
|
||||
<td><ng-container i18n="transaction.adjusted-vsize|Transaction Adjusted VSize">Adjusted vsize</ng-container>
|
||||
<a class="info-link" [routerLink]="['/docs/faq/' | relativeUrl]" fragment="what-is-adjusted-vsize">
|
||||
<fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon>
|
||||
</a>
|
||||
@@ -325,7 +325,7 @@
|
||||
<td [innerHTML]="'‎' + (tx.locktime | number)"></td>
|
||||
</tr>
|
||||
<tr *ngIf="sigops != null">
|
||||
<td><ng-template i18n="transaction.sigops|Transaction Sigops">Sigops</ng-template>
|
||||
<td><ng-container i18n="transaction.sigops|Transaction Sigops">Sigops</ng-container>
|
||||
<a class="info-link" [routerLink]="['/docs/faq/' | relativeUrl]" fragment="what-are-sigops">
|
||||
<fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon>
|
||||
</a>
|
||||
|
||||
@@ -21,7 +21,7 @@ import { ApiService } from '../../services/api.service';
|
||||
import { SeoService } from '../../services/seo.service';
|
||||
import { StorageService } from '../../services/storage.service';
|
||||
import { seoDescriptionNetwork } from '../../shared/common.utils';
|
||||
import { BlockExtended, CpfpInfo, RbfTree, MempoolPosition, DifficultyAdjustment, Acceleration } from '../../interfaces/node-api.interface';
|
||||
import { BlockExtended, CpfpInfo, RbfTree, MempoolPosition, DifficultyAdjustment, Acceleration, AccelerationPosition } from '../../interfaces/node-api.interface';
|
||||
import { LiquidUnblinding } from './liquid-ublinding';
|
||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||
import { Price, PriceService } from '../../services/price.service';
|
||||
@@ -38,6 +38,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
txId: string;
|
||||
txInBlockIndex: number;
|
||||
mempoolPosition: MempoolPosition;
|
||||
accelerationPositions: AccelerationPosition[];
|
||||
isLoadingTx = true;
|
||||
error: any = undefined;
|
||||
errorUnblinded: any = undefined;
|
||||
@@ -265,10 +266,14 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.now = Date.now();
|
||||
if (txPosition && txPosition.txid === this.txId && txPosition.position) {
|
||||
this.mempoolPosition = txPosition.position;
|
||||
this.accelerationPositions = txPosition.accelerationPositions;
|
||||
if (this.tx && !this.tx.status.confirmed) {
|
||||
const txFeePerVSize = this.getUnacceleratedFeeRate(this.tx, this.tx.acceleration || this.mempoolPosition?.accelerated);
|
||||
this.stateService.markBlock$.next({
|
||||
txid: txPosition.txid,
|
||||
mempoolPosition: this.mempoolPosition
|
||||
txFeePerVSize,
|
||||
mempoolPosition: this.mempoolPosition,
|
||||
accelerationPositions: this.accelerationPositions,
|
||||
});
|
||||
this.txInBlockIndex = this.mempoolPosition.block;
|
||||
|
||||
@@ -278,6 +283,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
}
|
||||
} else {
|
||||
this.mempoolPosition = null;
|
||||
this.accelerationPositions = null;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -400,11 +406,13 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
});
|
||||
this.fetchCpfp$.next(this.tx.txid);
|
||||
} else {
|
||||
const txFeePerVSize = this.getUnacceleratedFeeRate(this.tx, this.tx.acceleration || this.mempoolPosition?.accelerated);
|
||||
if (tx.cpfpChecked) {
|
||||
this.stateService.markBlock$.next({
|
||||
txid: tx.txid,
|
||||
txFeePerVSize: tx.effectiveFeePerVsize,
|
||||
txFeePerVSize,
|
||||
mempoolPosition: this.mempoolPosition,
|
||||
accelerationPositions: this.accelerationPositions,
|
||||
});
|
||||
this.cpfpInfo = {
|
||||
ancestors: tx.ancestors,
|
||||
@@ -619,6 +627,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.accelerationInfo = null;
|
||||
this.txInBlockIndex = null;
|
||||
this.mempoolPosition = null;
|
||||
this.accelerationPositions = null;
|
||||
document.body.scrollTo(0, 0);
|
||||
this.leaveTransaction();
|
||||
}
|
||||
@@ -632,6 +641,20 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
return +(cpfpTx.fee / (cpfpTx.weight / 4)).toFixed(1);
|
||||
}
|
||||
|
||||
getUnacceleratedFeeRate(tx: Transaction, accelerated: boolean): number {
|
||||
if (accelerated) {
|
||||
let ancestorVsize = tx.weight / 4;
|
||||
let ancestorFee = tx.fee;
|
||||
for (const ancestor of tx.ancestors || []) {
|
||||
ancestorVsize += (ancestor.weight / 4);
|
||||
ancestorFee += ancestor.fee;
|
||||
}
|
||||
return Math.min(tx.fee / (tx.weight / 4), (ancestorFee / ancestorVsize));
|
||||
} else {
|
||||
return tx.effectiveFeePerVsize;
|
||||
}
|
||||
}
|
||||
|
||||
setupGraph() {
|
||||
this.maxInOut = Math.min(this.inOutLimit, Math.max(this.tx?.vin?.length || 1, this.tx?.vout?.length + 1 || 1));
|
||||
this.graphHeight = this.graphExpanded ? this.maxInOut * 15 : Math.min(360, this.maxInOut * 80);
|
||||
|
||||
@@ -52,7 +52,7 @@ const routes: Routes = [
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'acceleration-list',
|
||||
path: 'acceleration/list',
|
||||
data: { networks: ['bitcoin'] },
|
||||
component: AccelerationsListComponent,
|
||||
},
|
||||
|
||||
@@ -196,6 +196,11 @@ export interface MempoolPosition {
|
||||
accelerated?: boolean
|
||||
}
|
||||
|
||||
export interface AccelerationPosition extends MempoolPosition {
|
||||
pool: string;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export interface RewardStats {
|
||||
startBlock: number;
|
||||
endBlock: number;
|
||||
|
||||
@@ -27,6 +27,8 @@ export interface WebsocketResponse {
|
||||
fees?: Recommendedfees;
|
||||
'track-tx'?: string;
|
||||
'track-address'?: string;
|
||||
'track-addresses'?: string[];
|
||||
'track-scriptpubkeys'?: string[];
|
||||
'track-asset'?: string;
|
||||
'track-mempool-block'?: number;
|
||||
'track-rbf'?: string;
|
||||
|
||||
@@ -40,6 +40,7 @@ export class CacheService {
|
||||
this.stateService.networkChanged$.subscribe((network) => {
|
||||
this.network = network;
|
||||
this.resetBlockCache();
|
||||
this.txCache = {};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { StateService } from './state.service';
|
||||
export class SeoService {
|
||||
network = '';
|
||||
baseTitle = 'mempool';
|
||||
baseDescription = 'Explore the full Bitcoin ecosystem with The Mempool Open Source Project™.';
|
||||
baseDescription = 'Explore the full Bitcoin ecosystem® with The Mempool Open Source Project®.';
|
||||
|
||||
canonicalLink: HTMLElement = document.getElementById('canonical');
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
|
||||
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs';
|
||||
import { Transaction } from '../interfaces/electrs.interface';
|
||||
import { IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, TransactionStripped } from '../interfaces/websocket.interface';
|
||||
import { BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface';
|
||||
import { AccelerationPosition, BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface';
|
||||
import { Router, NavigationStart } from '@angular/router';
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
import { filter, map, scan, shareReplay } from 'rxjs/operators';
|
||||
@@ -16,6 +16,7 @@ export interface MarkBlockState {
|
||||
mempoolBlockIndex?: number;
|
||||
txFeePerVSize?: number;
|
||||
mempoolPosition?: MempoolPosition;
|
||||
accelerationPositions?: AccelerationPosition[];
|
||||
}
|
||||
|
||||
export interface ILoadingIndicators { [name: string]: number; }
|
||||
@@ -116,7 +117,7 @@ export class StateService {
|
||||
utxoSpent$ = new Subject<object>();
|
||||
difficultyAdjustment$ = new ReplaySubject<DifficultyAdjustment>(1);
|
||||
mempoolTransactions$ = new Subject<Transaction>();
|
||||
mempoolTxPosition$ = new Subject<{ txid: string, position: MempoolPosition, cpfp: CpfpInfo | null}>();
|
||||
mempoolTxPosition$ = new Subject<{ txid: string, position: MempoolPosition, cpfp: CpfpInfo | null, accelerationPositions?: AccelerationPosition[] }>();
|
||||
mempoolRemovedTransactions$ = new Subject<Transaction>();
|
||||
blockTransactions$ = new Subject<Transaction>();
|
||||
isLoadingWebSocket$ = new ReplaySubject<boolean>(1);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</div>
|
||||
<p class="explore-tagline-mobile">
|
||||
<ng-container i18n="@@7deec1c1520f06170e1f8e8ddfbe4532312f638f">Explore the full Bitcoin ecosystem</ng-container>
|
||||
<ng-template [ngIf]="locale.substr(0, 2) === 'en'"> ™</ng-template>
|
||||
<ng-template [ngIf]="locale.substr(0, 2) === 'en'">®</ng-template>
|
||||
</p>
|
||||
<div class="site-options language-selector d-flex justify-content-center align-items-center" [class]="{'services': isServicesPage}">
|
||||
<div class="selector">
|
||||
@@ -32,7 +32,7 @@
|
||||
</a>
|
||||
<p class="explore-tagline-desktop">
|
||||
<ng-container i18n="@@7deec1c1520f06170e1f8e8ddfbe4532312f638f">Explore the full Bitcoin ecosystem</ng-container>
|
||||
<ng-template [ngIf]="locale.substr(0, 2) === 'en'"> ™</ng-template>
|
||||
<ng-template [ngIf]="locale.substr(0, 2) === 'en'">®</ng-template>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,16 +7,16 @@
|
||||
<script src="/resources/config.js"></script>
|
||||
<base href="/">
|
||||
|
||||
<meta name="description" content="Explore the full Bitcoin ecosystem with The Mempool Open Project™. See Bisq market prices, trading activity, and more.">
|
||||
<meta name="description" content="Explore the full Bitcoin ecosystem with The Mempool Open Source Project®. See Bisq market prices, trading activity, and more.">
|
||||
|
||||
<meta property="og:image" content="https://bisq.markets/resources/bisq/bisq-markets-preview.png" />
|
||||
<meta property="og:image:type" content="image/jpeg" />
|
||||
<meta property="og:description" content="Explore the full Bitcoin ecosystem with The Mempool Open Project™. See Bisq market prices, trading activity, and more." />
|
||||
<meta property="og:description" content="Explore the full Bitcoin ecosystem with The Mempool Open Source Project®. See Bisq market prices, trading activity, and more." />
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:site" content="https://bisq.markets/">
|
||||
<meta name="twitter:creator" content="@bisq_network">
|
||||
<meta name="twitter:title" content="The Mempool Open Source Project®">
|
||||
<meta name="twitter:description" content="Explore the full Bitcoin ecosystem with The Mempool Open Project™. See Bisq market prices, trading activity, and more." />
|
||||
<meta name="twitter:description" content="Explore the full Bitcoin ecosystem with The Mempool Open Source Project®. See Bisq market prices, trading activity, and more." />
|
||||
<meta name="twitter:image:src" content="https://bisq.markets/resources/bisq/bisq-markets-preview.png" />
|
||||
<meta name="twitter:domain" content="bisq.markets">
|
||||
|
||||
|
||||
@@ -7,17 +7,17 @@
|
||||
<script src="/resources/config.js"></script>
|
||||
<base href="/">
|
||||
|
||||
<meta name="description" content="Explore the full Bitcoin ecosystem with The Mempool Open Project™. See Liquid transactions & assets, get network info, and more.">
|
||||
<meta name="description" content="Explore the full Bitcoin ecosystem with The Mempool Open Source Project®. See Liquid transactions & assets, get network info, and more.">
|
||||
<meta property="og:image" content="https://liquid.network/resources/liquid/liquid-network-preview.png" />
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1000" />
|
||||
<meta property="og:image:height" content="500" />
|
||||
<meta property="og:description" content="Explore the full Bitcoin ecosystem with The Mempool Open Project™. See Liquid transactions & assets, get network info, and more." />
|
||||
<meta property="og:description" content="Explore the full Bitcoin ecosystem with The Mempool Open Source Project®. See Liquid transactions & assets, get network info, and more." />
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:site" content="@mempool">
|
||||
<meta name="twitter:creator" content="@mempool">
|
||||
<meta name="twitter:title" content="The Mempool Open Source Project®">
|
||||
<meta name="twitter:description" content="Explore the full Bitcoin ecosystem with The Mempool Open Project™. See Liquid transactions & assets, get network info, and more." />
|
||||
<meta name="twitter:description" content="Explore the full Bitcoin ecosystem with The Mempool Open Source Project®. See Liquid transactions & assets, get network info, and more." />
|
||||
<meta name="twitter:image:src" content="https://liquid.network/resources/liquid/liquid-network-preview.png" />
|
||||
<meta name="twitter:domain" content="liquid.network">
|
||||
|
||||
|
||||
@@ -1194,4 +1194,5 @@ app-global-footer {
|
||||
|
||||
.info-link fa-icon {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ var PATH;
|
||||
if (process.argv[2]) {
|
||||
PATH = process.argv[2];
|
||||
PATH += PATH.endsWith("/") ? "" : "/"
|
||||
PATH = path.normalize(PATH);
|
||||
PATH = path.resolve(path.normalize(PATH));
|
||||
console.log(`[sync-assets] using PATH ${PATH}`);
|
||||
if (!fs.existsSync(PATH)){
|
||||
console.log(`${LOG_TAG} ${PATH} does not exist, creating`);
|
||||
@@ -110,7 +110,7 @@ function downloadMiningPoolLogos$() {
|
||||
}
|
||||
let downloadedCount = 0;
|
||||
for (const poolLogo of poolLogos) {
|
||||
const filePath = PATH + `mining-pools/${poolLogo.name}`;
|
||||
const filePath = `${PATH}/mining-pools/${poolLogo.name}`;
|
||||
if (fs.existsSync(filePath)) {
|
||||
const localHash = getLocalHash(filePath);
|
||||
if (verbose) {
|
||||
@@ -124,7 +124,7 @@ function downloadMiningPoolLogos$() {
|
||||
}
|
||||
} else {
|
||||
console.log(`${LOG_TAG} ${poolLogo.name} is missing, downloading...`);
|
||||
const miningPoolsDir = PATH + `mining-pools/`;
|
||||
const miningPoolsDir = `${PATH}/mining-pools/`;
|
||||
if (!fs.existsSync(miningPoolsDir)){
|
||||
fs.mkdirSync(miningPoolsDir, { recursive: true });
|
||||
}
|
||||
@@ -179,7 +179,7 @@ function downloadPromoVideoSubtiles$() {
|
||||
}
|
||||
let downloadedCount = 0;
|
||||
for (const language of videoLanguages) {
|
||||
const filePath = PATH + `promo-video/${language.name}`;
|
||||
const filePath = `${PATH}/promo-video/${language.name}`;
|
||||
if (fs.existsSync(filePath)) {
|
||||
if (verbose) {
|
||||
console.log(`${LOG_TAG} ${language.name} remote promo video hash ${language.sha}`);
|
||||
@@ -193,7 +193,7 @@ function downloadPromoVideoSubtiles$() {
|
||||
}
|
||||
} else {
|
||||
console.log(`${LOG_TAG} ${language.name} is missing, downloading`);
|
||||
const promoVideosDir = PATH + `promo-video/`;
|
||||
const promoVideosDir = `${PATH}/promo-video/`;
|
||||
if (!fs.existsSync(promoVideosDir)){
|
||||
fs.mkdirSync(promoVideosDir, { recursive: true });
|
||||
}
|
||||
@@ -250,7 +250,7 @@ function downloadPromoVideo$() {
|
||||
if (item.name !== 'promo.mp4') {
|
||||
continue;
|
||||
}
|
||||
const filePath = PATH + `promo-video/mempool-promo.mp4`;
|
||||
const filePath = `${PATH}/promo-video/mempool-promo.mp4`;
|
||||
if (fs.existsSync(filePath)) {
|
||||
const localHash = getLocalHash(filePath);
|
||||
|
||||
@@ -288,16 +288,16 @@ if (configContent.BASE_MODULE && configContent.BASE_MODULE === 'liquid') {
|
||||
const testnetAssetsMinimalJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_testnet_db/master/index.minimal.json';
|
||||
|
||||
console.log(`${LOG_TAG} Downloading assets`);
|
||||
download(PATH + 'assets.json', assetsJsonUrl);
|
||||
download(`${PATH}/assets.json`, assetsJsonUrl);
|
||||
|
||||
console.log(`${LOG_TAG} Downloading assets minimal`);
|
||||
download(PATH + 'assets.minimal.json', assetsMinimalJsonUrl);
|
||||
download(`${PATH}/assets.minimal.json`, assetsMinimalJsonUrl);
|
||||
|
||||
console.log(`${LOG_TAG} Downloading testnet assets`);
|
||||
download(PATH + 'assets-testnet.json', testnetAssetsJsonUrl);
|
||||
download(`${PATH}/assets-testnet.json`, testnetAssetsJsonUrl);
|
||||
|
||||
console.log(`${LOG_TAG} Downloading testnet assets minimal`);
|
||||
download(PATH + 'assets-testnet.minimal.json', testnetAssetsMinimalJsonUrl);
|
||||
download(`${PATH}/assets-testnet.minimal.json`, testnetAssetsMinimalJsonUrl);
|
||||
} else {
|
||||
if (verbose) {
|
||||
console.log(`${LOG_TAG} BASE_MODULE is not set to Liquid (${configContent.BASE_MODULE}), skipping downloading assets`);
|
||||
|
||||
@@ -395,7 +395,7 @@ FREEBSD_PKG+=(zsh sudo git git-lfs screen curl wget calc neovim)
|
||||
FREEBSD_PKG+=(openssh-portable py39-pip rust llvm10 jq base64 libzmq4)
|
||||
FREEBSD_PKG+=(boost-libs autoconf automake gmake gcc libevent libtool pkgconf)
|
||||
FREEBSD_PKG+=(nginx rsync py39-certbot-nginx mariadb1011-server keybase)
|
||||
FREEBSD_PKG+=(geoipupdate)
|
||||
FREEBSD_PKG+=(geoipupdate redis)
|
||||
|
||||
FREEBSD_UNFURL_PKG=()
|
||||
FREEBSD_UNFURL_PKG+=(nvidia-driver-470 chromium xinit xterm twm ja-sourcehansans-otf)
|
||||
@@ -1016,7 +1016,6 @@ osSudo "${MEMPOOL_USER}" ln -s mempool/production/mempool-kill-all stop
|
||||
osSudo "${MEMPOOL_USER}" ln -s mempool/production/mempool-start-all start
|
||||
osSudo "${MEMPOOL_USER}" ln -s mempool/production/mempool-reset-all reset
|
||||
|
||||
|
||||
case $OS in
|
||||
FreeBSD)
|
||||
echo "[*] Installing syslog configuration"
|
||||
@@ -1024,6 +1023,9 @@ case $OS in
|
||||
osSudo "${ROOT_USER}" install -c -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/mempool-logger" /usr/local/bin/mempool-logger
|
||||
osSudo "${ROOT_USER}" install -c -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/syslog.conf" /usr/local/etc/syslog.d/mempool.conf
|
||||
|
||||
echo "[*] Installing redis configuration"
|
||||
osSudo "${ROOT_USER}" install -c -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/redis.conf" /usr/local/etc/redis.conf
|
||||
|
||||
echo "[*] Installing newsyslog configuration"
|
||||
osSudo "${ROOT_USER}" mkdir -p /usr/local/etc/newsyslog.conf.d
|
||||
osSudo "${ROOT_USER}" install -c -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/newsyslog-mempool-backend.conf" /usr/local/etc/newsyslog.conf.d/newsyslog-mempool-backend.conf
|
||||
|
||||
@@ -25,30 +25,54 @@
|
||||
"ESPLORA": {
|
||||
"UNIX_SOCKET_PATH": "/elements/socket/esplora-elements-liquid",
|
||||
"FALLBACK": [
|
||||
"http://node201.va1.mempool.space:3001",
|
||||
"http://node202.va1.mempool.space:3001",
|
||||
"http://node203.va1.mempool.space:3001",
|
||||
"http://node204.va1.mempool.space:3001",
|
||||
"http://node205.va1.mempool.space:3001",
|
||||
"http://node206.va1.mempool.space:3001",
|
||||
"http://node201.fmt.mempool.space:3001",
|
||||
"http://node202.fmt.mempool.space:3001",
|
||||
"http://node203.fmt.mempool.space:3001",
|
||||
"http://node204.fmt.mempool.space:3001",
|
||||
"http://node205.fmt.mempool.space:3001",
|
||||
"http://node206.fmt.mempool.space:3001",
|
||||
"http://node201.va1.mempool.space:3001",
|
||||
"http://node202.va1.mempool.space:3001",
|
||||
"http://node203.va1.mempool.space:3001",
|
||||
"http://node204.va1.mempool.space:3001",
|
||||
"http://node205.va1.mempool.space:3001",
|
||||
"http://node206.va1.mempool.space:3001",
|
||||
"http://node207.va1.mempool.space:3001",
|
||||
"http://node208.va1.mempool.space:3001",
|
||||
"http://node209.va1.mempool.space:3001",
|
||||
"http://node210.va1.mempool.space:3001",
|
||||
"http://node211.va1.mempool.space:3001",
|
||||
"http://node212.va1.mempool.space:3001",
|
||||
"http://node213.va1.mempool.space:3001",
|
||||
"http://node214.va1.mempool.space:3001",
|
||||
"http://node201.fra.mempool.space:3001",
|
||||
"http://node202.fra.mempool.space:3001",
|
||||
"http://node203.fra.mempool.space:3001",
|
||||
"http://node204.fra.mempool.space:3001",
|
||||
"http://node205.fra.mempool.space:3001",
|
||||
"http://node206.fra.mempool.space:3001",
|
||||
"http://node207.fra.mempool.space:3001",
|
||||
"http://node208.fra.mempool.space:3001",
|
||||
"http://node209.fra.mempool.space:3001",
|
||||
"http://node210.fra.mempool.space:3001",
|
||||
"http://node211.fra.mempool.space:3001",
|
||||
"http://node212.fra.mempool.space:3001",
|
||||
"http://node213.fra.mempool.space:3001",
|
||||
"http://node214.fra.mempool.space:3001",
|
||||
"http://node201.tk7.mempool.space:3001",
|
||||
"http://node202.tk7.mempool.space:3001",
|
||||
"http://node203.tk7.mempool.space:3001",
|
||||
"http://node204.tk7.mempool.space:3001",
|
||||
"http://node205.tk7.mempool.space:3001",
|
||||
"http://node206.tk7.mempool.space:3001"
|
||||
"http://node206.tk7.mempool.space:3001",
|
||||
"http://node207.tk7.mempool.space:3001",
|
||||
"http://node208.tk7.mempool.space:3001",
|
||||
"http://node209.tk7.mempool.space:3001",
|
||||
"http://node210.tk7.mempool.space:3001",
|
||||
"http://node211.tk7.mempool.space:3001",
|
||||
"http://node212.tk7.mempool.space:3001",
|
||||
"http://node213.tk7.mempool.space:3001",
|
||||
"http://node214.tk7.mempool.space:3001"
|
||||
]
|
||||
},
|
||||
"DATABASE": {
|
||||
|
||||
@@ -25,30 +25,54 @@
|
||||
"ESPLORA": {
|
||||
"UNIX_SOCKET_PATH": "/elements/socket/esplora-elements-liquidtestnet",
|
||||
"FALLBACK": [
|
||||
"http://node201.va1.mempool.space:3004",
|
||||
"http://node202.va1.mempool.space:3004",
|
||||
"http://node203.va1.mempool.space:3004",
|
||||
"http://node204.va1.mempool.space:3004",
|
||||
"http://node205.va1.mempool.space:3004",
|
||||
"http://node206.va1.mempool.space:3004",
|
||||
"http://node201.fmt.mempool.space:3004",
|
||||
"http://node202.fmt.mempool.space:3004",
|
||||
"http://node203.fmt.mempool.space:3004",
|
||||
"http://node204.fmt.mempool.space:3004",
|
||||
"http://node205.fmt.mempool.space:3004",
|
||||
"http://node206.fmt.mempool.space:3004",
|
||||
"http://node201.va1.mempool.space:3004",
|
||||
"http://node202.va1.mempool.space:3004",
|
||||
"http://node203.va1.mempool.space:3004",
|
||||
"http://node204.va1.mempool.space:3004",
|
||||
"http://node205.va1.mempool.space:3004",
|
||||
"http://node206.va1.mempool.space:3004",
|
||||
"http://node207.va1.mempool.space:3004",
|
||||
"http://node208.va1.mempool.space:3004",
|
||||
"http://node209.va1.mempool.space:3004",
|
||||
"http://node210.va1.mempool.space:3004",
|
||||
"http://node211.va1.mempool.space:3004",
|
||||
"http://node212.va1.mempool.space:3004",
|
||||
"http://node213.va1.mempool.space:3004",
|
||||
"http://node214.va1.mempool.space:3004",
|
||||
"http://node201.fra.mempool.space:3004",
|
||||
"http://node202.fra.mempool.space:3004",
|
||||
"http://node203.fra.mempool.space:3004",
|
||||
"http://node204.fra.mempool.space:3004",
|
||||
"http://node205.fra.mempool.space:3004",
|
||||
"http://node206.fra.mempool.space:3004",
|
||||
"http://node207.fra.mempool.space:3004",
|
||||
"http://node208.fra.mempool.space:3004",
|
||||
"http://node209.fra.mempool.space:3004",
|
||||
"http://node210.fra.mempool.space:3004",
|
||||
"http://node211.fra.mempool.space:3004",
|
||||
"http://node212.fra.mempool.space:3004",
|
||||
"http://node213.fra.mempool.space:3004",
|
||||
"http://node214.fra.mempool.space:3004",
|
||||
"http://node201.tk7.mempool.space:3004",
|
||||
"http://node202.tk7.mempool.space:3004",
|
||||
"http://node203.tk7.mempool.space:3004",
|
||||
"http://node204.tk7.mempool.space:3004",
|
||||
"http://node205.tk7.mempool.space:3004",
|
||||
"http://node206.tk7.mempool.space:3004"
|
||||
"http://node206.tk7.mempool.space:3004",
|
||||
"http://node207.tk7.mempool.space:3004",
|
||||
"http://node208.tk7.mempool.space:3004",
|
||||
"http://node209.tk7.mempool.space:3004",
|
||||
"http://node210.tk7.mempool.space:3004",
|
||||
"http://node211.tk7.mempool.space:3004",
|
||||
"http://node212.tk7.mempool.space:3004",
|
||||
"http://node213.tk7.mempool.space:3004",
|
||||
"http://node214.tk7.mempool.space:3004"
|
||||
]
|
||||
},
|
||||
"DATABASE": {
|
||||
|
||||
@@ -18,30 +18,54 @@
|
||||
"ESPLORA": {
|
||||
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-mainnet",
|
||||
"FALLBACK": [
|
||||
"http://node201.va1.mempool.space:3000",
|
||||
"http://node202.va1.mempool.space:3000",
|
||||
"http://node203.va1.mempool.space:3000",
|
||||
"http://node204.va1.mempool.space:3000",
|
||||
"http://node205.va1.mempool.space:3000",
|
||||
"http://node206.va1.mempool.space:3000",
|
||||
"http://node201.fmt.mempool.space:3000",
|
||||
"http://node202.fmt.mempool.space:3000",
|
||||
"http://node203.fmt.mempool.space:3000",
|
||||
"http://node204.fmt.mempool.space:3000",
|
||||
"http://node205.fmt.mempool.space:3000",
|
||||
"http://node206.fmt.mempool.space:3000",
|
||||
"http://node201.va1.mempool.space:3000",
|
||||
"http://node202.va1.mempool.space:3000",
|
||||
"http://node203.va1.mempool.space:3000",
|
||||
"http://node204.va1.mempool.space:3000",
|
||||
"http://node205.va1.mempool.space:3000",
|
||||
"http://node206.va1.mempool.space:3000",
|
||||
"http://node207.va1.mempool.space:3000",
|
||||
"http://node208.va1.mempool.space:3000",
|
||||
"http://node209.va1.mempool.space:3000",
|
||||
"http://node210.va1.mempool.space:3000",
|
||||
"http://node211.va1.mempool.space:3000",
|
||||
"http://node212.va1.mempool.space:3000",
|
||||
"http://node213.va1.mempool.space:3000",
|
||||
"http://node214.va1.mempool.space:3000",
|
||||
"http://node201.fra.mempool.space:3000",
|
||||
"http://node202.fra.mempool.space:3000",
|
||||
"http://node203.fra.mempool.space:3000",
|
||||
"http://node204.fra.mempool.space:3000",
|
||||
"http://node205.fra.mempool.space:3000",
|
||||
"http://node206.fra.mempool.space:3000",
|
||||
"http://node207.fra.mempool.space:3000",
|
||||
"http://node208.fra.mempool.space:3000",
|
||||
"http://node209.fra.mempool.space:3000",
|
||||
"http://node210.fra.mempool.space:3000",
|
||||
"http://node211.fra.mempool.space:3000",
|
||||
"http://node212.fra.mempool.space:3000",
|
||||
"http://node213.fra.mempool.space:3000",
|
||||
"http://node214.fra.mempool.space:3000",
|
||||
"http://node201.tk7.mempool.space:3000",
|
||||
"http://node202.tk7.mempool.space:3000",
|
||||
"http://node203.tk7.mempool.space:3000",
|
||||
"http://node204.tk7.mempool.space:3000",
|
||||
"http://node205.tk7.mempool.space:3000",
|
||||
"http://node206.tk7.mempool.space:3000"
|
||||
"http://node206.tk7.mempool.space:3000",
|
||||
"http://node207.tk7.mempool.space:3000",
|
||||
"http://node208.tk7.mempool.space:3000",
|
||||
"http://node209.tk7.mempool.space:3000",
|
||||
"http://node210.tk7.mempool.space:3000",
|
||||
"http://node211.tk7.mempool.space:3000",
|
||||
"http://node212.tk7.mempool.space:3000",
|
||||
"http://node213.tk7.mempool.space:3000",
|
||||
"http://node214.tk7.mempool.space:3000"
|
||||
]
|
||||
},
|
||||
"LIGHTNING": {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"POLL_RATE_MS": 1000,
|
||||
"INDEXING_BLOCKS_AMOUNT": -1,
|
||||
"BLOCKS_SUMMARIES_INDEXING": true,
|
||||
"GOGGLES_INDEXING": true,
|
||||
"AUDIT": true,
|
||||
"CPFP_INDEXING": true,
|
||||
"ADVANCED_GBT_AUDIT": true,
|
||||
@@ -38,30 +39,54 @@
|
||||
"ESPLORA": {
|
||||
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-mainnet",
|
||||
"FALLBACK": [
|
||||
"http://node201.va1.mempool.space:3000",
|
||||
"http://node202.va1.mempool.space:3000",
|
||||
"http://node203.va1.mempool.space:3000",
|
||||
"http://node204.va1.mempool.space:3000",
|
||||
"http://node205.va1.mempool.space:3000",
|
||||
"http://node206.va1.mempool.space:3000",
|
||||
"http://node201.fmt.mempool.space:3000",
|
||||
"http://node202.fmt.mempool.space:3000",
|
||||
"http://node203.fmt.mempool.space:3000",
|
||||
"http://node204.fmt.mempool.space:3000",
|
||||
"http://node205.fmt.mempool.space:3000",
|
||||
"http://node206.fmt.mempool.space:3000",
|
||||
"http://node201.va1.mempool.space:3000",
|
||||
"http://node202.va1.mempool.space:3000",
|
||||
"http://node203.va1.mempool.space:3000",
|
||||
"http://node204.va1.mempool.space:3000",
|
||||
"http://node205.va1.mempool.space:3000",
|
||||
"http://node206.va1.mempool.space:3000",
|
||||
"http://node207.va1.mempool.space:3000",
|
||||
"http://node208.va1.mempool.space:3000",
|
||||
"http://node209.va1.mempool.space:3000",
|
||||
"http://node210.va1.mempool.space:3000",
|
||||
"http://node211.va1.mempool.space:3000",
|
||||
"http://node212.va1.mempool.space:3000",
|
||||
"http://node213.va1.mempool.space:3000",
|
||||
"http://node214.va1.mempool.space:3000",
|
||||
"http://node201.fra.mempool.space:3000",
|
||||
"http://node202.fra.mempool.space:3000",
|
||||
"http://node203.fra.mempool.space:3000",
|
||||
"http://node204.fra.mempool.space:3000",
|
||||
"http://node205.fra.mempool.space:3000",
|
||||
"http://node206.fra.mempool.space:3000",
|
||||
"http://node207.fra.mempool.space:3000",
|
||||
"http://node208.fra.mempool.space:3000",
|
||||
"http://node209.fra.mempool.space:3000",
|
||||
"http://node210.fra.mempool.space:3000",
|
||||
"http://node211.fra.mempool.space:3000",
|
||||
"http://node212.fra.mempool.space:3000",
|
||||
"http://node213.fra.mempool.space:3000",
|
||||
"http://node214.fra.mempool.space:3000",
|
||||
"http://node201.tk7.mempool.space:3000",
|
||||
"http://node202.tk7.mempool.space:3000",
|
||||
"http://node203.tk7.mempool.space:3000",
|
||||
"http://node204.tk7.mempool.space:3000",
|
||||
"http://node205.tk7.mempool.space:3000",
|
||||
"http://node206.tk7.mempool.space:3000"
|
||||
"http://node206.tk7.mempool.space:3000",
|
||||
"http://node207.tk7.mempool.space:3000",
|
||||
"http://node208.tk7.mempool.space:3000",
|
||||
"http://node209.tk7.mempool.space:3000",
|
||||
"http://node210.tk7.mempool.space:3000",
|
||||
"http://node211.tk7.mempool.space:3000",
|
||||
"http://node212.tk7.mempool.space:3000",
|
||||
"http://node213.tk7.mempool.space:3000",
|
||||
"http://node214.tk7.mempool.space:3000"
|
||||
]
|
||||
},
|
||||
"DATABASE": {
|
||||
@@ -81,30 +106,54 @@
|
||||
"AUDIT": true,
|
||||
"AUDIT_START_HEIGHT": 774000,
|
||||
"SERVERS": [
|
||||
"node201.va1.mempool.space",
|
||||
"node202.va1.mempool.space",
|
||||
"node203.va1.mempool.space",
|
||||
"node204.va1.mempool.space",
|
||||
"node205.va1.mempool.space",
|
||||
"node206.va1.mempool.space",
|
||||
"node201.fmt.mempool.space",
|
||||
"node202.fmt.mempool.space",
|
||||
"node203.fmt.mempool.space",
|
||||
"node204.fmt.mempool.space",
|
||||
"node205.fmt.mempool.space",
|
||||
"node206.fmt.mempool.space",
|
||||
"node201.va1.mempool.space",
|
||||
"node202.va1.mempool.space",
|
||||
"node203.va1.mempool.space",
|
||||
"node204.va1.mempool.space",
|
||||
"node205.va1.mempool.space",
|
||||
"node206.va1.mempool.space",
|
||||
"node207.va1.mempool.space",
|
||||
"node208.va1.mempool.space",
|
||||
"node209.va1.mempool.space",
|
||||
"node210.va1.mempool.space",
|
||||
"node211.va1.mempool.space",
|
||||
"node212.va1.mempool.space",
|
||||
"node213.va1.mempool.space",
|
||||
"node214.va1.mempool.space",
|
||||
"node201.fra.mempool.space",
|
||||
"node202.fra.mempool.space",
|
||||
"node203.fra.mempool.space",
|
||||
"node204.fra.mempool.space",
|
||||
"node205.fra.mempool.space",
|
||||
"node206.fra.mempool.space",
|
||||
"node207.fra.mempool.space",
|
||||
"node208.fra.mempool.space",
|
||||
"node209.fra.mempool.space",
|
||||
"node210.fra.mempool.space",
|
||||
"node211.fra.mempool.space",
|
||||
"node212.fra.mempool.space",
|
||||
"node213.fra.mempool.space",
|
||||
"node214.fra.mempool.space",
|
||||
"node201.tk7.mempool.space",
|
||||
"node202.tk7.mempool.space",
|
||||
"node203.tk7.mempool.space",
|
||||
"node204.tk7.mempool.space",
|
||||
"node205.tk7.mempool.space",
|
||||
"node206.tk7.mempool.space"
|
||||
"node206.tk7.mempool.space",
|
||||
"node207.tk7.mempool.space",
|
||||
"node208.tk7.mempool.space",
|
||||
"node209.tk7.mempool.space",
|
||||
"node210.tk7.mempool.space",
|
||||
"node211.tk7.mempool.space",
|
||||
"node212.tk7.mempool.space",
|
||||
"node213.tk7.mempool.space",
|
||||
"node214.tk7.mempool.space"
|
||||
]
|
||||
},
|
||||
"REDIS": {
|
||||
|
||||
@@ -18,30 +18,54 @@
|
||||
"ESPLORA": {
|
||||
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-signet",
|
||||
"FALLBACK": [
|
||||
"http://node201.va1.mempool.space:3003",
|
||||
"http://node202.va1.mempool.space:3003",
|
||||
"http://node203.va1.mempool.space:3003",
|
||||
"http://node204.va1.mempool.space:3003",
|
||||
"http://node205.va1.mempool.space:3003",
|
||||
"http://node206.va1.mempool.space:3003",
|
||||
"http://node201.fmt.mempool.space:3003",
|
||||
"http://node202.fmt.mempool.space:3003",
|
||||
"http://node203.fmt.mempool.space:3003",
|
||||
"http://node204.fmt.mempool.space:3003",
|
||||
"http://node205.fmt.mempool.space:3003",
|
||||
"http://node206.fmt.mempool.space:3003",
|
||||
"http://node201.va1.mempool.space:3003",
|
||||
"http://node202.va1.mempool.space:3003",
|
||||
"http://node203.va1.mempool.space:3003",
|
||||
"http://node204.va1.mempool.space:3003",
|
||||
"http://node205.va1.mempool.space:3003",
|
||||
"http://node206.va1.mempool.space:3003",
|
||||
"http://node207.va1.mempool.space:3003",
|
||||
"http://node208.va1.mempool.space:3003",
|
||||
"http://node209.va1.mempool.space:3003",
|
||||
"http://node210.va1.mempool.space:3003",
|
||||
"http://node211.va1.mempool.space:3003",
|
||||
"http://node212.va1.mempool.space:3003",
|
||||
"http://node213.va1.mempool.space:3003",
|
||||
"http://node214.va1.mempool.space:3003",
|
||||
"http://node201.fra.mempool.space:3003",
|
||||
"http://node202.fra.mempool.space:3003",
|
||||
"http://node203.fra.mempool.space:3003",
|
||||
"http://node204.fra.mempool.space:3003",
|
||||
"http://node205.fra.mempool.space:3003",
|
||||
"http://node206.fra.mempool.space:3003",
|
||||
"http://node207.fra.mempool.space:3003",
|
||||
"http://node208.fra.mempool.space:3003",
|
||||
"http://node209.fra.mempool.space:3003",
|
||||
"http://node210.fra.mempool.space:3003",
|
||||
"http://node211.fra.mempool.space:3003",
|
||||
"http://node212.fra.mempool.space:3003",
|
||||
"http://node213.fra.mempool.space:3003",
|
||||
"http://node214.fra.mempool.space:3003",
|
||||
"http://node201.tk7.mempool.space:3003",
|
||||
"http://node202.tk7.mempool.space:3003",
|
||||
"http://node203.tk7.mempool.space:3003",
|
||||
"http://node204.tk7.mempool.space:3003",
|
||||
"http://node205.tk7.mempool.space:3003",
|
||||
"http://node206.tk7.mempool.space:3003"
|
||||
"http://node206.tk7.mempool.space:3003",
|
||||
"http://node207.tk7.mempool.space:3003",
|
||||
"http://node208.tk7.mempool.space:3003",
|
||||
"http://node209.tk7.mempool.space:3003",
|
||||
"http://node210.tk7.mempool.space:3003",
|
||||
"http://node211.tk7.mempool.space:3003",
|
||||
"http://node212.tk7.mempool.space:3003",
|
||||
"http://node213.tk7.mempool.space:3003",
|
||||
"http://node214.tk7.mempool.space:3003"
|
||||
]
|
||||
},
|
||||
"LIGHTNING": {
|
||||
|
||||
@@ -27,30 +27,54 @@
|
||||
"ESPLORA": {
|
||||
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-signet",
|
||||
"FALLBACK": [
|
||||
"http://node201.va1.mempool.space:3003",
|
||||
"http://node202.va1.mempool.space:3003",
|
||||
"http://node203.va1.mempool.space:3003",
|
||||
"http://node204.va1.mempool.space:3003",
|
||||
"http://node205.va1.mempool.space:3003",
|
||||
"http://node206.va1.mempool.space:3003",
|
||||
"http://node201.fmt.mempool.space:3003",
|
||||
"http://node202.fmt.mempool.space:3003",
|
||||
"http://node203.fmt.mempool.space:3003",
|
||||
"http://node204.fmt.mempool.space:3003",
|
||||
"http://node205.fmt.mempool.space:3003",
|
||||
"http://node206.fmt.mempool.space:3003",
|
||||
"http://node201.va1.mempool.space:3003",
|
||||
"http://node202.va1.mempool.space:3003",
|
||||
"http://node203.va1.mempool.space:3003",
|
||||
"http://node204.va1.mempool.space:3003",
|
||||
"http://node205.va1.mempool.space:3003",
|
||||
"http://node206.va1.mempool.space:3003",
|
||||
"http://node207.va1.mempool.space:3003",
|
||||
"http://node208.va1.mempool.space:3003",
|
||||
"http://node209.va1.mempool.space:3003",
|
||||
"http://node210.va1.mempool.space:3003",
|
||||
"http://node211.va1.mempool.space:3003",
|
||||
"http://node212.va1.mempool.space:3003",
|
||||
"http://node213.va1.mempool.space:3003",
|
||||
"http://node214.va1.mempool.space:3003",
|
||||
"http://node201.fra.mempool.space:3003",
|
||||
"http://node202.fra.mempool.space:3003",
|
||||
"http://node203.fra.mempool.space:3003",
|
||||
"http://node204.fra.mempool.space:3003",
|
||||
"http://node205.fra.mempool.space:3003",
|
||||
"http://node206.fra.mempool.space:3003",
|
||||
"http://node207.fra.mempool.space:3003",
|
||||
"http://node208.fra.mempool.space:3003",
|
||||
"http://node209.fra.mempool.space:3003",
|
||||
"http://node210.fra.mempool.space:3003",
|
||||
"http://node211.fra.mempool.space:3003",
|
||||
"http://node212.fra.mempool.space:3003",
|
||||
"http://node213.fra.mempool.space:3003",
|
||||
"http://node214.fra.mempool.space:3003",
|
||||
"http://node201.tk7.mempool.space:3003",
|
||||
"http://node202.tk7.mempool.space:3003",
|
||||
"http://node203.tk7.mempool.space:3003",
|
||||
"http://node204.tk7.mempool.space:3003",
|
||||
"http://node205.tk7.mempool.space:3003",
|
||||
"http://node206.tk7.mempool.space:3003"
|
||||
"http://node206.tk7.mempool.space:3003",
|
||||
"http://node207.tk7.mempool.space:3003",
|
||||
"http://node208.tk7.mempool.space:3003",
|
||||
"http://node209.tk7.mempool.space:3003",
|
||||
"http://node210.tk7.mempool.space:3003",
|
||||
"http://node211.tk7.mempool.space:3003",
|
||||
"http://node212.tk7.mempool.space:3003",
|
||||
"http://node213.tk7.mempool.space:3003",
|
||||
"http://node214.tk7.mempool.space:3003"
|
||||
]
|
||||
},
|
||||
"DATABASE": {
|
||||
|
||||
@@ -18,30 +18,54 @@
|
||||
"ESPLORA": {
|
||||
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-testnet",
|
||||
"FALLBACK": [
|
||||
"http://node201.va1.mempool.space:3002",
|
||||
"http://node202.va1.mempool.space:3002",
|
||||
"http://node203.va1.mempool.space:3002",
|
||||
"http://node204.va1.mempool.space:3002",
|
||||
"http://node205.va1.mempool.space:3002",
|
||||
"http://node206.va1.mempool.space:3002",
|
||||
"http://node201.fmt.mempool.space:3002",
|
||||
"http://node202.fmt.mempool.space:3002",
|
||||
"http://node203.fmt.mempool.space:3002",
|
||||
"http://node204.fmt.mempool.space:3002",
|
||||
"http://node205.fmt.mempool.space:3002",
|
||||
"http://node206.fmt.mempool.space:3002",
|
||||
"http://node201.va1.mempool.space:3002",
|
||||
"http://node202.va1.mempool.space:3002",
|
||||
"http://node203.va1.mempool.space:3002",
|
||||
"http://node204.va1.mempool.space:3002",
|
||||
"http://node205.va1.mempool.space:3002",
|
||||
"http://node206.va1.mempool.space:3002",
|
||||
"http://node207.va1.mempool.space:3002",
|
||||
"http://node208.va1.mempool.space:3002",
|
||||
"http://node209.va1.mempool.space:3002",
|
||||
"http://node210.va1.mempool.space:3002",
|
||||
"http://node211.va1.mempool.space:3002",
|
||||
"http://node212.va1.mempool.space:3002",
|
||||
"http://node213.va1.mempool.space:3002",
|
||||
"http://node214.va1.mempool.space:3002",
|
||||
"http://node201.fra.mempool.space:3002",
|
||||
"http://node202.fra.mempool.space:3002",
|
||||
"http://node203.fra.mempool.space:3002",
|
||||
"http://node204.fra.mempool.space:3002",
|
||||
"http://node205.fra.mempool.space:3002",
|
||||
"http://node206.fra.mempool.space:3002",
|
||||
"http://node207.fra.mempool.space:3002",
|
||||
"http://node208.fra.mempool.space:3002",
|
||||
"http://node209.fra.mempool.space:3002",
|
||||
"http://node210.fra.mempool.space:3002",
|
||||
"http://node211.fra.mempool.space:3002",
|
||||
"http://node212.fra.mempool.space:3002",
|
||||
"http://node213.fra.mempool.space:3002",
|
||||
"http://node214.fra.mempool.space:3002",
|
||||
"http://node201.tk7.mempool.space:3002",
|
||||
"http://node202.tk7.mempool.space:3002",
|
||||
"http://node203.tk7.mempool.space:3002",
|
||||
"http://node204.tk7.mempool.space:3002",
|
||||
"http://node205.tk7.mempool.space:3002",
|
||||
"http://node206.tk7.mempool.space:3002"
|
||||
"http://node206.tk7.mempool.space:3002",
|
||||
"http://node207.tk7.mempool.space:3002",
|
||||
"http://node208.tk7.mempool.space:3002",
|
||||
"http://node209.tk7.mempool.space:3002",
|
||||
"http://node210.tk7.mempool.space:3002",
|
||||
"http://node211.tk7.mempool.space:3002",
|
||||
"http://node212.tk7.mempool.space:3002",
|
||||
"http://node213.tk7.mempool.space:3002",
|
||||
"http://node214.tk7.mempool.space:3002"
|
||||
]
|
||||
},
|
||||
"LIGHTNING": {
|
||||
|
||||
@@ -27,30 +27,54 @@
|
||||
"ESPLORA": {
|
||||
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-testnet",
|
||||
"FALLBACK": [
|
||||
"http://node201.va1.mempool.space:3002",
|
||||
"http://node202.va1.mempool.space:3002",
|
||||
"http://node203.va1.mempool.space:3002",
|
||||
"http://node204.va1.mempool.space:3002",
|
||||
"http://node205.va1.mempool.space:3002",
|
||||
"http://node206.va1.mempool.space:3002",
|
||||
"http://node201.fmt.mempool.space:3002",
|
||||
"http://node202.fmt.mempool.space:3002",
|
||||
"http://node203.fmt.mempool.space:3002",
|
||||
"http://node204.fmt.mempool.space:3002",
|
||||
"http://node205.fmt.mempool.space:3002",
|
||||
"http://node206.fmt.mempool.space:3002",
|
||||
"http://node201.va1.mempool.space:3002",
|
||||
"http://node202.va1.mempool.space:3002",
|
||||
"http://node203.va1.mempool.space:3002",
|
||||
"http://node204.va1.mempool.space:3002",
|
||||
"http://node205.va1.mempool.space:3002",
|
||||
"http://node206.va1.mempool.space:3002",
|
||||
"http://node207.va1.mempool.space:3002",
|
||||
"http://node208.va1.mempool.space:3002",
|
||||
"http://node209.va1.mempool.space:3002",
|
||||
"http://node210.va1.mempool.space:3002",
|
||||
"http://node211.va1.mempool.space:3002",
|
||||
"http://node212.va1.mempool.space:3002",
|
||||
"http://node213.va1.mempool.space:3002",
|
||||
"http://node214.va1.mempool.space:3002",
|
||||
"http://node201.fra.mempool.space:3002",
|
||||
"http://node202.fra.mempool.space:3002",
|
||||
"http://node203.fra.mempool.space:3002",
|
||||
"http://node204.fra.mempool.space:3002",
|
||||
"http://node205.fra.mempool.space:3002",
|
||||
"http://node206.fra.mempool.space:3002",
|
||||
"http://node207.fra.mempool.space:3002",
|
||||
"http://node208.fra.mempool.space:3002",
|
||||
"http://node209.fra.mempool.space:3002",
|
||||
"http://node210.fra.mempool.space:3002",
|
||||
"http://node211.fra.mempool.space:3002",
|
||||
"http://node212.fra.mempool.space:3002",
|
||||
"http://node213.fra.mempool.space:3002",
|
||||
"http://node214.fra.mempool.space:3002",
|
||||
"http://node201.tk7.mempool.space:3002",
|
||||
"http://node202.tk7.mempool.space:3002",
|
||||
"http://node203.tk7.mempool.space:3002",
|
||||
"http://node204.tk7.mempool.space:3002",
|
||||
"http://node205.tk7.mempool.space:3002",
|
||||
"http://node206.tk7.mempool.space:3002"
|
||||
"http://node206.tk7.mempool.space:3002",
|
||||
"http://node207.tk7.mempool.space:3002",
|
||||
"http://node208.tk7.mempool.space:3002",
|
||||
"http://node209.tk7.mempool.space:3002",
|
||||
"http://node210.tk7.mempool.space:3002",
|
||||
"http://node211.tk7.mempool.space:3002",
|
||||
"http://node212.tk7.mempool.space:3002",
|
||||
"http://node213.tk7.mempool.space:3002",
|
||||
"http://node214.tk7.mempool.space:3002"
|
||||
]
|
||||
},
|
||||
"DATABASE": {
|
||||
|
||||
@@ -24,7 +24,13 @@ done
|
||||
# start nginx warm cacher
|
||||
for site in mainnet;do
|
||||
echo "starting mempool cache warmer: ${site}"
|
||||
screen -dmS "warmer-${site}" $HOME/mainnet/production/nginx-cache-warmer
|
||||
screen -dmS "warmer-${site}" $HOME/mempool/production/nginx-cache-warmer
|
||||
done
|
||||
|
||||
# start nginx hot cacher
|
||||
for site in mainnet;do
|
||||
echo "starting mempool cache heater: ${site}"
|
||||
screen -dmS "heater-${site}" $HOME/mempool/production/nginx-cache-heater
|
||||
done
|
||||
|
||||
exit 0
|
||||
|
||||
24
production/nginx-cache-heater
Executable file
24
production/nginx-cache-heater
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env zsh
|
||||
hostname=mempool.space
|
||||
|
||||
heat()
|
||||
{
|
||||
echo "$1"
|
||||
curl -i -s "$1" | head -1
|
||||
}
|
||||
|
||||
heatURLs=(
|
||||
'/api/v1/fees/recommended'
|
||||
)
|
||||
|
||||
while true
|
||||
do
|
||||
echo "starting heat cache cycle..."
|
||||
|
||||
for url in $heatURLs
|
||||
do
|
||||
heat "https://${hostname}${url}"
|
||||
done
|
||||
|
||||
sleep 0.5
|
||||
done
|
||||
@@ -22,6 +22,9 @@ location /api/v1/statistics {
|
||||
location /api/v1/mining {
|
||||
try_files /dev/null @mempool-api-v1-cache-warm;
|
||||
}
|
||||
location /api/v1/fees/recommended {
|
||||
try_files /dev/null @mempool-api-v1-cache-hot;
|
||||
}
|
||||
|
||||
# it's ok to cache blockchain data "forever", so we do 30d
|
||||
location /api/v1/block/ {
|
||||
@@ -81,6 +84,21 @@ location @mempool-api-v1-cache-forever {
|
||||
expires 30d;
|
||||
}
|
||||
|
||||
location @mempool-api-v1-cache-hot {
|
||||
proxy_pass $mempoolMainnet;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
proxy_cache_background_update on;
|
||||
proxy_cache_use_stale updating;
|
||||
proxy_cache api;
|
||||
proxy_cache_valid 200 1s;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location @mempool-api-v1-cache-warm {
|
||||
proxy_pass $mempoolMainnet;
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.63
|
||||
1.65
|
||||
|
||||
@@ -343,7 +343,7 @@ class Server {
|
||||
<meta charset="utf-8">
|
||||
<title>${ogTitle}</title>
|
||||
<link rel="canonical" href="${canonical}" />
|
||||
<meta name="description" content="The Mempool Open Source Project® - Explore the full Bitcoin ecosystem with mempool.space™"/>
|
||||
<meta name="description" content="The Mempool Open Source Project® - Explore the full Bitcoin ecosystem with mempool.space®"/>
|
||||
<meta property="og:image" content="${ogImageUrl}"/>
|
||||
<meta property="og:image:type" content="image/png"/>
|
||||
<meta property="og:image:width" content="${matchedRoute.render ? 1200 : 1000}"/>
|
||||
|
||||
Reference in New Issue
Block a user