Compare commits

..

103 Commits

Author SHA1 Message Date
wiz
ed485fa16a Release v2.4.1 2022-07-25 21:01:04 +02:00
wiz
507c8b18f4 Merge pull request #2153 from knorrium/knorrium/241_cherry_pick2
Fix block predition graph x axis labels
2022-07-23 16:14:09 +02:00
nymkappa
185223bffd Fix block predition graph x axis labels 2022-07-22 21:36:56 -07:00
wiz
dd4e120ab0 Merge pull request #2136 from knorrium/v241_patch
[Indexer] Set log level accordingly - Remove indexing ETAs
2022-07-19 09:12:39 -05:00
nymkappa
ae0789a3fa [Indexer] Set log level accordingly - Remove indexing ETAs 2022-07-18 20:08:15 -07:00
softsimon
ede5508397 Remove random scss calculation 2022-07-18 17:50:27 -05:00
wiz
a99b52a735 Merge pull request #2057 from mempool/simon/transifex-pull-0710
Pull from transifex
2022-07-10 13:57:58 +02:00
wiz
e58b71fd4f Merge pull request #2052 from mempool/nymkappa/bugfix/blocks-list-pagination-error
Fix pagination issue in blocks list
2022-07-10 13:56:38 +02:00
wiz
59d10fd3c6 Merge pull request #2051 from mempool/nymkappa/bugfix/diff-adj-table-raw-db-data
Fix diff adj table using raw db value
2022-07-10 13:14:56 +02:00
wiz
30e8b134bc Merge branch 'master' into nymkappa/bugfix/diff-adj-table-raw-db-data 2022-07-10 12:56:30 +02:00
nymkappa
cdf0fe0335 Fix first diff adjustmeent if INDEXING_BLOCK_AMOUNT is not -1 2022-07-10 12:39:22 +02:00
nymkappa
f4667c0892 Use raw diff adjustment for diff adj table widget 2022-07-10 12:39:21 +02:00
wiz
ac257b4165 Merge branch 'master' into nymkappa/bugfix/blocks-list-pagination-error 2022-07-10 12:37:51 +02:00
wiz
75ab2bc920 Merge pull request #2054 from mempool/nymkappa/bugfix/dont-insert-price-no-usd
Ignore prices without USD exchange rate
2022-07-10 12:37:43 +02:00
wiz
307c30e33b Merge pull request #2056 from mempool/nymkappa/feature/config-automatic-block-reindex
Disable automatic block re-indexing by default
2022-07-10 12:36:43 +02:00
softsimon
c7835b1326 Pull from transifex 2022-07-10 12:29:19 +02:00
wiz
7e389c8863 Merge branch 'master' into nymkappa/bugfix/dont-insert-price-no-usd 2022-07-10 12:17:08 +02:00
wiz
d697c0c45e Merge branch 'master' into nymkappa/feature/config-automatic-block-reindex 2022-07-10 12:10:38 +02:00
wiz
4fa4088694 Merge pull request #2033 from mempool/nymkappa/bugfix/cleanup-hashrate-indexing
Fix hashrate indexing, log difficulty adjustment progress
2022-07-10 12:06:34 +02:00
nymkappa
07cb4a49bc Index weekly mining pool hashrate only if there are blocks mined 2022-07-09 22:34:18 +02:00
nymkappa
067ee168dd Use oldest consecutive block timestamp as hashrate indexing limit 2022-07-09 22:11:27 +02:00
nymkappa
d8a90cce47 Use raw db hashrate instead of avg for indexing 2022-07-09 22:11:27 +02:00
nymkappa
e303a4c374 Removed hardcoded genesis timestamp
Fix duplicated genesis hashrate attempt
Add log during difficulty adjustment indexing
2022-07-09 22:11:26 +02:00
wiz
c75f485e54 Merge pull request #1999 from mempool/nymkappa/bugfix/schedule-indexing-updates
Run the mining indexer every hour to index missing/updated data
2022-07-09 20:07:21 +02:00
nymkappa
f3f0c688d8 Disable automatic block re-indexing by default 2022-07-09 19:04:35 +02:00
nymkappa
030020ea9e Run the mining indexer every hour to index missing/updated data 2022-07-09 18:25:16 +02:00
nymkappa
682682c74a Ignore prices without USD exchange rate 2022-07-09 16:13:01 +02:00
nymkappa
b5daf205a0 Fix pagination issue in blocks list 2022-07-09 16:02:43 +02:00
wiz
1037fbe52b Merge pull request #2041 from Emzy/ops/freebsd-nginx-conf
Install nginx config also for freebsd on prod install
2022-07-09 14:33:24 +02:00
wiz
051d151fb7 Merge pull request #2002 from mempool/nymkappa/feature/automatic-block-reindexing
Automatic block re-indexing upon pools.json update
2022-07-09 13:28:44 +02:00
Felipe Knorr Kuhn
7ba7440bb6 Merge branch 'master' into nymkappa/feature/automatic-block-reindexing 2022-07-09 00:04:35 +02:00
wiz
81f20e53ea Merge pull request #2043 from mempool/translations_frontend-src-locale-messages-xlf--master_nb
Translate '/frontend/src/locale/messages.xlf' in 'nb'
2022-07-08 18:56:09 +02:00
wiz
05a2c05a9e Merge pull request #2044 from mempool/translations_frontend-src-locale-messages-xlf--master_fr
Translate '/frontend/src/locale/messages.xlf' in 'fr'
2022-07-08 18:55:44 +02:00
wiz
44b1daeed2 Merge pull request #2046 from knorrium/knorrium/fix-package-lock
Fix missing dependencies
2022-07-08 18:55:17 +02:00
Felipe Knorr Kuhn
bed266abac Fix missing dependencies 2022-07-08 09:40:14 -07:00
wiz
8487548271 Merge pull request #2045 from mempool/wiz/update-nodejs-v16.16.0
Update to Node.js v16.16.0, use shared zlib for prod
2022-07-08 18:33:06 +02:00
wiz
c5e8a83ebb Update to Node.js v16.16.0, use shared zlib for prod 2022-07-08 17:22:56 +02:00
transifex-integration[bot]
b6f81bc83a Translate /frontend/src/locale/messages.xlf in nb
review completed for the source file '/frontend/src/locale/messages.xlf'
on the 'nb' language.
2022-07-08 15:07:38 +00:00
transifex-integration[bot]
294c278c42 Translate /frontend/src/locale/messages.xlf in fr
review completed for the source file '/frontend/src/locale/messages.xlf'
on the 'fr' language.
2022-07-08 15:07:38 +00:00
wiz
ff88a65936 Merge pull request #2036 from mempool/simon/amount-string-merge
Renaming value -> amount
2022-07-08 17:07:23 +02:00
wiz
76c7508224 Merge pull request #2037 from knorrium/knorrium/node_matrix
Run the CI action on 16 and 18
2022-07-08 17:06:51 +02:00
wiz
3c02131133 Merge pull request #2042 from mempool/simon/revert-angular-14-upgrade
Revert: Angular 14 upgrade
2022-07-08 17:03:49 +02:00
wiz
c89fd8c39f Merge pull request #2040 from Emzy/ops/disable-dialog
Remove the dialog for now on prod install
2022-07-08 16:55:40 +02:00
wiz
69623d71b2 Merge pull request #2039 from mempool/nymkappa/bugfix/use-core-for-block-list
Use bitcoin core to fetch blocks
2022-07-08 16:50:34 +02:00
softsimon
2d6f4d3bdb Revert: Angular 14 upgrade 2022-07-08 16:45:24 +02:00
nymkappa
97ff1e37aa Use bitcoin core instead of esplore for fetch blocks on bitcoin networks 2022-07-08 16:34:00 +02:00
Stephan Oeste
d0381e7850 Install nginx config also for freebsd on prod install 2022-07-08 15:59:39 +02:00
wiz
c4638f2ac5 Merge pull request #2035 from Emzy/ops/build-mempool
Add mempool build as final step on prod install
2022-07-08 15:34:50 +02:00
wiz
83c383b1ec Print onions on separate lines after prod install 2022-07-08 15:34:10 +02:00
Stephan Oeste
4f22864080 Add mempool build as final step on prod install 2022-07-08 15:29:59 +02:00
Stephan Oeste
92780daa78 Remove the dialog for now on prod install 2022-07-08 15:26:13 +02:00
Felipe Knorr Kuhn
a5e4b09e64 Set fail fast to false on the frontend jobs 2022-07-08 05:13:26 -07:00
Felipe Knorr Kuhn
1501dd23ab Continue running CI jobs if something fails 2022-07-08 04:49:51 -07:00
Felipe Knorr Kuhn
15ab134fa4 Run the CI action on 16 and 18 2022-07-08 04:44:19 -07:00
softsimon
05f0ba72e2 Renaming value -> amount 2022-07-08 13:31:10 +02:00
wiz
c9c5e8008c Merge pull request #2030 from mempool/simon/angular-14-upgrade
Angular 14 Upgrade
2022-07-08 12:10:10 +02:00
wiz
bdd3af6b6a Merge pull request #2032 from Emzy/ops/fix-elements-service
Fix service name to elements and add elements testnet for prod install
2022-07-08 11:57:36 +02:00
Stephan Oeste
6582c8b36f Fix service name to elements and add elements testnet for prod install 2022-07-08 11:54:26 +02:00
wiz
be838ec313 Merge pull request #2031 from Emzy/ops/fix-echo
Quote echo output in prod install
2022-07-08 11:33:03 +02:00
Stephan Oeste
92eef3a6c1 Quote echo output in prod install 2022-07-08 11:05:45 +02:00
softsimon
63939981c1 Angular 14 Upgrade 2022-07-08 10:44:07 +02:00
wiz
c9f788e3a4 Merge pull request #2012 from Emzy/ops/nginx-linux-patch
Installing Linux nginx config in prod install
2022-07-07 22:52:16 +02:00
wiz
0a866b468a Merge branch 'master' into nymkappa/feature/automatic-block-reindexing 2022-07-07 22:41:26 +02:00
wiz
8f0f755014 Merge pull request #2020 from mempool/nymkappa/bugfix/duplicate-genesis-hashrate-indexing
[Hashrate indexing] - Signet started in 2020 and not in 2009
2022-07-07 22:41:07 +02:00
nymkappa
5943b88ffe [Hashrate indexing] - Signet started in 2020 and not in 2009 2022-07-07 21:55:28 +02:00
wiz
c5dfe92e60 Merge pull request #2017 from mempool/translations_frontend-src-locale-messages-xlf--master_nb
Translate '/frontend/src/locale/messages.xlf' in 'nb'
2022-07-07 21:52:13 +02:00
wiz
753cf3cbac Merge pull request #2018 from mempool/translations_frontend-src-locale-messages-xlf--master_fr
Translate '/frontend/src/locale/messages.xlf' in 'fr'
2022-07-07 21:52:04 +02:00
wiz
4a64984d7f Merge pull request #2014 from mempool/wiz/cleanup-package-lock
Remove unused deps from backend/package-lock.json
2022-07-07 21:13:50 +02:00
Stephan Oeste
eeb84e5d42 Installing Linux nginx config in prod install 2022-07-07 21:09:00 +02:00
transifex-integration[bot]
50cd8c80d8 Translate /frontend/src/locale/messages.xlf in fr
review completed for the source file '/frontend/src/locale/messages.xlf'
on the 'fr' language.
2022-07-07 18:55:27 +00:00
wiz
567d4aebbc Merge branch 'master' into nymkappa/feature/automatic-block-reindexing 2022-07-07 20:55:08 +02:00
Felipe Knorr Kuhn
b53cc4c37c Merge branch 'master' into wiz/cleanup-package-lock 2022-07-07 11:41:59 -07:00
wiz
d46e1abd07 Merge pull request #2011 from mempool/wiz/fix-npm-install-no-optional
Fix npm install commands in Dockerfiles and ops scripts
2022-07-07 20:37:34 +02:00
wiz
0b0c0b458f Merge branch 'master' into wiz/fix-npm-install-no-optional 2022-07-07 20:36:58 +02:00
wiz
997b5a1c9d Merge pull request #2015 from mempool/simon/adding-dropdown-component-to-project
Moving ngx-bootrap-multiselect to the project
2022-07-07 20:36:35 +02:00
transifex-integration[bot]
4345661a0b Translate /frontend/src/locale/messages.xlf in nb
review completed for the source file '/frontend/src/locale/messages.xlf'
on the 'nb' language.
2022-07-07 18:30:31 +00:00
softsimon
5867c79a1f Moving ngx-bootrap-multiselect to the project 2022-07-07 20:14:58 +02:00
wiz
41f0619572 Merge pull request #2013 from Emzy/ops/set-user-ulimit
Set ulimit highter for all users in prod install
2022-07-07 19:56:38 +02:00
wiz
96b4ea6b50 Merge pull request #1998 from Emzy/ops/fix-linux-crontab
Make user crontab reliable in prod install script
2022-07-07 19:56:06 +02:00
wiz
8dda51a92a Remove unused deps from backend/package-lock.json 2022-07-07 19:33:43 +02:00
Stephan Oeste
b4bb54212c Set ulimit highter for all users in prod install 2022-07-07 19:22:47 +02:00
wiz
d57193c269 Fix npm install commands in Dockerfiles and ops scripts 2022-07-07 18:53:17 +02:00
wiz
4bc03c2d60 Merge branch 'master' into nymkappa/feature/automatic-block-reindexing 2022-07-07 18:21:04 +02:00
Stephan Oeste
bf969ec8f7 Make user crontab reliable in prod install script 2022-07-07 17:13:09 +02:00
wiz
6ead907e08 Merge pull request #1953 from mononaut/breathe-effect-framerate
limit pulsing blocks animation frame rate to 30FPS
2022-07-07 16:56:18 +02:00
wiz
243168a450 Merge branch 'master' into breathe-effect-framerate 2022-07-07 16:43:03 +02:00
wiz
06d2cf1b88 Merge pull request #2010 from mempool/simon/transifex-pull-0707
Pulled from Transifex
2022-07-07 16:42:24 +02:00
softsimon
d622162f33 Pulled from Transifex 2022-07-07 16:40:18 +02:00
wiz
12807583c2 Merge pull request #2003 from mempool/ops/fix-npm-install-prod
Add `--prod` to `npm install` in ops scripts
2022-07-07 16:36:11 +02:00
wiz
47f3d539c3 Merge pull request #2004 from mempool/nymkappa/bugfix/jumping-pagination
Set pagination font to monospace
2022-07-07 16:35:29 +02:00
wiz
993cd64126 Merge pull request #2008 from mempool/wiz/fix-mononaut-tooltip-width
Increase width of mononaut transaction tooltip
2022-07-07 16:34:56 +02:00
softsimon
a0e32ab0bd Auto resize block overview tooltip 2022-07-07 16:25:26 +02:00
wiz
409763b885 Merge pull request #2009 from mempool/simon/bisq-graphs-resize
Auto resize graphs on window resize
2022-07-07 15:58:55 +02:00
softsimon
e06819fc6f Auto resize graphs on window resize
fixes #1607
2022-07-07 15:46:13 +02:00
wiz
812783f2cd Increase width of mononaut transaction tooltip
Fixes #2007
2022-07-07 15:43:03 +02:00
Felipe Knorr Kuhn
849373a6d3 Merge branch 'master' into breathe-effect-framerate 2022-07-07 06:36:17 -07:00
nymkappa
5de559f5ad Set pagination font to monospace 2022-07-07 14:00:54 +02:00
wiz
bc068a0d9a Add --prod to npm install in ops scripts 2022-07-07 13:59:51 +02:00
nymkappa
4723a9d41b Re-index related blocks when mining pool.json changes 2022-07-07 13:41:09 +02:00
wiz
f9dfbf94ef Merge branch 'master' into breathe-effect-framerate 2022-07-06 23:45:29 +02:00
wiz
c7014fc6c8 Merge branch 'master' into breathe-effect-framerate 2022-07-06 01:24:59 +02:00
Felipe Knorr Kuhn
083634826e Merge branch 'master' into breathe-effect-framerate 2022-07-04 09:10:56 -07:00
Mononaut
2c73153db0 limit pulsing blocks animation frame rate to 30FPS
possibly resolves #1941
2022-06-29 16:09:04 +00:00
82 changed files with 17454 additions and 10882 deletions

View File

@@ -2,91 +2,93 @@ name: CI Pipeline for the Backend and Frontend
on:
pull_request:
types: [ opened, review_requested, synchronize ]
env:
NODE_VERSION: 16.15.0
types: [opened, review_requested, synchronize]
jobs:
backend:
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
strategy:
matrix:
flavor: ['dev', 'prod']
runs-on: 'ubuntu-latest'
node: ["16.16.0", "18.5.0"]
flavor: ["dev", "prod"]
fail-fast: false
runs-on: "ubuntu-latest"
name: Backend (${{ matrix.flavor }})
name: Backend (${{ matrix.flavor }}) - node ${{ matrix.node }}
steps:
- name: Checkout
uses: actions/checkout@v3
with:
path: ${{ matrix.flavor }}
path: ${{ matrix.node }}/${{ matrix.flavor }}
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
registry-url: 'https://registry.npmjs.org'
node-version: ${{ matrix.node }}
registry-url: "https://registry.npmjs.org"
- name: Install
if: ${{ matrix.flavor == 'dev'}}
run: npm ci
working-directory: ${{ matrix.flavor }}/backend
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/backend
- name: Install (Prod dependencies only)
if: ${{ matrix.flavor == 'prod'}}
run: npm ci --prod --no-optional
working-directory: ${{ matrix.flavor }}/backend
run: npm ci --omit=dev --omit=optional
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/backend
- name: Lint
- name: Lint
if: ${{ matrix.flavor == 'dev'}}
run: npm run lint
working-directory: ${{ matrix.flavor }}/backend
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/backend
# - name: Test
# run: npm run test
# - name: Test
# run: npm run test
- name: Build
run: npm run build
working-directory: ${{ matrix.flavor }}/backend
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/backend
frontend:
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
strategy:
matrix:
flavor: ['dev', 'prod']
runs-on: 'ubuntu-latest'
node: ["16.15.0", "18.5.0"]
flavor: ["dev", "prod"]
fail-fast: false
runs-on: "ubuntu-latest"
name: Frontend (${{ matrix.flavor }})
name: Frontend (${{ matrix.flavor }}) - node ${{ matrix.node }}
steps:
- name: Checkout
uses: actions/checkout@v3
with:
path: ${{ matrix.flavor }}
path: ${{ matrix.node }}/${{ matrix.flavor }}
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
registry-url: 'https://registry.npmjs.org'
node-version: ${{ matrix.node }}
registry-url: "https://registry.npmjs.org"
- name: Install (Prod dependencies only)
run: npm ci --prod --no-optional
run: npm ci --omit=dev --omit=optional
if: ${{ matrix.flavor == 'prod'}}
working-directory: ${{ matrix.flavor }}/frontend
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/frontend
- name: Install
if: ${{ matrix.flavor == 'dev'}}
run: npm ci
working-directory: ${{ matrix.flavor }}/frontend
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/frontend
- name: Lint
if: ${{ matrix.flavor == 'dev'}}
run: npm run lint
working-directory: ${{ matrix.flavor }}/frontend
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/frontend
# - name: Test
# - name: Test
# run: npm run test
- name: Build
run: npm run build
working-directory: ${{ matrix.flavor }}/frontend
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/frontend

2
.nvmrc
View File

@@ -1 +1 @@
v16.15.0
v16.16.0

View File

@@ -20,7 +20,8 @@
"EXTERNAL_MAX_RETRY": 1,
"EXTERNAL_RETRY_INTERVAL": 0,
"USER_AGENT": "mempool",
"STDOUT_LOG_MIN_PRIORITY": "debug"
"STDOUT_LOG_MIN_PRIORITY": "debug",
"AUTOMATIC_BLOCK_REINDEXING": false
},
"CORE_RPC": {
"HOST": "127.0.0.1",

View File

@@ -1,12 +1,12 @@
{
"name": "mempool-backend",
"version": "2.4.1-dev",
"version": "2.4.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "mempool-backend",
"version": "2.4.1-dev",
"version": "2.4.1",
"license": "GNU Affero General Public License v3.0",
"dependencies": {
"@mempool/electrum-client": "^1.1.7",
@@ -33,32 +33,6 @@
"prettier": "^2.7.1"
}
},
"node_modules/@babel/code-frame": {
"version": "7.12.11",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz",
"integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==",
"dev": true,
"dependencies": {
"@babel/highlight": "^7.10.4"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.12.11",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
"dev": true
},
"node_modules/@babel/highlight": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
"integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.10.4",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
}
},
"node_modules/@eslint/eslintrc": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "mempool-backend",
"version": "2.4.1-dev",
"version": "2.4.1",
"description": "Bitcoin mempool visualizer and blockchain explorer backend",
"license": "GNU Affero General Public License v3.0",
"homepage": "https://mempool.space",

View File

@@ -168,7 +168,7 @@ class Blocks {
blockExtended.extras.avgFeeRate = stats.avgfeerate;
}
if (['mainnet', 'testnet', 'signet', 'regtest'].includes(config.MEMPOOL.NETWORK)) {
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) {
let pool: PoolTag;
if (blockExtended.extras?.coinbaseTx !== undefined) {
pool = await this.$findBlockMiner(blockExtended.extras?.coinbaseTx);
@@ -280,8 +280,7 @@ class Blocks {
const runningFor = Math.max(1, Math.round((new Date().getTime() / 1000) - startedAt));
const blockPerSeconds = Math.max(1, indexedThisRun / elapsedSeconds);
const progress = Math.round(totalIndexed / indexedBlocks.length * 10000) / 100;
const timeLeft = Math.round((indexedBlocks.length - totalIndexed) / blockPerSeconds);
logger.debug(`Indexing block summary for #${block.height} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${totalIndexed}/${indexedBlocks.length} (${progress}%) | elapsed: ${runningFor} seconds | left: ~${timeLeft} seconds`);
logger.debug(`Indexing block summary for #${block.height} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${totalIndexed}/${indexedBlocks.length} (${progress}%) | elapsed: ${runningFor} seconds`);
timer = new Date().getTime() / 1000;
indexedThisRun = 0;
}
@@ -293,7 +292,11 @@ class Blocks {
totalIndexed++;
newlyIndexed++;
}
logger.notice(`Blocks summaries indexing completed: indexed ${newlyIndexed} blocks`);
if (newlyIndexed > 0) {
logger.notice(`Blocks summaries indexing completed: indexed ${newlyIndexed} blocks`);
} else {
logger.debug(`Blocks summaries indexing completed: indexed ${newlyIndexed} blocks`);
}
} catch (e) {
logger.err(`Blocks summaries indexing failed. Trying again in 10 seconds. Reason: ${(e instanceof Error ? e.message : e)}`);
throw e;
@@ -348,8 +351,7 @@ class Blocks {
const runningFor = Math.max(1, Math.round((new Date().getTime() / 1000) - startedAt));
const blockPerSeconds = Math.max(1, indexedThisRun / elapsedSeconds);
const progress = Math.round(totalIndexed / indexingBlockAmount * 10000) / 100;
const timeLeft = Math.round((indexingBlockAmount - totalIndexed) / blockPerSeconds);
logger.debug(`Indexing block #${blockHeight} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${totalIndexed}/${indexingBlockAmount} (${progress}%) | elapsed: ${runningFor} seconds | left: ~${timeLeft} seconds`);
logger.debug(`Indexing block #${blockHeight} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${totalIndexed}/${indexingBlockAmount} (${progress}%) | elapsed: ${runningFor} seconds`);
timer = new Date().getTime() / 1000;
indexedThisRun = 0;
loadingIndicators.setProgress('block-indexing', progress, false);
@@ -365,7 +367,11 @@ class Blocks {
currentBlockHeight -= chunkSize;
}
logger.notice(`Block indexing completed: indexed ${newlyIndexed} blocks`);
if (newlyIndexed > 0) {
logger.notice(`Block indexing completed: indexed ${newlyIndexed} blocks`);
} else {
logger.debug(`Block indexing completed: indexed ${newlyIndexed} blocks`);
}
loadingIndicators.setProgress('block-indexing', 100);
} catch (e) {
logger.err('Block indexing failed. Trying again in 10 seconds. Reason: ' + (e instanceof Error ? e.message : e));
@@ -405,7 +411,7 @@ class Blocks {
if (blockHeightTip >= 2016) {
const previousPeriodBlockHash = await bitcoinApi.$getBlockHash(blockHeightTip - heightDiff - 2016);
const previousPeriodBlock = await bitcoinApi.$getBlock(previousPeriodBlockHash);
const previousPeriodBlock = await bitcoinClient.getBlock(previousPeriodBlockHash)
this.previousDifficultyRetarget = (block.difficulty - previousPeriodBlock.difficulty) / previousPeriodBlock.difficulty * 100;
logger.debug(`Initial difficulty adjustment data set.`);
}
@@ -527,13 +533,15 @@ class Blocks {
}
}
const block = await bitcoinApi.$getBlock(hash);
let block = await bitcoinClient.getBlock(hash);
// Not Bitcoin network, return the block as it
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false) {
return block;
}
block = prepareBlock(block);
// Bitcoin network, add our custom data on top
const transactions = await this.$getTransactionsExtended(hash, block.height, true);
const blockExtended = await this.$getBlockExtended(block, transactions);
@@ -577,47 +585,39 @@ class Blocks {
}
public async $getBlocks(fromHeight?: number, limit: number = 15): Promise<BlockExtended[]> {
try {
let currentHeight = fromHeight !== undefined ? fromHeight : this.getCurrentBlockHeight();
const returnBlocks: BlockExtended[] = [];
if (currentHeight < 0) {
return returnBlocks;
}
if (currentHeight === 0 && Common.indexingEnabled()) {
currentHeight = await blocksRepository.$mostRecentBlockHeight();
}
// Check if block height exist in local cache to skip the hash lookup
const blockByHeight = this.getBlocks().find((b) => b.height === currentHeight);
let startFromHash: string | null = null;
if (blockByHeight) {
startFromHash = blockByHeight.id;
} else if (!Common.indexingEnabled()) {
startFromHash = await bitcoinApi.$getBlockHash(currentHeight);
}
let nextHash = startFromHash;
for (let i = 0; i < limit && currentHeight >= 0; i++) {
let block = this.getBlocks().find((b) => b.height === currentHeight);
if (block) {
returnBlocks.push(block);
} else if (Common.indexingEnabled()) {
block = await this.$indexBlock(currentHeight);
returnBlocks.push(block);
} else if (nextHash != null) {
block = prepareBlock(await bitcoinApi.$getBlock(nextHash));
nextHash = block.previousblockhash;
returnBlocks.push(block);
}
currentHeight--;
}
let currentHeight = fromHeight !== undefined ? fromHeight : await blocksRepository.$mostRecentBlockHeight();
const returnBlocks: BlockExtended[] = [];
if (currentHeight < 0) {
return returnBlocks;
} catch (e) {
throw e;
}
// Check if block height exist in local cache to skip the hash lookup
const blockByHeight = this.getBlocks().find((b) => b.height === currentHeight);
let startFromHash: string | null = null;
if (blockByHeight) {
startFromHash = blockByHeight.id;
} else if (!Common.indexingEnabled()) {
startFromHash = await bitcoinApi.$getBlockHash(currentHeight);
}
let nextHash = startFromHash;
for (let i = 0; i < limit && currentHeight >= 0; i++) {
let block = this.getBlocks().find((b) => b.height === currentHeight);
if (block) {
returnBlocks.push(block);
} else if (Common.indexingEnabled()) {
block = await this.$indexBlock(currentHeight);
returnBlocks.push(block);
} else if (nextHash != null) {
block = prepareBlock(await bitcoinClient.getBlock(nextHash));
nextHash = block.previousblockhash;
returnBlocks.push(block);
}
currentHeight--;
}
return returnBlocks;
}
public getLastDifficultyAdjustmentTime(): number {

View File

@@ -173,26 +173,25 @@ class Mining {
*/
public async $generatePoolHashrateHistory(): Promise<void> {
const now = new Date();
const lastestRunDate = await HashratesRepository.$getLatestRun('last_weekly_hashrates_indexing');
try {
const lastestRunDate = await HashratesRepository.$getLatestRun('last_weekly_hashrates_indexing');
// Run only if:
// * lastestRunDate is set to 0 (node backend restart, reorg)
// * we started a new week (around Monday midnight)
const runIndexing = lastestRunDate === 0 || now.getUTCDay() === 1 && lastestRunDate !== now.getUTCDate();
if (!runIndexing) {
return;
}
} catch (e) {
throw e;
// Run only if:
// * lastestRunDate is set to 0 (node backend restart, reorg)
// * we started a new week (around Monday midnight)
const runIndexing = lastestRunDate === 0 || now.getUTCDay() === 1 && lastestRunDate !== now.getUTCDate();
if (!runIndexing) {
return;
}
try {
const oldestConsecutiveBlockTimestamp = 1000 * (await BlocksRepository.$getOldestConsecutiveBlock()).timestamp;
const genesisBlock = await bitcoinClient.getBlock(await bitcoinClient.getBlockHash(0));
const genesisTimestamp = genesisBlock.time * 1000;
const indexedTimestamp = await HashratesRepository.$getWeeklyHashrateTimestamps();
const hashrates: any[] = [];
const genesisTimestamp = 1231006505000; // bitcoin-cli getblock 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
const lastMonday = new Date(now.setDate(now.getDate() - (now.getDay() + 6) % 7));
const lastMondayMidnight = this.getDateMidnight(lastMonday);
let toTimestamp = lastMondayMidnight.getTime();
@@ -207,7 +206,7 @@ class Mining {
logger.debug(`Indexing weekly mining pool hashrate`);
loadingIndicators.setProgress('weekly-hashrate-indexing', 0);
while (toTimestamp > genesisTimestamp) {
while (toTimestamp > genesisTimestamp && toTimestamp > oldestConsecutiveBlockTimestamp) {
const fromTimestamp = toTimestamp - 604800000;
// Skip already indexed weeks
@@ -217,14 +216,6 @@ class Mining {
continue;
}
// Check if we have blocks for the previous week (which mean that the week
// we are currently indexing has complete data)
const blockStatsPreviousWeek: any = await BlocksRepository.$blockCountBetweenTimestamp(
null, (fromTimestamp - 604800000) / 1000, (toTimestamp - 604800000) / 1000);
if (blockStatsPreviousWeek.blockCount === 0) { // We are done indexing
break;
}
const blockStats: any = await BlocksRepository.$blockCountBetweenTimestamp(
null, fromTimestamp / 1000, toTimestamp / 1000);
const lastBlockHashrate = await bitcoinClient.getNetworkHashPs(blockStats.blockCount,
@@ -232,34 +223,35 @@ class Mining {
let pools = await PoolsRepository.$getPoolsInfoBetween(fromTimestamp / 1000, toTimestamp / 1000);
const totalBlocks = pools.reduce((acc, pool) => acc + pool.blockCount, 0);
pools = pools.map((pool: any) => {
pool.hashrate = (pool.blockCount / totalBlocks) * lastBlockHashrate;
pool.share = (pool.blockCount / totalBlocks);
return pool;
});
for (const pool of pools) {
hashrates.push({
hashrateTimestamp: toTimestamp / 1000,
avgHashrate: pool['hashrate'],
poolId: pool.poolId,
share: pool['share'],
type: 'weekly',
if (totalBlocks > 0) {
pools = pools.map((pool: any) => {
pool.hashrate = (pool.blockCount / totalBlocks) * lastBlockHashrate;
pool.share = (pool.blockCount / totalBlocks);
return pool;
});
}
newlyIndexed += hashrates.length;
await HashratesRepository.$saveHashrates(hashrates);
hashrates.length = 0;
for (const pool of pools) {
hashrates.push({
hashrateTimestamp: toTimestamp / 1000,
avgHashrate: pool['hashrate'] ,
poolId: pool.poolId,
share: pool['share'],
type: 'weekly',
});
}
newlyIndexed += hashrates.length;
await HashratesRepository.$saveHashrates(hashrates);
hashrates.length = 0;
}
const elapsedSeconds = Math.max(1, Math.round((new Date().getTime() / 1000) - timer));
if (elapsedSeconds > 1) {
const runningFor = Math.max(1, Math.round((new Date().getTime() / 1000) - startedAt));
const weeksPerSeconds = Math.max(1, Math.round(indexedThisRun / elapsedSeconds));
const progress = Math.round(totalIndexed / totalWeekIndexed * 10000) / 100;
const timeLeft = Math.round((totalWeekIndexed - totalIndexed) / weeksPerSeconds);
const formattedDate = new Date(fromTimestamp).toUTCString();
logger.debug(`Getting weekly pool hashrate for ${formattedDate} | ~${weeksPerSeconds.toFixed(2)} weeks/sec | total: ~${totalIndexed}/${Math.round(totalWeekIndexed)} (${progress}%) | elapsed: ${runningFor} seconds | left: ~${timeLeft} seconds`);
logger.debug(`Getting weekly pool hashrate for ${formattedDate} | ~${weeksPerSeconds.toFixed(2)} weeks/sec | total: ~${totalIndexed}/${Math.round(totalWeekIndexed)} (${progress}%) | elapsed: ${runningFor} seconds`);
timer = new Date().getTime() / 1000;
indexedThisRun = 0;
loadingIndicators.setProgress('weekly-hashrate-indexing', progress, false);
@@ -272,6 +264,8 @@ class Mining {
await HashratesRepository.$setLatestRun('last_weekly_hashrates_indexing', new Date().getUTCDate());
if (newlyIndexed > 0) {
logger.notice(`Weekly mining pools hashrates indexing completed: indexed ${newlyIndexed}`);
} else {
logger.debug(`Weekly mining pools hashrates indexing completed: indexed ${newlyIndexed}`);
}
loadingIndicators.setProgress('weekly-hashrate-indexing', 100);
} catch (e) {
@@ -285,20 +279,19 @@ class Mining {
* [INDEXING] Generate daily hashrate data
*/
public async $generateNetworkHashrateHistory(): Promise<void> {
try {
// We only run this once a day around midnight
const latestRunDate = await HashratesRepository.$getLatestRun('last_hashrates_indexing');
const now = new Date().getUTCDate();
if (now === latestRunDate) {
return;
}
} catch (e) {
throw e;
// We only run this once a day around midnight
const latestRunDate = await HashratesRepository.$getLatestRun('last_hashrates_indexing');
const now = new Date().getUTCDate();
if (now === latestRunDate) {
return;
}
const oldestConsecutiveBlockTimestamp = 1000 * (await BlocksRepository.$getOldestConsecutiveBlock()).timestamp;
try {
const indexedTimestamp = (await HashratesRepository.$getNetworkDailyHashrate(null)).map(hashrate => hashrate.timestamp);
const genesisTimestamp = 1231006505000; // bitcoin-cli getblock 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
const genesisBlock = await bitcoinClient.getBlock(await bitcoinClient.getBlockHash(0));
const genesisTimestamp = genesisBlock.time * 1000;
const indexedTimestamp = (await HashratesRepository.$getRawNetworkDailyHashrate(null)).map(hashrate => hashrate.timestamp);
const lastMidnight = this.getDateMidnight(new Date());
let toTimestamp = Math.round(lastMidnight.getTime());
const hashrates: any[] = [];
@@ -313,7 +306,7 @@ class Mining {
logger.debug(`Indexing daily network hashrate`);
loadingIndicators.setProgress('daily-hashrate-indexing', 0);
while (toTimestamp > genesisTimestamp) {
while (toTimestamp > genesisTimestamp && toTimestamp > oldestConsecutiveBlockTimestamp) {
const fromTimestamp = toTimestamp - 86400000;
// Skip already indexed days
@@ -323,17 +316,9 @@ class Mining {
continue;
}
// Check if we have blocks for the previous day (which mean that the day
// we are currently indexing has complete data)
const blockStatsPreviousDay: any = await BlocksRepository.$blockCountBetweenTimestamp(
null, (fromTimestamp - 86400000) / 1000, (toTimestamp - 86400000) / 1000);
if (blockStatsPreviousDay.blockCount === 0 && config.MEMPOOL.NETWORK === 'mainnet') { // We are done indexing
break;
}
const blockStats: any = await BlocksRepository.$blockCountBetweenTimestamp(
null, fromTimestamp / 1000, toTimestamp / 1000);
const lastBlockHashrate = await bitcoinClient.getNetworkHashPs(blockStats.blockCount,
const lastBlockHashrate = blockStats.blockCount === 0 ? 0 : await bitcoinClient.getNetworkHashPs(blockStats.blockCount,
blockStats.lastBlockHeight);
hashrates.push({
@@ -355,9 +340,8 @@ class Mining {
const runningFor = Math.max(1, Math.round((new Date().getTime() / 1000) - startedAt));
const daysPerSeconds = Math.max(1, Math.round(indexedThisRun / elapsedSeconds));
const progress = Math.round(totalIndexed / totalDayIndexed * 10000) / 100;
const timeLeft = Math.round((totalDayIndexed - totalIndexed) / daysPerSeconds);
const formattedDate = new Date(fromTimestamp).toUTCString();
logger.debug(`Getting network daily hashrate for ${formattedDate} | ~${daysPerSeconds.toFixed(2)} days/sec | total: ~${totalIndexed}/${Math.round(totalDayIndexed)} (${progress}%) | elapsed: ${runningFor} seconds | left: ~${timeLeft} seconds`);
logger.debug(`Getting network daily hashrate for ${formattedDate} | ~${daysPerSeconds.toFixed(2)} days/sec | total: ~${totalIndexed}/${Math.round(totalDayIndexed)} (${progress}%) | elapsed: ${runningFor} seconds`);
timer = new Date().getTime() / 1000;
indexedThisRun = 0;
loadingIndicators.setProgress('daily-hashrate-indexing', progress);
@@ -369,7 +353,7 @@ class Mining {
}
// Add genesis block manually
if (toTimestamp <= genesisTimestamp && !indexedTimestamp.includes(genesisTimestamp)) {
if (config.MEMPOOL.INDEXING_BLOCKS_AMOUNT === -1 && !indexedTimestamp.includes(genesisTimestamp / 1000)) {
hashrates.push({
hashrateTimestamp: genesisTimestamp / 1000,
avgHashrate: await bitcoinClient.getNetworkHashPs(1, 1),
@@ -385,6 +369,8 @@ class Mining {
await HashratesRepository.$setLatestRun('last_hashrates_indexing', new Date().getUTCDate());
if (newlyIndexed > 0) {
logger.notice(`Daily network hashrate indexing completed: indexed ${newlyIndexed} days`);
} else {
logger.debug(`Daily network hashrate indexing completed: indexed ${newlyIndexed} days`);
}
loadingIndicators.setProgress('daily-hashrate-indexing', 100);
} catch (e) {
@@ -405,27 +391,37 @@ class Mining {
}
const blocks: any = await BlocksRepository.$getBlocksDifficulty();
let currentDifficulty = 0;
const genesisBlock = await bitcoinClient.getBlock(await bitcoinClient.getBlockHash(0));
let currentDifficulty = genesisBlock.difficulty;
let totalIndexed = 0;
if (indexedHeights[0] === false) {
if (config.MEMPOOL.INDEXING_BLOCKS_AMOUNT === -1 && indexedHeights[0] !== true) {
await DifficultyAdjustmentsRepository.$saveAdjustments({
time: 1231006505,
time: genesisBlock.time,
height: 0,
difficulty: 1.0,
difficulty: currentDifficulty,
adjustment: 0.0,
});
}
const oldestConsecutiveBlock = await BlocksRepository.$getOldestConsecutiveBlock();
if (config.MEMPOOL.INDEXING_BLOCKS_AMOUNT !== -1) {
currentDifficulty = oldestConsecutiveBlock.difficulty;
}
let totalBlockChecked = 0;
let timer = new Date().getTime() / 1000;
for (const block of blocks) {
if (block.difficulty !== currentDifficulty) {
if (block.height === 0 || indexedHeights[block.height] === true) { // Already indexed
currentDifficulty = block.difficulty;
if (indexedHeights[block.height] === true) { // Already indexed
if (block.height >= oldestConsecutiveBlock.height) {
currentDifficulty = block.difficulty;
}
continue;
}
let adjustment = block.difficulty / Math.max(1, currentDifficulty);
let adjustment = block.difficulty / currentDifficulty;
adjustment = Math.round(adjustment * 1000000) / 1000000; // Remove float point noise
await DifficultyAdjustmentsRepository.$saveAdjustments({
@@ -436,12 +432,24 @@ class Mining {
});
totalIndexed++;
currentDifficulty = block.difficulty;
if (block.height >= oldestConsecutiveBlock.height) {
currentDifficulty = block.difficulty;
}
}
totalBlockChecked++;
const elapsedSeconds = Math.max(1, Math.round((new Date().getTime() / 1000) - timer));
if (elapsedSeconds > 5) {
const progress = Math.round(totalBlockChecked / blocks.length * 100);
logger.info(`Indexing difficulty adjustment at block #${block.height} | Progress: ${progress}%`);
timer = new Date().getTime() / 1000;
}
}
if (totalIndexed > 0) {
logger.notice(`Indexed ${totalIndexed} difficulty adjustments`);
} else {
logger.debug(`Indexed ${totalIndexed} difficulty adjustments`);
}
}

View File

@@ -1,6 +1,7 @@
import DB from '../database';
import logger from '../logger';
import config from '../config';
import BlocksRepository from '../repositories/BlocksRepository';
interface Pool {
name: string;
@@ -32,7 +33,6 @@ class PoolsParser {
// First we save every entries without paying attention to pool duplication
const poolsDuplicated: Pool[] = [];
logger.debug('Parse coinbase_tags');
const coinbaseTags = Object.entries(poolsJson['coinbase_tags']);
for (let i = 0; i < coinbaseTags.length; ++i) {
poolsDuplicated.push({
@@ -43,7 +43,6 @@ class PoolsParser {
'slug': ''
});
}
logger.debug('Parse payout_addresses');
const addressesTags = Object.entries(poolsJson['payout_addresses']);
for (let i = 0; i < addressesTags.length; ++i) {
poolsDuplicated.push({
@@ -56,7 +55,6 @@ class PoolsParser {
}
// Then, we find unique mining pool names
logger.debug('Identify unique mining pools');
const poolNames: string[] = [];
for (let i = 0; i < poolsDuplicated.length; ++i) {
if (poolNames.indexOf(poolsDuplicated[i].name) === -1) {
@@ -119,8 +117,15 @@ class PoolsParser {
'slug': slug
};
if (existingPools.find((pool) => pool.name === poolNames[i]) !== undefined) {
finalPoolDataUpdate.push(poolObj);
const existingPool = existingPools.find((pool) => pool.name === poolNames[i]);
if (existingPool !== undefined) {
// Check if any data was actually updated
const equals = (a, b) =>
a.length === b.length &&
a.every((v, i) => v === b[i]);
if (!equals(JSON.parse(existingPool.addresses), poolObj.addresses) || !equals(JSON.parse(existingPool.regexes), poolObj.regexes)) {
finalPoolDataUpdate.push(poolObj);
}
} else {
logger.debug(`Add '${finalPoolName}' mining pool`);
finalPoolDataAdd.push(poolObj);
@@ -140,40 +145,51 @@ class PoolsParser {
return;
}
logger.debug(`Update pools table now`);
if (finalPoolDataAdd.length > 0 || finalPoolDataUpdate.length > 0) {
logger.debug(`Update pools table now`);
// Add new mining pools into the database
let queryAdd: string = 'INSERT INTO pools(name, link, regexes, addresses, slug) VALUES ';
for (let i = 0; i < finalPoolDataAdd.length; ++i) {
queryAdd += `('${finalPoolDataAdd[i].name}', '${finalPoolDataAdd[i].link}',
'${JSON.stringify(finalPoolDataAdd[i].regexes)}', '${JSON.stringify(finalPoolDataAdd[i].addresses)}',
${JSON.stringify(finalPoolDataAdd[i].slug)}),`;
}
queryAdd = queryAdd.slice(0, -1) + ';';
// Add new mining pools into the database
let queryAdd: string = 'INSERT INTO pools(name, link, regexes, addresses, slug) VALUES ';
for (let i = 0; i < finalPoolDataAdd.length; ++i) {
queryAdd += `('${finalPoolDataAdd[i].name}', '${finalPoolDataAdd[i].link}',
'${JSON.stringify(finalPoolDataAdd[i].regexes)}', '${JSON.stringify(finalPoolDataAdd[i].addresses)}',
${JSON.stringify(finalPoolDataAdd[i].slug)}),`;
}
queryAdd = queryAdd.slice(0, -1) + ';';
// Updated existing mining pools in the database
const updateQueries: string[] = [];
for (let i = 0; i < finalPoolDataUpdate.length; ++i) {
updateQueries.push(`
UPDATE pools
SET name='${finalPoolDataUpdate[i].name}', link='${finalPoolDataUpdate[i].link}',
regexes='${JSON.stringify(finalPoolDataUpdate[i].regexes)}', addresses='${JSON.stringify(finalPoolDataUpdate[i].addresses)}',
slug='${finalPoolDataUpdate[i].slug}'
WHERE name='${finalPoolDataUpdate[i].name}'
;`);
// Updated existing mining pools in the database
const updateQueries: string[] = [];
for (let i = 0; i < finalPoolDataUpdate.length; ++i) {
updateQueries.push(`
UPDATE pools
SET name='${finalPoolDataUpdate[i].name}', link='${finalPoolDataUpdate[i].link}',
regexes='${JSON.stringify(finalPoolDataUpdate[i].regexes)}', addresses='${JSON.stringify(finalPoolDataUpdate[i].addresses)}',
slug='${finalPoolDataUpdate[i].slug}'
WHERE name='${finalPoolDataUpdate[i].name}'
;`);
}
try {
await this.$deleteBlocskToReindex(finalPoolDataUpdate);
if (finalPoolDataAdd.length > 0) {
await DB.query({ sql: queryAdd, timeout: 120000 });
}
for (const query of updateQueries) {
await DB.query({ sql: query, timeout: 120000 });
}
await this.insertUnknownPool();
logger.info('Mining pools.json import completed');
} catch (e) {
logger.err(`Cannot import pools in the database`);
throw e;
}
}
try {
if (finalPoolDataAdd.length > 0) {
await DB.query({ sql: queryAdd, timeout: 120000 });
}
for (const query of updateQueries) {
await DB.query({ sql: query, timeout: 120000 });
}
await this.insertUnknownPool();
logger.info('Mining pools.json import completed');
} catch (e) {
logger.err(`Cannot import pools in the database`);
logger.err(`Cannot insert unknown pool in the database`);
throw e;
}
}
@@ -201,6 +217,36 @@ class PoolsParser {
logger.err('Unable to insert "Unknown" mining pool');
}
}
/**
* Delete blocks which needs to be reindexed
*/
private async $deleteBlocskToReindex(finalPoolDataUpdate: any[]) {
if (config.MEMPOOL.AUTOMATIC_BLOCK_REINDEXING === false) {
return;
}
const blockCount = await BlocksRepository.$blockCount(null, null);
if (blockCount === 0) {
return;
}
for (const updatedPool of finalPoolDataUpdate) {
const [pool]: any[] = await DB.query(`SELECT id, name from pools where slug = "${updatedPool.slug}"`);
if (pool.length > 0) {
logger.notice(`Deleting blocks from ${pool[0].name} mining pool for future re-indexing`);
await DB.query(`DELETE FROM blocks WHERE pool_id = ${pool[0].id}`);
}
}
// Ignore early days of Bitcoin as there were not mining pool yet
logger.notice('Deleting blocks with unknown mining pool from height 130635 for future re-indexing');
const [unknownPool] = await DB.query(`SELECT id from pools where slug = "unknown"`);
await DB.query(`DELETE FROM blocks WHERE pool_id = ${unknownPool[0].id} AND height > 130635`);
logger.notice('Truncating hashrates for future re-indexing');
await DB.query(`DELETE FROM hashrates`);
}
}
export default new PoolsParser();

View File

@@ -23,6 +23,7 @@ interface IConfig {
EXTERNAL_RETRY_INTERVAL: number;
USER_AGENT: string;
STDOUT_LOG_MIN_PRIORITY: 'emerg' | 'alert' | 'crit' | 'err' | 'warn' | 'notice' | 'info' | 'debug';
AUTOMATIC_BLOCK_REINDEXING: boolean;
};
ESPLORA: {
REST_API_URL: string;
@@ -113,6 +114,7 @@ const defaults: IConfig = {
'EXTERNAL_RETRY_INTERVAL': 0,
'USER_AGENT': 'mempool',
'STDOUT_LOG_MIN_PRIORITY': 'debug',
'AUTOMATIC_BLOCK_REINDEXING': false,
},
'ESPLORA': {
'REST_API_URL': 'http://127.0.0.1:3000',

View File

@@ -35,6 +35,8 @@ class Indexer {
this.runIndexer = false;
this.indexerRunning = true;
logger.debug(`Running mining indexer`);
try {
const chainValid = await blocks.$generateBlockDatabase();
if (chainValid === false) {
@@ -54,9 +56,15 @@ class Indexer {
this.indexerRunning = false;
logger.err(`Indexer failed, trying again in 10 seconds. Reason: ` + (e instanceof Error ? e.message : e));
setTimeout(() => this.reindex(), 10000);
this.indexerRunning = false;
return;
}
this.indexerRunning = false;
const runEvery = 1000 * 3600; // 1 hour
logger.debug(`Indexing completed. Next run planned at ${new Date(new Date().getTime() + runEvery).toUTCString()}`);
setTimeout(() => this.reindex(), runEvery);
}
async $resetHashratesIndexingState() {

View File

@@ -446,7 +446,7 @@ class BlocksRepository {
++idx;
}
logger.info(`${idx} blocks hash validated in ${new Date().getTime() - start} ms`);
logger.debug(`${idx} blocks hash validated in ${new Date().getTime() - start} ms`);
return true;
} catch (e) {
logger.err('Cannot validate chain of block hash. Reason: ' + (e instanceof Error ? e.message : e));
@@ -610,6 +610,24 @@ class BlocksRepository {
throw e;
}
}
/**
* Return the oldest block from a consecutive chain of block from the most recent one
*/
public async $getOldestConsecutiveBlock(): Promise<any> {
try {
const [rows]: any = await DB.query(`SELECT height, UNIX_TIMESTAMP(blockTimestamp) as timestamp, difficulty FROM blocks ORDER BY height DESC`);
for (let i = 0; i < rows.length - 1; ++i) {
if (rows[i].height - rows[i + 1].height > 1) {
return rows[i];
}
}
return rows[rows.length - 1];
} catch (e) {
logger.err('Cannot generate block size and weight history. Reason: ' + (e instanceof Error ? e.message : e));
throw e;
}
}
}
export default new BlocksRepository();

View File

@@ -46,9 +46,38 @@ class DifficultyAdjustmentsRepository {
query += ` GROUP BY UNIX_TIMESTAMP(time) DIV ${86400}`;
if (descOrder === true) {
query += ` ORDER BY time DESC`;
query += ` ORDER BY height DESC`;
} else {
query += ` ORDER BY time`;
query += ` ORDER BY height`;
}
try {
const [rows] = await DB.query(query);
return rows as IndexedDifficultyAdjustment[];
} catch (e) {
logger.err(`Cannot get difficulty adjustments from the database. Reason: ` + (e instanceof Error ? e.message : e));
throw e;
}
}
public async $getRawAdjustments(interval: string | null, descOrder: boolean = false): Promise<IndexedDifficultyAdjustment[]> {
interval = Common.getSqlInterval(interval);
let query = `SELECT
UNIX_TIMESTAMP(time) as time,
height as height,
difficulty as difficulty,
adjustment as adjustment
FROM difficulty_adjustments`;
if (interval) {
query += ` WHERE time BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
}
if (descOrder === true) {
query += ` ORDER BY height DESC`;
} else {
query += ` ORDER BY height`;
}
try {

View File

@@ -1,6 +1,5 @@
import { escape } from 'mysql2';
import { Common } from '../api/common';
import config from '../config';
import DB from '../database';
import logger from '../logger';
import PoolsRepository from './PoolsRepository';
@@ -30,6 +29,32 @@ class HashratesRepository {
}
}
public async $getRawNetworkDailyHashrate(interval: string | null): Promise<any[]> {
interval = Common.getSqlInterval(interval);
let query = `SELECT
UNIX_TIMESTAMP(hashrate_timestamp) as timestamp,
avg_hashrate as avgHashrate
FROM hashrates`;
if (interval) {
query += ` WHERE hashrate_timestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()
AND hashrates.type = 'daily'`;
} else {
query += ` WHERE hashrates.type = 'daily'`;
}
query += ` ORDER by hashrate_timestamp`;
try {
const [rows]: any[] = await DB.query(query);
return rows;
} catch (e) {
logger.err('Cannot fetch network hashrate history. Reason: ' + (e instanceof Error ? e.message : e));
throw e;
}
}
public async $getNetworkDailyHashrate(interval: string | null): Promise<any[]> {
interval = Common.getSqlInterval(interval);

View File

@@ -4,6 +4,12 @@ import { Prices } from '../tasks/price-updater';
class PricesRepository {
public async $savePrices(time: number, prices: Prices): Promise<void> {
if (prices.USD === -1) {
// Some historical price entries have not USD prices, so we just ignore them to avoid future UX issues
// As of today there are only 4 (on 2013-09-05, 2013-09-19, 2013-09-12 and 2013-09-26) so that's fine
return;
}
try {
await DB.query(`
INSERT INTO prices(time, USD, EUR, GBP, CAD, CHF, AUD, JPY)
@@ -17,17 +23,17 @@ class PricesRepository {
}
public async $getOldestPriceTime(): Promise<number> {
const [oldestRow] = await DB.query(`SELECT UNIX_TIMESTAMP(time) as time from prices ORDER BY time LIMIT 1`);
const [oldestRow] = await DB.query(`SELECT UNIX_TIMESTAMP(time) as time from prices WHERE USD != -1 ORDER BY time LIMIT 1`);
return oldestRow[0] ? oldestRow[0].time : 0;
}
public async $getLatestPriceTime(): Promise<number> {
const [oldestRow] = await DB.query(`SELECT UNIX_TIMESTAMP(time) as time from prices ORDER BY time DESC LIMIT 1`);
const [oldestRow] = await DB.query(`SELECT UNIX_TIMESTAMP(time) as time from prices WHERE USD != -1 ORDER BY time DESC LIMIT 1`);
return oldestRow[0] ? oldestRow[0].time : 0;
}
public async $getPricesTimes(): Promise<number[]> {
const [times]: any[] = await DB.query(`SELECT UNIX_TIMESTAMP(time) as time from prices`);
const [times]: any[] = await DB.query(`SELECT UNIX_TIMESTAMP(time) as time from prices WHERE USD != -1`);
return times.map(time => time.time);
}
}

View File

@@ -734,7 +734,7 @@ class Routes {
public async $getDifficultyAdjustments(req: Request, res: Response) {
try {
const difficulty = await DifficultyAdjustmentsRepository.$getAdjustments(req.params.interval, true);
const difficulty = await DifficultyAdjustmentsRepository.$getRawAdjustments(req.params.interval, true);
res.header('Pragma', 'public');
res.header('Cache-control', 'public');
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
@@ -790,7 +790,7 @@ class Routes {
public async getBlocks(req: Request, res: Response) {
try {
if (['mainnet', 'testnet', 'signet', 'regtest'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin
const height = req.params.height === undefined ? undefined : parseInt(req.params.height, 10);
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
res.json(await blocks.$getBlocks(height, 15));

View File

@@ -177,6 +177,8 @@ class PriceUpdater {
}
if (insertedCount > 0) {
logger.notice(`Inserted ${insertedCount} MtGox USD weekly price history into db`);
} else {
logger.debug(`Inserted ${insertedCount} MtGox USD weekly price history into db`);
}
// Insert Kraken weekly prices
@@ -251,6 +253,8 @@ class PriceUpdater {
if (totalInserted > 0) {
logger.notice(`Inserted ${totalInserted} hourly historical prices into the db`);
} else {
logger.debug(`Inserted ${totalInserted} hourly historical prices into the db`);
}
}
}

View File

@@ -3,14 +3,14 @@ import { BlockExtended } from '../mempool.interfaces';
export function prepareBlock(block: any): BlockExtended {
return <BlockExtended>{
id: block.id ?? block.hash, // hash for indexed block
timestamp: block.timestamp ?? block.blockTimestamp, // blockTimestamp for indexed block
timestamp: block.timestamp ?? block.time ?? block.blockTimestamp, // blockTimestamp for indexed block
height: block.height,
version: block.version,
bits: block.bits,
bits: (typeof block.bits === 'string' ? parseInt(block.bits, 16): block.bits),
nonce: block.nonce,
difficulty: block.difficulty,
merkle_root: block.merkle_root,
tx_count: block.tx_count,
merkle_root: block.merkle_root ?? block.merkleroot,
tx_count: block.tx_count ?? block.nTx,
size: block.size,
weight: block.weight,
previousblockhash: block.previousblockhash,

View File

@@ -1,4 +1,4 @@
FROM node:16.15.0-buster-slim AS builder
FROM node:16.16.0-buster-slim AS builder
ARG commitHash
ENV DOCKER_COMMIT_HASH=${commitHash}
@@ -8,10 +8,10 @@ COPY . .
RUN apt-get update
RUN apt-get install -y build-essential python3 pkg-config
RUN npm install
RUN npm install --omit=dev --omit=optional
RUN npm run build
FROM node:16.15.0-buster-slim
FROM node:16.16.0-buster-slim
WORKDIR /backend

View File

@@ -20,7 +20,8 @@
"USER_AGENT": "__MEMPOOL_USER_AGENT__",
"STDOUT_LOG_MIN_PRIORITY": "__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__",
"INDEXING_BLOCKS_AMOUNT": __MEMPOOL_INDEXING_BLOCKS_AMOUNT__,
"BLOCKS_SUMMARIES_INDEXING": __MEMPOOL_BLOCKS_SUMMARIES_INDEXING__
"BLOCKS_SUMMARIES_INDEXING": __MEMPOOL_BLOCKS_SUMMARIES_INDEXING__,
"AUTOMATIC_BLOCK_REINDEXING": __MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__
},
"CORE_RPC": {
"HOST": "__CORE_RPC_HOST__",

View File

@@ -22,6 +22,8 @@ __MEMPOOL_EXTERNAL_MAX_RETRY__=${MEMPOOL_EXTERNAL_MAX_RETRY:=1}
__MEMPOOL_EXTERNAL_RETRY_INTERVAL__=${MEMPOOL_EXTERNAL_RETRY_INTERVAL:=0}
__MEMPOOL_USER_AGENT__=${MEMPOOL_USER_AGENT:=mempool}
__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__=${MEMPOOL_STDOUT_LOG_MIN_PRIORITY:=info}
__MEMPOOL_INDEXING_BLOCKS_AMOUNT__=${MEMPOOL_INDEXING_BLOCKS_AMOUNT:=false}
__MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__=${MEMPOOL_AUTOMATIC_BLOCK_REINDEXING:=false}
# CORE_RPC
__CORE_RPC_HOST__=${CORE_RPC_HOST:=127.0.0.1}
@@ -110,6 +112,8 @@ sed -i "s!__MEMPOOL_EXTERNAL_MAX_RETRY__!${__MEMPOOL_EXTERNAL_MAX_RETRY__}!g" me
sed -i "s!__MEMPOOL_EXTERNAL_RETRY_INTERVAL__!${__MEMPOOL_EXTERNAL_RETRY_INTERVAL__}!g" mempool-config.json
sed -i "s!__MEMPOOL_USER_AGENT__!${__MEMPOOL_USER_AGENT__}!g" mempool-config.json
sed -i "s/__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__/${__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__}/g" mempool-config.json
sed -i "s/__MEMPOOL_INDEXING_BLOCKS_AMOUNT__/${__MEMPOOL_INDEXING_BLOCKS_AMOUNT__}/g" mempool-config.json
sed -i "s/__MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__/${__MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__}/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

View File

@@ -1,4 +1,4 @@
FROM node:16.15.0-buster-slim AS builder
FROM node:16.16.0-buster-slim AS builder
ARG commitHash
ENV DOCKER_COMMIT_HASH=${commitHash}
@@ -8,7 +8,7 @@ WORKDIR /build
COPY . .
RUN apt-get update
RUN apt-get install -y build-essential rsync
RUN npm i
RUN npm install --omit=dev --omit=optional
RUN npm run build
FROM nginx:1.17.8-alpine

View File

@@ -1,12 +1,12 @@
{
"name": "mempool-frontend",
"version": "2.4.1-dev",
"version": "2.4.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "mempool-frontend",
"version": "2.4.1-dev",
"version": "2.4.1",
"license": "GNU Affero General Public License v3.0",
"dependencies": {
"@angular-devkit/build-angular": "~13.3.7",
@@ -36,7 +36,6 @@
"echarts": "~5.3.2",
"express": "^4.17.1",
"lightweight-charts": "~3.8.0",
"ngx-bootrap-multiselect": "^2.0.0",
"ngx-echarts": "8.0.1",
"ngx-infinite-scroll": "^10.0.1",
"qrcode": "1.5.0",
@@ -11042,6 +11041,12 @@
"@sideway/pinpoint": "^2.0.0"
}
},
"node_modules/jquery": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
"integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==",
"peer": true
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -12788,19 +12793,6 @@
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
},
"node_modules/ngx-bootrap-multiselect": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ngx-bootrap-multiselect/-/ngx-bootrap-multiselect-2.0.0.tgz",
"integrity": "sha512-GV/2MigCS5oi6P+zWtFSmq1TLWW1kcKsJNAXLP3hHXxmY3HgMKeUPk57o3T+YHje73JRp5reXMhEIlYuoOmoRg==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/common": "^10.0.6",
"@angular/core": "^10.0.6",
"@angular/forms": "^10.0.6"
}
},
"node_modules/ngx-echarts": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-8.0.1.tgz",
@@ -13805,6 +13797,17 @@
"node": ">=10.13.0"
}
},
"node_modules/popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/portfinder": {
"version": "1.0.28",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
@@ -26083,6 +26086,12 @@
"@sideway/pinpoint": "^2.0.0"
}
},
"jquery": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
"integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==",
"peer": true
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -27418,14 +27427,6 @@
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
},
"ngx-bootrap-multiselect": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ngx-bootrap-multiselect/-/ngx-bootrap-multiselect-2.0.0.tgz",
"integrity": "sha512-GV/2MigCS5oi6P+zWtFSmq1TLWW1kcKsJNAXLP3hHXxmY3HgMKeUPk57o3T+YHje73JRp5reXMhEIlYuoOmoRg==",
"requires": {
"tslib": "^2.0.0"
}
},
"ngx-echarts": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-8.0.1.tgz",
@@ -28187,6 +28188,12 @@
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="
},
"popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"peer": true
},
"portfinder": {
"version": "1.0.28",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "mempool-frontend",
"version": "2.4.1-dev",
"version": "2.4.1",
"description": "Bitcoin mempool visualizer and blockchain explorer backend",
"license": "GNU Affero General Public License v3.0",
"homepage": "https://mempool.space",
@@ -90,7 +90,6 @@
"echarts": "~5.3.2",
"express": "^4.17.1",
"lightweight-charts": "~3.8.0",
"ngx-bootrap-multiselect": "^2.0.0",
"ngx-echarts": "8.0.1",
"ngx-infinite-scroll": "^10.0.1",
"qrcode": "1.5.0",

View File

@@ -25,7 +25,7 @@ PROXY_CONFIG = [
'!/bisq', '!/bisq/**', '!/bisq/',
'!/liquid', '!/liquid/**', '!/liquid/',
'!/liquidtestnet', '!/liquidtestnet/**', '!/liquidtestnet/',
'/testnet/api/**', '/signet/api/**', '!/api/v1/asset/**', '!/api/asset/**'
'/testnet/api/**', '/signet/api/**'
],
target: "https://mempool.space",
ws: true,
@@ -59,12 +59,6 @@ PROXY_CONFIG = [
secure: false,
changeOrigin: true
},
{
context: ['/api/v1/asset/**/icon', '/api/asset/**'],
target: "https://liquid.network",
secure: false,
changeOrigin: true
},
{
context: ['/api/liquidtestnet**', '/liquidtestnet/api/**'],
target: "https://liquid.network",

View File

@@ -3,7 +3,7 @@
<div class="d-block float-right" id="filter">
<form [formGroup]="radioGroupForm">
<ngx-bootrap-multiselect [options]="txTypeOptions" [settings]="txTypeDropdownSettings" [texts]="txTypeDropdownTexts" formControlName="txTypes"></ngx-bootrap-multiselect>
<ngx-bootstrap-multiselect [options]="txTypeOptions" [settings]="txTypeDropdownSettings" [texts]="txTypeDropdownTexts" formControlName="txTypes"></ngx-bootstrap-multiselect>
</form>
</div>

View File

@@ -7,7 +7,7 @@ import { BisqApiService } from '../bisq-api.service';
import { SeoService } from 'src/app/services/seo.service';
import { FormGroup, FormBuilder } from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router';
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts } from 'ngx-bootrap-multiselect';
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts } from 'src/app/components/ngx-bootstrap-multiselect/types'
import { WebsocketService } from 'src/app/services/websocket.service';
@Component({

View File

@@ -1,7 +1,6 @@
import { NgModule } from '@angular/core';
import { BisqRoutingModule } from './bisq.routing.module';
import { SharedModule } from '../shared/shared.module';
import { NgxBootstrapMultiselectModule } from 'ngx-bootrap-multiselect';
import { LightweightChartsComponent } from './lightweight-charts/lightweight-charts.component';
import { LightweightChartsAreaComponent } from './lightweight-charts-area/lightweight-charts-area.component';
@@ -24,6 +23,10 @@ import { BisqStatsComponent } from './bisq-stats/bisq-stats.component';
import { BsqAmountComponent } from './bsq-amount/bsq-amount.component';
import { BisqTradesComponent } from './bisq-trades/bisq-trades.component';
import { CommonModule } from '@angular/common';
import { AutofocusDirective } from '../components/ngx-bootstrap-multiselect/autofocus.directive';
import { MultiSelectSearchFilter } from '../components/ngx-bootstrap-multiselect/search-filter.pipe';
import { OffClickDirective } from '../components/ngx-bootstrap-multiselect/off-click.directive';
import { NgxDropdownMultiselectComponent } from '../components/ngx-bootstrap-multiselect/ngx-bootstrap-multiselect.component';
@NgModule({
declarations: [
@@ -44,16 +47,21 @@ import { CommonModule } from '@angular/common';
BisqMarketComponent,
BisqTradesComponent,
BisqMainDashboardComponent,
NgxDropdownMultiselectComponent,
AutofocusDirective,
OffClickDirective,
],
imports: [
CommonModule,
BisqRoutingModule,
SharedModule,
FontAwesomeModule,
NgxBootstrapMultiselectModule,
],
providers: [
BisqApiService,
MultiSelectSearchFilter,
AutofocusDirective,
OffClickDirective,
]
})
export class BisqModule {

View File

@@ -1,5 +1,5 @@
import { createChart, CrosshairMode, isBusinessDay } from 'lightweight-charts';
import { ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
import { ChangeDetectionStrategy, Component, ElementRef, HostListener, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-lightweight-charts-area',
@@ -25,6 +25,15 @@ export class LightweightChartsAreaComponent implements OnInit, OnChanges, OnDest
private element: ElementRef,
) { }
@HostListener('window:resize', ['$event'])
resizeCanvas(): void {
this.width = this.element.nativeElement.parentElement.offsetWidth;
this.chart.applyOptions({
width: this.width,
height: this.height,
});
}
ngOnInit() {
this.width = this.element.nativeElement.parentElement.offsetWidth;
this.container = document.createElement('div');

View File

@@ -1,5 +1,5 @@
import { createChart, CrosshairMode } from 'lightweight-charts';
import { ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
import { ChangeDetectionStrategy, Component, ElementRef, HostListener, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-lightweight-charts',
@@ -21,6 +21,14 @@ export class LightweightChartsComponent implements OnInit, OnChanges, OnDestroy
private element: ElementRef,
) { }
@HostListener('window:resize', ['$event'])
resizeCanvas(): void {
this.chart.applyOptions({
width: this.element.nativeElement.parentElement.offsetWidth,
height: this.height,
});
}
ngOnInit() {
this.chart = createChart(this.element.nativeElement, {
width: this.element.nativeElement.parentElement.offsetWidth,

View File

@@ -9,13 +9,13 @@
<table>
<tbody>
<tr>
<td i18n="shared.transaction">Transaction</td>
<td class="td-width" i18n="shared.transaction">Transaction</td>
<td>
<a [routerLink]="['/tx/' | relativeUrl, txid]">{{ txid | shortenString : 16}}</a>
</td>
</tr>
<tr>
<td class="td-width" i18n="transaction.value|Transaction value">Value</td>
<td class="td-width" i18n="dashboard.latest-transactions.amount">Amount</td>
<td><app-amount [satoshis]="value"></app-amount></td>
</tr>
<tr>
@@ -23,13 +23,13 @@
<td>{{ fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> &nbsp; <span class="fiat"><app-fiat [value]="fee"></app-fiat></span></td>
</tr>
<tr>
<td i18n="transaction.fee-rate|Transaction fee rate">Fee rate</td>
<td class="td-width" i18n="transaction.fee-rate|Transaction fee rate">Fee rate</td>
<td>
{{ feeRate | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
</td>
</tr>
<tr>
<td i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td>
<td class="td-width" i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td>
<td [innerHTML]="'&lrm;' + (vsize | vbytes: 2)"></td>
</tr>
</tbody>

View File

@@ -9,10 +9,14 @@
justify-content: space-between;
padding: 10px 15px;
text-align: left;
width: 320px;
min-width: 320px;
pointer-events: none;
&.clickable {
pointer-events: all;
}
}
.td-width {
padding-right: 10px;
}

View File

@@ -143,7 +143,7 @@ export class BlockPredictionGraphComponent implements OnInit {
boundaryGap: false,
axisLine: { onZero: true },
axisLabel: {
formatter: val => formatterXAxisTimeCategory(this.locale, this.timespan, parseInt(val, 10)),
formatter: val => formatterXAxisTimeCategory(this.locale, this.timespan, parseInt(val, 10) * 1000),
align: 'center',
fontSize: 11,
lineHeight: 12,

View File

@@ -114,7 +114,11 @@
}
.flashing {
animation: opacityPulse 2s ease-out;
/* force compositing */
will-change: opacity;
transform: translateZ(0);
/* effective max frame rate = (#keyframes - 1) x steps / duration */
animation: opacityPulse 2s steps(30, end);
animation-iteration-count: infinite;
opacity: 1;
}

View File

@@ -17,7 +17,11 @@
}
.flashing {
animation: opacityPulse 2s ease-out;
/* force compositing */
will-change: opacity;
transform: translateZ(0);
/* effective max frame rate = (#keyframes - 1) x steps / duration */
animation: opacityPulse 2s steps(30, end);
animation-iteration-count: infinite;
opacity: 1;
}

View File

@@ -0,0 +1,41 @@
import { Directive, ElementRef, Host, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
@Directive({
selector: '[ssAutofocus]'
})
export class AutofocusDirective implements OnInit, OnChanges {
/**
* Will set focus if set to falsy value or not set at all
*/
@Input() ssAutofocus: any;
get element(): { focus?: Function } {
return this.elemRef.nativeElement;
}
constructor(
@Host() private elemRef: ElementRef,
) { }
ngOnInit() {
this.focus();
}
ngOnChanges(changes: SimpleChanges) {
const ssAutofocusChange = changes.ssAutofocus;
if (ssAutofocusChange && !ssAutofocusChange.isFirstChange()) {
this.focus();
}
}
focus() {
if (this.ssAutofocus) {
return;
}
this.element.focus && this.element.focus();
}
}

View File

@@ -0,0 +1,48 @@
a {
outline: none !important;
}
.dropdown-inline {
display: inline-block;
}
.dropdown-toggle .caret {
margin-left: 4px;
white-space: nowrap;
display: inline-block;
}
.chunkydropdown-menu {
min-width: 20em;
}
.chunkyrow {
line-height: 2;
margin-left: 1em;
font-size: 2em;
}
.slider {
width:3.8em;
height:3.8em;
display:block;
-webkit-transition: all 0.125s linear;
-moz-transition: all 0.125s linear;
-o-transition: all 0.125s linear;
transition: all 0.125s linear;
margin-left: 0.125em;
margin-top: auto;
}
.slideron {
margin-left: 1.35em;
}
.content_wrapper{
display: table-cell;
vertical-align: middle;
}
.search-container {
padding: 0px 5px 5px 5px;
}

View File

@@ -0,0 +1,72 @@
<div *ngIf="options" class="dropdown" [ngClass]="settings.containerClasses" [class.open]="isVisible" (offClick)="clickedOutside()">
<button type="button" class="dropdown-toggle" [ngClass]="settings.buttonClasses" (click)="toggleDropdown($event)" [disabled]="disabled"
[ssAutofocus]="!focusBack">
{{ title }}
<span class="caret"></span>
</button>
<div #scroller *ngIf="isVisible" class="dropdown-menu" [ngClass]="{'chunkydropdown-menu': settings.checkedStyle == 'visual' }"
(scroll)="settings.isLazyLoad ? checkScrollPosition($event) : null" (wheel)="settings.stopScrollPropagation ? checkScrollPropagation($event, scroller) : null"
[class.pull-right]="settings.pullRight" [class.dropdown-menu-right]="settings.pullRight" [style.max-height]="settings.maxHeight"
style="display: block; height: auto; overflow-y: auto;" (keydown.tab)="focusItem(1, $event)" (keydown.shift.tab)="focusItem(-1, $event)">
<div class="input-group search-container" *ngIf="settings.enableSearch && (renderFilteredOptions.length > 1 || filterControl.value.length > 0)">
<div class="input-group-prepend">
<span class="input-group-text" id="basic-addon1">
<i class="fa fa-search" aria-hidden="true"></i>
</span>
</div>
<input type="text" class="form-control" ssAutofocus [formControl]="filterControl" [placeholder]="texts.searchPlaceholder"
class="form-control">
<div class="input-group-append" *ngIf="filterControl.value.length>0">
<button class="btn btn-default btn-secondary" type="button" (click)="clearSearch($event)">
<i class="fa fa-times"></i>
</button>
</div>
</div>
<a role="menuitem" href="javascript:;" tabindex="-1" class="dropdown-item check-control check-control-check" *ngIf="settings.showCheckAll && !disabledSelection && renderFilteredOptions.length > 1"
(click)="checkAll()">
<span style="width: 16px;"><span [ngClass]="{'glyphicon glyphicon-ok': settings.checkedStyle !== 'fontawesome','fa fa-check': settings.checkedStyle === 'fontawesome'}"></span></span>
{{ texts.checkAll }}
</a>
<a role="menuitem" href="javascript:;" tabindex="-1" class="dropdown-item check-control check-control-uncheck" *ngIf="settings.showUncheckAll && !disabledSelection && renderFilteredOptions.length > 1"
(click)="uncheckAll()">
<span style="width: 16px;"><span [ngClass]="{'glyphicon glyphicon-remove': settings.checkedStyle !== 'fontawesome','fa fa-times': settings.checkedStyle === 'fontawesome'}"></span></span>
{{ texts.uncheckAll }}
</a>
<a *ngIf="settings.showCheckAll || settings.showUncheckAll" href="javascript:;" class="dropdown-divider divider"></a>
<a *ngIf="!renderItems" href="javascript:;" class="dropdown-item empty">{{ texts.searchNoRenderText }}</a>
<a *ngIf="renderItems && !renderFilteredOptions.length" href="javascript:;" class="dropdown-item empty">{{ texts.searchEmptyResult }}</a>
<a class="dropdown-item" href="javascript:;" *ngFor="let option of renderFilteredOptions; trackBy: trackById" [class.active]="isSelected(option)"
[ngStyle]="getItemStyle(option)" [ngClass]="option.classes" [class.dropdown-header]="option.isLabel" [ssAutofocus]="option !== focusedItem"
tabindex="-1" (click)="setSelected($event, option)" (keydown.space)="setSelected($event, option)" (keydown.enter)="setSelected($event, option)">
<span *ngIf="!option.isLabel; else label" role="menuitem" tabindex="-1" [style.padding-left]="this.parents.length>0&&this.parents.indexOf(option.id)<0&&'30px'"
[ngStyle]="getItemStyleSelectionDisabled()">
<ng-container [ngSwitch]="settings.checkedStyle">
<input *ngSwitchCase="'checkboxes'" type="checkbox" [checked]="isSelected(option)" (click)="preventCheckboxCheck($event, option)"
[disabled]="isCheckboxDisabled(option)" [ngStyle]="getItemStyleSelectionDisabled()" />
<span *ngSwitchCase="'glyphicon'" style="width: 16px;" class="glyphicon" [class.glyphicon-ok]="isSelected(option)" [class.glyphicon-lock]="isCheckboxDisabled(option)"></span>
<span *ngSwitchCase="'fontawesome'" style="width: 16px;display: inline-block;">
<span *ngIf="isSelected(option)"><i class="fa fa-check" aria-hidden="true"></i></span>
<span *ngIf="isCheckboxDisabled(option)"><i class="fa fa-lock" aria-hidden="true"></i></span>
</span>
<span *ngSwitchCase="'visual'" style="display:block;float:left; border-radius: 0.2em; border: 0.1em solid rgba(44, 44, 44, 0.63);background:rgba(0, 0, 0, 0.1);width: 5.5em;">
<div class="slider" [ngClass]="{'slideron': isSelected(option)}">
<img *ngIf="option.image != null" [src]="option.image" style="height: 100%; width: 100%; object-fit: contain" />
<div *ngIf="option.image == null" style="height: 100%; width: 100%;text-align: center; display: table; background-color:rgba(0, 0, 0, 0.74)">
<div class="content_wrapper">
<span style="font-size:3em;color:white" class="glyphicon glyphicon-eye-close"></span>
</div>
</div>
</div>
</span>
</ng-container>
<span [ngClass]="{'chunkyrow': settings.checkedStyle == 'visual' }" [class.disabled]="isCheckboxDisabled(option)" [ngClass]="settings.itemClasses"
[style.font-weight]="this.parents.indexOf(option.id)>=0?'bold':'normal'">
{{ option.name }}
</span>
</span>
<ng-template #label>
<span [class.disabled]="isCheckboxDisabled(option)">{{ option.name }}</span>
</ng-template>
</a>
</div>
</div>

View File

@@ -0,0 +1,710 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DoCheck,
EventEmitter,
forwardRef,
Input,
IterableDiffers,
OnChanges,
OnDestroy,
OnInit,
Output,
SimpleChanges,
} from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
FormBuilder,
FormControl,
NG_VALUE_ACCESSOR,
Validator,
} from '@angular/forms';
import { takeUntil } from 'rxjs/operators';
import { MultiSelectSearchFilter } from './search-filter.pipe';
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts, } from './types';
import { Subject, Observable } from 'rxjs';
const MULTISELECT_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NgxDropdownMultiselectComponent),
multi: true,
};
// tslint:disable-next-line: no-conflicting-lifecycle
@Component({
selector: 'ngx-bootstrap-multiselect',
templateUrl: './ngx-bootstrap-multiselect.component.html',
styleUrls: ['./ngx-bootstrap-multiselect.component.css'],
providers: [MULTISELECT_VALUE_ACCESSOR, MultiSelectSearchFilter],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class NgxDropdownMultiselectComponent implements OnInit,
OnChanges,
DoCheck,
OnDestroy,
ControlValueAccessor,
Validator {
private localIsVisible = false;
private workerDocClicked = false;
filterControl: FormControl = this.fb.control('');
@Input() options: Array<IMultiSelectOption>;
@Input() settings: IMultiSelectSettings;
@Input() texts: IMultiSelectTexts;
@Input() disabled = false;
@Input() disabledSelection = false;
@Input() searchFunction: (str: string) => RegExp = this._escapeRegExp;
@Output() selectionLimitReached = new EventEmitter();
@Output() dropdownClosed = new EventEmitter();
@Output() dropdownOpened = new EventEmitter();
@Output() added = new EventEmitter();
@Output() removed = new EventEmitter();
@Output() lazyLoad = new EventEmitter();
@Output() filter: Observable<string> = this.filterControl.valueChanges;
get focusBack(): boolean {
return this.settings.focusBack && this._focusBack;
}
destroyed$ = new Subject<any>();
filteredOptions: IMultiSelectOption[] = [];
lazyLoadOptions: IMultiSelectOption[] = [];
renderFilteredOptions: IMultiSelectOption[] = [];
model: any[] = [];
prevModel: any[] = [];
parents: any[];
title: string;
differ: any;
numSelected = 0;
set isVisible(val: boolean) {
this.localIsVisible = val;
this.workerDocClicked = val ? false : this.workerDocClicked;
}
get isVisible(): boolean {
return this.localIsVisible;
}
renderItems = true;
checkAllSearchRegister = new Set();
checkAllStatus = false;
loadedValueIds = [];
_focusBack = false;
focusedItem: IMultiSelectOption | undefined;
defaultSettings: IMultiSelectSettings = {
closeOnClickOutside: true,
pullRight: false,
enableSearch: false,
searchRenderLimit: 0,
searchRenderAfter: 1,
searchMaxLimit: 0,
searchMaxRenderedItems: 0,
checkedStyle: 'checkboxes',
buttonClasses: 'btn btn-primary dropdown-toggle',
containerClasses: 'dropdown-inline',
selectionLimit: 0,
minSelectionLimit: 0,
closeOnSelect: false,
autoUnselect: false,
showCheckAll: false,
showUncheckAll: false,
fixedTitle: false,
dynamicTitleMaxItems: 3,
maxHeight: '300px',
isLazyLoad: false,
stopScrollPropagation: false,
loadViewDistance: 1,
selectAddedValues: false,
ignoreLabels: false,
maintainSelectionOrderInTitle: false,
focusBack: true
};
defaultTexts: IMultiSelectTexts = {
checkAll: 'Select all',
uncheckAll: 'Unselect all',
checked: 'selected',
checkedPlural: 'selected',
searchPlaceholder: 'Search...',
searchEmptyResult: 'Nothing found...',
searchNoRenderText: 'Type in search box to see results...',
defaultTitle: 'Select',
allSelected: 'All selected',
};
get searchLimit(): number | undefined {
return this.settings.searchRenderLimit;
}
get searchRenderAfter(): number | undefined {
return this.settings.searchRenderAfter;
}
get searchLimitApplied(): boolean {
return this.searchLimit > 0 && this.options.length > this.searchLimit;
}
constructor(
private fb: FormBuilder,
private searchFilter: MultiSelectSearchFilter,
differs: IterableDiffers,
private cdRef: ChangeDetectorRef
) {
this.differ = differs.find([]).create(null);
this.settings = this.defaultSettings;
this.texts = this.defaultTexts;
}
clickedOutside(): void {
if (!this.isVisible || !this.settings.closeOnClickOutside) { return; }
this.isVisible = false;
this._focusBack = true;
this.dropdownClosed.emit();
}
getItemStyle(option: IMultiSelectOption): any {
const style = {};
if (!option.isLabel) {
style['cursor'] = 'pointer';
}
if (option.disabled) {
style['cursor'] = 'default';
}
}
getItemStyleSelectionDisabled(): any {
if (this.disabledSelection) {
return { cursor: 'default' };
}
}
ngOnInit(): void {
this.title = this.texts.defaultTitle || '';
this.filterControl.valueChanges.pipe(takeUntil(this.destroyed$)).subscribe(() => {
this.updateRenderItems();
if (this.settings.isLazyLoad) {
this.load();
}
});
}
ngOnChanges(changes: SimpleChanges) {
if (changes['options']) {
this.options = this.options || [];
this.parents = this.options
.filter(option => typeof option.parentId === 'number')
.map(option => option.parentId);
this.updateRenderItems();
if (
this.settings.isLazyLoad &&
this.settings.selectAddedValues &&
this.loadedValueIds.length === 0
) {
this.loadedValueIds = this.loadedValueIds.concat(
changes.options.currentValue.map(value => value.id)
);
}
if (
this.settings.isLazyLoad &&
this.settings.selectAddedValues &&
changes.options.previousValue
) {
const addedValues = changes.options.currentValue.filter(
value => this.loadedValueIds.indexOf(value.id) === -1
);
this.loadedValueIds.concat(addedValues.map(value => value.id));
if (this.checkAllStatus) {
this.addChecks(addedValues);
} else if (this.checkAllSearchRegister.size > 0) {
this.checkAllSearchRegister.forEach((searchValue: string) =>
this.addChecks(this.applyFilters(addedValues, searchValue))
);
}
}
if (this.texts) {
this.updateTitle();
}
this.fireModelChange();
}
if (changes['settings']) {
this.settings = { ...this.defaultSettings, ...this.settings };
}
if (changes['texts']) {
this.texts = { ...this.defaultTexts, ...this.texts };
if (!changes['texts'].isFirstChange()) { this.updateTitle(); }
}
}
ngOnDestroy() {
this.destroyed$.next(false);
}
updateRenderItems() {
this.renderItems =
!this.searchLimitApplied ||
this.filterControl.value.length >= this.searchRenderAfter;
this.filteredOptions = this.applyFilters(
this.options,
this.settings.isLazyLoad ? '' : this.filterControl.value
);
this.renderFilteredOptions = this.renderItems ? this.filteredOptions : [];
this.focusedItem = undefined;
}
applyFilters(options: IMultiSelectOption[], value: string): IMultiSelectOption[] {
return this.searchFilter.transform(
options,
value,
this.settings.searchMaxLimit,
this.settings.searchMaxRenderedItems,
this.searchFunction
);
}
fireModelChange(): void {
if (this.model != this.prevModel) {
this.prevModel = this.model;
this.onModelChange(this.model);
this.onModelTouched();
this.cdRef.markForCheck();
}
}
onModelChange: Function = (_: any) => { };
onModelTouched: Function = () => { };
writeValue(value: any): void {
if (value !== undefined && value !== null) {
this.model = Array.isArray(value) ? value : [value];
this.ngDoCheck();
} else {
this.model = [];
}
}
registerOnChange(fn: Function): void {
this.onModelChange = fn;
}
registerOnTouched(fn: Function): void {
this.onModelTouched = fn;
}
setDisabledState(isDisabled: boolean) {
this.disabled = isDisabled;
}
ngDoCheck() {
const changes = this.differ.diff(this.model);
if (changes) {
this.updateNumSelected();
this.updateTitle();
}
}
validate(_c: AbstractControl): { [key: string]: any } {
if (this.model && this.model.length) {
return {
required: {
valid: false
}
};
}
if (this.options.filter(o => this.model.indexOf(o.id) && !o.disabled).length === 0) {
return {
selection: {
valid: false
}
};
}
return null;
}
registerOnValidatorChange(_fn: () => void): void {
throw new Error('Method not implemented.');
}
clearSearch(event: Event) {
this.maybeStopPropagation(event);
this.filterControl.setValue('');
}
toggleDropdown(e?: Event) {
if (this.isVisible) {
this._focusBack = true;
}
this.isVisible = !this.isVisible;
this.isVisible ? this.dropdownOpened.emit() : this.dropdownClosed.emit();
this.focusedItem = undefined;
}
closeDropdown(e?: Event) {
this.isVisible = true;
this.toggleDropdown(e);
}
isSelected(option: IMultiSelectOption): boolean {
return this.model && this.model.indexOf(option.id) > -1;
}
setSelected(_event: Event, option: IMultiSelectOption) {
if (option.isLabel) {
return;
}
if (option.disabled) {
return;
}
if (this.disabledSelection) {
return;
}
setTimeout(() => {
this.maybeStopPropagation(_event);
this.maybePreventDefault(_event);
const index = this.model.indexOf(option.id);
const isAtSelectionLimit =
this.settings.selectionLimit > 0 &&
this.model.length >= this.settings.selectionLimit;
const removeItem = (idx, id): void => {
this.model.splice(idx, 1);
this.removed.emit(id);
if (
this.settings.isLazyLoad &&
this.lazyLoadOptions.some(val => val.id === id)
) {
this.lazyLoadOptions.splice(
this.lazyLoadOptions.indexOf(
this.lazyLoadOptions.find(val => val.id === id)
),
1
);
}
};
if (index > -1) {
if (
this.settings.minSelectionLimit === undefined ||
this.numSelected > this.settings.minSelectionLimit
) {
removeItem(index, option.id);
}
const parentIndex =
option.parentId && this.model.indexOf(option.parentId);
if (parentIndex > -1) {
removeItem(parentIndex, option.parentId);
} else if (this.parents.indexOf(option.id) > -1) {
this.options
.filter(
child =>
this.model.indexOf(child.id) > -1 &&
child.parentId === option.id
)
.forEach(child =>
removeItem(this.model.indexOf(child.id), child.id)
);
}
} else if (isAtSelectionLimit && !this.settings.autoUnselect) {
this.selectionLimitReached.emit(this.model.length);
return;
} else {
const addItem = (id): void => {
this.model.push(id);
this.added.emit(id);
if (
this.settings.isLazyLoad &&
!this.lazyLoadOptions.some(val => val.id === id)
) {
this.lazyLoadOptions.push(option);
}
};
addItem(option.id);
if (!isAtSelectionLimit) {
if (option.parentId && !this.settings.ignoreLabels) {
const children = this.options.filter(
child =>
child.id !== option.id && child.parentId === option.parentId
);
if (children.every(child => this.model.indexOf(child.id) > -1)) {
addItem(option.parentId);
}
} else if (this.parents.indexOf(option.id) > -1) {
const children = this.options.filter(
child =>
this.model.indexOf(child.id) < 0 && child.parentId === option.id
);
children.forEach(child => addItem(child.id));
}
} else {
removeItem(0, this.model[0]);
}
}
if (this.settings.closeOnSelect) {
this.toggleDropdown();
}
this.model = this.model.slice();
this.fireModelChange();
}, 0)
}
updateNumSelected() {
this.numSelected =
this.model.filter(id => this.parents.indexOf(id) < 0).length || 0;
}
updateTitle() {
let numSelectedOptions = this.options.length;
if (this.settings.ignoreLabels) {
numSelectedOptions = this.options.filter(
(option: IMultiSelectOption) => !option.isLabel
).length;
}
if (this.numSelected === 0 || this.settings.fixedTitle) {
this.title = this.texts ? this.texts.defaultTitle : '';
} else if (
this.settings.displayAllSelectedText &&
this.model.length === numSelectedOptions
) {
this.title = this.texts ? this.texts.allSelected : '';
} else if (
this.settings.dynamicTitleMaxItems &&
this.settings.dynamicTitleMaxItems >= this.numSelected
) {
const useOptions =
this.settings.isLazyLoad && this.lazyLoadOptions.length
? this.lazyLoadOptions
: this.options;
let titleSelections: Array<IMultiSelectOption>;
if (this.settings.maintainSelectionOrderInTitle) {
const optionIds = useOptions.map((selectOption: IMultiSelectOption, idx: number) => selectOption.id);
titleSelections = this.model
.map((selectedId) => optionIds.indexOf(selectedId))
.filter((optionIndex) => optionIndex > -1)
.map((optionIndex) => useOptions[optionIndex]);
} else {
titleSelections = useOptions.filter((option: IMultiSelectOption) => this.model.indexOf(option.id) > -1);
}
this.title = titleSelections.map((option: IMultiSelectOption) => option.name).join(', ');
} else {
this.title =
this.numSelected +
' ' +
(this.numSelected === 1
? this.texts.checked
: this.texts.checkedPlural);
}
this.cdRef.markForCheck();
}
searchFilterApplied() {
return (
this.settings.enableSearch &&
this.filterControl.value &&
this.filterControl.value.length > 0
);
}
addChecks(options) {
const checkedOptions = options
.filter((option: IMultiSelectOption) => {
if (
!option.disabled &&
(
this.model.indexOf(option.id) === -1 &&
!(this.settings.ignoreLabels && option.isLabel)
)
) {
this.added.emit(option.id);
return true;
}
return false;
})
.map((option: IMultiSelectOption) => option.id);
this.model = this.model.concat(checkedOptions);
}
checkAll(): void {
if (!this.disabledSelection) {
this.addChecks(
!this.searchFilterApplied() ? this.options : this.filteredOptions
);
if (this.settings.isLazyLoad && this.settings.selectAddedValues) {
if (this.searchFilterApplied() && !this.checkAllStatus) {
this.checkAllSearchRegister.add(this.filterControl.value);
} else {
this.checkAllSearchRegister.clear();
this.checkAllStatus = true;
}
this.load();
}
this.fireModelChange();
}
}
uncheckAll(): void {
if (!this.disabledSelection) {
const checkedOptions = this.model;
let unCheckedOptions = !this.searchFilterApplied()
? this.model
: this.filteredOptions.map((option: IMultiSelectOption) => option.id);
// set unchecked options only to the ones that were checked
unCheckedOptions = checkedOptions.filter(item => unCheckedOptions.indexOf(item) > -1);
this.model = this.model.filter((id: number) => {
if (
(unCheckedOptions.indexOf(id) < 0 &&
this.settings.minSelectionLimit === undefined) ||
unCheckedOptions.indexOf(id) < this.settings.minSelectionLimit
) {
return true;
} else {
this.removed.emit(id);
return false;
}
});
if (this.settings.isLazyLoad && this.settings.selectAddedValues) {
if (this.searchFilterApplied()) {
if (this.checkAllSearchRegister.has(this.filterControl.value)) {
this.checkAllSearchRegister.delete(this.filterControl.value);
this.checkAllSearchRegister.forEach(function(searchTerm) {
const filterOptions = this.applyFilters(this.options.filter(option => unCheckedOptions.indexOf(option.id) > -1), searchTerm);
this.addChecks(filterOptions);
});
}
} else {
this.checkAllSearchRegister.clear();
this.checkAllStatus = false;
}
this.load();
}
this.fireModelChange();
}
}
preventCheckboxCheck(event: Event, option: IMultiSelectOption): void {
if (
option.disabled ||
(
this.settings.selectionLimit &&
!this.settings.autoUnselect &&
this.model.length >= this.settings.selectionLimit &&
this.model.indexOf(option.id) === -1 &&
this.maybePreventDefault(event)
)
) {
this.maybePreventDefault(event);
}
}
isCheckboxDisabled(option?: IMultiSelectOption): boolean {
return this.disabledSelection || option && option.disabled;
}
checkScrollPosition(ev): void {
const scrollTop = ev.target.scrollTop;
const scrollHeight = ev.target.scrollHeight;
const scrollElementHeight = ev.target.clientHeight;
const roundingPixel = 1;
const gutterPixel = 1;
if (
scrollTop >=
scrollHeight -
(1 + this.settings.loadViewDistance) * scrollElementHeight -
roundingPixel -
gutterPixel
) {
this.load();
}
}
checkScrollPropagation(ev, element): void {
const scrollTop = element.scrollTop;
const scrollHeight = element.scrollHeight;
const scrollElementHeight = element.clientHeight;
if (
(ev.deltaY > 0 && scrollTop + scrollElementHeight >= scrollHeight) ||
(ev.deltaY < 0 && scrollTop <= 0)
) {
ev = ev || window.event;
this.maybePreventDefault(ev);
ev.returnValue = false;
}
}
trackById(idx: number, selectOption: IMultiSelectOption): void {
return selectOption.id;
}
load(): void {
this.lazyLoad.emit({
length: this.options.length,
filter: this.filterControl.value,
checkAllSearches: this.checkAllSearchRegister,
checkAllStatus: this.checkAllStatus,
});
}
focusItem(dir: number, e?: Event): void {
if (!this.isVisible) {
return;
}
this.maybePreventDefault(e);
const idx = this.filteredOptions.indexOf(this.focusedItem);
if (idx === -1) {
this.focusedItem = this.filteredOptions[0];
return;
}
const nextIdx = idx + dir;
const newIdx =
nextIdx < 0
? this.filteredOptions.length - 1
: nextIdx % this.filteredOptions.length;
this.focusedItem = this.filteredOptions[newIdx];
}
private maybePreventDefault(e?: Event): void {
if (e && e.preventDefault) {
e.preventDefault();
}
}
private maybeStopPropagation(e?: Event): void {
if (e && e.stopPropagation) {
e.stopPropagation();
}
}
private _escapeRegExp(str: string): RegExp {
const regExpStr = str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
return new RegExp(regExpStr, 'i');
}
}

View File

@@ -0,0 +1,39 @@
import { Directive, HostListener } from '@angular/core';
import { EventEmitter } from '@angular/core';
import { Output } from '@angular/core';
@Directive({
// tslint:disable-next-line:directive-selector
selector: '[offClick]',
})
export class OffClickDirective {
@Output('offClick') onOffClick = new EventEmitter<any>();
private _clickEvent: MouseEvent;
private _touchEvent: TouchEvent;
@HostListener('click', ['$event'])
public onClick(event: MouseEvent): void {
this._clickEvent = event;
}
@HostListener('touchstart', ['$event'])
public onTouch(event: TouchEvent): void {
this._touchEvent = event;
}
@HostListener('document:click', ['$event'])
public onDocumentClick(event: MouseEvent): void {
if (event !== this._clickEvent) {
this.onOffClick.emit(event);
}
}
@HostListener('document:touchstart', ['$event'])
public onDocumentTouch(event: TouchEvent): void {
if (event !== this._touchEvent) {
this.onOffClick.emit(event);
}
}
}

View File

@@ -0,0 +1,130 @@
import { Pipe, PipeTransform } from '@angular/core';
import { IMultiSelectOption } from './types';
interface StringHashMap<T> {
[k: string]: T;
}
@Pipe({
name: 'searchFilter'
})
export class MultiSelectSearchFilter implements PipeTransform {
private _lastOptions: IMultiSelectOption[];
private _searchCache: StringHashMap<IMultiSelectOption[]> = {};
private _searchCacheInclusive: StringHashMap<boolean | number> = {};
private _prevSkippedItems: StringHashMap<number> = {};
transform(
options: IMultiSelectOption[],
str = '',
limit = 0,
renderLimit = 0,
searchFunction: (str: string) => RegExp,
): IMultiSelectOption[] {
str = str.toLowerCase();
// Drop cache because options were updated
if (options !== this._lastOptions) {
this._lastOptions = options;
this._searchCache = {};
this._searchCacheInclusive = {};
this._prevSkippedItems = {};
}
const filteredOpts = this._searchCache.hasOwnProperty(str)
? this._searchCache[str]
: this._doSearch(options, str, limit, searchFunction);
const isUnderLimit = options.length <= limit;
return isUnderLimit
? filteredOpts
: this._limitRenderedItems(filteredOpts, renderLimit);
}
private _getSubsetOptions(
options: IMultiSelectOption[],
prevOptions: IMultiSelectOption[],
prevSearchStr: string
) {
const prevInclusiveOrIdx = this._searchCacheInclusive[prevSearchStr];
if (prevInclusiveOrIdx === true) {
// If have previous results and it was inclusive, do only subsearch
return prevOptions;
} else if (typeof prevInclusiveOrIdx === 'number') {
// Or reuse prev results with unchecked ones
return [...prevOptions, ...options.slice(prevInclusiveOrIdx)];
}
return options;
}
private _doSearch(options: IMultiSelectOption[], str: string, limit: number, searchFunction: (str: string) => RegExp) {
const prevStr = str.slice(0, -1);
const prevResults = this._searchCache[prevStr];
const prevResultShift = this._prevSkippedItems[prevStr] || 0;
if (prevResults) {
options = this._getSubsetOptions(options, prevResults, prevStr);
}
const optsLength = options.length;
const maxFound = limit > 0 ? Math.min(limit, optsLength) : optsLength;
const regexp = searchFunction(str);
const filteredOpts: IMultiSelectOption[] = [];
let i = 0, founded = 0, removedFromPrevResult = 0;
const doesOptionMatch = (option: IMultiSelectOption) => regexp.test(option.name);
const getChildren = (option: IMultiSelectOption) =>
options.filter(child => child.parentId === option.id);
const getParent = (option: IMultiSelectOption) =>
options.find(parent => option.parentId === parent.id);
const foundFn = (item: any) => { filteredOpts.push(item); founded++; };
const notFoundFn = prevResults ? () => removedFromPrevResult++ : () => { };
for (; i < optsLength && founded < maxFound; ++i) {
const option = options[i];
const directMatch = doesOptionMatch(option);
if (directMatch) {
foundFn(option);
continue;
}
if (typeof option.parentId === 'undefined') {
const childrenMatch = getChildren(option).some(doesOptionMatch);
if (childrenMatch) {
foundFn(option);
continue;
}
}
if (typeof option.parentId !== 'undefined') {
const parentMatch = doesOptionMatch(getParent(option));
if (parentMatch) {
foundFn(option);
continue;
}
}
notFoundFn();
}
const totalIterations = i + prevResultShift;
this._searchCache[str] = filteredOpts;
this._searchCacheInclusive[str] = i === optsLength || totalIterations;
this._prevSkippedItems[str] = removedFromPrevResult + prevResultShift;
return filteredOpts;
}
private _limitRenderedItems<T>(items: T[], limit: number): T[] {
return items.length > limit && limit > 0 ? items.slice(0, limit) : items;
}
}

View File

@@ -0,0 +1,82 @@
export interface IMultiSelectOption {
id: any;
name: string;
disabled?: boolean;
isLabel?: boolean;
parentId?: any;
params?: any;
classes?: string;
image?: string;
}
export interface IMultiSelectSettings {
pullRight?: boolean;
enableSearch?: boolean;
closeOnClickOutside?: boolean;
/**
* 0 - By default
* If `enableSearch=true` and total amount of items more then `searchRenderLimit` (0 - No limit)
* then render items only when user typed more then or equal `searchRenderAfter` charachters
*/
searchRenderLimit?: number;
/**
* 3 - By default
*/
searchRenderAfter?: number;
/**
* 0 - By default
* If >0 will render only N first items
*/
searchMaxLimit?: number;
/**
* 0 - By default
* Used with searchMaxLimit to further limit rendering for optimization
* Should be less than searchMaxLimit to take effect
*/
searchMaxRenderedItems?: number;
checkedStyle?: 'checkboxes' | 'glyphicon' | 'fontawesome' | 'visual';
buttonClasses?: string;
itemClasses?: string;
containerClasses?: string;
selectionLimit?: number;
minSelectionLimit?: number;
closeOnSelect?: boolean;
autoUnselect?: boolean;
showCheckAll?: boolean;
showUncheckAll?: boolean;
fixedTitle?: boolean;
dynamicTitleMaxItems?: number;
maxHeight?: string;
displayAllSelectedText?: boolean;
isLazyLoad?: boolean;
loadViewDistance?: number;
stopScrollPropagation?: boolean;
selectAddedValues?: boolean;
/**
* false - By default
* If activated label IDs don't count and won't be written to the model.
*/
ignoreLabels?: boolean;
/**
* false - By default
* If activated, the title will show selections in the order they were selected.
*/
maintainSelectionOrderInTitle?: boolean;
/**
* @default true
* Set the focus back to the input control when the dropdown closed
*/
focusBack?: boolean;
}
export interface IMultiSelectTexts {
checkAll?: string;
uncheckAll?: string;
checked?: string;
checkedPlural?: string;
searchPlaceholder?: string;
searchEmptyResult?: string;
searchNoRenderText?: string;
defaultTitle?: string;
allSelected?: string;
}

View File

@@ -27,15 +27,7 @@ $width: 500;
$height: 500;
// Create the explosion...
$box-shadow: ();
$box-shadow2: ();
@for $i from 0 through $particles {
$box-shadow: $box-shadow,
random($width) - math.div($width, 1.2) + px
random($height) - math.div($height, 1.2) + px
hsl(random(360), 100%, 50%);
$box-shadow2: $box-shadow2, 0 0 #fff
}
@mixin keyframes ($animationName) {
@-webkit-keyframes #{$animationName} {
@content;
@@ -103,7 +95,6 @@ body {
width: 5px;
height: 5px;
border-radius: 50%;
box-shadow: $box-shadow2;
@include animation((1s bang ease-out infinite backwards, 1s gravity ease-in infinite backwards, 5s position linear infinite backwards));
}
@@ -112,9 +103,9 @@ body {
@include animation-duration((1.25s, 1.25s, 6.25s));
}
@include keyframes(bang) {
to {
box-shadow:$box-shadow;
@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
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -878,6 +878,10 @@
<context context-type="sourcefile">src/app/bisq/bisq-transactions/bisq-transactions.component.html</context>
<context context-type="linenumber">20,21</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/block-overview-tooltip/block-overview-tooltip.component.html</context>
<context context-type="linenumber">18</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dashboard/dashboard.component.html</context>
<context context-type="linenumber">124,125</context>
@@ -1263,14 +1267,14 @@
<source>Trades</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/bisq/lightweight-charts-area/lightweight-charts-area.component.ts</context>
<context context-type="linenumber">90</context>
<context context-type="linenumber">99</context>
</context-group>
</trans-unit>
<trans-unit id="bisq-graph-volume" datatype="html">
<source>Volume</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/bisq/lightweight-charts-area/lightweight-charts-area.component.ts</context>
<context context-type="linenumber">91</context>
<context context-type="linenumber">100</context>
</context-group>
</trans-unit>
<trans-unit id="4b137ec8bf73a47063740b75c0c40d5fd3c48015" datatype="html">
@@ -1918,15 +1922,6 @@
<context context-type="linenumber">264,266</context>
</context-group>
</trans-unit>
<trans-unit id="bc4c61d3713989e3c8c6610fca3ea1ca1cb19edb" datatype="html">
<source>Value</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/block-overview-tooltip/block-overview-tooltip.component.html</context>
<context context-type="linenumber">18</context>
</context-group>
<note priority="1" from="description">Transaction value</note>
<note priority="1" from="meaning">transaction.value</note>
</trans-unit>
<trans-unit id="cb1b52c13b95fa29ea4044f2bbe0ac623b890c80" datatype="html">
<source>Fee</source>
<context-group purpose="location">

File diff suppressed because it is too large Load Diff

View File

@@ -1046,3 +1046,7 @@ th {
box-shadow: -10px -15px 75px rgba(#eba814, 1);
transition: 100ms all ease-in;
}
.page-item {
font-family: monospace;
}

View File

@@ -82,11 +82,11 @@ pkg install -y zsh sudo git screen curl wget neovim rsync nginx openssl openssh-
### Node.js + npm
Build Node.js v16.15 and npm v8 from source using `nvm`:
Build Node.js v16.16.0 and npm v8 from source using `nvm`:
```
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | zsh
source $HOME/.zshrc
nvm install v16.15.0
nvm install v16.16.0 --shared-zlib
nvm alias default node
```

View File

@@ -183,6 +183,9 @@ case $OS in
TOR_PKG=tor
TOR_USER=_tor
TOR_GROUP=_tor
NGINX_USER=www
NGINX_ETC_FOLDER=/usr/local/etc/nginx
NGINX_CONFIGURATION=/usr/local/etc/nginx/nginx.conf
CERTBOT_PKG=py39-certbot
;;
@@ -197,6 +200,8 @@ case $OS in
TOR_USER=debian-tor
TOR_GROUP=debian-tor
CERTBOT_PKG=python3-certbot-nginx
NGINX_USER=www-data
NGINX_ETC_FOLDER=/etc/nginx
NGINX_CONFIGURATION=/etc/nginx/nginx.conf
;;
esac
@@ -646,193 +651,193 @@ ext4CreateDir()
# does bitcoin exist?
##########
# dialog #
##########
: ${DIALOG=dialog}
: ${DIALOG_OK=0}
: ${DIALOG_CANCEL=1}
: ${DIALOG_HELP=2}
: ${DIALOG_EXTRA=3}
: ${DIALOG_ITEM_HELP=4}
: ${DIALOG_ESC=255}
: ${SIG_OFFNE=0}
: ${SIG_HUP=1}
: ${SIG_INT=2}
: ${SIG_QUIT=3}
: ${SIG_KILL=9}
: ${SIG_TERM=15}
input=`tempfile 2>/dev/null` || input=/tmp/input$$
output=`tempfile 2>/dev/null` || output=/tmp/test$$
trap "rm -f $input $output" $SIG_OFFNE $SIG_HUP $SIG_INT $SIG_TRAP $SIG_TERM
DIALOG_ERROR=254
export DIALOG_ERROR
backtitle="Mempool Fullnode Installer"
title="Mempool Fullnode Installer"
returncode=0
#################
# dialog part 1 #
#################
$CUT >$input <<-EOF
Tor:Enable Tor v3 HS Onion:ON
Certbot:Enable HTTPS using Certbot:ON
Mainnet:Enable Bitcoin Mainnet:ON
Mainnet-Minfee:Enable Bitcoin Mainnet Minfee:ON
Testnet:Enable Bitcoin Testnet:ON
Liquid:Enable Elements Liquid:ON
Bisq:Enable Bisq:ON
Lightmode:Enable Electrs Lightmode to save disk space:ON
Smalldisk:Disable Electrs Compaction to save disk space:ON
Firewall:Enable Firewall:ON
EOF
cat $input | sed -e 's/^/"/' -e 's/:/" "/g' -e 's/$/"/' >$output
cat $output >$input
$DIALOG --backtitle "${backtitle}" \
--title "${title}" "$@" \
--checklist "Toggle the features below to configure your fullnode:\n" \
20 80 10 \
--file $input 2> $output
retval=$?
tempfile=$output
if [ $retval != $DIALOG_OK ];then
echo "Installation aborted."
exit 1
fi
if grep Tor $tempfile >/dev/null 2>&1;then
TOR_INSTALL=ON
else
TOR_INSTALL=OFF
fi
if grep Certbot $tempfile >/dev/null 2>&1;then
CERTBOT_INSTALL=ON
else
CERTBOT_INSTALL=OFF
fi
if grep Mainnet $tempfile >/dev/null 2>&1;then
BITCOIN_MAINNET_ENABLE=ON
else
BITCOIN_MAINNET_ENABLE=OFF
fi
if grep Mainnet-Minfee $tempfile >/dev/null 2>&1;then
BITCOIN_MAINNET_MINFEE_ENABLE=ON
else
BITCOIN_MAINNET_MINFEE_ENABLE=OFF
fi
if grep Testnet $tempfile >/dev/null 2>&1;then
BITCOIN_TESTNET_ENABLE=ON
else
BITCOIN_TESTNET_ENABLE=OFF
fi
if grep Liquid $tempfile >/dev/null 2>&1;then
ELEMENTS_INSTALL=ON
ELEMENTS_LIQUID_ENABLE=ON
else
ELEMENTS_INSTALL=OFF
ELEMENTS_LIQUID_ENABLE=OFF
fi
if grep Bisq $tempfile >/dev/null 2>&1;then
BISQ_INSTALL=ON
BISQ_MAINNET_ENABLE=ON
else
BISQ_INSTALL=OFF
BISQ_MAINNET_ENABLE=OFF
fi
if grep Lightmode $tempfile >/dev/null 2>&1;then
BITCOIN_ELECTRS_LIGHT_MODE=ON
else
BITCOIN_ELECTRS_LIGHT_MODE=OFF
fi
if grep Smalldisk $tempfile >/dev/null 2>&1;then
BITCOIN_ELECTRS_LIGHT_MODE=ON
else
BITCOIN_ELECTRS_LIGHT_MODE=OFF
fi
#################
# dialog part 2 #
#################
$DIALOG --cr-wrap \
--title "INPUT BOX" --clear \
--inputbox "$@" \
"Enter the FQDN hostname for obtaining an SSL certificate using Certbot:" 0 0 "${HOSTNAME}" 2> $tempfile
HOSTNAME=$(cat $tempfile)
#################
# dialog part 3 #
#################
# --form text height width formheight
# [ label y x item y x flen ilen ]
#"BISQ_BLOCKNOTIFY_HOST" 0 1 "${BISQ_BLOCKNOTIFY_HOST}" 0 30 0 0 \
$DIALOG --ok-label "Submit" \
--backtitle "$backtitle" "$@" \
--form "Your fullnode will be installed as follows:" 0 0 0 \
"BISQ_LATEST_RELEASE" 1 1 "${BISQ_LATEST_RELEASE}" 1 35 35 0 \
"BISQ_REPO_BRANCH" 2 1 "${BISQ_REPO_BRANCH}" 2 35 35 0 \
"BISQ_REPO_NAME" 3 1 "${BISQ_REPO_NAME}" 3 35 35 0 \
"BISQ_REPO_URL" 4 1 "${BISQ_REPO_URL}" 4 35 35 0 \
"BITCOIN_ELECTRS_LATEST_RELEASE" 5 1 "${BITCOIN_ELECTRS_LATEST_RELEASE}" 5 35 35 0 \
"BITCOIN_ELECTRS_LIGHT_MODE" 6 1 "${BITCOIN_ELECTRS_LIGHT_MODE}" 6 35 35 0 \
"BITCOIN_ELECTRS_REPO_BRANCH" 7 1 "${BITCOIN_ELECTRS_REPO_BRANCH}" 7 35 35 0 \
"BITCOIN_ELECTRS_REPO_NAME" 8 1 "${BITCOIN_ELECTRS_REPO_NAME}" 8 35 35 0 \
"BITCOIN_ELECTRS_REPO_URL" 9 1 "${BITCOIN_ELECTRS_REPO_URL}" 9 35 35 0 \
"BITCOIN_LATEST_RELEASE" 10 1 "${BITCOIN_LATEST_RELEASE}" 10 35 35 0 \
"BITCOIN_MAINNET_ENABLE" 11 1 "${BITCOIN_MAINNET_ENABLE}" 11 35 35 0 \
"BITCOIN_REPO_BRANCH" 12 1 "${BITCOIN_REPO_BRANCH}" 12 35 35 0 \
"BITCOIN_REPO_NAME" 13 1 "${BITCOIN_REPO_NAME}" 13 35 35 0 \
"BITCOIN_REPO_URL" 14 1 "${BITCOIN_REPO_URL}" 14 35 35 0 \
"BITCOIN_TESTNET_ENABLE" 15 1 "${BITCOIN_TESTNET_ENABLE}" 15 35 35 0 \
"ELEMENTS_INSTALL" 16 1 "${ELEMENTS_INSTALL}" 16 35 35 0 \
"ELEMENTS_LATEST_RELEASE" 17 1 "${ELEMENTS_LATEST_RELEASE}" 17 35 35 0 \
"ELEMENTS_LIQUID_ENABLE" 18 1 "${ELEMENTS_LIQUID_ENABLE}" 18 35 35 0 \
"ELEMENTS_REPO_BRANCH" 19 1 "${ELEMENTS_REPO_BRANCH}" 19 35 35 0 \
"ELEMENTS_REPO_NAME" 20 1 "${ELEMENTS_REPO_NAME}" 20 35 35 0 \
"ELEMENTS_REPO_URL" 21 1 "${ELEMENTS_REPO_URL}" 21 35 35 0 \
"MEMPOOL_LATEST_RELEASE" 22 1 "${MEMPOOL_LATEST_RELEASE}" 22 35 35 0 \
"MEMPOOL_LIQUID_HTTP_HOST" 23 1 "${MEMPOOL_LIQUID_HTTP_HOST}" 23 35 35 0 \
"MEMPOOL_LIQUID_HTTP_PORT" 24 1 "${MEMPOOL_LIQUID_HTTP_PORT}" 24 35 35 0 \
"MEMPOOL_MAINNET_HTTP_HOST" 25 1 "${MEMPOOL_MAINNET_HTTP_HOST}" 25 35 35 0 \
"MEMPOOL_MAINNET_HTTP_PORT" 26 1 "${MEMPOOL_MAINNET_HTTP_PORT}" 26 35 35 0 \
"MEMPOOL_REPO_BRANCH" 27 1 "${MEMPOOL_REPO_BRANCH}" 27 35 35 0 \
"MEMPOOL_REPO_NAME" 28 1 "${MEMPOOL_REPO_NAME}" 28 35 35 0 \
"MEMPOOL_REPO_URL" 29 1 "${MEMPOOL_REPO_URL}" 29 35 35 0 \
"MEMPOOL_TESTNET_HTTP_HOST" 30 1 "${MEMPOOL_TESTNET_HTTP_HOST}" 30 35 35 0 \
"MEMPOOL_TESTNET_HTTP_PORT" 31 1 "${MEMPOOL_TESTNET_HTTP_PORT}" 31 35 35 0 \
"MEMPOOL_TOR_HS" 32 1 "${MEMPOOL_TOR_HS}" 32 35 35 0 \
"HOSTNAME" 33 1 "${HOSTNAME}" 33 35 35 0 \
"TOR_INSTALL" 34 1 "${TOR_INSTALL}" 34 35 35 0 \
"CERTBOT_INSTALL" 35 1 "${CERTBOT_INSTALL}" 35 35 35 0 \
2> $tempfile
retval=$?
if [ $retval != $DIALOG_OK ];then
echo "Installation aborted."
exit 1
fi
###########
## dialog #
###########
#
#: ${DIALOG=dialog}
#
#: ${DIALOG_OK=0}
#: ${DIALOG_CANCEL=1}
#: ${DIALOG_HELP=2}
#: ${DIALOG_EXTRA=3}
#: ${DIALOG_ITEM_HELP=4}
#: ${DIALOG_ESC=255}
#
#: ${SIG_OFFNE=0}
#: ${SIG_HUP=1}
#: ${SIG_INT=2}
#: ${SIG_QUIT=3}
#: ${SIG_KILL=9}
#: ${SIG_TERM=15}
#
#input=`tempfile 2>/dev/null` || input=/tmp/input$$
#output=`tempfile 2>/dev/null` || output=/tmp/test$$
#trap "rm -f $input $output" $SIG_OFFNE $SIG_HUP $SIG_INT $SIG_TRAP $SIG_TERM
#
#DIALOG_ERROR=254
#export DIALOG_ERROR
#
#backtitle="Mempool Fullnode Installer"
#title="Mempool Fullnode Installer"
#returncode=0
#
##################
## dialog part 1 #
##################
#
#$CUT >$input <<-EOF
#Tor:Enable Tor v3 HS Onion:ON
#Certbot:Enable HTTPS using Certbot:ON
#Mainnet:Enable Bitcoin Mainnet:ON
#Mainnet-Minfee:Enable Bitcoin Mainnet Minfee:ON
#Testnet:Enable Bitcoin Testnet:ON
#Liquid:Enable Elements Liquid:ON
#Bisq:Enable Bisq:ON
#Lightmode:Enable Electrs Lightmode to save disk space:ON
#Smalldisk:Disable Electrs Compaction to save disk space:ON
#Firewall:Enable Firewall:ON
#EOF
#
#cat $input | sed -e 's/^/"/' -e 's/:/" "/g' -e 's/$/"/' >$output
#cat $output >$input
#
#$DIALOG --backtitle "${backtitle}" \
# --title "${title}" "$@" \
# --checklist "Toggle the features below to configure your fullnode:\n" \
# 20 80 10 \
# --file $input 2> $output
#
#retval=$?
#
#tempfile=$output
#if [ $retval != $DIALOG_OK ];then
# echo "Installation aborted."
# exit 1
#fi
#
#if grep Tor $tempfile >/dev/null 2>&1;then
# TOR_INSTALL=ON
#else
# TOR_INSTALL=OFF
#fi
#
#if grep Certbot $tempfile >/dev/null 2>&1;then
# CERTBOT_INSTALL=ON
#else
# CERTBOT_INSTALL=OFF
#fi
#
#if grep Mainnet $tempfile >/dev/null 2>&1;then
# BITCOIN_MAINNET_ENABLE=ON
#else
# BITCOIN_MAINNET_ENABLE=OFF
#fi
#
#if grep Mainnet-Minfee $tempfile >/dev/null 2>&1;then
# BITCOIN_MAINNET_MINFEE_ENABLE=ON
#else
# BITCOIN_MAINNET_MINFEE_ENABLE=OFF
#fi
#
#if grep Testnet $tempfile >/dev/null 2>&1;then
# BITCOIN_TESTNET_ENABLE=ON
#else
# BITCOIN_TESTNET_ENABLE=OFF
#fi
#
#if grep Liquid $tempfile >/dev/null 2>&1;then
# ELEMENTS_INSTALL=ON
# ELEMENTS_LIQUID_ENABLE=ON
#else
# ELEMENTS_INSTALL=OFF
# ELEMENTS_LIQUID_ENABLE=OFF
#fi
#
#if grep Bisq $tempfile >/dev/null 2>&1;then
# BISQ_INSTALL=ON
# BISQ_MAINNET_ENABLE=ON
#else
# BISQ_INSTALL=OFF
# BISQ_MAINNET_ENABLE=OFF
#fi
#
#if grep Lightmode $tempfile >/dev/null 2>&1;then
# BITCOIN_ELECTRS_LIGHT_MODE=ON
#else
# BITCOIN_ELECTRS_LIGHT_MODE=OFF
#fi
#
#if grep Smalldisk $tempfile >/dev/null 2>&1;then
# BITCOIN_ELECTRS_LIGHT_MODE=ON
#else
# BITCOIN_ELECTRS_LIGHT_MODE=OFF
#fi
#
##################
## dialog part 2 #
##################
#
#$DIALOG --cr-wrap \
# --title "INPUT BOX" --clear \
# --inputbox "$@" \
#"Enter the FQDN hostname for obtaining an SSL certificate using Certbot:" 0 0 "${HOSTNAME}" 2> $tempfile
#HOSTNAME=$(cat $tempfile)
#
##################
## dialog part 3 #
##################
#
## --form text height width formheight
## [ label y x item y x flen ilen ]
# #"BISQ_BLOCKNOTIFY_HOST" 0 1 "${BISQ_BLOCKNOTIFY_HOST}" 0 30 0 0 \
#
#$DIALOG --ok-label "Submit" \
# --backtitle "$backtitle" "$@" \
# --form "Your fullnode will be installed as follows:" 0 0 0 \
# "BISQ_LATEST_RELEASE" 1 1 "${BISQ_LATEST_RELEASE}" 1 35 35 0 \
# "BISQ_REPO_BRANCH" 2 1 "${BISQ_REPO_BRANCH}" 2 35 35 0 \
# "BISQ_REPO_NAME" 3 1 "${BISQ_REPO_NAME}" 3 35 35 0 \
# "BISQ_REPO_URL" 4 1 "${BISQ_REPO_URL}" 4 35 35 0 \
# "BITCOIN_ELECTRS_LATEST_RELEASE" 5 1 "${BITCOIN_ELECTRS_LATEST_RELEASE}" 5 35 35 0 \
# "BITCOIN_ELECTRS_LIGHT_MODE" 6 1 "${BITCOIN_ELECTRS_LIGHT_MODE}" 6 35 35 0 \
# "BITCOIN_ELECTRS_REPO_BRANCH" 7 1 "${BITCOIN_ELECTRS_REPO_BRANCH}" 7 35 35 0 \
# "BITCOIN_ELECTRS_REPO_NAME" 8 1 "${BITCOIN_ELECTRS_REPO_NAME}" 8 35 35 0 \
# "BITCOIN_ELECTRS_REPO_URL" 9 1 "${BITCOIN_ELECTRS_REPO_URL}" 9 35 35 0 \
# "BITCOIN_LATEST_RELEASE" 10 1 "${BITCOIN_LATEST_RELEASE}" 10 35 35 0 \
# "BITCOIN_MAINNET_ENABLE" 11 1 "${BITCOIN_MAINNET_ENABLE}" 11 35 35 0 \
# "BITCOIN_REPO_BRANCH" 12 1 "${BITCOIN_REPO_BRANCH}" 12 35 35 0 \
# "BITCOIN_REPO_NAME" 13 1 "${BITCOIN_REPO_NAME}" 13 35 35 0 \
# "BITCOIN_REPO_URL" 14 1 "${BITCOIN_REPO_URL}" 14 35 35 0 \
# "BITCOIN_TESTNET_ENABLE" 15 1 "${BITCOIN_TESTNET_ENABLE}" 15 35 35 0 \
# "ELEMENTS_INSTALL" 16 1 "${ELEMENTS_INSTALL}" 16 35 35 0 \
# "ELEMENTS_LATEST_RELEASE" 17 1 "${ELEMENTS_LATEST_RELEASE}" 17 35 35 0 \
# "ELEMENTS_LIQUID_ENABLE" 18 1 "${ELEMENTS_LIQUID_ENABLE}" 18 35 35 0 \
# "ELEMENTS_REPO_BRANCH" 19 1 "${ELEMENTS_REPO_BRANCH}" 19 35 35 0 \
# "ELEMENTS_REPO_NAME" 20 1 "${ELEMENTS_REPO_NAME}" 20 35 35 0 \
# "ELEMENTS_REPO_URL" 21 1 "${ELEMENTS_REPO_URL}" 21 35 35 0 \
# "MEMPOOL_LATEST_RELEASE" 22 1 "${MEMPOOL_LATEST_RELEASE}" 22 35 35 0 \
# "MEMPOOL_LIQUID_HTTP_HOST" 23 1 "${MEMPOOL_LIQUID_HTTP_HOST}" 23 35 35 0 \
# "MEMPOOL_LIQUID_HTTP_PORT" 24 1 "${MEMPOOL_LIQUID_HTTP_PORT}" 24 35 35 0 \
# "MEMPOOL_MAINNET_HTTP_HOST" 25 1 "${MEMPOOL_MAINNET_HTTP_HOST}" 25 35 35 0 \
# "MEMPOOL_MAINNET_HTTP_PORT" 26 1 "${MEMPOOL_MAINNET_HTTP_PORT}" 26 35 35 0 \
# "MEMPOOL_REPO_BRANCH" 27 1 "${MEMPOOL_REPO_BRANCH}" 27 35 35 0 \
# "MEMPOOL_REPO_NAME" 28 1 "${MEMPOOL_REPO_NAME}" 28 35 35 0 \
# "MEMPOOL_REPO_URL" 29 1 "${MEMPOOL_REPO_URL}" 29 35 35 0 \
# "MEMPOOL_TESTNET_HTTP_HOST" 30 1 "${MEMPOOL_TESTNET_HTTP_HOST}" 30 35 35 0 \
# "MEMPOOL_TESTNET_HTTP_PORT" 31 1 "${MEMPOOL_TESTNET_HTTP_PORT}" 31 35 35 0 \
# "MEMPOOL_TOR_HS" 32 1 "${MEMPOOL_TOR_HS}" 32 35 35 0 \
# "HOSTNAME" 33 1 "${HOSTNAME}" 33 35 35 0 \
# "TOR_INSTALL" 34 1 "${TOR_INSTALL}" 34 35 35 0 \
# "CERTBOT_INSTALL" 35 1 "${CERTBOT_INSTALL}" 35 35 35 0 \
#2> $tempfile
#
#retval=$?
#
#if [ $retval != $DIALOG_OK ];then
# echo "Installation aborted."
# exit 1
#fi
############################
# START DOING ACTUAL STUFF #
@@ -841,8 +846,6 @@ fi
date
echo "[*] Mempool installation script for ${OS}"
set -x
###################################
# create filesystems if necessary #
###################################
@@ -906,7 +909,7 @@ echo "[*] Installing nvm.sh from GitHub"
osSudo "${MEMPOOL_USER}" sh -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | zsh'
echo "[*] Building NodeJS via nvm.sh"
osSudo "${MEMPOOL_USER}" zsh -c 'source ~/.zshrc ; nvm install v16.15.0'
osSudo "${MEMPOOL_USER}" zsh -c 'source ~/.zshrc ; nvm install v16.16.0 --shared-zlib'
####################
# Tor installation #
@@ -925,6 +928,8 @@ if [ "${TOR_INSTALL}" = ON ];then
osSudo "${ROOT_USER}" /bin/sh -c "echo HiddenServiceDir ${TOR_RESOURCES}/${MEMPOOL_TOR_HS}/ >> ${TOR_CONFIGURATION}"
osSudo "${ROOT_USER}" /bin/sh -c "echo HiddenServicePort 80 127.0.0.1:81 >> ${TOR_CONFIGURATION}"
osSudo "${ROOT_USER}" /bin/sh -c "echo HiddenServiceVersion 3 >> ${TOR_CONFIGURATION}"
else
osSudo "${ROOT_USER}" sed -i.orig "s!__TOR_RESOURCES__!${TOR_RESOURCES}!" "${TOR_CONFIGURATION}"
fi
case $OS in
@@ -936,7 +941,7 @@ if [ "${TOR_INSTALL}" = ON ];then
# start tor now so it can bootstrap in time for bitcoin starting a few mins later
echo "[*] Starting Tor service"
osSudo "${ROOT_USER}" service tor start
osSudo "${ROOT_USER}" service tor restart
fi
########################
@@ -1282,7 +1287,25 @@ if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
;;
Debian)
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/linux/liquid.service" "${DEBIAN_SERVICE_HOME}"
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/linux/elements-liquid.service" "${DEBIAN_SERVICE_HOME}"
;;
esac
fi
#######################################
# Bitcoin instance for Liquid Testnet #
#######################################
if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
echo "[*] Installing Bitcoin Liquid service"
case $OS in
FreeBSD)
echo "[*] FIXME: Bitcoin Liquid service must be installed manually on FreeBSD"
;;
Debian)
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/linux/elements-liquidtestnet.service" "${DEBIAN_SERVICE_HOME}"
;;
esac
fi
@@ -1295,18 +1318,6 @@ if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
echo "[*] Installing Bitcoin Mainnet electrs start script"
osSudo "${ROOT_USER}" install -c -o "${BITCOIN_USER}" -g "${BITCOIN_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-mainnet" "${BITCOIN_ELECTRS_HOME}"
echo "[*] Installing Bitcoin crontab"
case $OS in
FreeBSD)
echo [*] FIXME: must only crontab enabled daemons
osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/bitcoin.crontab"
osSudo "${ROOT_USER}" crontab -u "${MINFEE_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/minfee.crontab"
;;
Debian)
(crontab -l ; echo "@reboot sleep 30 ; screen -dmS mainnet /bitcoin/electrs/electrs-start-mainnet") | osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" -
;;
esac
echo "[*] Configuring Bitcoin Mainnet RPC credentials in electrs start script"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-mainnet"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-mainnet"
@@ -1321,13 +1332,6 @@ if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
echo "[*] Installing Bitcoin Testnet electrs start script"
osSudo "${ROOT_USER}" install -c -o "${BITCOIN_USER}" -g "${BITCOIN_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-testnet" "${BITCOIN_ELECTRS_HOME}"
case $OS in
Debian)
echo "[*] Installing Bitcoin-testnet crontab"
(crontab -l ; echo "@reboot sleep 70 ; screen -dmS testnet /bitcoin/electrs/electrs-start-testnet") | osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" -
;;
esac
echo "[*] Configuring Bitcoin Testnet RPC credentials in electrs start script"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-testnet"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-testnet"
@@ -1342,13 +1346,6 @@ if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
echo "[*] Installing Bitcoin Signet electrs start script"
osSudo "${ROOT_USER}" install -c -o "${BITCOIN_USER}" -g "${BITCOIN_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-signet" "${BITCOIN_ELECTRS_HOME}"
case $OS in
Debian)
echo "[*] Installing Bitcoin-signet crontab"
(crontab -l ; echo "@reboot sleep 90 ; screen -dmS signet /bitcoin/electrs/electrs-start-signet") | osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" -
;;
esac
echo "[*] Configuring Bitcoin Signet RPC credentials in electrs start script"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-signet"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-signet"
@@ -1366,12 +1363,9 @@ if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
echo "[*] Installing Elements crontab"
case $OS in
FreeBSD)
echo [*] FIXME: must only crontab enabled daemons
echo "[*] FIXME: must only crontab enabled daemons"
osSudo "${ROOT_USER}" crontab -u "${ELEMENTS_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/elements.crontab"
;;
Debian)
(crontab -l ; echo "6 * * * * cd $HOME/asset_registry_db && git pull origin master >/dev/null 2>&1") | osSudo "${ROOT_USER}" crontab -u "${ELEMENTS_USER}" -
;;
esac
echo "[*] Configuring Elements Liquid RPC credentials in electrs start script"
@@ -1388,13 +1382,6 @@ if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
echo "[*] Installing Elements Liquid Testnet electrs start script"
osSudo "${ROOT_USER}" install -c -o "${ELEMENTS_USER}" -g "${ELEMENTS_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-liquidtestnet" "${ELEMENTS_ELECTRS_HOME}"
case $OS in
Debian)
echo "[*] Installing Elements-testnet crontab"
(crontab -l ; echo "6 * * * * cd $HOME/asset_registry_testnet_db && git pull origin master >/dev/null 2>&1") | osSudo "${ROOT_USER}" crontab -u "${ELEMENTS_USER}" -
;;
esac
echo "[*] Installing Elements Liquid Testnet RPC credentials"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${ELEMENTS_HOME}/elements.conf"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${ELEMENTS_HOME}/elements.conf"
@@ -1407,6 +1394,45 @@ if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
osSudo "${ROOT_USER}" sed -i.orig "s!__ELECTRS_DATA_ROOT__!${ELECTRS_DATA_ROOT}!" "${ELEMENTS_ELECTRS_HOME}/electrs-start-liquidtestnet"
fi
################################
# Install all Electrs Cronjobs #
################################
echo "[*] Installing crontabs"
case $OS in
FreeBSD)
echo "[*] FIXME: must only crontab enabled daemons"
osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/bitcoin.crontab"
osSudo "${ROOT_USER}" crontab -u "${MINFEE_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/minfee.crontab"
;;
Debian)
crontab_bitcoin=()
if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
echo "[*] Installing Electrs Mainnet Cronjob"
crontab_bitcoin+="@reboot sleep 30 ; screen -dmS mainnet /bitcoin/electrs/electrs-start-mainnet\n"
fi
if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
echo "[*] Installing Electrs Testnet Cronjob"
crontab_bitcoin+="@reboot sleep 70 ; screen -dmS testnet /bitcoin/electrs/electrs-start-testnet\n"
fi
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
echo "[*] Installing Electrs Signet Cronjob"
crontab_bitcoin+="@reboot sleep 90 ; screen -dmS signet /bitcoin/electrs/electrs-start-signet\n"
fi
echo "${crontab_bitcoin}" | crontab -u "${BITCOIN_USER}" -
crontab_elements=()
if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
echo "[*] Installing Liquid Asset Mainnet Cronjob"
crontab_elements+="6 * * * * cd $HOME/asset_registry_db && git pull origin master >/dev/null 2>&1\n"
fi
if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
echo "[*] Installing Liquid Asset Testnet Cronjob"
crontab_elements+="6 * * * * cd $HOME/asset_registry_testnet_db && git pull origin master >/dev/null 2>&1\n"
fi
echo "${crontab_elements}" | crontab -u "${ELEMENTS_USER}" -
;;
esac
#####################################
# Bisq instance for Bitcoin Mainnet #
#####################################
@@ -1505,19 +1531,24 @@ _EOF_
##### nginx
echo "[*] Read tor v3 onion hostnames"
NGINX_MEMPOOL_ONION=$(cat "${TOR_RESOURCES}/mempool/hostname")
NGINX_BISQ_ONION=$(cat "${TOR_RESOURCES}/bisq/hostname")
NGINX_LIQUID_ONION=$(cat "${TOR_RESOURCES}/liquid/hostname")
echo "[*] Adding Nginx configuration"
case $OS in
FreeBSD)
echo "[*] FIXME: nginx must be configured manually on FreeBSD"
;;
Debian)
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/nginx/nginx.conf" "${NGINX_CONFIGURATION}"
#echo "[*] Restarting Nginx"
#osSudo "${ROOT_USER}" service nginx restart
;;
esac
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/nginx/nginx.conf" "${NGINX_CONFIGURATION}"
mkdir -p /var/cache/nginx/services /var/cache/nginx/api
chown ${NGINX_USER}: /var/cache/nginx/services /var/cache/nginx/api
ln -s /mempool/mempool /etc/nginx/mempool
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_USER__!${NGINX_USER}!" "${NGINX_CONFIGURATION}"
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_ETC_FOLDER__!${NGINX_ETC_FOLDER}!" "${NGINX_CONFIGURATION}"
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_MEMPOOL_ONION__!${NGINX_MEMPOOL_ONION%.onion}!" "${NGINX_CONFIGURATION}"
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_BISQ_ONION__!${NGINX_BISQ_ONION%.onion}!" "${NGINX_CONFIGURATION}"
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_LIQUID_ONION__!${NGINX_LIQUID_ONIONi%.onion}!" "${NGINX_CONFIGURATION}"
echo "[*] Restarting Nginx"
osSudo "${ROOT_USER}" service nginx restart
##### OS systemd
@@ -1548,11 +1579,40 @@ case $OS in
osSudo "${ROOT_USER}" systemctl enable bisq.service
fi
if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
osSudo "${ROOT_USER}" systemctl enable liquid.service
osSudo "${ROOT_USER}" systemctl enable elements-liquid.service
fi
if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
osSudo "${ROOT_USER}" systemctl enable elements-liquidtestnet.service
fi
;;
esac
##### OS set Linux user ulimits
echo "[*] Setting ulimits for users"
case $OS in
FreeBSD)
;;
Debian)
cat >> /etc/security/limits.conf <<EOF
* soft nproc 200000
* hard nproc 200000
* soft nofile 200000
* hard nofile 200000
EOF
echo "session required pam_limits.so" >> /etc/pam.d/common-session
;;
esac
##### Build Mempool
echo "[*] Build Mempool"
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME} && ./upgrade"
##### OS services
#if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
@@ -1628,6 +1688,21 @@ esac
##### finish
case $OS in
FreeBSD)
;;
Debian)
echo "This are the generated Tor addresses:"
echo "${NGINX_MEMPOOL_ONION}"
echo "${NGINX_BISQ_ONION}"
echo "${NGINX_LIQUID_ONION}"
;;
esac
echo
echo 'Please reboot to start all the services.'
echo '[*] Done!'
exit 0

View File

@@ -1,13 +1,13 @@
[Unit]
Description=Elementsd
Description=Elementsd-liquid
After=network.target
[Service]
ExecStart=/usr/local/bin/elementsd -daemon -printtoconsole -chain=liquidtestnet -pid=/elements/elements-testnet.pid
ExecStart=/usr/local/bin/elementsd -daemon -printtoconsole -chain=liquidv1 -pid=/elements/elements-liquid.pid
ExecStop=/usr/local/bin/elements-cli stop
Type=forking
PIDFile=/elements/elements-testnet.pid
PIDFile=/elements/elements-liquid.pid
Restart=on-failure
User=elements

View File

@@ -1,13 +1,13 @@
[Unit]
Description=Elementsd
Description=Elementsd-liquidtestnet
After=network.target
[Service]
ExecStart=/usr/local/bin/elementsd -daemon -printtoconsole -chain=liquidv1 -pid=/elements/elements.pid
ExecStart=/usr/local/bin/elementsd -daemon -printtoconsole -chain=liquidtestnet -pid=/elements/elements-liquidtestnet.pid
ExecStop=/usr/local/bin/elements-cli stop
Type=forking
PIDFile=/elements/elements.pid
PIDFile=/elements/elements-liquidtestnet.pid
Restart=on-failure
User=elements

View File

@@ -56,7 +56,7 @@ build_frontend()
if [ ! -e "mempool-frontend-config.json" ];then
cp "${HOME}/mempool/production/mempool-frontend-config.${site}.json" "mempool-frontend-config.json"
fi
npm install --no-optional || exit 1
npm install --omit=dev --omit=optional || exit 1
npm run build || exit 1
}
@@ -75,7 +75,7 @@ build_backend()
-e "s!__ELEMENTS_RPC_PASS__!${ELEMENTS_RPC_PASS}!" \
"mempool-config.json"
fi
npm install --no-optional || exit 1
npm install --omit=dev --omit=optional || exit 1
npm run build || exit 1
}

View File

@@ -1,4 +1,4 @@
user nobody;
user __NGINX_USER__;
pid /var/run/nginx.pid;
worker_processes auto;
@@ -10,11 +10,11 @@ events {
}
http {
# DNS servers for on-demand recursive resolver
# DNS servers for on-demand resolution, change if desired
resolver 8.8.8.8;
# include default mime types
include /usr/local/etc/nginx/mime.types;
include __NGINX_ETC_FOLDER__/mime.types;
default_type application/octet-stream;
# HTTP basic configuration
@@ -32,9 +32,13 @@ http {
# MEMPOOL.NINJA
server {
# clearnet v4/v6
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name mempool.ninja;
#listen 443 ssl http2;
#listen [::]:443 ssl http2;
server_name _;
# tor v3
listen 127.0.0.1:81;
set $onion "__NGINX_MEMPOOL_ONION__";
# for services from mempool.space like contributors on about page
set $mempoolSpaceServices "https://mempool.space";
@@ -52,30 +56,24 @@ http {
set $esploraTestnet "http://esplora-bitcoin-testnet";
set $esploraSignet "http://esplora-bitcoin-signet";
# tor v3
listen 127.0.0.1:81;
set $onion "mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad";
# filesystem paths
root /mempool/public_html/mainnet/;
access_log /var/log/nginx/mempool-access.log;
error_log /var/log/nginx/mempool-error.log;
# ssl configuration
ssl_certificate /usr/local/etc/letsencrypt/live/mempool.ninja/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/mempool.ninja/privkey.pem;
include /usr/local/etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /usr/local/etc/letsencrypt/ssl-dhparams.pem;
# site configuration
include mempool/production/nginx/server-mempool.conf;
}
# BISQ.NINJA
server {
# clearnet v4/v6
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name bisq.ninja;
#listen 443 ssl http2;
#listen [::]:443 ssl http2;
server_name _;
# tor v3
listen 127.0.0.1:82;
set $onion "__NGINX_BISQ_ONION__";
# for services from mempool.space like contributors on about page
set $mempoolSpaceServices "https://mempool.space";
@@ -86,30 +84,24 @@ http {
# for blockstream/esplora daemon, see upstream-esplora.conf
set $esploraMainnet "http://esplora-bitcoin-mainnet";
# tor v3
listen 127.0.0.1:82;
set $onion "bisqmktse2cabavbr2xjq7xw3h6g5ottemo5rolfcwt6aly6tp5fdryd";
# filesystem paths
root /mempool/public_html/bisq/;
access_log /var/log/nginx/bisq-access.log;
error_log /var/log/nginx/bisq-error.log;
# ssl configuration
ssl_certificate /usr/local/etc/letsencrypt/live/bisq.ninja/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/bisq.ninja/privkey.pem;
include /usr/local/etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /usr/local/etc/letsencrypt/ssl-dhparams.pem;
# site configuration
include mempool/production/nginx/server-bisq.conf;
}
# LIQUID.PLACE
server {
# clearnet v4/v6
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name liquid.place;
#listen 443 ssl http2;
#listen [::]:443 ssl http2;
server_name _;
# tor v3
listen 127.0.0.1:83;
set $onion "__NGINX_LIQUID_ONION__";
# for services from mempool.space like contributors on about page
set $mempoolSpaceServices "https://mempool.space";
@@ -122,36 +114,12 @@ http {
set $esploraMainnet "http://esplora-liquid-mainnet";
set $esploraTestnet "http://esplora-liquid-testnet";
# tor v3
listen 127.0.0.1:83;
set $onion "liquidmom47f6s3m53ebfxn47p76a6tlnxib3wp6deux7wuzotdr6cyd";
# filesystem paths
root /mempool/public_html/liquid/;
access_log /var/log/nginx/liquid-access.log;
error_log /var/log/nginx/liquid-error.log;
# ssl configuration
ssl_certificate /usr/local/etc/letsencrypt/live/liquid.place/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/liquid.place/privkey.pem;
include /usr/local/etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /usr/local/etc/letsencrypt/ssl-dhparams.pem;
# site configuration
include mempool/production/nginx/server-liquid.conf;
}
# HTTP to HTTPS redirect
server {
# clearnet v4/v6
listen 80;
listen [::]:80;
server_name _;
# only redirect for our hosted domains
if ($host ~ "^(mempool.ninja|bisq.ninja|liquid.place)$")
{
return 301 https://$host$request_uri;
}
return 503;
}
}

View File

@@ -3,21 +3,21 @@ SOCKSPort 9050
ControlPort 9051
Log notice syslog
DataDirectory /var/db/tor
DataDirectory __TOR_RESOURCES__
DataDirectoryGroupReadable 1
CookieAuthentication 1
CookieAuthFile /var/db/tor/control_auth_cookie
CookieAuthFile __TOR_RESOURCES__/control_auth_cookie
CookieAuthFileGroupReadable 1
HiddenServiceDir /var/db/tor/mempool
HiddenServiceDir __TOR_RESOURCES__/mempool
HiddenServicePort 80 127.0.0.1:81
HiddenServiceVersion 3
HiddenServiceDir /var/db/tor/bisq
HiddenServiceDir __TOR_RESOURCES__/bisq
HiddenServicePort 80 127.0.0.1:82
HiddenServiceVersion 3
HiddenServiceDir /var/db/tor/liquid
HiddenServiceDir __TOR_RESOURCES__/liquid
HiddenServicePort 80 127.0.0.1:83
HiddenServiceVersion 3