From 3db1486bfb1ac7a684c6d48691591e4adbe7591e Mon Sep 17 00:00:00 2001
From: nymkappa <1612910616@pm.me>
Date: Sun, 26 Mar 2023 14:35:10 +0900
Subject: [PATCH 001/446] Fix transaction amount overflow
---
.../transactions-list.component.html | 4 ++--
.../transactions-list.component.scss | 11 ++++++++++-
2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.html b/frontend/src/app/components/transactions-list/transactions-list.component.html
index cb54e1870..1549f7871 100644
--- a/frontend/src/app/components/transactions-list/transactions-list.component.html
+++ b/frontend/src/app/components/transactions-list/transactions-list.component.html
@@ -77,7 +77,7 @@
-
+ 1000000}">
@@ -206,7 +206,7 @@
-
+ 1000000}">
diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.scss b/frontend/src/app/components/transactions-list/transactions-list.component.scss
index 08d7d7486..5d6dd7d61 100644
--- a/frontend/src/app/components/transactions-list/transactions-list.component.scss
+++ b/frontend/src/app/components/transactions-list/transactions-list.component.scss
@@ -46,7 +46,16 @@
}
td.amount {
- width: 32.5%;
+ width: 36%;
+ @media (max-width: 576px) {
+ width: 50%;
+ }
+}
+td.amount.large {
+ width: 45%;
+ @media (max-width: 576px) {
+ width: 60%;
+ }
}
.extra-info {
From 125b9e4525132304ca11c24eca871bd392adbc05 Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Wed, 24 May 2023 15:49:35 -0400
Subject: [PATCH 002/446] Restore liquid max block weight to defaults
---
production/mempool-frontend-config.liquid.json | 1 -
1 file changed, 1 deletion(-)
diff --git a/production/mempool-frontend-config.liquid.json b/production/mempool-frontend-config.liquid.json
index 6a7c79d52..1a4fc2998 100644
--- a/production/mempool-frontend-config.liquid.json
+++ b/production/mempool-frontend-config.liquid.json
@@ -11,7 +11,6 @@
"LIQUID_WEBSITE_URL": "https://liquid.network",
"BISQ_WEBSITE_URL": "https://bisq.markets",
"ITEMS_PER_PAGE": 25,
- "BLOCK_WEIGHT_UNITS": 300000,
"MEMPOOL_BLOCKS_AMOUNT": 2,
"KEEP_BLOCKS_AMOUNT": 16
}
From e9386ec003682ff921737709d234f4832822b0c8 Mon Sep 17 00:00:00 2001
From: Antoni Spaanderman <56turtle56@gmail.com>
Date: Sun, 26 Mar 2023 16:39:45 +0200
Subject: [PATCH 003/446] Add Bitcoin Core RPC cookie authentication option
---
backend/mempool-config.sample.json | 8 +++++--
.../__fixtures__/mempool-config.template.json | 10 ++++++---
backend/src/__tests__/config.test.ts | 8 +++++--
.../bitcoin/bitcoin-api-abstract-factory.ts | 1 +
backend/src/api/bitcoin/bitcoin-client.ts | 3 +++
.../src/api/bitcoin/bitcoin-second-client.ts | 3 +++
backend/src/config.ts | 8 +++++++
backend/src/rpc-api/jsonrpc.ts | 21 ++++++++++++++++---
docker/README.md | 16 ++++++++++----
docker/backend/mempool-config.json | 8 +++++--
docker/backend/start.sh | 8 +++++++
11 files changed, 78 insertions(+), 16 deletions(-)
diff --git a/backend/mempool-config.sample.json b/backend/mempool-config.sample.json
index 32becd00d..68b5c3801 100644
--- a/backend/mempool-config.sample.json
+++ b/backend/mempool-config.sample.json
@@ -35,7 +35,9 @@
"PORT": 8332,
"USERNAME": "mempool",
"PASSWORD": "mempool",
- "TIMEOUT": 60000
+ "TIMEOUT": 60000,
+ "COOKIE": false,
+ "COOKIE_PATH": "/path/to/bitcoin/.cookie"
},
"ELECTRUM": {
"HOST": "127.0.0.1",
@@ -52,7 +54,9 @@
"PORT": 8332,
"USERNAME": "mempool",
"PASSWORD": "mempool",
- "TIMEOUT": 60000
+ "TIMEOUT": 60000,
+ "COOKIE": false,
+ "COOKIE_PATH": "/path/to/bitcoin/.cookie"
},
"DATABASE": {
"ENABLED": true,
diff --git a/backend/src/__fixtures__/mempool-config.template.json b/backend/src/__fixtures__/mempool-config.template.json
index 919784464..1d5c7135a 100644
--- a/backend/src/__fixtures__/mempool-config.template.json
+++ b/backend/src/__fixtures__/mempool-config.template.json
@@ -36,7 +36,9 @@
"PORT": 15,
"USERNAME": "__CORE_RPC_USERNAME__",
"PASSWORD": "__CORE_RPC_PASSWORD__",
- "TIMEOUT": 1000
+ "TIMEOUT": 1000,
+ "COOKIE": "__CORE_RPC_COOKIE__",
+ "COOKIE_PATH": "__CORE_RPC_COOKIE_PATH__"
},
"ELECTRUM": {
"HOST": "__ELECTRUM_HOST__",
@@ -53,7 +55,9 @@
"PORT": 17,
"USERNAME": "__SECOND_CORE_RPC_USERNAME__",
"PASSWORD": "__SECOND_CORE_RPC_PASSWORD__",
- "TIMEOUT": 2000
+ "TIMEOUT": 2000,
+ "COOKIE": "__SECOND_CORE_RPC_COOKIE__",
+ "COOKIE_PATH": "__SECOND_CORE_RPC_COOKIE_PATH__"
},
"DATABASE": {
"ENABLED": false,
@@ -119,4 +123,4 @@
"CLIGHTNING": {
"SOCKET": "__CLIGHTNING_SOCKET__"
}
-}
\ No newline at end of file
+}
diff --git a/backend/src/__tests__/config.test.ts b/backend/src/__tests__/config.test.ts
index 278d83f50..f4cf719c6 100644
--- a/backend/src/__tests__/config.test.ts
+++ b/backend/src/__tests__/config.test.ts
@@ -54,7 +54,9 @@ describe('Mempool Backend Config', () => {
PORT: 8332,
USERNAME: 'mempool',
PASSWORD: 'mempool',
- TIMEOUT: 60000
+ TIMEOUT: 60000,
+ COOKIE: false,
+ COOKIE_PATH: ''
});
expect(config.SECOND_CORE_RPC).toStrictEqual({
@@ -62,7 +64,9 @@ describe('Mempool Backend Config', () => {
PORT: 8332,
USERNAME: 'mempool',
PASSWORD: 'mempool',
- TIMEOUT: 60000
+ TIMEOUT: 60000,
+ COOKIE: false,
+ COOKIE_PATH: ''
});
expect(config.DATABASE).toStrictEqual({
diff --git a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts
index 7b2802d1b..f8dfe8c26 100644
--- a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts
+++ b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts
@@ -25,4 +25,5 @@ export interface BitcoinRpcCredentials {
user: string;
pass: string;
timeout: number;
+ cookie?: string;
}
diff --git a/backend/src/api/bitcoin/bitcoin-client.ts b/backend/src/api/bitcoin/bitcoin-client.ts
index 429638984..f0dab4441 100644
--- a/backend/src/api/bitcoin/bitcoin-client.ts
+++ b/backend/src/api/bitcoin/bitcoin-client.ts
@@ -2,12 +2,15 @@ import config from '../../config';
const bitcoin = require('../../rpc-api/index');
import { BitcoinRpcCredentials } from './bitcoin-api-abstract-factory';
+export const defaultCookiePath = `${process.env.HOME}/.bitcoin/${{mainnet:'',testnet:'testnet3/',signet:'signet/'}[config.MEMPOOL.NETWORK]}.cookie`;
+
const nodeRpcCredentials: BitcoinRpcCredentials = {
host: config.CORE_RPC.HOST,
port: config.CORE_RPC.PORT,
user: config.CORE_RPC.USERNAME,
pass: config.CORE_RPC.PASSWORD,
timeout: config.CORE_RPC.TIMEOUT,
+ cookie: config.CORE_RPC.COOKIE ? config.CORE_RPC.COOKIE_PATH || defaultCookiePath : undefined,
};
export default new bitcoin.Client(nodeRpcCredentials);
diff --git a/backend/src/api/bitcoin/bitcoin-second-client.ts b/backend/src/api/bitcoin/bitcoin-second-client.ts
index 7f81a96a0..85d05556e 100644
--- a/backend/src/api/bitcoin/bitcoin-second-client.ts
+++ b/backend/src/api/bitcoin/bitcoin-second-client.ts
@@ -2,12 +2,15 @@ import config from '../../config';
const bitcoin = require('../../rpc-api/index');
import { BitcoinRpcCredentials } from './bitcoin-api-abstract-factory';
+import { defaultCookiePath } from './bitcoin-client';
+
const nodeRpcCredentials: BitcoinRpcCredentials = {
host: config.SECOND_CORE_RPC.HOST,
port: config.SECOND_CORE_RPC.PORT,
user: config.SECOND_CORE_RPC.USERNAME,
pass: config.SECOND_CORE_RPC.PASSWORD,
timeout: config.SECOND_CORE_RPC.TIMEOUT,
+ cookie: config.SECOND_CORE_RPC.COOKIE ? config.SECOND_CORE_RPC.COOKIE_PATH || defaultCookiePath : undefined,
};
export default new bitcoin.Client(nodeRpcCredentials);
diff --git a/backend/src/config.ts b/backend/src/config.ts
index ff5ea4f9f..b9a3f366a 100644
--- a/backend/src/config.ts
+++ b/backend/src/config.ts
@@ -70,6 +70,8 @@ interface IConfig {
USERNAME: string;
PASSWORD: string;
TIMEOUT: number;
+ COOKIE: boolean;
+ COOKIE_PATH: string;
};
SECOND_CORE_RPC: {
HOST: string;
@@ -77,6 +79,8 @@ interface IConfig {
USERNAME: string;
PASSWORD: string;
TIMEOUT: number;
+ COOKIE: boolean;
+ COOKIE_PATH: string;
};
DATABASE: {
ENABLED: boolean;
@@ -180,6 +184,8 @@ const defaults: IConfig = {
'USERNAME': 'mempool',
'PASSWORD': 'mempool',
'TIMEOUT': 60000,
+ 'COOKIE': false,
+ 'COOKIE_PATH': '' // default value depends on network, see src/api/bitcoin/bitcoin-client
},
'SECOND_CORE_RPC': {
'HOST': '127.0.0.1',
@@ -187,6 +193,8 @@ const defaults: IConfig = {
'USERNAME': 'mempool',
'PASSWORD': 'mempool',
'TIMEOUT': 60000,
+ 'COOKIE': false,
+ 'COOKIE_PATH': ''
},
'DATABASE': {
'ENABLED': true,
diff --git a/backend/src/rpc-api/jsonrpc.ts b/backend/src/rpc-api/jsonrpc.ts
index 4f7a38baa..0bcbdc16c 100644
--- a/backend/src/rpc-api/jsonrpc.ts
+++ b/backend/src/rpc-api/jsonrpc.ts
@@ -1,5 +1,6 @@
var http = require('http')
var https = require('https')
+import { readFileSync } from 'fs';
var JsonRPC = function (opts) {
// @ts-ignore
@@ -55,7 +56,13 @@ JsonRPC.prototype.call = function (method, params) {
}
// use HTTP auth if user and password set
- if (this.opts.user && this.opts.pass) {
+ if (this.opts.cookie) {
+ if (!this.cachedCookie) {
+ this.cachedCookie = readFileSync(this.opts.cookie).toString();
+ }
+ // @ts-ignore
+ requestOptions.auth = this.cachedCookie;
+ } else if (this.opts.user && this.opts.pass) {
// @ts-ignore
requestOptions.auth = this.opts.user + ':' + this.opts.pass
}
@@ -93,7 +100,7 @@ JsonRPC.prototype.call = function (method, params) {
reject(err)
})
- request.on('response', function (response) {
+ request.on('response', (response) => {
clearTimeout(reqTimeout)
// We need to buffer the response chunks in a nonblocking way.
@@ -104,7 +111,7 @@ JsonRPC.prototype.call = function (method, params) {
// When all the responses are finished, we decode the JSON and
// depending on whether it's got a result or an error, we call
// emitSuccess or emitError on the promise.
- response.on('end', function () {
+ response.on('end', () => {
var err
if (cbCalled) return
@@ -113,6 +120,14 @@ JsonRPC.prototype.call = function (method, params) {
try {
var decoded = JSON.parse(buffer)
} catch (e) {
+ // if we authenticated using a cookie and it failed, read the cookie file again
+ if (
+ response.statusCode === 401 /* Unauthorized */ &&
+ this.opts.cookie
+ ) {
+ this.cachedCookie = undefined;
+ }
+
if (response.statusCode !== 200) {
err = new Error('Invalid params, response status code: ' + response.statusCode)
err.code = -32602
diff --git a/docker/README.md b/docker/README.md
index b669b37c8..74b9be0d9 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -162,7 +162,9 @@ Corresponding `docker-compose.yml` overrides:
"PORT": 8332,
"USERNAME": "mempool",
"PASSWORD": "mempool",
- "TIMEOUT": 60000
+ "TIMEOUT": 60000,
+ "COOKIE": "false",
+ "COOKIE_PATH": ""
},
```
@@ -174,7 +176,9 @@ Corresponding `docker-compose.yml` overrides:
CORE_RPC_PORT: ""
CORE_RPC_USERNAME: ""
CORE_RPC_PASSWORD: ""
- CORE_RPC_TIMEOUT: 60000
+ CORE_RPC_TIMEOUT: 60000,
+ CORE_RPC_COOKIE: ""
+ CORE_RPC_COOKIE_PATH: ""
...
```
@@ -229,7 +233,9 @@ Corresponding `docker-compose.yml` overrides:
"PORT": 8332,
"USERNAME": "mempool",
"PASSWORD": "mempool",
- "TIMEOUT": 60000
+ "TIMEOUT": 60000,
+ "COOKIE": "false",
+ "COOKIE_PATH": ""
},
```
@@ -241,7 +247,9 @@ Corresponding `docker-compose.yml` overrides:
SECOND_CORE_RPC_PORT: ""
SECOND_CORE_RPC_USERNAME: ""
SECOND_CORE_RPC_PASSWORD: ""
- SECOND_CORE_RPC_TIMEOUT: ""
+ SECOND_CORE_RPC_TIMEOUT: "",
+ SECOND_CORE_RPC_COOKIE: ""
+ SECOND_CORE_RPC_COOKIE_PATH: ""
...
```
diff --git a/docker/backend/mempool-config.json b/docker/backend/mempool-config.json
index 06ed51f41..7a1db21b4 100644
--- a/docker/backend/mempool-config.json
+++ b/docker/backend/mempool-config.json
@@ -36,7 +36,9 @@
"PORT": __CORE_RPC_PORT__,
"USERNAME": "__CORE_RPC_USERNAME__",
"PASSWORD": "__CORE_RPC_PASSWORD__",
- "TIMEOUT": __CORE_RPC_TIMEOUT__
+ "TIMEOUT": __CORE_RPC_TIMEOUT__,
+ "COOKIE": __CORE_RPC_COOKIE__,
+ "COOKIE_PATH": "__CORE_RPC_COOKIE_PATH__"
},
"ELECTRUM": {
"HOST": "__ELECTRUM_HOST__",
@@ -53,7 +55,9 @@
"PORT": __SECOND_CORE_RPC_PORT__,
"USERNAME": "__SECOND_CORE_RPC_USERNAME__",
"PASSWORD": "__SECOND_CORE_RPC_PASSWORD__",
- "TIMEOUT": __SECOND_CORE_RPC_TIMEOUT__
+ "TIMEOUT": __SECOND_CORE_RPC_TIMEOUT__,
+ "COOKIE": __SECOND_CORE_RPC_COOKIE__,
+ "COOKIE_PATH": "__SECOND_CORE_RPC_COOKIE_PATH__"
},
"DATABASE": {
"ENABLED": __DATABASE_ENABLED__,
diff --git a/docker/backend/start.sh b/docker/backend/start.sh
index cb1108a05..0aa8fde5c 100755
--- a/docker/backend/start.sh
+++ b/docker/backend/start.sh
@@ -38,6 +38,8 @@ __CORE_RPC_PORT__=${CORE_RPC_PORT:=8332}
__CORE_RPC_USERNAME__=${CORE_RPC_USERNAME:=mempool}
__CORE_RPC_PASSWORD__=${CORE_RPC_PASSWORD:=mempool}
__CORE_RPC_TIMEOUT__=${CORE_RPC_TIMEOUT:=60000}
+__CORE_RPC_COOKIE__=${CORE_RPC_COOKIE:=false}
+__CORE_RPC_COOKIE_PATH__=${CORE_RPC_COOKIE:=""}
# ELECTRUM
__ELECTRUM_HOST__=${ELECTRUM_HOST:=127.0.0.1}
@@ -55,6 +57,8 @@ __SECOND_CORE_RPC_PORT__=${SECOND_CORE_RPC_PORT:=8332}
__SECOND_CORE_RPC_USERNAME__=${SECOND_CORE_RPC_USERNAME:=mempool}
__SECOND_CORE_RPC_PASSWORD__=${SECOND_CORE_RPC_PASSWORD:=mempool}
__SECOND_CORE_RPC_TIMEOUT__=${SECOND_CORE_RPC_TIMEOUT:=60000}
+__SECOND_CORE_RPC_COOKIE__=${SECOND_CORE_RPC_COOKIE:=false}
+__SECOND_CORE_RPC_COOKIE_PATH__=${SECOND_CORE_RPC_COOKIE:=""}
# DATABASE
__DATABASE_ENABLED__=${DATABASE_ENABLED:=true}
@@ -165,6 +169,8 @@ sed -i "s!__CORE_RPC_PORT__!${__CORE_RPC_PORT__}!g" mempool-config.json
sed -i "s!__CORE_RPC_USERNAME__!${__CORE_RPC_USERNAME__}!g" mempool-config.json
sed -i "s!__CORE_RPC_PASSWORD__!${__CORE_RPC_PASSWORD__}!g" mempool-config.json
sed -i "s!__CORE_RPC_TIMEOUT__!${__CORE_RPC_TIMEOUT__}!g" mempool-config.json
+sed -i "s!__CORE_RPC_COOKIE__!${__CORE_RPC_COOKIE__}!g" mempool-config.json
+sed -i "s!__CORE_RPC_COOKIE_PATH__!${__CORE_RPC_COOKIE_PATH__}!g" mempool-config.json
sed -i "s!__ELECTRUM_HOST__!${__ELECTRUM_HOST__}!g" mempool-config.json
sed -i "s!__ELECTRUM_PORT__!${__ELECTRUM_PORT__}!g" mempool-config.json
@@ -179,6 +185,8 @@ sed -i "s!__SECOND_CORE_RPC_PORT__!${__SECOND_CORE_RPC_PORT__}!g" mempool-config
sed -i "s!__SECOND_CORE_RPC_USERNAME__!${__SECOND_CORE_RPC_USERNAME__}!g" mempool-config.json
sed -i "s!__SECOND_CORE_RPC_PASSWORD__!${__SECOND_CORE_RPC_PASSWORD__}!g" mempool-config.json
sed -i "s!__SECOND_CORE_RPC_TIMEOUT__!${__SECOND_CORE_RPC_TIMEOUT__}!g" mempool-config.json
+sed -i "s!__SECOND_CORE_RPC_COOKIE__!${__SECOND_CORE_RPC_COOKIE__}!g" mempool-config.json
+sed -i "s!__SECOND_CORE_RPC_COOKIE_PATH__!${__SECOND_CORE_RPC_COOKIE_PATH__}!g" mempool-config.json
sed -i "s!__DATABASE_ENABLED__!${__DATABASE_ENABLED__}!g" mempool-config.json
sed -i "s!__DATABASE_HOST__!${__DATABASE_HOST__}!g" mempool-config.json
From dc491a598405cfaa641dfbed011e5a5cd3812654 Mon Sep 17 00:00:00 2001
From: Antoni Spaanderman <56turtle56@gmail.com>
Date: Mon, 27 Mar 2023 21:28:45 +0200
Subject: [PATCH 004/446] change default cookie path to /bitcoin/.cookie
---
backend/src/__tests__/config.test.ts | 4 ++--
backend/src/api/bitcoin/bitcoin-client.ts | 4 +---
backend/src/api/bitcoin/bitcoin-second-client.ts | 4 +---
backend/src/config.ts | 4 ++--
4 files changed, 6 insertions(+), 10 deletions(-)
diff --git a/backend/src/__tests__/config.test.ts b/backend/src/__tests__/config.test.ts
index f4cf719c6..d1d8222bd 100644
--- a/backend/src/__tests__/config.test.ts
+++ b/backend/src/__tests__/config.test.ts
@@ -56,7 +56,7 @@ describe('Mempool Backend Config', () => {
PASSWORD: 'mempool',
TIMEOUT: 60000,
COOKIE: false,
- COOKIE_PATH: ''
+ COOKIE_PATH: '/bitcoin/.cookie'
});
expect(config.SECOND_CORE_RPC).toStrictEqual({
@@ -66,7 +66,7 @@ describe('Mempool Backend Config', () => {
PASSWORD: 'mempool',
TIMEOUT: 60000,
COOKIE: false,
- COOKIE_PATH: ''
+ COOKIE_PATH: '/bitcoin/.cookie'
});
expect(config.DATABASE).toStrictEqual({
diff --git a/backend/src/api/bitcoin/bitcoin-client.ts b/backend/src/api/bitcoin/bitcoin-client.ts
index f0dab4441..e8b30a888 100644
--- a/backend/src/api/bitcoin/bitcoin-client.ts
+++ b/backend/src/api/bitcoin/bitcoin-client.ts
@@ -2,15 +2,13 @@ import config from '../../config';
const bitcoin = require('../../rpc-api/index');
import { BitcoinRpcCredentials } from './bitcoin-api-abstract-factory';
-export const defaultCookiePath = `${process.env.HOME}/.bitcoin/${{mainnet:'',testnet:'testnet3/',signet:'signet/'}[config.MEMPOOL.NETWORK]}.cookie`;
-
const nodeRpcCredentials: BitcoinRpcCredentials = {
host: config.CORE_RPC.HOST,
port: config.CORE_RPC.PORT,
user: config.CORE_RPC.USERNAME,
pass: config.CORE_RPC.PASSWORD,
timeout: config.CORE_RPC.TIMEOUT,
- cookie: config.CORE_RPC.COOKIE ? config.CORE_RPC.COOKIE_PATH || defaultCookiePath : undefined,
+ cookie: config.CORE_RPC.COOKIE ? config.CORE_RPC.COOKIE_PATH : undefined,
};
export default new bitcoin.Client(nodeRpcCredentials);
diff --git a/backend/src/api/bitcoin/bitcoin-second-client.ts b/backend/src/api/bitcoin/bitcoin-second-client.ts
index 85d05556e..6ae9cefb0 100644
--- a/backend/src/api/bitcoin/bitcoin-second-client.ts
+++ b/backend/src/api/bitcoin/bitcoin-second-client.ts
@@ -2,15 +2,13 @@ import config from '../../config';
const bitcoin = require('../../rpc-api/index');
import { BitcoinRpcCredentials } from './bitcoin-api-abstract-factory';
-import { defaultCookiePath } from './bitcoin-client';
-
const nodeRpcCredentials: BitcoinRpcCredentials = {
host: config.SECOND_CORE_RPC.HOST,
port: config.SECOND_CORE_RPC.PORT,
user: config.SECOND_CORE_RPC.USERNAME,
pass: config.SECOND_CORE_RPC.PASSWORD,
timeout: config.SECOND_CORE_RPC.TIMEOUT,
- cookie: config.SECOND_CORE_RPC.COOKIE ? config.SECOND_CORE_RPC.COOKIE_PATH || defaultCookiePath : undefined,
+ cookie: config.SECOND_CORE_RPC.COOKIE ? config.SECOND_CORE_RPC.COOKIE_PATH : undefined,
};
export default new bitcoin.Client(nodeRpcCredentials);
diff --git a/backend/src/config.ts b/backend/src/config.ts
index b9a3f366a..eb1b0af21 100644
--- a/backend/src/config.ts
+++ b/backend/src/config.ts
@@ -185,7 +185,7 @@ const defaults: IConfig = {
'PASSWORD': 'mempool',
'TIMEOUT': 60000,
'COOKIE': false,
- 'COOKIE_PATH': '' // default value depends on network, see src/api/bitcoin/bitcoin-client
+ 'COOKIE_PATH': '/bitcoin/.cookie'
},
'SECOND_CORE_RPC': {
'HOST': '127.0.0.1',
@@ -194,7 +194,7 @@ const defaults: IConfig = {
'PASSWORD': 'mempool',
'TIMEOUT': 60000,
'COOKIE': false,
- 'COOKIE_PATH': ''
+ 'COOKIE_PATH': '/bitcoin/.cookie'
},
'DATABASE': {
'ENABLED': true,
From 0d69ad43ab26c7d2150e4b8460e762b7e06241e0 Mon Sep 17 00:00:00 2001
From: Antoni Spaanderman <56turtle56@gmail.com>
Date: Sun, 18 Jun 2023 20:34:56 +0200
Subject: [PATCH 005/446] fix config issues
---
.../src/__fixtures__/mempool-config.template.json | 4 ++--
docker/README.md | 12 ++++++------
docker/backend/mempool-config.json | 2 +-
docker/backend/start.sh | 4 ++--
4 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/backend/src/__fixtures__/mempool-config.template.json b/backend/src/__fixtures__/mempool-config.template.json
index 1d5c7135a..cdf188b88 100644
--- a/backend/src/__fixtures__/mempool-config.template.json
+++ b/backend/src/__fixtures__/mempool-config.template.json
@@ -37,7 +37,7 @@
"USERNAME": "__CORE_RPC_USERNAME__",
"PASSWORD": "__CORE_RPC_PASSWORD__",
"TIMEOUT": 1000,
- "COOKIE": "__CORE_RPC_COOKIE__",
+ "COOKIE": false,
"COOKIE_PATH": "__CORE_RPC_COOKIE_PATH__"
},
"ELECTRUM": {
@@ -56,7 +56,7 @@
"USERNAME": "__SECOND_CORE_RPC_USERNAME__",
"PASSWORD": "__SECOND_CORE_RPC_PASSWORD__",
"TIMEOUT": 2000,
- "COOKIE": "__SECOND_CORE_RPC_COOKIE__",
+ "COOKIE": false,
"COOKIE_PATH": "__SECOND_CORE_RPC_COOKIE_PATH__"
},
"DATABASE": {
diff --git a/docker/README.md b/docker/README.md
index 74b9be0d9..997a330b4 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -163,7 +163,7 @@ Corresponding `docker-compose.yml` overrides:
"USERNAME": "mempool",
"PASSWORD": "mempool",
"TIMEOUT": 60000,
- "COOKIE": "false",
+ "COOKIE": false,
"COOKIE_PATH": ""
},
```
@@ -176,8 +176,8 @@ Corresponding `docker-compose.yml` overrides:
CORE_RPC_PORT: ""
CORE_RPC_USERNAME: ""
CORE_RPC_PASSWORD: ""
- CORE_RPC_TIMEOUT: 60000,
- CORE_RPC_COOKIE: ""
+ CORE_RPC_TIMEOUT: 60000
+ CORE_RPC_COOKIE: false
CORE_RPC_COOKIE_PATH: ""
...
```
@@ -234,7 +234,7 @@ Corresponding `docker-compose.yml` overrides:
"USERNAME": "mempool",
"PASSWORD": "mempool",
"TIMEOUT": 60000,
- "COOKIE": "false",
+ "COOKIE": false,
"COOKIE_PATH": ""
},
```
@@ -247,8 +247,8 @@ Corresponding `docker-compose.yml` overrides:
SECOND_CORE_RPC_PORT: ""
SECOND_CORE_RPC_USERNAME: ""
SECOND_CORE_RPC_PASSWORD: ""
- SECOND_CORE_RPC_TIMEOUT: "",
- SECOND_CORE_RPC_COOKIE: ""
+ SECOND_CORE_RPC_TIMEOUT: ""
+ SECOND_CORE_RPC_COOKIE: false
SECOND_CORE_RPC_COOKIE_PATH: ""
...
```
diff --git a/docker/backend/mempool-config.json b/docker/backend/mempool-config.json
index 7a1db21b4..a91a52d6b 100644
--- a/docker/backend/mempool-config.json
+++ b/docker/backend/mempool-config.json
@@ -129,4 +129,4 @@
"GEOLITE2_ASN": "__MAXMIND_GEOLITE2_ASN__",
"GEOIP2_ISP": "__MAXMIND_GEOIP2_ISP__"
}
-}
\ No newline at end of file
+}
diff --git a/docker/backend/start.sh b/docker/backend/start.sh
index 0aa8fde5c..1dce5e811 100755
--- a/docker/backend/start.sh
+++ b/docker/backend/start.sh
@@ -39,7 +39,7 @@ __CORE_RPC_USERNAME__=${CORE_RPC_USERNAME:=mempool}
__CORE_RPC_PASSWORD__=${CORE_RPC_PASSWORD:=mempool}
__CORE_RPC_TIMEOUT__=${CORE_RPC_TIMEOUT:=60000}
__CORE_RPC_COOKIE__=${CORE_RPC_COOKIE:=false}
-__CORE_RPC_COOKIE_PATH__=${CORE_RPC_COOKIE:=""}
+__CORE_RPC_COOKIE_PATH__=${CORE_RPC_COOKIE_PATH:=""}
# ELECTRUM
__ELECTRUM_HOST__=${ELECTRUM_HOST:=127.0.0.1}
@@ -58,7 +58,7 @@ __SECOND_CORE_RPC_USERNAME__=${SECOND_CORE_RPC_USERNAME:=mempool}
__SECOND_CORE_RPC_PASSWORD__=${SECOND_CORE_RPC_PASSWORD:=mempool}
__SECOND_CORE_RPC_TIMEOUT__=${SECOND_CORE_RPC_TIMEOUT:=60000}
__SECOND_CORE_RPC_COOKIE__=${SECOND_CORE_RPC_COOKIE:=false}
-__SECOND_CORE_RPC_COOKIE_PATH__=${SECOND_CORE_RPC_COOKIE:=""}
+__SECOND_CORE_RPC_COOKIE_PATH__=${SECOND_CORE_RPC_COOKIE_PATH:=""}
# DATABASE
__DATABASE_ENABLED__=${DATABASE_ENABLED:=true}
From 1339b98281b235b6e4a5a15db041df835b37a686 Mon Sep 17 00:00:00 2001
From: junderw
Date: Sat, 27 Aug 2022 16:35:20 +0900
Subject: [PATCH 006/446] Feature: Readable RegExp constructor
---
frontend/src/app/shared/common.utils.ts | 69 ++++++++++++++++++++++++-
1 file changed, 68 insertions(+), 1 deletion(-)
diff --git a/frontend/src/app/shared/common.utils.ts b/frontend/src/app/shared/common.utils.ts
index 7d206f4b5..6cbe35386 100644
--- a/frontend/src/app/shared/common.utils.ts
+++ b/frontend/src/app/shared/common.utils.ts
@@ -119,6 +119,7 @@ export function convertRegion(input, to: 'name' | 'abbreviated'): string {
}
}
+
export function haversineDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
const rlat1 = lat1 * Math.PI / 180;
const rlon1 = lon1 * Math.PI / 180;
@@ -135,4 +136,70 @@ export function haversineDistance(lat1: number, lon1: number, lat2: number, lon2
export function kmToMiles(km: number): number {
return km * 0.62137119;
-}
\ No newline at end of file
+}
+
+// all base58 characters
+const BASE58_CHARS = '[a-km-zA-HJ-NP-Z1-9]';
+// all bech32 characters (after the separator)
+const BECH32_CHARS = '[ac-hj-np-z02-9]';
+// All characters usable in bech32 human readable portion (before the 1 separator)
+// Note: Technically the spec says "all US ASCII characters" but in practice only alphabet is used.
+// Note: If HRP contains the separator (1) then the separator is "the last instance of separator"
+const BECH32_HRP_CHARS = '[a-zA-Z0-9]';
+// Hex characters
+const HEX_CHARS = '[a-fA-F0-9]';
+// A regex to say "A single 0 OR any number with no leading zeroes"
+// (?: // Start a non-capturing group
+// 0 // A single 0
+// | // OR
+// [1-9][0-9]* // Any succession of numbers starting with 1-9
+// ) // End the non-capturing group.
+const ZERO_INDEX_NUMBER_CHARS = '(?:0|[1-9][0-9]*)';
+export type RegexType = 'address' | 'blockhash' | 'transaction' | 'blockheight';
+export type Network = 'testnet' | 'signet' | 'liquid' | 'bisq' | 'mainnet';
+export function getRegex(type: RegexType, network: Network): RegExp {
+ let regex = '^'; // ^ = Start of string
+ switch (type) {
+ // Match a block height number
+ // [Testing Order]: any order is fine
+ case 'blockheight':
+ regex += ZERO_INDEX_NUMBER_CHARS; // block height is a 0 indexed number
+ break;
+ // Match a 32 byte block hash in hex. Assumes at least 32 bits of difficulty.
+ // [Testing Order]: Must always be tested before 'transaction'
+ case 'blockhash':
+ regex += '0{8}'; // Starts with exactly 8 zeroes in a row
+ regex += `${HEX_CHARS}{56}`; // Continues with exactly 56 hex letters/numbers
+ break;
+ // Match a 32 byte tx hash in hex. Contains optional output index specifier.
+ // [Testing Order]: Must always be tested after 'blockhash'
+ case 'transaction':
+ regex += `${HEX_CHARS}{64}`; // Exactly 64 hex letters/numbers
+ regex += '(?:'; // Start a non-capturing group
+ regex += ':'; // 1 instances of the symbol ":"
+ regex += ZERO_INDEX_NUMBER_CHARS; // A zero indexed number
+ regex += ')?'; // End the non-capturing group. This group appears 0 or 1 times
+ break;
+ case 'address':
+ // TODO
+ switch (network) {
+ case 'mainnet':
+ break;
+ case 'testnet':
+ break;
+ case 'signet':
+ break;
+ case 'liquid':
+ break;
+ case 'bisq':
+ break;
+ default:
+ throw new Error('Invalid Network (Unreachable error in TypeScript)');
+ }
+ break;
+ default:
+ throw new Error('Invalid RegexType (Unreachable error in TypeScript)');
+ }
+ regex += '$'; // $ = End of string
+ return new RegExp(regex);
+}
From c0d3f295eec2f483e8006b6f06de6fd8b3650a5b Mon Sep 17 00:00:00 2001
From: junderw
Date: Sun, 28 Aug 2022 00:07:13 +0900
Subject: [PATCH 007/446] Finished Regex portion
---
.../search-form/search-form.component.ts | 25 ++-
frontend/src/app/shared/common.utils.ts | 191 ++++++++++++++----
2 files changed, 178 insertions(+), 38 deletions(-)
diff --git a/frontend/src/app/components/search-form/search-form.component.ts b/frontend/src/app/components/search-form/search-form.component.ts
index ab42fe1f7..8031195f0 100644
--- a/frontend/src/app/components/search-form/search-form.component.ts
+++ b/frontend/src/app/components/search-form/search-form.component.ts
@@ -9,6 +9,7 @@ import { ElectrsApiService } from '../../services/electrs-api.service';
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
import { ApiService } from '../../services/api.service';
import { SearchResultsComponent } from './search-results/search-results.component';
+import { ADDRESS_REGEXES, getRegex } from '../../shared/common.utils';
@Component({
selector: 'app-search-form',
@@ -38,6 +39,7 @@ export class SearchFormComponent implements OnInit {
regexBlockhash = /^[0]{8}[a-fA-F0-9]{56}$/;
regexTransaction = /^([a-fA-F0-9]{64})(:\d+)?$/;
regexBlockheight = /^[0-9]{1,9}$/;
+
focus$ = new Subject();
click$ = new Subject();
@@ -58,8 +60,13 @@ export class SearchFormComponent implements OnInit {
private elementRef: ElementRef,
) { }
- ngOnInit(): void {
- this.stateService.networkChanged$.subscribe((network) => this.network = network);
+
+ ngOnInit() {
+ this.stateService.networkChanged$.subscribe((network) => {
+ this.network = network;
+ // TODO: Eventually change network type here from string to enum of consts
+ this.regexAddress = getRegex('address', network as any);
+ });
this.searchForm = this.formBuilder.group({
searchText: ['', Validators.required],
@@ -203,6 +210,20 @@ export class SearchFormComponent implements OnInit {
this.isSearching = true;
if (!this.regexTransaction.test(searchText) && this.regexAddress.test(searchText)) {
this.navigate('/address/', searchText);
+ } else if (
+ // If the search text matches any other network besides this one
+ ADDRESS_REGEXES
+ .filter(([, network]) => network !== this.network)
+ .some(([regex]) => regex.test(searchText))
+ ) {
+ // Gather all network matches as string[]
+ const networks = ADDRESS_REGEXES.filter(([regex, network]) =>
+ network !== this.network &&
+ regex.test(searchText)
+ ).map(([, network]) => network);
+ // ###############################################
+ // TODO: Create the search items for the drop down
+ // ###############################################
} else if (this.regexBlockhash.test(searchText) || this.regexBlockheight.test(searchText)) {
this.navigate('/block/', searchText);
} else if (this.regexTransaction.test(searchText)) {
diff --git a/frontend/src/app/shared/common.utils.ts b/frontend/src/app/shared/common.utils.ts
index 6cbe35386..bbc9143c0 100644
--- a/frontend/src/app/shared/common.utils.ts
+++ b/frontend/src/app/shared/common.utils.ts
@@ -139,67 +139,186 @@ export function kmToMiles(km: number): number {
}
// all base58 characters
-const BASE58_CHARS = '[a-km-zA-HJ-NP-Z1-9]';
+const BASE58_CHARS = `[a-km-zA-HJ-NP-Z1-9]`;
+
// all bech32 characters (after the separator)
-const BECH32_CHARS = '[ac-hj-np-z02-9]';
-// All characters usable in bech32 human readable portion (before the 1 separator)
-// Note: Technically the spec says "all US ASCII characters" but in practice only alphabet is used.
-// Note: If HRP contains the separator (1) then the separator is "the last instance of separator"
-const BECH32_HRP_CHARS = '[a-zA-Z0-9]';
+const BECH32_CHARS_LW = `[ac-hj-np-z02-9]`;
+const BECH32_CHARS_UP = `[AC-HJ-NP-Z02-9]`;
+
// Hex characters
-const HEX_CHARS = '[a-fA-F0-9]';
+const HEX_CHARS = `[a-fA-F0-9]`;
+
// A regex to say "A single 0 OR any number with no leading zeroes"
-// (?: // Start a non-capturing group
-// 0 // A single 0
-// | // OR
-// [1-9][0-9]* // Any succession of numbers starting with 1-9
-// ) // End the non-capturing group.
-const ZERO_INDEX_NUMBER_CHARS = '(?:0|[1-9][0-9]*)';
-export type RegexType = 'address' | 'blockhash' | 'transaction' | 'blockheight';
-export type Network = 'testnet' | 'signet' | 'liquid' | 'bisq' | 'mainnet';
-export function getRegex(type: RegexType, network: Network): RegExp {
- let regex = '^'; // ^ = Start of string
+// (?: // Start a non-capturing group
+// 0 // A single 0
+// | // OR
+// [1-9]\d* // Any succession of numbers starting with 1-9
+// ) // End the non-capturing group.
+const ZERO_INDEX_NUMBER_CHARS = `(?:0|[1-9]\d*)`;
+
+// Formatting of the address regex is for readability,
+// We should ignore formatting it with automated formatting tools like prettier.
+//
+// prettier-ignore
+const ADDRESS_CHARS = {
+ mainnet: {
+ base58: `[13]` // Starts with a single 1 or 3
+ + BASE58_CHARS
+ + `{26,33}`, // Repeat the previous char 26-33 times.
+ // Version byte 0x00 (P2PKH) can be as short as 27 characters, up to 34 length
+ // P2SH must be 34 length
+ bech32: `(?:`
+ + `bc1` // Starts with bc1
+ + BECH32_CHARS_LW
+ + `{6,100}` // As per bech32, 6 char checksum is minimum
+ + `|`
+ + `BC1` // All upper case version
+ + BECH32_CHARS_UP
+ + `{6,100}`
+ + `)`,
+ },
+ testnet: {
+ base58: `[mn2]` // Starts with a single m, n, or 2 (P2PKH is m or n, 2 is P2SH)
+ + BASE58_CHARS
+ + `{33,34}`, // m|n is 34 length, 2 is 35 length (We match the first letter separately)
+ bech32: `(?:`
+ + `tb1` // Starts with bc1
+ + BECH32_CHARS_LW
+ + `{6,100}` // As per bech32, 6 char checksum is minimum
+ + `|`
+ + `TB1` // All upper case version
+ + BECH32_CHARS_UP
+ + `{6,100}`
+ + `)`,
+ },
+ signet: {
+ base58: `[mn2]`
+ + BASE58_CHARS
+ + `{33,34}`,
+ bech32: `(?:`
+ + `tb1` // Starts with tb1
+ + BECH32_CHARS_LW
+ + `{6,100}`
+ + `|`
+ + `TB1` // All upper case version
+ + BECH32_CHARS_UP
+ + `{6,100}`
+ + `)`,
+ },
+ liquid: {
+ base58: `[GHPQ]` // G|H is P2PKH, P|Q is P2SH
+ + BASE58_CHARS
+ + `{33}`, // All min-max lengths are 34
+ bech32: `(?:`
+ + `(?:` // bech32 liquid starts with ex or lq
+ + `ex`
+ + `|`
+ + `lq`
+ + `)`
+ + BECH32_CHARS_LW // blech32 and bech32 are the same alphabet and protocol, different checksums.
+ + `{6,100}`
+ + `|`
+ + `(?:` // Same as above but all upper case
+ + `EX`
+ + `|`
+ + `LQ`
+ + `)`
+ + BECH32_CHARS_UP
+ + `{6,100}`
+ + `)`,
+ },
+ bisq: {
+ base58: `B1` // bisq base58 addrs start with B1
+ + BASE58_CHARS
+ + `{33}`, // always length 35
+ bech32: `(?:`
+ + `bbc1` // Starts with bbc1
+ + BECH32_CHARS_LW
+ + `{6,100}`
+ + `|`
+ + `BBC1` // All upper case version
+ + BECH32_CHARS_UP
+ + `{6,100}`
+ + `)`,
+ },
+}
+type RegexTypeNoAddr = `blockhash` | `transaction` | `blockheight`;
+export type RegexType = `address` | RegexTypeNoAddr;
+
+export const NETWORKS = [`testnet`, `signet`, `liquid`, `bisq`, `mainnet`] as const;
+export type Network = typeof NETWORKS[number]; // Turn const array into union type
+
+export const ADDRESS_REGEXES: [RegExp, string][] = NETWORKS
+ .map(network => [getRegex('address', network), network])
+
+export function getRegex(type: RegexTypeNoAddr): RegExp;
+export function getRegex(type: 'address', network: Network): RegExp;
+export function getRegex(type: RegexType, network?: Network): RegExp {
+ let regex = `^`; // ^ = Start of string
switch (type) {
// Match a block height number
// [Testing Order]: any order is fine
- case 'blockheight':
+ case `blockheight`:
regex += ZERO_INDEX_NUMBER_CHARS; // block height is a 0 indexed number
break;
// Match a 32 byte block hash in hex. Assumes at least 32 bits of difficulty.
- // [Testing Order]: Must always be tested before 'transaction'
- case 'blockhash':
- regex += '0{8}'; // Starts with exactly 8 zeroes in a row
+ // [Testing Order]: Must always be tested before `transaction`
+ case `blockhash`:
+ regex += `0{8}`; // Starts with exactly 8 zeroes in a row
regex += `${HEX_CHARS}{56}`; // Continues with exactly 56 hex letters/numbers
break;
// Match a 32 byte tx hash in hex. Contains optional output index specifier.
- // [Testing Order]: Must always be tested after 'blockhash'
- case 'transaction':
+ // [Testing Order]: Must always be tested after `blockhash`
+ case `transaction`:
regex += `${HEX_CHARS}{64}`; // Exactly 64 hex letters/numbers
- regex += '(?:'; // Start a non-capturing group
- regex += ':'; // 1 instances of the symbol ":"
+ regex += `(?:`; // Start a non-capturing group
+ regex += `:`; // 1 instances of the symbol ":"
regex += ZERO_INDEX_NUMBER_CHARS; // A zero indexed number
- regex += ')?'; // End the non-capturing group. This group appears 0 or 1 times
+ regex += `)?`; // End the non-capturing group. This group appears 0 or 1 times
break;
- case 'address':
- // TODO
+ // Match any one of the many address types
+ // [Testing Order]: While possible that a bech32 address happens to be 64 hex
+ // characters in the future (current lengths are not 64), it is highly unlikely
+ // Order therefore, does not matter.
+ case `address`:
+ if (!network) {
+ throw new Error(`Must pass network when type is address`);
+ }
+ regex += `(?:`; // Start a non-capturing group (each network has multiple options)
switch (network) {
- case 'mainnet':
+ case `mainnet`:
+ regex += ADDRESS_CHARS.mainnet.base58;
+ regex += `|`; // OR
+ regex += ADDRESS_CHARS.mainnet.bech32;
break;
- case 'testnet':
+ case `testnet`:
+ regex += ADDRESS_CHARS.testnet.base58;
+ regex += `|`; // OR
+ regex += ADDRESS_CHARS.testnet.bech32;
break;
- case 'signet':
+ case `signet`:
+ regex += ADDRESS_CHARS.signet.base58;
+ regex += `|`; // OR
+ regex += ADDRESS_CHARS.signet.bech32;
break;
- case 'liquid':
+ case `liquid`:
+ regex += ADDRESS_CHARS.liquid.base58;
+ regex += `|`; // OR
+ regex += ADDRESS_CHARS.liquid.bech32;
break;
- case 'bisq':
+ case `bisq`:
+ regex += ADDRESS_CHARS.bisq.base58;
+ regex += `|`; // OR
+ regex += ADDRESS_CHARS.bisq.bech32;
break;
default:
- throw new Error('Invalid Network (Unreachable error in TypeScript)');
+ throw new Error(`Invalid Network ${network} (Unreachable error in TypeScript)`);
}
+ regex += `)`; // End the non-capturing group
break;
default:
- throw new Error('Invalid RegexType (Unreachable error in TypeScript)');
+ throw new Error(`Invalid RegexType ${type} (Unreachable error in TypeScript)`);
}
- regex += '$'; // $ = End of string
+ regex += `$`; // $ = End of string
return new RegExp(regex);
}
From 0a51b752e62d82b6986c8177cc63a48991383fcb Mon Sep 17 00:00:00 2001
From: junderw
Date: Sun, 28 Aug 2022 16:07:46 +0900
Subject: [PATCH 008/446] Improve types and add liquidtestnet for regex
---
frontend/src/app/shared/common.utils.ts | 38 +++++++++++++++++++++++--
1 file changed, 35 insertions(+), 3 deletions(-)
diff --git a/frontend/src/app/shared/common.utils.ts b/frontend/src/app/shared/common.utils.ts
index bbc9143c0..288fc0362 100644
--- a/frontend/src/app/shared/common.utils.ts
+++ b/frontend/src/app/shared/common.utils.ts
@@ -160,7 +160,12 @@ const ZERO_INDEX_NUMBER_CHARS = `(?:0|[1-9]\d*)`;
// We should ignore formatting it with automated formatting tools like prettier.
//
// prettier-ignore
-const ADDRESS_CHARS = {
+const ADDRESS_CHARS: {
+ [k in Network]: {
+ base58: string;
+ bech32: string;
+ };
+} = {
mainnet: {
base58: `[13]` // Starts with a single 1 or 3
+ BASE58_CHARS
@@ -227,6 +232,28 @@ const ADDRESS_CHARS = {
+ `{6,100}`
+ `)`,
},
+ liquidtestnet: {
+ base58: `[89]` // ???(TODO: find version) is P2PKH, 8|9 is P2SH
+ + BASE58_CHARS
+ + `{33}`, // P2PKH is ???(TODO: find size), P2SH is 34
+ bech32: `(?:`
+ + `(?:` // bech32 liquid testnet starts with tex or tlq
+ + `tex` // TODO: Why does mempool use this and not ert|el like in the elements source?
+ + `|`
+ + `tlq` // TODO: does this exist?
+ + `)`
+ + BECH32_CHARS_LW // blech32 and bech32 are the same alphabet and protocol, different checksums.
+ + `{6,100}`
+ + `|`
+ + `(?:` // Same as above but all upper case
+ + `TEX`
+ + `|`
+ + `TLQ`
+ + `)`
+ + BECH32_CHARS_UP
+ + `{6,100}`
+ + `)`,
+ },
bisq: {
base58: `B1` // bisq base58 addrs start with B1
+ BASE58_CHARS
@@ -245,10 +272,10 @@ const ADDRESS_CHARS = {
type RegexTypeNoAddr = `blockhash` | `transaction` | `blockheight`;
export type RegexType = `address` | RegexTypeNoAddr;
-export const NETWORKS = [`testnet`, `signet`, `liquid`, `bisq`, `mainnet`] as const;
+export const NETWORKS = [`testnet`, `signet`, `liquid`, `liquidtestnet`, `bisq`, `mainnet`] as const;
export type Network = typeof NETWORKS[number]; // Turn const array into union type
-export const ADDRESS_REGEXES: [RegExp, string][] = NETWORKS
+export const ADDRESS_REGEXES: [RegExp, Network][] = NETWORKS
.map(network => [getRegex('address', network), network])
export function getRegex(type: RegexTypeNoAddr): RegExp;
@@ -306,6 +333,11 @@ export function getRegex(type: RegexType, network?: Network): RegExp {
regex += `|`; // OR
regex += ADDRESS_CHARS.liquid.bech32;
break;
+ case `liquidtestnet`:
+ regex += ADDRESS_CHARS.liquidtestnet.base58;
+ regex += `|`; // OR
+ regex += ADDRESS_CHARS.liquidtestnet.bech32;
+ break;
case `bisq`:
regex += ADDRESS_CHARS.bisq.base58;
regex += `|`; // OR
From 3d900a38497d7d6b588abba8d06c2af2c8c5d663 Mon Sep 17 00:00:00 2001
From: junderw
Date: Sun, 28 Aug 2022 16:28:42 +0900
Subject: [PATCH 009/446] Fix: Prevent regex clash with channel IDs
---
frontend/src/app/shared/common.utils.ts | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/frontend/src/app/shared/common.utils.ts b/frontend/src/app/shared/common.utils.ts
index 288fc0362..8b914beda 100644
--- a/frontend/src/app/shared/common.utils.ts
+++ b/frontend/src/app/shared/common.utils.ts
@@ -149,12 +149,13 @@ const BECH32_CHARS_UP = `[AC-HJ-NP-Z02-9]`;
const HEX_CHARS = `[a-fA-F0-9]`;
// A regex to say "A single 0 OR any number with no leading zeroes"
-// (?: // Start a non-capturing group
-// 0 // A single 0
-// | // OR
-// [1-9]\d* // Any succession of numbers starting with 1-9
-// ) // End the non-capturing group.
-const ZERO_INDEX_NUMBER_CHARS = `(?:0|[1-9]\d*)`;
+// Capped at 13 digits so as to not be confused with lightning channel IDs (which are around 17 digits)
+// (?: // Start a non-capturing group
+// 0 // A single 0
+// | // OR
+// [1-9][0-9]{0,12} // Any succession of numbers up to 13 digits starting with 1-9
+// ) // End the non-capturing group.
+const ZERO_INDEX_NUMBER_CHARS = `(?:0|[1-9][0-9]{0,12})`;
// Formatting of the address regex is for readability,
// We should ignore formatting it with automated formatting tools like prettier.
From d825143b3562c4ab493f56ae67a4eadcfde7a469 Mon Sep 17 00:00:00 2001
From: junderw
Date: Sun, 4 Sep 2022 21:31:02 +0900
Subject: [PATCH 010/446] Search for full address in separate network if
matches
---
.../search-form/search-form.component.ts | 26 +-
frontend/src/app/shared/common.utils.ts | 1 -
.../pipes/relative-url/relative-url.pipe.ts | 4 +-
frontend/src/app/shared/regex.utils.ts | 224 ++++++++++++++++++
4 files changed, 235 insertions(+), 20 deletions(-)
create mode 100644 frontend/src/app/shared/regex.utils.ts
diff --git a/frontend/src/app/components/search-form/search-form.component.ts b/frontend/src/app/components/search-form/search-form.component.ts
index 8031195f0..a9e31221a 100644
--- a/frontend/src/app/components/search-form/search-form.component.ts
+++ b/frontend/src/app/components/search-form/search-form.component.ts
@@ -9,7 +9,7 @@ import { ElectrsApiService } from '../../services/electrs-api.service';
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
import { ApiService } from '../../services/api.service';
import { SearchResultsComponent } from './search-results/search-results.component';
-import { ADDRESS_REGEXES, getRegex } from '../../shared/common.utils';
+import { findOtherNetworks, getRegex } from '../../shared/regex.utils';
@Component({
selector: 'app-search-form',
@@ -208,22 +208,13 @@ export class SearchFormComponent implements OnInit {
const searchText = result || this.searchForm.value.searchText.trim();
if (searchText) {
this.isSearching = true;
+
+ const otherNetworks = findOtherNetworks(searchText, this.network as any);
if (!this.regexTransaction.test(searchText) && this.regexAddress.test(searchText)) {
this.navigate('/address/', searchText);
- } else if (
- // If the search text matches any other network besides this one
- ADDRESS_REGEXES
- .filter(([, network]) => network !== this.network)
- .some(([regex]) => regex.test(searchText))
- ) {
- // Gather all network matches as string[]
- const networks = ADDRESS_REGEXES.filter(([regex, network]) =>
- network !== this.network &&
- regex.test(searchText)
- ).map(([, network]) => network);
- // ###############################################
- // TODO: Create the search items for the drop down
- // ###############################################
+ } else if (otherNetworks.length > 0) {
+ // Change the network to the first match
+ this.navigate('/address/', searchText, undefined, otherNetworks[0]);
} else if (this.regexBlockhash.test(searchText) || this.regexBlockheight.test(searchText)) {
this.navigate('/block/', searchText);
} else if (this.regexTransaction.test(searchText)) {
@@ -252,8 +243,9 @@ export class SearchFormComponent implements OnInit {
}
}
- navigate(url: string, searchText: string, extras?: any): void {
- this.router.navigate([this.relativeUrlPipe.transform(url), searchText], extras);
+
+ navigate(url: string, searchText: string, extras?: any, swapNetwork?: string) {
+ this.router.navigate([this.relativeUrlPipe.transform(url, swapNetwork), searchText], extras);
this.searchTriggered.emit();
this.searchForm.setValue({
searchText: '',
diff --git a/frontend/src/app/shared/common.utils.ts b/frontend/src/app/shared/common.utils.ts
index 8b914beda..e50ba13b7 100644
--- a/frontend/src/app/shared/common.utils.ts
+++ b/frontend/src/app/shared/common.utils.ts
@@ -119,7 +119,6 @@ export function convertRegion(input, to: 'name' | 'abbreviated'): string {
}
}
-
export function haversineDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
const rlat1 = lat1 * Math.PI / 180;
const rlon1 = lon1 * Math.PI / 180;
diff --git a/frontend/src/app/shared/pipes/relative-url/relative-url.pipe.ts b/frontend/src/app/shared/pipes/relative-url/relative-url.pipe.ts
index d7fe612fe..83f5f20df 100644
--- a/frontend/src/app/shared/pipes/relative-url/relative-url.pipe.ts
+++ b/frontend/src/app/shared/pipes/relative-url/relative-url.pipe.ts
@@ -10,8 +10,8 @@ export class RelativeUrlPipe implements PipeTransform {
private stateService: StateService,
) { }
- transform(value: string): string {
- let network = this.stateService.network;
+ transform(value: string, swapNetwork?: string): string {
+ let network = swapNetwork || this.stateService.network;
if (this.stateService.env.BASE_MODULE === 'liquid' && network === 'liquidtestnet') {
network = 'testnet';
} else if (this.stateService.env.BASE_MODULE !== 'mempool') {
diff --git a/frontend/src/app/shared/regex.utils.ts b/frontend/src/app/shared/regex.utils.ts
new file mode 100644
index 000000000..bac256c8d
--- /dev/null
+++ b/frontend/src/app/shared/regex.utils.ts
@@ -0,0 +1,224 @@
+// all base58 characters
+const BASE58_CHARS = `[a-km-zA-HJ-NP-Z1-9]`;
+
+// all bech32 characters (after the separator)
+const BECH32_CHARS_LW = `[ac-hj-np-z02-9]`;
+const BECH32_CHARS_UP = `[AC-HJ-NP-Z02-9]`;
+
+// Hex characters
+const HEX_CHARS = `[a-fA-F0-9]`;
+
+// A regex to say "A single 0 OR any number with no leading zeroes"
+// Capped at 13 digits so as to not be confused with lightning channel IDs (which are around 17 digits)
+// (?: // Start a non-capturing group
+// 0 // A single 0
+// | // OR
+// [1-9][0-9]{0,12} // Any succession of numbers up to 13 digits starting with 1-9
+// ) // End the non-capturing group.
+const ZERO_INDEX_NUMBER_CHARS = `(?:0|[1-9][0-9]{0,12})`;
+
+// Formatting of the address regex is for readability,
+// We should ignore formatting it with automated formatting tools like prettier.
+//
+// prettier-ignore
+const ADDRESS_CHARS: {
+ [k in Network]: {
+ base58: string;
+ bech32: string;
+ };
+} = {
+ mainnet: {
+ base58: `[13]` // Starts with a single 1 or 3
+ + BASE58_CHARS
+ + `{26,33}`, // Repeat the previous char 26-33 times.
+ // Version byte 0x00 (P2PKH) can be as short as 27 characters, up to 34 length
+ // P2SH must be 34 length
+ bech32: `(?:`
+ + `bc1` // Starts with bc1
+ + BECH32_CHARS_LW
+ + `{6,100}` // As per bech32, 6 char checksum is minimum
+ + `|`
+ + `BC1` // All upper case version
+ + BECH32_CHARS_UP
+ + `{6,100}`
+ + `)`,
+ },
+ testnet: {
+ base58: `[mn2]` // Starts with a single m, n, or 2 (P2PKH is m or n, 2 is P2SH)
+ + BASE58_CHARS
+ + `{33,34}`, // m|n is 34 length, 2 is 35 length (We match the first letter separately)
+ bech32: `(?:`
+ + `tb1` // Starts with bc1
+ + BECH32_CHARS_LW
+ + `{6,100}` // As per bech32, 6 char checksum is minimum
+ + `|`
+ + `TB1` // All upper case version
+ + BECH32_CHARS_UP
+ + `{6,100}`
+ + `)`,
+ },
+ signet: {
+ base58: `[mn2]`
+ + BASE58_CHARS
+ + `{33,34}`,
+ bech32: `(?:`
+ + `tb1` // Starts with tb1
+ + BECH32_CHARS_LW
+ + `{6,100}`
+ + `|`
+ + `TB1` // All upper case version
+ + BECH32_CHARS_UP
+ + `{6,100}`
+ + `)`,
+ },
+ liquid: {
+ base58: `[GHPQ]` // G|H is P2PKH, P|Q is P2SH
+ + BASE58_CHARS
+ + `{33}`, // All min-max lengths are 34
+ bech32: `(?:`
+ + `(?:` // bech32 liquid starts with ex or lq
+ + `ex`
+ + `|`
+ + `lq`
+ + `)`
+ + BECH32_CHARS_LW // blech32 and bech32 are the same alphabet and protocol, different checksums.
+ + `{6,100}`
+ + `|`
+ + `(?:` // Same as above but all upper case
+ + `EX`
+ + `|`
+ + `LQ`
+ + `)`
+ + BECH32_CHARS_UP
+ + `{6,100}`
+ + `)`,
+ },
+ liquidtestnet: {
+ base58: `[89]` // ???(TODO: find version) is P2PKH, 8|9 is P2SH
+ + BASE58_CHARS
+ + `{33}`, // P2PKH is ???(TODO: find size), P2SH is 34
+ bech32: `(?:`
+ + `(?:` // bech32 liquid testnet starts with tex or tlq
+ + `tex` // TODO: Why does mempool use this and not ert|el like in the elements source?
+ + `|`
+ + `tlq` // TODO: does this exist?
+ + `)`
+ + BECH32_CHARS_LW // blech32 and bech32 are the same alphabet and protocol, different checksums.
+ + `{6,100}`
+ + `|`
+ + `(?:` // Same as above but all upper case
+ + `TEX`
+ + `|`
+ + `TLQ`
+ + `)`
+ + BECH32_CHARS_UP
+ + `{6,100}`
+ + `)`,
+ },
+ bisq: {
+ base58: `B1` // bisq base58 addrs start with B1
+ + BASE58_CHARS
+ + `{33}`, // always length 35
+ bech32: `(?:`
+ + `bbc1` // Starts with bbc1
+ + BECH32_CHARS_LW
+ + `{6,100}`
+ + `|`
+ + `BBC1` // All upper case version
+ + BECH32_CHARS_UP
+ + `{6,100}`
+ + `)`,
+ },
+}
+type RegexTypeNoAddr = `blockhash` | `transaction` | `blockheight`;
+export type RegexType = `address` | RegexTypeNoAddr;
+
+export const NETWORKS = [`testnet`, `signet`, `liquid`, `liquidtestnet`, `bisq`, `mainnet`] as const;
+export type Network = typeof NETWORKS[number]; // Turn const array into union type
+
+export const ADDRESS_REGEXES: [RegExp, Network][] = NETWORKS
+ .map(network => [getRegex('address', network), network])
+
+export function findOtherNetworks(address: string, skipNetwork: Network): Network[] {
+ return ADDRESS_REGEXES.filter(([regex, network]) =>
+ network !== skipNetwork &&
+ regex.test(address)
+ ).map(([, network]) => network);
+}
+
+export function getRegex(type: RegexTypeNoAddr): RegExp;
+export function getRegex(type: 'address', network: Network): RegExp;
+export function getRegex(type: RegexType, network?: Network): RegExp {
+ let regex = `^`; // ^ = Start of string
+ switch (type) {
+ // Match a block height number
+ // [Testing Order]: any order is fine
+ case `blockheight`:
+ regex += ZERO_INDEX_NUMBER_CHARS; // block height is a 0 indexed number
+ break;
+ // Match a 32 byte block hash in hex. Assumes at least 32 bits of difficulty.
+ // [Testing Order]: Must always be tested before `transaction`
+ case `blockhash`:
+ regex += `0{8}`; // Starts with exactly 8 zeroes in a row
+ regex += `${HEX_CHARS}{56}`; // Continues with exactly 56 hex letters/numbers
+ break;
+ // Match a 32 byte tx hash in hex. Contains optional output index specifier.
+ // [Testing Order]: Must always be tested after `blockhash`
+ case `transaction`:
+ regex += `${HEX_CHARS}{64}`; // Exactly 64 hex letters/numbers
+ regex += `(?:`; // Start a non-capturing group
+ regex += `:`; // 1 instances of the symbol ":"
+ regex += ZERO_INDEX_NUMBER_CHARS; // A zero indexed number
+ regex += `)?`; // End the non-capturing group. This group appears 0 or 1 times
+ break;
+ // Match any one of the many address types
+ // [Testing Order]: While possible that a bech32 address happens to be 64 hex
+ // characters in the future (current lengths are not 64), it is highly unlikely
+ // Order therefore, does not matter.
+ case `address`:
+ if (!network) {
+ throw new Error(`Must pass network when type is address`);
+ }
+ regex += `(?:`; // Start a non-capturing group (each network has multiple options)
+ switch (network) {
+ case `mainnet`:
+ regex += ADDRESS_CHARS.mainnet.base58;
+ regex += `|`; // OR
+ regex += ADDRESS_CHARS.mainnet.bech32;
+ break;
+ case `testnet`:
+ regex += ADDRESS_CHARS.testnet.base58;
+ regex += `|`; // OR
+ regex += ADDRESS_CHARS.testnet.bech32;
+ break;
+ case `signet`:
+ regex += ADDRESS_CHARS.signet.base58;
+ regex += `|`; // OR
+ regex += ADDRESS_CHARS.signet.bech32;
+ break;
+ case `liquid`:
+ regex += ADDRESS_CHARS.liquid.base58;
+ regex += `|`; // OR
+ regex += ADDRESS_CHARS.liquid.bech32;
+ break;
+ case `liquidtestnet`:
+ regex += ADDRESS_CHARS.liquidtestnet.base58;
+ regex += `|`; // OR
+ regex += ADDRESS_CHARS.liquidtestnet.bech32;
+ break;
+ case `bisq`:
+ regex += ADDRESS_CHARS.bisq.base58;
+ regex += `|`; // OR
+ regex += ADDRESS_CHARS.bisq.bech32;
+ break;
+ default:
+ throw new Error(`Invalid Network ${network} (Unreachable error in TypeScript)`);
+ }
+ regex += `)`; // End the non-capturing group
+ break;
+ default:
+ throw new Error(`Invalid RegexType ${type} (Unreachable error in TypeScript)`);
+ }
+ regex += `$`; // $ = End of string
+ return new RegExp(regex);
+}
From 2c59992d3fbc28f64312802c622564b97a946cd6 Mon Sep 17 00:00:00 2001
From: junderw
Date: Sun, 4 Sep 2022 21:53:52 +0900
Subject: [PATCH 011/446] Fix E2E error
---
.../search-form/search-form.component.ts | 2 +-
frontend/src/app/shared/common.utils.ts | 218 ------------------
2 files changed, 1 insertion(+), 219 deletions(-)
diff --git a/frontend/src/app/components/search-form/search-form.component.ts b/frontend/src/app/components/search-form/search-form.component.ts
index a9e31221a..18b4048ef 100644
--- a/frontend/src/app/components/search-form/search-form.component.ts
+++ b/frontend/src/app/components/search-form/search-form.component.ts
@@ -65,7 +65,7 @@ export class SearchFormComponent implements OnInit {
this.stateService.networkChanged$.subscribe((network) => {
this.network = network;
// TODO: Eventually change network type here from string to enum of consts
- this.regexAddress = getRegex('address', network as any);
+ this.regexAddress = getRegex('address', network as any || 'mainnet');
});
this.searchForm = this.formBuilder.group({
diff --git a/frontend/src/app/shared/common.utils.ts b/frontend/src/app/shared/common.utils.ts
index e50ba13b7..87c952c31 100644
--- a/frontend/src/app/shared/common.utils.ts
+++ b/frontend/src/app/shared/common.utils.ts
@@ -136,221 +136,3 @@ export function haversineDistance(lat1: number, lon1: number, lat2: number, lon2
export function kmToMiles(km: number): number {
return km * 0.62137119;
}
-
-// all base58 characters
-const BASE58_CHARS = `[a-km-zA-HJ-NP-Z1-9]`;
-
-// all bech32 characters (after the separator)
-const BECH32_CHARS_LW = `[ac-hj-np-z02-9]`;
-const BECH32_CHARS_UP = `[AC-HJ-NP-Z02-9]`;
-
-// Hex characters
-const HEX_CHARS = `[a-fA-F0-9]`;
-
-// A regex to say "A single 0 OR any number with no leading zeroes"
-// Capped at 13 digits so as to not be confused with lightning channel IDs (which are around 17 digits)
-// (?: // Start a non-capturing group
-// 0 // A single 0
-// | // OR
-// [1-9][0-9]{0,12} // Any succession of numbers up to 13 digits starting with 1-9
-// ) // End the non-capturing group.
-const ZERO_INDEX_NUMBER_CHARS = `(?:0|[1-9][0-9]{0,12})`;
-
-// Formatting of the address regex is for readability,
-// We should ignore formatting it with automated formatting tools like prettier.
-//
-// prettier-ignore
-const ADDRESS_CHARS: {
- [k in Network]: {
- base58: string;
- bech32: string;
- };
-} = {
- mainnet: {
- base58: `[13]` // Starts with a single 1 or 3
- + BASE58_CHARS
- + `{26,33}`, // Repeat the previous char 26-33 times.
- // Version byte 0x00 (P2PKH) can be as short as 27 characters, up to 34 length
- // P2SH must be 34 length
- bech32: `(?:`
- + `bc1` // Starts with bc1
- + BECH32_CHARS_LW
- + `{6,100}` // As per bech32, 6 char checksum is minimum
- + `|`
- + `BC1` // All upper case version
- + BECH32_CHARS_UP
- + `{6,100}`
- + `)`,
- },
- testnet: {
- base58: `[mn2]` // Starts with a single m, n, or 2 (P2PKH is m or n, 2 is P2SH)
- + BASE58_CHARS
- + `{33,34}`, // m|n is 34 length, 2 is 35 length (We match the first letter separately)
- bech32: `(?:`
- + `tb1` // Starts with bc1
- + BECH32_CHARS_LW
- + `{6,100}` // As per bech32, 6 char checksum is minimum
- + `|`
- + `TB1` // All upper case version
- + BECH32_CHARS_UP
- + `{6,100}`
- + `)`,
- },
- signet: {
- base58: `[mn2]`
- + BASE58_CHARS
- + `{33,34}`,
- bech32: `(?:`
- + `tb1` // Starts with tb1
- + BECH32_CHARS_LW
- + `{6,100}`
- + `|`
- + `TB1` // All upper case version
- + BECH32_CHARS_UP
- + `{6,100}`
- + `)`,
- },
- liquid: {
- base58: `[GHPQ]` // G|H is P2PKH, P|Q is P2SH
- + BASE58_CHARS
- + `{33}`, // All min-max lengths are 34
- bech32: `(?:`
- + `(?:` // bech32 liquid starts with ex or lq
- + `ex`
- + `|`
- + `lq`
- + `)`
- + BECH32_CHARS_LW // blech32 and bech32 are the same alphabet and protocol, different checksums.
- + `{6,100}`
- + `|`
- + `(?:` // Same as above but all upper case
- + `EX`
- + `|`
- + `LQ`
- + `)`
- + BECH32_CHARS_UP
- + `{6,100}`
- + `)`,
- },
- liquidtestnet: {
- base58: `[89]` // ???(TODO: find version) is P2PKH, 8|9 is P2SH
- + BASE58_CHARS
- + `{33}`, // P2PKH is ???(TODO: find size), P2SH is 34
- bech32: `(?:`
- + `(?:` // bech32 liquid testnet starts with tex or tlq
- + `tex` // TODO: Why does mempool use this and not ert|el like in the elements source?
- + `|`
- + `tlq` // TODO: does this exist?
- + `)`
- + BECH32_CHARS_LW // blech32 and bech32 are the same alphabet and protocol, different checksums.
- + `{6,100}`
- + `|`
- + `(?:` // Same as above but all upper case
- + `TEX`
- + `|`
- + `TLQ`
- + `)`
- + BECH32_CHARS_UP
- + `{6,100}`
- + `)`,
- },
- bisq: {
- base58: `B1` // bisq base58 addrs start with B1
- + BASE58_CHARS
- + `{33}`, // always length 35
- bech32: `(?:`
- + `bbc1` // Starts with bbc1
- + BECH32_CHARS_LW
- + `{6,100}`
- + `|`
- + `BBC1` // All upper case version
- + BECH32_CHARS_UP
- + `{6,100}`
- + `)`,
- },
-}
-type RegexTypeNoAddr = `blockhash` | `transaction` | `blockheight`;
-export type RegexType = `address` | RegexTypeNoAddr;
-
-export const NETWORKS = [`testnet`, `signet`, `liquid`, `liquidtestnet`, `bisq`, `mainnet`] as const;
-export type Network = typeof NETWORKS[number]; // Turn const array into union type
-
-export const ADDRESS_REGEXES: [RegExp, Network][] = NETWORKS
- .map(network => [getRegex('address', network), network])
-
-export function getRegex(type: RegexTypeNoAddr): RegExp;
-export function getRegex(type: 'address', network: Network): RegExp;
-export function getRegex(type: RegexType, network?: Network): RegExp {
- let regex = `^`; // ^ = Start of string
- switch (type) {
- // Match a block height number
- // [Testing Order]: any order is fine
- case `blockheight`:
- regex += ZERO_INDEX_NUMBER_CHARS; // block height is a 0 indexed number
- break;
- // Match a 32 byte block hash in hex. Assumes at least 32 bits of difficulty.
- // [Testing Order]: Must always be tested before `transaction`
- case `blockhash`:
- regex += `0{8}`; // Starts with exactly 8 zeroes in a row
- regex += `${HEX_CHARS}{56}`; // Continues with exactly 56 hex letters/numbers
- break;
- // Match a 32 byte tx hash in hex. Contains optional output index specifier.
- // [Testing Order]: Must always be tested after `blockhash`
- case `transaction`:
- regex += `${HEX_CHARS}{64}`; // Exactly 64 hex letters/numbers
- regex += `(?:`; // Start a non-capturing group
- regex += `:`; // 1 instances of the symbol ":"
- regex += ZERO_INDEX_NUMBER_CHARS; // A zero indexed number
- regex += `)?`; // End the non-capturing group. This group appears 0 or 1 times
- break;
- // Match any one of the many address types
- // [Testing Order]: While possible that a bech32 address happens to be 64 hex
- // characters in the future (current lengths are not 64), it is highly unlikely
- // Order therefore, does not matter.
- case `address`:
- if (!network) {
- throw new Error(`Must pass network when type is address`);
- }
- regex += `(?:`; // Start a non-capturing group (each network has multiple options)
- switch (network) {
- case `mainnet`:
- regex += ADDRESS_CHARS.mainnet.base58;
- regex += `|`; // OR
- regex += ADDRESS_CHARS.mainnet.bech32;
- break;
- case `testnet`:
- regex += ADDRESS_CHARS.testnet.base58;
- regex += `|`; // OR
- regex += ADDRESS_CHARS.testnet.bech32;
- break;
- case `signet`:
- regex += ADDRESS_CHARS.signet.base58;
- regex += `|`; // OR
- regex += ADDRESS_CHARS.signet.bech32;
- break;
- case `liquid`:
- regex += ADDRESS_CHARS.liquid.base58;
- regex += `|`; // OR
- regex += ADDRESS_CHARS.liquid.bech32;
- break;
- case `liquidtestnet`:
- regex += ADDRESS_CHARS.liquidtestnet.base58;
- regex += `|`; // OR
- regex += ADDRESS_CHARS.liquidtestnet.bech32;
- break;
- case `bisq`:
- regex += ADDRESS_CHARS.bisq.base58;
- regex += `|`; // OR
- regex += ADDRESS_CHARS.bisq.bech32;
- break;
- default:
- throw new Error(`Invalid Network ${network} (Unreachable error in TypeScript)`);
- }
- regex += `)`; // End the non-capturing group
- break;
- default:
- throw new Error(`Invalid RegexType ${type} (Unreachable error in TypeScript)`);
- }
- regex += `$`; // $ = End of string
- return new RegExp(regex);
-}
From 213800f563a2731a991cfbe2ce76b3ec419a92ad Mon Sep 17 00:00:00 2001
From: softsimon
Date: Wed, 19 Jul 2023 16:46:02 +0900
Subject: [PATCH 012/446] Merge error fix
---
.../app/components/search-form/search-form.component.ts | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/frontend/src/app/components/search-form/search-form.component.ts b/frontend/src/app/components/search-form/search-form.component.ts
index 18b4048ef..eb8adbd83 100644
--- a/frontend/src/app/components/search-form/search-form.component.ts
+++ b/frontend/src/app/components/search-form/search-form.component.ts
@@ -35,10 +35,10 @@ export class SearchFormComponent implements OnInit {
}
}
- regexAddress = /^([a-km-zA-HJ-NP-Z1-9]{26,35}|[a-km-zA-HJ-NP-Z1-9]{80}|[A-z]{2,5}1[a-zA-HJ-NP-Z0-9]{39,59})$/;
- regexBlockhash = /^[0]{8}[a-fA-F0-9]{56}$/;
- regexTransaction = /^([a-fA-F0-9]{64})(:\d+)?$/;
- regexBlockheight = /^[0-9]{1,9}$/;
+ regexAddress = getRegex('address', 'mainnet'); // Default to mainnet
+ regexBlockhash = getRegex('blockhash');
+ regexTransaction = getRegex('transaction');
+ regexBlockheight = getRegex('blockheight');
focus$ = new Subject();
click$ = new Subject();
From d7ac326f920ae40fe7e93c140ad3760ac5ab4d98 Mon Sep 17 00:00:00 2001
From: nymkappa <1612910616@pm.me>
Date: Sun, 23 Jul 2023 12:02:04 +0900
Subject: [PATCH 013/446] [block list] improve block list when db = false
---
.../blocks-list/blocks-list.component.html | 66 ++++++++++---------
.../blocks-list/blocks-list.component.ts | 17 +++--
2 files changed, 46 insertions(+), 37 deletions(-)
diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html
index 39fbb95e0..6248e6868 100644
--- a/frontend/src/app/components/blocks-list/blocks-list.component.html
+++ b/frontend/src/app/components/blocks-list/blocks-list.component.html
@@ -1,6 +1,6 @@
-
+
Blocks
@@ -9,28 +9,28 @@
- Height
- Height
+ Pool
- Timestamp
- Timestamp
+ Health
- Reward
- Fees
-
- Fees
+
+ TXs
- Transactions
- Size
+ Transactions
+ Size
{{ block.height }}
-
-
+
+
+
-
+
{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}
-
+
Unknown
-
+
-
+
-
+
= 0" [class.negative]="block.extras.feeDelta < 0">
{{ block.extras.feeDelta > 0 ? '+' : '' }}{{ (block.extras.feeDelta * 100) | amountShortener: 2 }}%
-
+
{{ block.tx_count | number }}
-
+
@@ -82,34 +88,34 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.ts b/frontend/src/app/components/blocks-list/blocks-list.component.ts
index 1af6572fc..b1c40ec6f 100644
--- a/frontend/src/app/components/blocks-list/blocks-list.component.ts
+++ b/frontend/src/app/components/blocks-list/blocks-list.component.ts
@@ -17,6 +17,7 @@ export class BlocksList implements OnInit {
blocks$: Observable = undefined;
+ isMempoolModule = false;
indexingAvailable = false;
auditAvailable = false;
isLoading = true;
@@ -36,6 +37,7 @@ export class BlocksList implements OnInit {
public stateService: StateService,
private cd: ChangeDetectorRef,
) {
+ this.isMempoolModule = this.stateService.env.BASE_MODULE === 'mempool';
}
ngOnInit(): void {
@@ -64,11 +66,10 @@ export class BlocksList implements OnInit {
this.lastBlockHeight = Math.max(...blocks.map(o => o.height));
}),
map(blocks => {
- if (this.indexingAvailable) {
+ if (this.stateService.env.BASE_MODULE === 'mempool') {
for (const block of blocks) {
// @ts-ignore: Need to add an extra field for the template
- block.extras.pool.logo = `/resources/mining-pools/` +
- block.extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg';
+ block.extras.pool.logo = `/resources/mining-pools/` + block.extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg';
}
}
if (this.widget) {
@@ -99,7 +100,7 @@ export class BlocksList implements OnInit {
}
if (blocks[1]) {
this.blocksCount = Math.max(this.blocksCount, blocks[1][0].height) + 1;
- if (this.stateService.env.MINING_DASHBOARD) {
+ if (this.isMempoolModule) {
// @ts-ignore: Need to add an extra field for the template
blocks[1][0].extras.pool.logo = `/resources/mining-pools/` +
blocks[1][0].extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg';
@@ -110,9 +111,11 @@ export class BlocksList implements OnInit {
return acc;
}, []),
switchMap((blocks) => {
- blocks.forEach(block => {
- block.extras.feeDelta = block.extras.expectedFees ? (block.extras.totalFees - block.extras.expectedFees) / block.extras.expectedFees : 0;
- });
+ if (this.isMempoolModule && this.auditAvailable) {
+ blocks.forEach(block => {
+ block.extras.feeDelta = block.extras.expectedFees ? (block.extras.totalFees - block.extras.expectedFees) / block.extras.expectedFees : 0;
+ });
+ }
return of(blocks);
})
);
From 597073c9b6ae5eae166690aa7fcf1e3c5745a292 Mon Sep 17 00:00:00 2001
From: nymkappa <1612910616@pm.me>
Date: Sun, 23 Jul 2023 12:18:21 +0900
Subject: [PATCH 014/446] [block list] re-enable block fee if !auditAvailable
in widget
---
.../src/app/components/blocks-list/blocks-list.component.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html
index 6248e6868..85e2ea17f 100644
--- a/frontend/src/app/components/blocks-list/blocks-list.component.html
+++ b/frontend/src/app/components/blocks-list/blocks-list.component.html
@@ -17,7 +17,7 @@
i18n-ngbTooltip="latest-blocks.health" ngbTooltip="Health" placement="bottom" #health [disableTooltip]="!isEllipsisActive(health)">Health
Reward
- Fees
+ Fees
TXs
@@ -65,7 +65,7 @@
-
+
From f7ec5ca82ee95d0f9e5559a9f578ec463f77d083 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 3 Aug 2023 17:47:30 +0200
Subject: [PATCH 015/446] Update liquid-master-page.component.html
ngIf for BISQ_ENABLED was missing
---
.../liquid-master-page/liquid-master-page.component.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html b/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html
index 476c78622..50296b895 100644
--- a/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html
+++ b/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html
@@ -53,7 +53,7 @@
Signet
Testnet
- Bisq
+ Bisq
Liquid
Liquid Testnet
@@ -98,4 +98,4 @@
-
\ No newline at end of file
+
From 223f6df3713729eb02dbedd4eefdf16a43c53dc9 Mon Sep 17 00:00:00 2001
From: nymkappa <1612910616@pm.me>
Date: Sat, 5 Aug 2023 10:52:11 +0900
Subject: [PATCH 016/446] fix 'large' class trigger
---
.../transactions-list/transactions-list.component.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.html b/frontend/src/app/components/transactions-list/transactions-list.component.html
index ef34bf822..24d34d6ec 100644
--- a/frontend/src/app/components/transactions-list/transactions-list.component.html
+++ b/frontend/src/app/components/transactions-list/transactions-list.component.html
@@ -81,7 +81,7 @@
- 1000000}">
+ 1000000000}">
@@ -222,7 +222,7 @@
- 1000000}">
+ 1000000000}">
From fc73dcc3444b73b31110c45734a0155524302685 Mon Sep 17 00:00:00 2001
From: Stephan Oeste
Date: Mon, 7 Aug 2023 20:08:30 +0200
Subject: [PATCH 017/446] Fixing proxy_buffer_size error nginx.conf
---
production/nginx/http-proxy-cache.conf | 1 +
1 file changed, 1 insertion(+)
diff --git a/production/nginx/http-proxy-cache.conf b/production/nginx/http-proxy-cache.conf
index f870939b3..60c6d4f82 100644
--- a/production/nginx/http-proxy-cache.conf
+++ b/production/nginx/http-proxy-cache.conf
@@ -3,3 +3,4 @@ proxy_cache_path /var/cache/nginx/api keys_zone=api:20m levels=1:2 inactive=600s
proxy_cache_path /var/cache/nginx/services keys_zone=services:20m levels=1:2 inactive=600s max_size=100m;
proxy_cache_path /var/cache/nginx/markets keys_zone=markets:20m levels=1:2 inactive=600s max_size=100m;
types_hash_max_size 2048;
+proxy_buffer_size 8k;
From 89be841e6429d3900ace94a7b1cd9cbc44dbfee8 Mon Sep 17 00:00:00 2001
From: nymkappa <1612910616@pm.me>
Date: Tue, 8 Aug 2023 17:10:59 +0900
Subject: [PATCH 018/446] [accelerator] hide accelerate button if already
accelerating
---
.../app/components/transaction/transaction.component.html | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html
index 9584cecfd..0d8e4d4c3 100644
--- a/frontend/src/app/components/transaction/transaction.component.html
+++ b/frontend/src/app/components/transaction/transaction.component.html
@@ -101,7 +101,8 @@
= 7" [ngIfElse]="belowBlockLimit">
In several hours (or more)
- Accelerate
+
+ Accelerate
@@ -111,7 +112,8 @@
- Accelerate
+
+ Accelerate
From b988a4c526c4e76ff8f9ba493e689de2b8d05f73 Mon Sep 17 00:00:00 2001
From: nymkappa <1612910616@pm.me>
Date: Thu, 10 Aug 2023 18:01:05 +0900
Subject: [PATCH 019/446] use new services api to fetch chad/whale profile
image
---
frontend/src/app/components/about/about.component.html | 2 +-
frontend/src/resources/profile/unknown.svg | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html
index e9d5ec3b2..3b7647891 100644
--- a/frontend/src/app/components/about/about.component.html
+++ b/frontend/src/app/components/about/about.component.html
@@ -207,7 +207,7 @@
-
+
diff --git a/frontend/src/resources/profile/unknown.svg b/frontend/src/resources/profile/unknown.svg
index 50a548c3c..236f054c4 100644
--- a/frontend/src/resources/profile/unknown.svg
+++ b/frontend/src/resources/profile/unknown.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
From 6199216c5407364354e32ee5776c12ddbdfced1b Mon Sep 17 00:00:00 2001
From: nymkappa <1612910616@pm.me>
Date: Thu, 10 Aug 2023 18:08:30 +0900
Subject: [PATCH 020/446] use new services api to fetch chads profile image as
well
---
frontend/src/app/components/about/about.component.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html
index 3b7647891..e9e87d1c1 100644
--- a/frontend/src/app/components/about/about.component.html
+++ b/frontend/src/app/components/about/about.component.html
@@ -219,7 +219,7 @@
From b3f90e298127ad0199ca3c0312fb3022ecfd4a28 Mon Sep 17 00:00:00 2001
From: hunicus <93150691+hunicus@users.noreply.github.com>
Date: Sun, 13 Aug 2023 08:23:57 +0900
Subject: [PATCH 021/446] Add mempool enterprise note to top-level readme
---
README.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index dd2e62478..9bc988970 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,9 @@ It is an open-source project developed and operated for the benefit of the Bitco
Mempool can be self-hosted on a wide variety of your own hardware, ranging from a simple one-click installation on a Raspberry Pi full-node distro all the way to a robust production instance on a powerful FreeBSD server.
-**Most people should use a one-click install method.** Other install methods are meant for developers and others with experience managing servers.
+Most people should use a one-click install method .
+
+Other install methods are meant for developers and others with experience managing servers. If you want support for your own production instance of Mempool, or if you'd like to have your own instance of Mempool run by the mempool.space team on their own global ISP infrastructure—check out Mempool Enterprise® .
## One-Click Installation
From 102579baa972b80e08b637d034dfe54804cb7b9b Mon Sep 17 00:00:00 2001
From: hunicus <93150691+hunicus@users.noreply.github.com>
Date: Sun, 13 Aug 2023 13:00:47 +0900
Subject: [PATCH 022/446] Add enterprise note to production readme
---
production/README.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/production/README.md b/production/README.md
index 87b8bb0a1..8e325bb1b 100644
--- a/production/README.md
+++ b/production/README.md
@@ -2,7 +2,9 @@
These instructions are for setting up a serious production Mempool website for Bitcoin (mainnet, testnet, signet), Liquid (mainnet, testnet), and Bisq.
-Again, this setup is no joke—home users should use [one of the other installation methods](../#installation-methods). Support is only provided to [enterprise sponsors](https://mempool.space/enterprise).
+Again, this setup is no joke—home users should use [one of the other installation methods](../#installation-methods). Support is only provided to project sponsors through [Mempool Enterprise®](https://mempool.space/enterprise).
+
+You can also have the mempool.space team run a highly-performant and highly-available instance of Mempool for you on their own global ISP infrastructure. See Mempool Enterprise® for more details.
### Server Hardware
From 0a918b8fa84270754ee3afa47c5dd9acca4c54e7 Mon Sep 17 00:00:00 2001
From: hunicus <93150691+hunicus@users.noreply.github.com>
Date: Sun, 13 Aug 2023 13:04:58 +0900
Subject: [PATCH 023/446] Add enterprise note to backend readme
---
backend/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/README.md b/backend/README.md
index 6a0cb821c..0582aca8c 100644
--- a/backend/README.md
+++ b/backend/README.md
@@ -2,7 +2,7 @@
These instructions are mostly intended for developers.
-If you choose to use these instructions for a production setup, be aware that you will still probably need to do additional configuration for your specific OS, environment, use-case, etc. We do our best here to provide a good starting point, but only proceed if you know what you're doing. Mempool only provides support for custom setups to [enterprise sponsors](https://mempool.space/enterprise).
+If you choose to use these instructions for a production setup, be aware that you will still probably need to do additional configuration for your specific OS, environment, use-case, etc. We do our best here to provide a good starting point, but only proceed if you know what you're doing. Mempool only provides support for custom setups to project sponsors through [Mempool Enterprise®](https://mempool.space/enterprise).
See other ways to set up Mempool on [the main README](/../../#installation-methods).
From 1dd66e669544a1e6f8d19fd135f96909979c3f78 Mon Sep 17 00:00:00 2001
From: hunicus <93150691+hunicus@users.noreply.github.com>
Date: Wed, 16 Aug 2023 12:43:36 +0900
Subject: [PATCH 024/446] Add bimi svg
---
frontend/src/resources/bimi.svg | 1 +
1 file changed, 1 insertion(+)
create mode 100644 frontend/src/resources/bimi.svg
diff --git a/frontend/src/resources/bimi.svg b/frontend/src/resources/bimi.svg
new file mode 100644
index 000000000..78a877552
--- /dev/null
+++ b/frontend/src/resources/bimi.svg
@@ -0,0 +1 @@
+Mempool Space K.K.
\ No newline at end of file
From 8ea7bb907c519002ae17ccfaae52075ed6a2525f Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Tue, 22 Aug 2023 02:40:00 +0900
Subject: [PATCH 025/446] Fix js error on mouseover on difficulty skeleton
---
.../src/app/components/difficulty/difficulty.component.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frontend/src/app/components/difficulty/difficulty.component.ts b/frontend/src/app/components/difficulty/difficulty.component.ts
index 7f305416f..81084f524 100644
--- a/frontend/src/app/components/difficulty/difficulty.component.ts
+++ b/frontend/src/app/components/difficulty/difficulty.component.ts
@@ -194,7 +194,7 @@ export class DifficultyComponent implements OnInit {
@HostListener('pointerdown', ['$event'])
onPointerDown(event): void {
- if (this.epochSvgElement.nativeElement?.contains(event.target)) {
+ if (this.epochSvgElement?.nativeElement?.contains(event.target)) {
this.onPointerMove(event);
event.preventDefault();
}
@@ -202,7 +202,7 @@ export class DifficultyComponent implements OnInit {
@HostListener('pointermove', ['$event'])
onPointerMove(event): void {
- if (this.epochSvgElement.nativeElement?.contains(event.target)) {
+ if (this.epochSvgElement?.nativeElement?.contains(event.target)) {
this.tooltipPosition = { x: event.clientX, y: event.clientY };
this.cd.markForCheck();
}
From f295392dcab8c2b8c7beef54503c8cb5790e9578 Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Tue, 15 Aug 2023 20:54:03 +0900
Subject: [PATCH 026/446] Record purging rate in statistics
---
backend/src/api/database-migration.ts | 7 ++++++-
backend/src/api/statistics/statistics-api.ts | 10 ++++++++--
backend/src/api/statistics/statistics.ts | 4 ++++
backend/src/mempool.interfaces.ts | 2 ++
4 files changed, 20 insertions(+), 3 deletions(-)
diff --git a/backend/src/api/database-migration.ts b/backend/src/api/database-migration.ts
index b7dc39493..89ef7a7be 100644
--- a/backend/src/api/database-migration.ts
+++ b/backend/src/api/database-migration.ts
@@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
import { RowDataPacket } from 'mysql2';
class DatabaseMigration {
- private static currentVersion = 65;
+ private static currentVersion = 66;
private queryTimeout = 3600_000;
private statisticsAddedIndexed = false;
private uniqueLogs: string[] = [];
@@ -553,6 +553,11 @@ class DatabaseMigration {
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD accelerated_txs JSON DEFAULT "[]"');
await this.updateToSchemaVersion(65);
}
+
+ if (databaseSchemaVersion < 66) {
+ await this.$executeQuery('ALTER TABLE `statistics` ADD min_fee FLOAT UNSIGNED DEFAULT NULL');
+ await this.updateToSchemaVersion(66);
+ }
}
/**
diff --git a/backend/src/api/statistics/statistics-api.ts b/backend/src/api/statistics/statistics-api.ts
index 9df12d704..d76b77a37 100644
--- a/backend/src/api/statistics/statistics-api.ts
+++ b/backend/src/api/statistics/statistics-api.ts
@@ -15,6 +15,7 @@ class StatisticsApi {
mempool_byte_weight,
fee_data,
total_fee,
+ min_fee,
vsize_1,
vsize_2,
vsize_3,
@@ -54,7 +55,7 @@ class StatisticsApi {
vsize_1800,
vsize_2000
)
- VALUES (NOW(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ VALUES (NOW(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)`;
const [result]: any = await DB.query(query);
return result.insertId;
@@ -73,6 +74,7 @@ class StatisticsApi {
mempool_byte_weight,
fee_data,
total_fee,
+ min_fee,
vsize_1,
vsize_2,
vsize_3,
@@ -112,7 +114,7 @@ class StatisticsApi {
vsize_1800,
vsize_2000
)
- VALUES (${statistics.added}, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
+ VALUES (${statistics.added}, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
const params: (string | number)[] = [
@@ -122,6 +124,7 @@ class StatisticsApi {
statistics.mempool_byte_weight,
statistics.fee_data,
statistics.total_fee,
+ statistics.min_fee,
statistics.vsize_1,
statistics.vsize_2,
statistics.vsize_3,
@@ -172,6 +175,7 @@ class StatisticsApi {
return `SELECT
UNIX_TIMESTAMP(added) as added,
CAST(avg(vbytes_per_second) as DOUBLE) as vbytes_per_second,
+ CAST(avg(min_fee) as DOUBLE) as min_fee,
CAST(avg(vsize_1) as DOUBLE) as vsize_1,
CAST(avg(vsize_2) as DOUBLE) as vsize_2,
CAST(avg(vsize_3) as DOUBLE) as vsize_3,
@@ -220,6 +224,7 @@ class StatisticsApi {
return `SELECT
UNIX_TIMESTAMP(added) as added,
CAST(avg(vbytes_per_second) as DOUBLE) as vbytes_per_second,
+ CAST(avg(min_fee) as DOUBLE) as min_fee,
vsize_1,
vsize_2,
vsize_3,
@@ -404,6 +409,7 @@ class StatisticsApi {
vbytes_per_second: s.vbytes_per_second,
mempool_byte_weight: s.mempool_byte_weight,
total_fee: s.total_fee,
+ min_fee: s.min_fee,
vsizes: [
s.vsize_1,
s.vsize_2,
diff --git a/backend/src/api/statistics/statistics.ts b/backend/src/api/statistics/statistics.ts
index 27554f36d..494777aad 100644
--- a/backend/src/api/statistics/statistics.ts
+++ b/backend/src/api/statistics/statistics.ts
@@ -89,6 +89,9 @@ class Statistics {
}
});
+ // get minFee and convert to sats/vb
+ const minFee = memPool.getMempoolInfo().mempoolminfee * 100000;
+
try {
const insertId = await statisticsApi.$create({
added: 'NOW()',
@@ -98,6 +101,7 @@ class Statistics {
mempool_byte_weight: totalWeight,
total_fee: totalFee,
fee_data: '',
+ min_fee: minFee,
vsize_1: weightVsizeFees['1'] || 0,
vsize_2: weightVsizeFees['2'] || 0,
vsize_3: weightVsizeFees['3'] || 0,
diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts
index c08846191..db04ded43 100644
--- a/backend/src/mempool.interfaces.ts
+++ b/backend/src/mempool.interfaces.ts
@@ -299,6 +299,7 @@ export interface Statistic {
total_fee: number;
mempool_byte_weight: number;
fee_data: string;
+ min_fee: number;
vsize_1: number;
vsize_2: number;
@@ -345,6 +346,7 @@ export interface OptimizedStatistic {
vbytes_per_second: number;
total_fee: number;
mempool_byte_weight: number;
+ min_fee: number;
vsizes: number[];
}
From 4664e5551302419ff220a1052b84a64f95cbc370 Mon Sep 17 00:00:00 2001
From: nymkappa <1612910616@pm.me>
Date: Wed, 30 Aug 2023 18:58:21 +0200
Subject: [PATCH 027/446] [footer] fix positioning when menu component is not
loaded
---
.../master-page/master-page.component.html | 1 +
.../master-page/master-page.component.scss | 16 +++++++++++++++-
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/frontend/src/app/components/master-page/master-page.component.html b/frontend/src/app/components/master-page/master-page.component.html
index 8863b335f..c1327cd11 100644
--- a/frontend/src/app/components/master-page/master-page.component.html
+++ b/frontend/src/app/components/master-page/master-page.component.html
@@ -78,6 +78,7 @@
+
diff --git a/frontend/src/app/components/master-page/master-page.component.scss b/frontend/src/app/components/master-page/master-page.component.scss
index b82d973de..ad95d162f 100644
--- a/frontend/src/app/components/master-page/master-page.component.scss
+++ b/frontend/src/app/components/master-page/master-page.component.scss
@@ -238,4 +238,18 @@ nav {
main {
transition: 0.2s;
transition-property: max-width;
-}
\ No newline at end of file
+}
+
+// empty sidenav
+.sidenav {
+ z-index: 1;
+ background-color: transparent;
+ width: 225px;
+ height: calc(100vh - 65px);
+ position: sticky;
+ top: 65px;
+ transition: 0.25s;
+ margin-left: -250px;
+ box-shadow: 5px 0px 30px 0px #000;
+ padding-bottom: 20px;
+}
From d09668aaa647ff7a10c65db0b60112ad8fddbf15 Mon Sep 17 00:00:00 2001
From: nymkappa <1612910616@pm.me>
Date: Sun, 3 Sep 2023 12:20:30 +0300
Subject: [PATCH 028/446] [accelerator] login CTA with redirection
---
.../accelerate-preview.component.html | 15 +++++++++++++--
.../accelerate-preview.component.ts | 8 +++++---
.../transaction/transaction.component.html | 4 ++--
.../transaction/transaction.component.ts | 12 ++++++++----
4 files changed, 28 insertions(+), 11 deletions(-)
diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html
index 9bb66eda1..813b8954c 100644
--- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html
+++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html
@@ -75,7 +75,7 @@
{{ option.fee | number }} sats
- ~
+ ~
@@ -228,7 +228,7 @@
-
+
Available balance
@@ -244,6 +244,17 @@
+
+
+
+
+
+
+
+ Login
+
+
+
diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts
index 1c356a80b..6bcc5e958 100644
--- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts
+++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts
@@ -1,4 +1,5 @@
import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener } from '@angular/core';
+import { Router } from '@angular/router';
import { ApiService } from '../../services/api.service';
import { Subscription, catchError, of, tap } from 'rxjs';
import { StorageService } from '../../services/storage.service';
@@ -62,7 +63,8 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
constructor(
private apiService: ApiService,
- private storageService: StorageService
+ private storageService: StorageService,
+ private router: Router,
) { }
ngOnDestroy(): void {
@@ -73,7 +75,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
ngOnChanges(changes: SimpleChanges): void {
if (changes.scrollEvent) {
- this.scrollToPreview('acceleratePreviewAnchor', 'center');
+ this.scrollToPreview('acceleratePreviewAnchor', 'start');
}
}
@@ -126,7 +128,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
this.maxCost = this.userBid + this.estimate.mempoolBaseFee + this.estimate.vsizeFee;
if (!this.error) {
- this.scrollToPreview('acceleratePreviewAnchor', 'center');
+ this.scrollToPreview('acceleratePreviewAnchor', 'start');
}
}
}),
diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html
index 006870864..9b810d598 100644
--- a/frontend/src/app/components/transaction/transaction.component.html
+++ b/frontend/src/app/components/transaction/transaction.component.html
@@ -80,7 +80,7 @@
-
+
Accelerate
@@ -135,7 +135,7 @@
-
+
Features
diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts
index 505c4686d..1f6aec5e2 100644
--- a/frontend/src/app/components/transaction/transaction.component.ts
+++ b/frontend/src/app/components/transaction/transaction.component.ts
@@ -619,10 +619,14 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
// simulate normal anchor fragment behavior
applyFragment(): void {
const anchor = Array.from(this.fragmentParams.entries()).find(([frag, value]) => value === '');
- if (anchor) {
- const anchorElement = document.getElementById(anchor[0]);
- if (anchorElement) {
- anchorElement.scrollIntoView();
+ if (anchor?.length) {
+ if (anchor[0] === 'accelerate') {
+ setTimeout(this.onAccelerateClicked.bind(this), 100);
+ } else {
+ const anchorElement = document.getElementById(anchor[0]);
+ if (anchorElement) {
+ anchorElement.scrollIntoView();
+ }
}
}
}
From 0e1a9d861938c5d8be5061945d56fee653140b46 Mon Sep 17 00:00:00 2001
From: nymkappa <1612910616@pm.me>
Date: Sun, 3 Sep 2023 16:05:12 +0300
Subject: [PATCH 029/446] [accelerator] blinking cta, polish accel preview
---
.../accelerate-fee-graph.component.scss | 1 -
.../accelerate-preview.component.html | 10 +++----
.../accelerate-preview.component.scss | 23 ++++++++++++++-
.../transaction/transaction.component.html | 7 +++--
.../transaction/transaction.component.scss | 29 +++++++++++++++++--
5 files changed, 59 insertions(+), 11 deletions(-)
diff --git a/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.scss b/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.scss
index 6137b53ee..69534cbad 100644
--- a/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.scss
+++ b/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.scss
@@ -2,7 +2,6 @@
height: 100%;
min-width: 120px;
width: 120px;
- max-height: 90vh;
margin-left: 4em;
margin-right: 1.5em;
padding-bottom: 63px;
diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html
index 9bb66eda1..18bc8672b 100644
--- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html
+++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html
@@ -75,7 +75,7 @@
{{ option.fee | number }} sats
- ~
+ ~
@@ -88,12 +88,12 @@
-
+
Estimated cost
-
-
+
+
Maximum cost
-
+
diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.scss b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.scss
index 433c05520..814b37e7a 100644
--- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.scss
+++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.scss
@@ -28,7 +28,10 @@
.feerate.active {
background-color: #105fb0 !important;
opacity: 1;
- border: 1px solid white !important;
+ border: 1px solid #007fff !important;
+}
+.feerate:focus {
+ box-shadow: none !important;
}
.estimateDisabled {
@@ -41,6 +44,24 @@
margin-top: 0.5em;
}
+.tab {
+ &:first-child {
+ margin-right: 1px;
+ }
+ border: solid 1px black;
+ border-bottom: none;
+ background-color: #323655;
+ border-top-left-radius: 10px !important;
+ border-top-right-radius: 10px !important;
+}
+.tab.active {
+ background-color: #5d659d !important;
+ opacity: 1;
+}
+.tab:focus {
+ box-shadow: none !important;
+}
+
.table-accelerator {
tr {
text-wrap: wrap;
diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html
index 006870864..81bbec1aa 100644
--- a/frontend/src/app/components/transaction/transaction.component.html
+++ b/frontend/src/app/components/transaction/transaction.component.html
@@ -6,8 +6,11 @@
-
-
Accelerate this transaction using Mempool Accelerator ™
+
+
+
Accelerate
+
this transaction using Mempool Accelerator ™
+
×
diff --git a/frontend/src/app/components/transaction/transaction.component.scss b/frontend/src/app/components/transaction/transaction.component.scss
index 2e076600e..38c4d7755 100644
--- a/frontend/src/app/components/transaction/transaction.component.scss
+++ b/frontend/src/app/components/transaction/transaction.component.scss
@@ -218,8 +218,33 @@
}
}
-.link.accelerator {
- cursor: pointer;
+.alert-purple {
+ background-color: #5c3a88;
+ width: 100%;
+}
+
+// Blinking block
+@keyframes shadowyBackground {
+ 0% {
+ box-shadow: 0px 0px 20px rgba(#eba814, 1);
+ }
+ 50% {
+ box-shadow: 0px 0px 20px rgba(#eba814, .3);
+ }
+ 100% {
+ box-shadow: 0px 0px 20px rgba(#ffae00, 1);
+ }
+}
+
+.blink-bg {
+ color: #fff;
+ background: repeating-linear-gradient(#daad0a 0%, #daad0a 5%, #987805 100%) !important;
+ animation: shadowyBackground 1s infinite;
+ box-shadow: 0px 0px 20px rgba(#eba814, 1);
+ transition: 100ms all ease-in;
+ margin-right: 8px;
+ font-size: 16px;
+ border: 1px solid gold;
}
.eta {
From d630e7217a7a9823ae2d5a94e0c959c879dc85c1 Mon Sep 17 00:00:00 2001
From: hunicus <93150691+hunicus@users.noreply.github.com>
Date: Wed, 6 Sep 2023 01:42:19 +0900
Subject: [PATCH 030/446] Make enterprise links not relative
---
frontend/src/app/docs/api-docs/api-docs-data.ts | 2 +-
frontend/src/app/docs/api-docs/api-docs.component.html | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/frontend/src/app/docs/api-docs/api-docs-data.ts b/frontend/src/app/docs/api-docs/api-docs-data.ts
index 3a857fe6a..ce0b7eaef 100644
--- a/frontend/src/app/docs/api-docs/api-docs-data.ts
+++ b/frontend/src/app/docs/api-docs/api-docs-data.ts
@@ -2922,7 +2922,7 @@ export const restApiDocsData = [
fragment: "get-blocks-bulk",
title: "GET Blocks (Bulk)",
description: {
- default: "
Returns details on the range of blocks between :minHeight
and :maxHeight
, inclusive, up to 10 blocks. If :maxHeight
is not specified, it defaults to the current tip.
To return data for more than 10 blocks, consider becoming an enterprise sponsor .
"
+ default: "
Returns details on the range of blocks between :minHeight
and :maxHeight
, inclusive, up to 10 blocks. If :maxHeight
is not specified, it defaults to the current tip.
To return data for more than 10 blocks, consider becoming an enterprise sponsor .
"
},
urlString: "/v1/blocks-bulk/:minHeight[/:maxHeight]",
showConditions: bitcoinNetworks,
diff --git a/frontend/src/app/docs/api-docs/api-docs.component.html b/frontend/src/app/docs/api-docs/api-docs.component.html
index 40a7ae486..1e95b27ed 100644
--- a/frontend/src/app/docs/api-docs/api-docs.component.html
+++ b/frontend/src/app/docs/api-docs/api-docs.component.html
@@ -40,7 +40,7 @@
Below is a reference for the {{ network.val === '' ? 'Bitcoin' : network.val.charAt(0).toUpperCase() + network.val.slice(1) }} REST API service .
-
Note that we enforce rate limits. If you exceed these limits, you will get an HTTP 429 error. If you repeatedly exceed the limits, you may be banned from accessing the service altogether. Consider an enterprise sponsorship if you need higher API limits.
+
Note that we enforce rate limits. If you exceed these limits, you will get an HTTP 429 error. If you repeatedly exceed the limits, you may be banned from accessing the service altogether. Consider an enterprise sponsorship if you need higher API limits.
-1 )">{{ item.title }}
@@ -123,7 +123,7 @@
{{electrsPort}}
SSL
Enabled
-
Electrum RPC interface for Bitcoin Signet is publicly available . Electrum RPC interface for all other networks is available to sponsors only—whitelisting is required.
+
Electrum RPC interface for Bitcoin Signet is publicly available . Electrum RPC interface for all other networks is available to sponsors only—whitelisting is required.
@@ -280,7 +280,7 @@
- You can manually install Mempool on your own server, but this requires advanced sysadmin skills since you will be manually configuring everything. You could also use our Docker images .
In any case, we only provide support for manual deployments to enterprise sponsors .
+ You can manually install Mempool on your own server, but this requires advanced sysadmin skills since you will be manually configuring everything. You could also use our Docker images .
In any case, we only provide support for manual deployments to enterprise sponsors .
For casual users, we strongly suggest installing Mempool using one of the 1-click install methods .
From 71d4aa9ddd2a91274ebe31d30329df0b5cc6aee8 Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Wed, 6 Sep 2023 08:19:41 +0900
Subject: [PATCH 031/446] Add mempool count line to graph
---
backend/src/api/statistics/statistics-api.ts | 2 +
.../mempool-graph/mempool-graph.component.ts | 98 ++++++++++++++++---
.../statistics/statistics.component.html | 10 +-
.../statistics/statistics.component.ts | 2 +
.../src/app/interfaces/node-api.interface.ts | 1 +
5 files changed, 100 insertions(+), 13 deletions(-)
diff --git a/backend/src/api/statistics/statistics-api.ts b/backend/src/api/statistics/statistics-api.ts
index 9df12d704..ecc0222c1 100644
--- a/backend/src/api/statistics/statistics-api.ts
+++ b/backend/src/api/statistics/statistics-api.ts
@@ -171,6 +171,7 @@ class StatisticsApi {
private getQueryForDaysAvg(div: number, interval: string) {
return `SELECT
UNIX_TIMESTAMP(added) as added,
+ CAST(avg(unconfirmed_transactions) as DOUBLE) as unconfirmed_transactions,
CAST(avg(vbytes_per_second) as DOUBLE) as vbytes_per_second,
CAST(avg(vsize_1) as DOUBLE) as vsize_1,
CAST(avg(vsize_2) as DOUBLE) as vsize_2,
@@ -401,6 +402,7 @@ class StatisticsApi {
return statistic.map((s) => {
return {
added: s.added,
+ count: s.unconfirmed_transactions,
vbytes_per_second: s.vbytes_per_second,
mempool_byte_weight: s.mempool_byte_weight,
total_fee: s.total_fee,
diff --git a/frontend/src/app/components/mempool-graph/mempool-graph.component.ts b/frontend/src/app/components/mempool-graph/mempool-graph.component.ts
index 6c9795c89..d31044be9 100644
--- a/frontend/src/app/components/mempool-graph/mempool-graph.component.ts
+++ b/frontend/src/app/components/mempool-graph/mempool-graph.component.ts
@@ -1,6 +1,7 @@
import { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnChanges } from '@angular/core';
import { VbytesPipe } from '../../shared/pipes/bytes-pipe/vbytes.pipe';
import { WuBytesPipe } from '../../shared/pipes/bytes-pipe/wubytes.pipe';
+import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe';
import { formatNumber } from '@angular/common';
import { OptimizedMempoolStats } from '../../interfaces/node-api.interface';
import { StateService } from '../../services/state.service';
@@ -26,6 +27,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
@Input() data: any[];
@Input() filterSize = 100000;
@Input() limitFilterFee = 1;
+ @Input() hideCount: boolean = false;
@Input() height: number | string = 200;
@Input() top: number | string = 20;
@Input() right: number | string = 10;
@@ -50,10 +52,13 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
inverted: boolean;
chartInstance: any = undefined;
weightMode: boolean = false;
+ isWidget: boolean = false;
+ showCount: boolean = true;
constructor(
private vbytesPipe: VbytesPipe,
private wubytesPipe: WuBytesPipe,
+ private amountShortenerPipe: AmountShortenerPipe,
private stateService: StateService,
private storageService: StorageService,
@Inject(LOCALE_ID) private locale: string,
@@ -62,12 +67,16 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
ngOnInit(): void {
this.isLoading = true;
this.inverted = this.storageService.getValue('inverted-graph') === 'true';
+ this.isWidget = this.template === 'widget';
+ this.showCount = !this.isWidget && !this.hideCount;
}
- ngOnChanges() {
+ ngOnChanges(changes) {
if (!this.data) {
return;
}
+ this.isWidget = this.template === 'widget';
+ this.showCount = !this.isWidget && !this.hideCount;
this.windowPreference = this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference');
this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([]));
this.mountFeeChart();
@@ -96,10 +105,12 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
mempoolStats.reverse();
const labels = mempoolStats.map(stats => stats.added);
const finalArrayVByte = this.generateArray(mempoolStats);
+ const finalArrayCount = this.generateCountArray(mempoolStats);
return {
labels: labels,
- series: finalArrayVByte
+ series: finalArrayVByte,
+ countSeries: finalArrayCount,
};
}
@@ -124,9 +135,13 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
return finalArray;
}
+ generateCountArray(mempoolStats: OptimizedMempoolStats[]) {
+ return mempoolStats.map(stats => [stats.added * 1000, stats.count]);
+ }
+
mountFeeChart() {
this.orderLevels();
- const { series } = this.mempoolVsizeFeesData;
+ const { series, countSeries } = this.mempoolVsizeFeesData;
const seriesGraph = [];
const newColors = [];
@@ -178,6 +193,29 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
});
}
}
+ if (this.showCount) {
+ newColors.push('white');
+ seriesGraph.push({
+ zlevel: 1,
+ yAxisIndex: 1,
+ name: 'count',
+ type: 'line',
+ stack: 'count',
+ smooth: false,
+ markPoint: false,
+ lineStyle: {
+ width: 2,
+ opacity: 1,
+ },
+ symbol: 'none',
+ silent: true,
+ areaStyle: {
+ color: null,
+ opacity: 0,
+ },
+ data: countSeries,
+ });
+ }
this.mempoolVsizeFeesOptions = {
series: this.inverted ? [...seriesGraph].reverse() : seriesGraph,
@@ -201,7 +239,11 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
label: {
formatter: (params: any) => {
if (params.axisDimension === 'y') {
- return this.vbytesPipe.transform(params.value, 2, 'vB', 'MvB', true)
+ if (params.axisIndex === 0) {
+ return this.vbytesPipe.transform(params.value, 2, 'vB', 'MvB', true);
+ } else {
+ return this.amountShortenerPipe.transform(params.value, 2, undefined, true);
+ }
} else {
return formatterXAxis(this.locale, this.windowPreference, params.value);
}
@@ -214,7 +256,11 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
const itemFormatted = [];
let totalParcial = 0;
let progressPercentageText = '';
- const items = this.inverted ? [...params].reverse() : params;
+ let countItem;
+ let items = this.inverted ? [...params].reverse() : params;
+ if (items[items.length - 1].seriesName === 'count') {
+ countItem = items.pop();
+ }
items.map((item: any, index: number) => {
totalParcial += item.value[1];
const progressPercentage = (item.value[1] / totalValue) * 100;
@@ -276,6 +322,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
`);
});
const classActive = (this.template === 'advanced') ? 'fees-wrapper-tooltip-chart-advanced' : '';
+ const titleCount = $localize`Count`;
const titleRange = $localize`Range`;
const titleSize = $localize`:@@7faaaa08f56427999f3be41df1093ce4089bbd75:Size`;
const titleSum = $localize`Sum`;
@@ -286,6 +333,25 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
${this.vbytesPipe.transform(totalValue, 2, 'vB', 'MvB', false)}
+ ` +
+ (this.showCount && countItem ? `
+
+
+
+
+
+
+ ${titleCount}
+
+
+
+ ${this.amountShortenerPipe.transform(countItem.value[1], 2, undefined, true)}
+
+
+
+
+ ` : '')
+ + `
@@ -305,12 +371,12 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
`;
}
},
- dataZoom: (this.template === 'widget' && this.isMobile()) ? null : [{
+ dataZoom: (this.isWidget && this.isMobile()) ? null : [{
type: 'inside',
realtime: true,
- zoomLock: (this.template === 'widget') ? true : false,
+ zoomLock: (this.isWidget) ? true : false,
zoomOnMouseWheel: (this.template === 'advanced') ? true : false,
- moveOnMouseMove: (this.template === 'widget') ? true : false,
+ moveOnMouseMove: (this.isWidget) ? true : false,
maxSpan: 100,
minSpan: 10,
}, {
@@ -339,7 +405,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
},
xAxis: [
{
- name: this.template === 'widget' ? '' : formatterXAxisLabel(this.locale, this.windowPreference),
+ name: this.isWidget ? '' : formatterXAxisLabel(this.locale, this.windowPreference),
nameLocation: 'middle',
nameTextStyle: {
padding: [20, 0, 0, 0],
@@ -357,7 +423,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
},
}
],
- yAxis: {
+ yAxis: [{
type: 'value',
axisLine: { onZero: false },
axisLabel: {
@@ -371,7 +437,17 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
opacity: 0.25,
}
}
- },
+ }, this.showCount ? {
+ type: 'value',
+ position: 'right',
+ axisLine: { onZero: false },
+ axisLabel: {
+ formatter: (value: number) => (`${this.amountShortenerPipe.transform(value, 2, undefined, true)}`),
+ },
+ splitLine: {
+ show: false,
+ }
+ } : null],
};
}
diff --git a/frontend/src/app/components/statistics/statistics.component.html b/frontend/src/app/components/statistics/statistics.component.html
index 29089e43d..02a26ed52 100644
--- a/frontend/src/app/components/statistics/statistics.component.html
+++ b/frontend/src/app/components/statistics/statistics.component.html
@@ -69,6 +69,12 @@
+
+
+ {{ titleCount }}
+
+
diff --git a/frontend/src/app/components/statistics/statistics.component.ts b/frontend/src/app/components/statistics/statistics.component.ts
index 69bf9ea8f..ba9068975 100644
--- a/frontend/src/app/components/statistics/statistics.component.ts
+++ b/frontend/src/app/components/statistics/statistics.component.ts
@@ -32,6 +32,7 @@ export class StatisticsComponent implements OnInit {
chartColors = chartColors;
filterSize = 100000;
filterFeeIndex = 1;
+ showCount = true;
maxFeeIndex: number;
dropDownOpen = false;
@@ -46,6 +47,7 @@ export class StatisticsComponent implements OnInit {
inverted: boolean;
feeLevelDropdownData = [];
timespan = '';
+ titleCount = $localize`Count`;
constructor(
@Inject(LOCALE_ID) private locale: string,
diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts
index a9f069b56..08de11a6a 100644
--- a/frontend/src/app/interfaces/node-api.interface.ts
+++ b/frontend/src/app/interfaces/node-api.interface.ts
@@ -2,6 +2,7 @@ import { Block, Transaction } from "./electrs.interface";
export interface OptimizedMempoolStats {
added: number;
+ count: number;
vbytes_per_second: number;
total_fee: number;
mempool_byte_weight: number;
From 9ed7e80c44359d33ebe41f6605ef12ba31ff391b Mon Sep 17 00:00:00 2001
From: nymkappa <1612910616@pm.me>
Date: Tue, 12 Sep 2023 14:15:29 +0200
Subject: [PATCH 032/446] fix typo post merge
---
.../src/app/components/transaction/transaction.component.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html
index ee9247aab..956d27aba 100644
--- a/frontend/src/app/components/transaction/transaction.component.html
+++ b/frontend/src/app/components/transaction/transaction.component.html
@@ -128,7 +128,7 @@
- Accelerate
+ Accelerate
From 0712195de4c6a01e83930baa5939c1af93ee444a Mon Sep 17 00:00:00 2001
From: orangesurf
Date: Tue, 12 Sep 2023 19:29:33 +0100
Subject: [PATCH 033/446] Add orangesurf contributor declaration
---
contributors/orangesurf.txt | 3 +++
1 file changed, 3 insertions(+)
create mode 100644 contributors/orangesurf.txt
diff --git a/contributors/orangesurf.txt b/contributors/orangesurf.txt
new file mode 100644
index 000000000..c760a9125
--- /dev/null
+++ b/contributors/orangesurf.txt
@@ -0,0 +1,3 @@
+I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of September 12, 2023.
+
+Signed: orange surf
From a1b75c377296d52f55a23258b7299095cfe555fb Mon Sep 17 00:00:00 2001
From: orangesurf
Date: Tue, 12 Sep 2023 19:45:18 +0100
Subject: [PATCH 034/446] Update to reflect registered trademark
---
frontend/src/app/components/about/about.component.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html
index 6bff838aa..ad05b7d71 100644
--- a/frontend/src/app/components/about/about.component.html
+++ b/frontend/src/app/components/about/about.component.html
@@ -10,7 +10,7 @@
-
The Mempool Open Source Project ™
+
The Mempool Open Source Project ®
Our mempool and blockchain explorer for the Bitcoin community, focusing on the transaction fee market and multi-layer ecosystem, completely self-hosted without any trusted third-parties.
From 0ba4e98f52cc539cea112317b54f60c6388304f4 Mon Sep 17 00:00:00 2001
From: orangesurf
Date: Tue, 12 Sep 2023 19:55:00 +0100
Subject: [PATCH 035/446] Replace other instances of tm use for The Mempool
Open Source Project
---
frontend/src/app/components/about/about.component.ts | 2 +-
.../components/privacy-policy/privacy-policy.component.ts | 2 +-
.../trademark-policy/trademark-policy.component.ts | 2 +-
frontend/src/index.mempool.html | 6 +++---
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/frontend/src/app/components/about/about.component.ts b/frontend/src/app/components/about/about.component.ts
index 3cda0dc20..8aa0422e8 100644
--- a/frontend/src/app/components/about/about.component.ts
+++ b/frontend/src/app/components/about/about.component.ts
@@ -43,7 +43,7 @@ export class AboutComponent implements OnInit {
ngOnInit() {
this.backendInfo$ = this.stateService.backendInfo$;
this.seoService.setTitle($localize`:@@004b222ff9ef9dd4771b777950ca1d0e4cd4348a:About`);
- this.seoService.setDescription($localize`:@@meta.description.about:Learn more about The Mempool Open Source Project™\: enterprise sponsors, individual sponsors, integrations, who contributes, FOSS licensing, and more.`);
+ this.seoService.setDescription($localize`:@@meta.description.about:Learn more about The Mempool Open Source Project®\: enterprise sponsors, individual sponsors, integrations, who contributes, FOSS licensing, and more.`);
this.websocketService.want(['blocks']);
this.profiles$ = this.apiService.getAboutPageProfiles$().pipe(
diff --git a/frontend/src/app/components/privacy-policy/privacy-policy.component.ts b/frontend/src/app/components/privacy-policy/privacy-policy.component.ts
index 7a44070eb..b98390731 100644
--- a/frontend/src/app/components/privacy-policy/privacy-policy.component.ts
+++ b/frontend/src/app/components/privacy-policy/privacy-policy.component.ts
@@ -17,6 +17,6 @@ export class PrivacyPolicyComponent {
ngOnInit(): void {
this.seoService.setTitle('Privacy Policy');
- this.seoService.setDescription('Trusted third parties are security holes, as are trusted first parties...you should only trust your own self-hosted instance of The Mempool Open Source Project™.');
+ this.seoService.setDescription('Trusted third parties are security holes, as are trusted first parties...you should only trust your own self-hosted instance of The Mempool Open Source Project®.');
}
}
diff --git a/frontend/src/app/components/trademark-policy/trademark-policy.component.ts b/frontend/src/app/components/trademark-policy/trademark-policy.component.ts
index 08f16264a..b8f53afcf 100644
--- a/frontend/src/app/components/trademark-policy/trademark-policy.component.ts
+++ b/frontend/src/app/components/trademark-policy/trademark-policy.component.ts
@@ -17,6 +17,6 @@ export class TrademarkPolicyComponent {
ngOnInit(): void {
this.seoService.setTitle('Trademark Policy');
- this.seoService.setDescription('An overview of the trademarks registered by Mempool Space K.K. and The Mempool Open Source Project™ and what we consider to be lawful usage of those trademarks.');
+ this.seoService.setDescription('An overview of the trademarks registered by Mempool Space K.K. and The Mempool Open Source Project® and what we consider to be lawful usage of those trademarks.');
}
}
diff --git a/frontend/src/index.mempool.html b/frontend/src/index.mempool.html
index 03edc08ef..def14434e 100644
--- a/frontend/src/index.mempool.html
+++ b/frontend/src/index.mempool.html
@@ -7,17 +7,17 @@
-
+
-
+
-
+
From e3fffd8fb22d38b27aab7592fe5bf2b47cb2be57 Mon Sep 17 00:00:00 2001
From: nymkappa <1612910616@pm.me>
Date: Wed, 13 Sep 2023 09:21:31 +0200
Subject: [PATCH 036/446] [UI] fix menu scroll issue
---
frontend/src/app/components/menu/menu.component.scss | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/frontend/src/app/components/menu/menu.component.scss b/frontend/src/app/components/menu/menu.component.scss
index f1f39b1de..a27048981 100644
--- a/frontend/src/app/components/menu/menu.component.scss
+++ b/frontend/src/app/components/menu/menu.component.scss
@@ -9,11 +9,14 @@
margin-left: -250px;
box-shadow: 5px 0px 30px 0px #000;
padding-bottom: 20px;
+ @media (max-width: 613px) {
+ top: 105px;
+ }
}
.scrollable {
overflow-x: hidden;
- overflow-y: scroll;
+ overflow-y: auto;
}
.sidenav.open {
From a0004382771b784e865d29fadc5e0143f1f8ccd2 Mon Sep 17 00:00:00 2001
From: nymkappa <1612910616@pm.me>
Date: Wed, 13 Sep 2023 09:30:11 +0200
Subject: [PATCH 037/446] [UI] menu username ellipsis
---
frontend/src/app/components/menu/menu.component.html | 2 +-
frontend/src/app/components/menu/menu.component.scss | 6 ++++++
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/frontend/src/app/components/menu/menu.component.html b/frontend/src/app/components/menu/menu.component.html
index e89ace64a..1c247a94b 100644
--- a/frontend/src/app/components/menu/menu.component.html
+++ b/frontend/src/app/components/menu/menu.component.html
@@ -3,7 +3,7 @@
- (tx.weight / 4)">
+
Adjusted vsize
-
+
Weight
@@ -314,9 +314,9 @@
Locktime
- (tx.weight / 4)">
+
Sigops
-
+
Transaction hex
diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts
index 4743e5cd6..5260cd668 100644
--- a/frontend/src/app/components/transaction/transaction.component.ts
+++ b/frontend/src/app/components/transaction/transaction.component.ts
@@ -62,6 +62,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
rbfReplaces: string[];
rbfInfo: RbfTree;
cpfpInfo: CpfpInfo | null;
+ sigops: number | null;
+ adjustedVsize: number | null;
showCpfpDetails = false;
fetchCpfp$ = new Subject();
fetchRbfHistory$ = new Subject();
@@ -343,6 +345,10 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
if (tx.fee === undefined) {
this.tx.fee = 0;
}
+ if (this.tx.sigops != null) {
+ this.sigops = this.tx.sigops;
+ this.adjustedVsize = Math.max(this.tx.weight / 4, this.sigops * 5);
+ }
this.tx.feePerVsize = tx.fee / (tx.weight / 4);
this.isLoadingTx = false;
this.error = undefined;
@@ -543,6 +549,10 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
}
this.cpfpInfo = cpfpInfo;
+ if (this.cpfpInfo.adjustedVsize && this.cpfpInfo.sigops != null) {
+ this.sigops = this.cpfpInfo.sigops;
+ this.adjustedVsize = this.cpfpInfo.adjustedVsize;
+ }
this.hasEffectiveFeeRate = hasRelatives || (this.tx.effectiveFeePerVsize && (Math.abs(this.tx.effectiveFeePerVsize - this.tx.feePerVsize) > 0.01));
}
@@ -569,6 +579,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.replaced = false;
this.transactionTime = -1;
this.cpfpInfo = null;
+ this.adjustedVsize = null;
+ this.sigops = null;
this.hasEffectiveFeeRate = false;
this.rbfInfo = null;
this.rbfReplaces = [];
diff --git a/frontend/src/app/interfaces/electrs.interface.ts b/frontend/src/app/interfaces/electrs.interface.ts
index 2d604a9de..58a02ad79 100644
--- a/frontend/src/app/interfaces/electrs.interface.ts
+++ b/frontend/src/app/interfaces/electrs.interface.ts
@@ -26,6 +26,7 @@ export interface Transaction {
_outspends?: Outspend[];
_channels?: TransactionChannels;
price?: Price;
+ sigops?: number;
}
export interface TransactionChannels {
From 29299e622ec8cd7a96703900fcdbf2f6014b5b4d Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Mon, 13 Nov 2023 03:42:12 +0000
Subject: [PATCH 151/446] Calculate sigops in /api/v1/tx/:txid for mined txs
---
backend/src/api/bitcoin/bitcoin.routes.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts
index 240fb07ce..105b0be14 100644
--- a/backend/src/api/bitcoin/bitcoin.routes.ts
+++ b/backend/src/api/bitcoin/bitcoin.routes.ts
@@ -251,7 +251,7 @@ class BitcoinRoutes {
private async getTransaction(req: Request, res: Response) {
try {
- const transaction = await transactionUtils.$getTransactionExtended(req.params.txId, true);
+ const transaction = await transactionUtils.$getTransactionExtended(req.params.txId, true, false, false, true);
res.json(transaction);
} catch (e) {
let statusCode = 500;
From 3639dcc92aef898fd9ede92a8e8aa8a87ea421e9 Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Fri, 10 Nov 2023 07:45:02 +0000
Subject: [PATCH 152/446] Recover from stale PID file
---
backend/src/database.ts | 32 +++++++++++++++++++++++++-------
1 file changed, 25 insertions(+), 7 deletions(-)
diff --git a/backend/src/database.ts b/backend/src/database.ts
index c27f28d23..655dad47c 100644
--- a/backend/src/database.ts
+++ b/backend/src/database.ts
@@ -4,6 +4,7 @@ import config from './config';
import { createPool, Pool, PoolConnection } from 'mysql2/promise';
import logger from './logger';
import { FieldPacket, OkPacket, PoolOptions, ResultSetHeader, RowDataPacket } from 'mysql2/typings/mysql';
+import { execSync } from 'child_process';
class DB {
constructor() {
@@ -105,18 +106,34 @@ import { FieldPacket, OkPacket, PoolOptions, ResultSetHeader, RowDataPacket } fr
public getPidLock(): boolean {
const filePath = path.join(config.DATABASE.PID_DIR || __dirname, `/mempool-${config.DATABASE.DATABASE}.pid`);
+ this.enforcePidLock(filePath);
+ fs.writeFileSync(filePath, `${process.pid}`);
+ return true;
+ }
+
+ private enforcePidLock(filePath: string): void {
if (fs.existsSync(filePath)) {
- const pid = fs.readFileSync(filePath).toString();
- if (pid !== `${process.pid}`) {
- const msg = `Already running on PID ${pid} (or pid file '${filePath}' is stale)`;
+ const pid = parseInt(fs.readFileSync(filePath, 'utf-8'));
+ if (pid === process.pid) {
+ logger.warn('PID file already exists for this process');
+ return;
+ }
+
+ let cmd;
+ try {
+ cmd = execSync(`ps -p ${pid} -o args=`);
+ } catch (e) {
+ logger.warn(`Stale PID file at ${filePath}, but no process running on that PID ${pid}`);
+ return;
+ }
+
+ if (cmd && cmd.toString()?.includes('node')) {
+ const msg = `Another mempool nodejs process is already running on PID ${pid}`;
logger.err(msg);
throw new Error(msg);
} else {
- return true;
+ logger.warn(`Stale PID file at ${filePath}, but the PID ${pid} does not belong to a running mempool instance`);
}
- } else {
- fs.writeFileSync(filePath, `${process.pid}`);
- return true;
}
}
@@ -124,6 +141,7 @@ import { FieldPacket, OkPacket, PoolOptions, ResultSetHeader, RowDataPacket } fr
const filePath = path.join(config.DATABASE.PID_DIR || __dirname, `/mempool-${config.DATABASE.DATABASE}.pid`);
if (fs.existsSync(filePath)) {
const pid = fs.readFileSync(filePath).toString();
+ // only release our own pid file
if (pid === `${process.pid}`) {
fs.unlinkSync(filePath);
}
From 8c4b488251cc2aae70d142a78715b33e2ad3431c Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Mon, 13 Nov 2023 07:33:53 +0000
Subject: [PATCH 153/446] handle SIGHUP exit code
---
backend/src/index.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/src/index.ts b/backend/src/index.ts
index e7e1afa3d..59c92fbf3 100644
--- a/backend/src/index.ts
+++ b/backend/src/index.ts
@@ -92,7 +92,7 @@ class Server {
logger.notice(`Starting Mempool Server${worker ? ' (worker)' : ''}... (${backendInfo.getShortCommitHash()})`);
// Register cleanup listeners for exit events
- ['exit', 'SIGINT', 'SIGTERM', 'SIGUSR1', 'SIGUSR2', 'uncaughtException', 'unhandledRejection'].forEach(event => {
+ ['exit', 'SIGHUP', 'SIGINT', 'SIGTERM', 'SIGUSR1', 'SIGUSR2', 'uncaughtException', 'unhandledRejection'].forEach(event => {
process.on(event, () => { this.onExit(event); });
});
From c6a92083a8621e50a7b5410ea9909d4e4233da0a Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Mon, 13 Nov 2023 07:53:13 +0000
Subject: [PATCH 154/446] Fix pid parsing on release lock
---
backend/src/database.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/backend/src/database.ts b/backend/src/database.ts
index 655dad47c..0638aa319 100644
--- a/backend/src/database.ts
+++ b/backend/src/database.ts
@@ -140,9 +140,9 @@ import { execSync } from 'child_process';
public releasePidLock(): void {
const filePath = path.join(config.DATABASE.PID_DIR || __dirname, `/mempool-${config.DATABASE.DATABASE}.pid`);
if (fs.existsSync(filePath)) {
- const pid = fs.readFileSync(filePath).toString();
+ const pid = parseInt(fs.readFileSync(filePath, 'utf-8'));
// only release our own pid file
- if (pid === `${process.pid}`) {
+ if (pid === process.pid) {
fs.unlinkSync(filePath);
}
}
From c393483590266295c1029e9a4d4f422de53ec91d Mon Sep 17 00:00:00 2001
From: nymkappa <1612910616@pm.me>
Date: Tue, 14 Nov 2023 10:51:51 +0900
Subject: [PATCH 155/446] [graph] add toggle to show/hide outliers in
transaction vBytes per second graph
---
.../statistics/statistics.component.html | 20 +++++++++-----
.../statistics/statistics.component.scss | 9 +++++++
.../statistics/statistics.component.ts | 27 ++++++++++---------
3 files changed, 38 insertions(+), 18 deletions(-)
diff --git a/frontend/src/app/components/statistics/statistics.component.html b/frontend/src/app/components/statistics/statistics.component.html
index 02a26ed52..3f0ea61f5 100644
--- a/frontend/src/app/components/statistics/statistics.component.html
+++ b/frontend/src/app/components/statistics/statistics.component.html
@@ -109,12 +109,20 @@
diff --git a/frontend/src/app/components/statistics/statistics.component.scss b/frontend/src/app/components/statistics/statistics.component.scss
index d883e48c3..3d4813fb5 100644
--- a/frontend/src/app/components/statistics/statistics.component.scss
+++ b/frontend/src/app/components/statistics/statistics.component.scss
@@ -222,4 +222,13 @@
border-top-right-radius: 0;
}
}
+}
+
+.vbytes-title {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ @media (max-width: 767px) {
+ display: block;
+ }
}
\ No newline at end of file
diff --git a/frontend/src/app/components/statistics/statistics.component.ts b/frontend/src/app/components/statistics/statistics.component.ts
index 6bc58b6d7..e80ab83ad 100644
--- a/frontend/src/app/components/statistics/statistics.component.ts
+++ b/frontend/src/app/components/statistics/statistics.component.ts
@@ -35,7 +35,7 @@ export class StatisticsComponent implements OnInit {
showCount = false;
maxFeeIndex: number;
dropDownOpen = false;
-
+ outlierCappingEnabled = false;
mempoolStats: OptimizedMempoolStats[] = [];
mempoolVsizeFeesData: any;
@@ -156,12 +156,14 @@ export class StatisticsComponent implements OnInit {
}
this.maxFeeIndex = maxTier;
- this.capExtremeVbytesValues();
-
this.mempoolTransactionsWeightPerSecondData = {
labels: labels,
series: [mempoolStats.map((stats) => [stats.added * 1000, stats.vbytes_per_second])],
};
+
+ if (this.outlierCappingEnabled) {
+ this.capExtremeVbytesValues();
+ }
}
saveGraphPreference() {
@@ -211,24 +213,25 @@ export class StatisticsComponent implements OnInit {
}
});
}
-
+
/**
* All value higher that "median * capRatio" are capped
*/
+ onOutlierToggleChange(e) {
+ this.outlierCappingEnabled = e.target.checked;
+ this.handleNewMempoolData(this.mempoolStats);
+ }
capExtremeVbytesValues() {
if (this.stateService.network.length !== 0) {
return; // Only cap on Bitcoin mainnet
}
- let capRatio = 10;
- if (['1m', '3m', '6m', '1y', '2y', '3y', '4y'].includes(this.graphWindowPreference)) {
- capRatio = 4;
- }
+ let capRatio = 4;
// Find median value
const vBytes: number[] = [];
- for (const stat of this.mempoolStats) {
- vBytes.push(stat.vbytes_per_second);
+ for (const stat of this.mempoolTransactionsWeightPerSecondData.series[0]) {
+ vBytes.push(stat[1]);
}
const sorted = vBytes.slice().sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);
@@ -238,8 +241,8 @@ export class StatisticsComponent implements OnInit {
}
// Cap
- for (const stat of this.mempoolStats) {
- stat.vbytes_per_second = Math.min(median * capRatio, stat.vbytes_per_second);
+ for (const stat of this.mempoolTransactionsWeightPerSecondData.series[0]) {
+ stat[1] = Math.min(median * capRatio, stat[1]);
}
}
From 1ae34e069c1056f87cf7a7747df5d338c74643ef Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Tue, 14 Nov 2023 05:00:05 +0000
Subject: [PATCH 156/446] Fix silently unhandled database exceptions
---
backend/src/api/blocks.ts | 9 +++++++--
backend/src/database.ts | 10 ++++++++--
backend/src/index.ts | 20 +++++++++++++++-----
backend/src/indexer.ts | 7 ++++++-
4 files changed, 36 insertions(+), 10 deletions(-)
diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts
index 64dc1d5ba..730e603f3 100644
--- a/backend/src/api/blocks.ts
+++ b/backend/src/api/blocks.ts
@@ -761,8 +761,13 @@ class Blocks {
this.updateTimerProgress(timer, `saved ${this.currentBlockHeight} to database`);
if (!fastForwarded) {
- const lastestPriceId = await PricesRepository.$getLatestPriceId();
- this.updateTimerProgress(timer, `got latest price id ${this.currentBlockHeight}`);
+ let lastestPriceId;
+ try {
+ lastestPriceId = await PricesRepository.$getLatestPriceId();
+ this.updateTimerProgress(timer, `got latest price id ${this.currentBlockHeight}`);
+ } catch (e) {
+ logger.debug('failed to fetch latest price id from db: ' + (e instanceof Error ? e.message : e));
+ }
if (priceUpdater.historyInserted === true && lastestPriceId !== null) {
await blocksRepository.$saveBlockPrices([{
height: blockExtended.height,
diff --git a/backend/src/database.ts b/backend/src/database.ts
index 0638aa319..21d90261d 100644
--- a/backend/src/database.ts
+++ b/backend/src/database.ts
@@ -55,14 +55,20 @@ import { execSync } from 'child_process';
}).then(result => {
resolve(result);
}).catch(error => {
+ logger.debug(`database query "${query.slice(0, 100)}" failed!`);
reject(error);
}).finally(() => {
clearTimeout(timer);
});
});
} else {
- const pool = await this.getPool();
- return pool.query(query, params);
+ try {
+ const pool = await this.getPool();
+ return pool.query(query, params);
+ } catch (e) {
+ logger.debug(`database query "${query.slice(0, 100)}" failed!`);
+ throw e;
+ }
}
}
diff --git a/backend/src/index.ts b/backend/src/index.ts
index 59c92fbf3..039aad8af 100644
--- a/backend/src/index.ts
+++ b/backend/src/index.ts
@@ -92,9 +92,15 @@ class Server {
logger.notice(`Starting Mempool Server${worker ? ' (worker)' : ''}... (${backendInfo.getShortCommitHash()})`);
// Register cleanup listeners for exit events
- ['exit', 'SIGHUP', 'SIGINT', 'SIGTERM', 'SIGUSR1', 'SIGUSR2', 'uncaughtException', 'unhandledRejection'].forEach(event => {
+ ['exit', 'SIGHUP', 'SIGINT', 'SIGTERM', 'SIGUSR1', 'SIGUSR2'].forEach(event => {
process.on(event, () => { this.onExit(event); });
});
+ process.on('uncaughtException', (error) => {
+ this.onUnhandledException('uncaughtException', error);
+ });
+ process.on('unhandledRejection', (reason, promise) => {
+ this.onUnhandledException('unhandledRejection', reason);
+ });
if (config.MEMPOOL.BACKEND === 'esplora') {
bitcoinApi.startHealthChecks();
@@ -314,14 +320,18 @@ class Server {
}
}
- onExit(exitEvent): void {
+ onExit(exitEvent, code = 0): void {
+ logger.debug(`onExit for signal: ${exitEvent}`);
if (config.DATABASE.ENABLED) {
DB.releasePidLock();
}
- process.exit(0);
+ process.exit(code);
+ }
+
+ onUnhandledException(type, error): void {
+ console.error(`${type}:`, error);
+ this.onExit(type, 1);
}
}
-
-
((): Server => new Server())();
diff --git a/backend/src/indexer.ts b/backend/src/indexer.ts
index 7ec65d9c9..add0abca3 100644
--- a/backend/src/indexer.ts
+++ b/backend/src/indexer.ts
@@ -76,7 +76,12 @@ class Indexer {
if (task === 'blocksPrices' && !this.tasksRunning.includes(task) && !['testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) {
this.tasksRunning.push(task);
- const lastestPriceId = await PricesRepository.$getLatestPriceId();
+ let lastestPriceId;
+ try {
+ lastestPriceId = await PricesRepository.$getLatestPriceId();
+ } catch (e) {
+ logger.debug('failed to fetch latest price id from db: ' + (e instanceof Error ? e.message : e));
+ }
if (priceUpdater.historyInserted === false || lastestPriceId === null) {
logger.debug(`Blocks prices indexer is waiting for the price updater to complete`, logger.tags.mining);
setTimeout(() => {
From 08b68ef8ba895cb82ad763bd5ab924991a53469a Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Tue, 14 Nov 2023 05:33:48 +0000
Subject: [PATCH 157/446] handle exception in db transaction rollback
---
backend/src/database.ts | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/backend/src/database.ts b/backend/src/database.ts
index 21d90261d..a9893fdac 100644
--- a/backend/src/database.ts
+++ b/backend/src/database.ts
@@ -72,6 +72,15 @@ import { execSync } from 'child_process';
}
}
+ private async $rollbackAtomic(connection: PoolConnection): Promise {
+ try {
+ await connection.rollback();
+ await connection.release();
+ } catch (e) {
+ logger.warn('Failed to rollback incomplete db transaction: ' + (e instanceof Error ? e.message : e));
+ }
+ }
+
public async $atomicQuery(queries: { query, params }[]): Promise<[T, FieldPacket[]][]>
{
@@ -90,9 +99,8 @@ import { execSync } from 'child_process';
return results;
} catch (e) {
- logger.err('Could not complete db transaction, rolling back: ' + (e instanceof Error ? e.message : e));
- connection.rollback();
- connection.release();
+ logger.warn('Could not complete db transaction, rolling back: ' + (e instanceof Error ? e.message : e));
+ this.$rollbackAtomic(connection);
throw e;
} finally {
connection.release();
From 29cbdf6cd56457893de25ca2a9542aa81937327a Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Tue, 14 Nov 2023 05:52:27 +0000
Subject: [PATCH 158/446] handle null query in error log
---
backend/src/database.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/backend/src/database.ts b/backend/src/database.ts
index a9893fdac..9d21ac28e 100644
--- a/backend/src/database.ts
+++ b/backend/src/database.ts
@@ -55,7 +55,7 @@ import { execSync } from 'child_process';
}).then(result => {
resolve(result);
}).catch(error => {
- logger.debug(`database query "${query.slice(0, 100)}" failed!`);
+ logger.debug(`database query "${query?.slice(0, 100)}" failed!`);
reject(error);
}).finally(() => {
clearTimeout(timer);
@@ -66,7 +66,7 @@ import { execSync } from 'child_process';
const pool = await this.getPool();
return pool.query(query, params);
} catch (e) {
- logger.debug(`database query "${query.slice(0, 100)}" failed!`);
+ logger.debug(`database query "${query?.slice(0, 100)}" failed!`);
throw e;
}
}
From 5c0a59d2f61a5ee64f9dfd7cc15a39186a5812c1 Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Tue, 14 Nov 2023 06:13:06 +0000
Subject: [PATCH 159/446] handle unknown query types in db error log
---
backend/src/database.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/backend/src/database.ts b/backend/src/database.ts
index 9d21ac28e..4eba8cb6f 100644
--- a/backend/src/database.ts
+++ b/backend/src/database.ts
@@ -55,7 +55,7 @@ import { execSync } from 'child_process';
}).then(result => {
resolve(result);
}).catch(error => {
- logger.debug(`database query "${query?.slice(0, 100)}" failed!`);
+ logger.debug(`database query "${query?.sql?.slice(0, 160) || (typeof(query) === 'string' || query instanceof String ? query?.slice(0, 160) : 'unknown query')}" failed!`);
reject(error);
}).finally(() => {
clearTimeout(timer);
@@ -66,7 +66,7 @@ import { execSync } from 'child_process';
const pool = await this.getPool();
return pool.query(query, params);
} catch (e) {
- logger.debug(`database query "${query?.slice(0, 100)}" failed!`);
+ logger.debug(`database query "${query?.sql?.slice(0, 160) || (typeof(query) === 'string' || query instanceof String ? query?.slice(0, 160) : 'unknown query')}" failed!`);
throw e;
}
}
From 00887bc24b69305c9b42ede05f3cd956661bebee Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Thu, 21 Sep 2023 17:50:53 +0100
Subject: [PATCH 160/446] Refactor indexer scheduling to avoid accumulating
identical tasks
---
backend/src/api/blocks.ts | 4 +-
backend/src/index.ts | 2 +-
backend/src/indexer.ts | 100 ++++++++++++++++++++++++++------------
3 files changed, 71 insertions(+), 35 deletions(-)
diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts
index 730e603f3..6b4a14a0e 100644
--- a/backend/src/api/blocks.ts
+++ b/backend/src/api/blocks.ts
@@ -776,9 +776,7 @@ class Blocks {
this.updateTimerProgress(timer, `saved prices for ${this.currentBlockHeight}`);
} else {
logger.debug(`Cannot save block price for ${blockExtended.height} because the price updater hasnt completed yet. Trying again in 10 seconds.`, logger.tags.mining);
- setTimeout(() => {
- indexer.runSingleTask('blocksPrices');
- }, 10000);
+ indexer.scheduleSingleTask('blocksPrices', 10000);
}
// Save blocks summary for visualization if it's enabled
diff --git a/backend/src/index.ts b/backend/src/index.ts
index 039aad8af..0c28df0a8 100644
--- a/backend/src/index.ts
+++ b/backend/src/index.ts
@@ -206,7 +206,7 @@ class Server {
}
const newMempool = await bitcoinApi.$getRawMempool();
const numHandledBlocks = await blocks.$updateBlocks();
- const pollRate = config.MEMPOOL.POLL_RATE_MS * (indexer.indexerRunning ? 10 : 1);
+ const pollRate = config.MEMPOOL.POLL_RATE_MS * (indexer.indexerIsRunning() ? 10 : 1);
if (numHandledBlocks === 0) {
await memPool.$updateMempool(newMempool, pollRate);
}
diff --git a/backend/src/indexer.ts b/backend/src/indexer.ts
index add0abca3..2e2f8b037 100644
--- a/backend/src/indexer.ts
+++ b/backend/src/indexer.ts
@@ -15,11 +15,18 @@ export interface CoreIndex {
best_block_height: number;
}
+type TaskName = 'blocksPrices' | 'coinStatsIndex';
+
class Indexer {
- runIndexer = true;
- indexerRunning = false;
- tasksRunning: string[] = [];
- coreIndexes: CoreIndex[] = [];
+ private runIndexer = true;
+ private indexerRunning = false;
+ private tasksRunning: { [key in TaskName]?: boolean; } = {};
+ private tasksScheduled: { [key in TaskName]?: NodeJS.Timeout; } = {};
+ private coreIndexes: CoreIndex[] = [];
+
+ public indexerIsRunning(): boolean {
+ return this.indexerRunning;
+ }
/**
* Check which core index is available for indexing
@@ -69,38 +76,69 @@ class Indexer {
}
}
- public async runSingleTask(task: 'blocksPrices' | 'coinStatsIndex'): Promise {
- if (!Common.indexingEnabled()) {
+ /**
+ * schedules a single task to run in `timeout` ms
+ * only one task of each type may be scheduled
+ *
+ * @param {TaskName} task - the type of task
+ * @param {number} timeout - delay in ms
+ * @param {boolean} replace - `true` replaces any already scheduled task (works like a debounce), `false` ignores subsequent requests (works like a throttle)
+ */
+ public scheduleSingleTask(task: TaskName, timeout: number = 10000, replace = false): void {
+ if (this.tasksScheduled[task]) {
+ if (!replace) { //throttle
+ return;
+ } else { // debounce
+ clearTimeout(this.tasksScheduled[task]);
+ }
+ }
+ this.tasksScheduled[task] = setTimeout(async () => {
+ try {
+ await this.runSingleTask(task);
+ } catch (e) {
+ logger.err(`Unexpected error in scheduled task ${task}: ` + (e instanceof Error ? e.message : e));
+ } finally {
+ clearTimeout(this.tasksScheduled[task]);
+ }
+ }, timeout);
+ }
+
+ /**
+ * Runs a single task immediately
+ *
+ * (use `scheduleSingleTask` instead to queue a task to run after some timeout)
+ */
+ public async runSingleTask(task: TaskName): Promise {
+ if (!Common.indexingEnabled() || this.tasksRunning[task]) {
return;
}
+ this.tasksRunning[task] = true;
- if (task === 'blocksPrices' && !this.tasksRunning.includes(task) && !['testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) {
- this.tasksRunning.push(task);
- let lastestPriceId;
- try {
- lastestPriceId = await PricesRepository.$getLatestPriceId();
- } catch (e) {
- logger.debug('failed to fetch latest price id from db: ' + (e instanceof Error ? e.message : e));
- }
- if (priceUpdater.historyInserted === false || lastestPriceId === null) {
- logger.debug(`Blocks prices indexer is waiting for the price updater to complete`, logger.tags.mining);
- setTimeout(() => {
- this.tasksRunning = this.tasksRunning.filter(runningTask => runningTask !== task);
- this.runSingleTask('blocksPrices');
- }, 10000);
- } else {
- logger.debug(`Blocks prices indexer will run now`, logger.tags.mining);
- await mining.$indexBlockPrices();
- this.tasksRunning = this.tasksRunning.filter(runningTask => runningTask !== task);
- }
+ switch (task) {
+ case 'blocksPrices': {
+ if (!['testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) {
+ let lastestPriceId;
+ try {
+ lastestPriceId = await PricesRepository.$getLatestPriceId();
+ } catch (e) {
+ logger.debug('failed to fetch latest price id from db: ' + (e instanceof Error ? e.message : e));
+ } if (priceUpdater.historyInserted === false || lastestPriceId === null) {
+ logger.debug(`Blocks prices indexer is waiting for the price updater to complete`, logger.tags.mining);
+ this.scheduleSingleTask(task, 10000);
+ } else {
+ logger.debug(`Blocks prices indexer will run now`, logger.tags.mining);
+ await mining.$indexBlockPrices();
+ }
+ }
+ } break;
+
+ case 'coinStatsIndex': {
+ logger.debug(`Indexing coinStatsIndex now`);
+ await mining.$indexCoinStatsIndex();
+ } break;
}
- if (task === 'coinStatsIndex' && !this.tasksRunning.includes(task)) {
- this.tasksRunning.push(task);
- logger.debug(`Indexing coinStatsIndex now`);
- await mining.$indexCoinStatsIndex();
- this.tasksRunning = this.tasksRunning.filter(runningTask => runningTask !== task);
- }
+ this.tasksRunning[task] = false;
}
public async $run(): Promise {
From fa040ca19f53842b0fe2eac33dc20f7320acab18 Mon Sep 17 00:00:00 2001
From: nymkappa <1612910616@pm.me>
Date: Tue, 14 Nov 2023 15:53:37 +0900
Subject: [PATCH 161/446] [frontend] export MasterPageComponent for re-use in
hybrid build
---
frontend/src/app/master-page.module.ts | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/frontend/src/app/master-page.module.ts b/frontend/src/app/master-page.module.ts
index bfc1aed53..f1c0e2efa 100644
--- a/frontend/src/app/master-page.module.ts
+++ b/frontend/src/app/master-page.module.ts
@@ -109,12 +109,9 @@ export class MasterPageRoutingModule { }
],
declarations: [
MasterPageComponent,
+ ],
+ exports: [
+ MasterPageComponent,
]
})
export class MasterPageModule { }
-
-
-
-
-
-
From 7aad6641120233aa2bb9cb3b9cf413b213670bc6 Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Tue, 14 Nov 2023 07:49:03 +0000
Subject: [PATCH 162/446] Support different log levels for database query error
messages
---
backend/src/database.ts | 15 ++++++++++-----
backend/src/logger.ts | 2 ++
.../src/repositories/NodesSocketsRepository.ts | 2 +-
3 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/backend/src/database.ts b/backend/src/database.ts
index 4eba8cb6f..dc543bbbc 100644
--- a/backend/src/database.ts
+++ b/backend/src/database.ts
@@ -2,6 +2,7 @@ import * as fs from 'fs';
import path from 'path';
import config from './config';
import { createPool, Pool, PoolConnection } from 'mysql2/promise';
+import { LogLevel } from './logger';
import logger from './logger';
import { FieldPacket, OkPacket, PoolOptions, ResultSetHeader, RowDataPacket } from 'mysql2/typings/mysql';
import { execSync } from 'child_process';
@@ -33,7 +34,7 @@ import { execSync } from 'child_process';
}
public async query(query, params?, connection?: PoolConnection): Promise<[T, FieldPacket[]]>
+ OkPacket[] | ResultSetHeader>(query, params?, errorLogLevel: LogLevel | 'silent' = 'debug', connection?: PoolConnection): Promise<[T, FieldPacket[]]>
{
this.checkDBFlag();
let hardTimeout;
@@ -55,7 +56,9 @@ import { execSync } from 'child_process';
}).then(result => {
resolve(result);
}).catch(error => {
- logger.debug(`database query "${query?.sql?.slice(0, 160) || (typeof(query) === 'string' || query instanceof String ? query?.slice(0, 160) : 'unknown query')}" failed!`);
+ if (errorLogLevel !== 'silent') {
+ logger[errorLogLevel](`database query "${query?.sql?.slice(0, 160) || (typeof(query) === 'string' || query instanceof String ? query?.slice(0, 160) : 'unknown query')}" failed!`);
+ }
reject(error);
}).finally(() => {
clearTimeout(timer);
@@ -66,7 +69,9 @@ import { execSync } from 'child_process';
const pool = await this.getPool();
return pool.query(query, params);
} catch (e) {
- logger.debug(`database query "${query?.sql?.slice(0, 160) || (typeof(query) === 'string' || query instanceof String ? query?.slice(0, 160) : 'unknown query')}" failed!`);
+ if (errorLogLevel !== 'silent') {
+ logger[errorLogLevel](`database query "${query?.sql?.slice(0, 160) || (typeof(query) === 'string' || query instanceof String ? query?.slice(0, 160) : 'unknown query')}" failed!`);
+ }
throw e;
}
}
@@ -82,7 +87,7 @@ import { execSync } from 'child_process';
}
public async $atomicQuery(queries: { query, params }[]): Promise<[T, FieldPacket[]][]>
+ OkPacket[] | ResultSetHeader>(queries: { query, params }[], errorLogLevel: LogLevel | 'silent' = 'debug'): Promise<[T, FieldPacket[]][]>
{
const pool = await this.getPool();
const connection = await pool.getConnection();
@@ -91,7 +96,7 @@ import { execSync } from 'child_process';
const results: [T, FieldPacket[]][] = [];
for (const query of queries) {
- const result = await this.query(query.query, query.params, connection) as [T, FieldPacket[]];
+ const result = await this.query(query.query, query.params, errorLogLevel, connection) as [T, FieldPacket[]];
results.push(result);
}
diff --git a/backend/src/logger.ts b/backend/src/logger.ts
index efafe894e..364c529e7 100644
--- a/backend/src/logger.ts
+++ b/backend/src/logger.ts
@@ -157,4 +157,6 @@ class Logger {
}
}
+export type LogLevel = 'emerg' | 'alert' | 'crit' | 'err' | 'warn' | 'notice' | 'info' | 'debug';
+
export default new Logger();
diff --git a/backend/src/repositories/NodesSocketsRepository.ts b/backend/src/repositories/NodesSocketsRepository.ts
index af594e6e1..e85126de4 100644
--- a/backend/src/repositories/NodesSocketsRepository.ts
+++ b/backend/src/repositories/NodesSocketsRepository.ts
@@ -14,7 +14,7 @@ class NodesSocketsRepository {
await DB.query(`
INSERT INTO nodes_sockets(public_key, socket, type)
VALUE (?, ?, ?)
- `, [socket.publicKey, socket.addr, socket.network]);
+ `, [socket.publicKey, socket.addr, socket.network], 'silent');
} catch (e: any) {
if (e.errno !== 1062) { // ER_DUP_ENTRY - Not an issue, just ignore this
logger.err(`Cannot save node socket (${[socket.publicKey, socket.addr, socket.network]}) into db. Reason: ` + (e instanceof Error ? e.message : e));
From fdd18317f9afc110a97dbbf77538f3ef47257fb5 Mon Sep 17 00:00:00 2001
From: wiz
Date: Tue, 14 Nov 2023 17:46:42 +0900
Subject: [PATCH 163/446] ops: Kill node before sh in stop script
---
production/mempool-kill-all | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/production/mempool-kill-all b/production/mempool-kill-all
index 6abb1a935..b3f88c6da 100755
--- a/production/mempool-kill-all
+++ b/production/mempool-kill-all
@@ -1,16 +1,19 @@
#!/usr/bin/env zsh
-# kill "while true" loops
-killall sh
-
# kill actual node backends
-killall node
+killall node 2>/dev/null
+
+# kill "while true" loops
+killall sh 2>/dev/null
# kill unfurler chrome instances
-killall chrome
+killall chrome 2>/dev/null
# kill xorg
-killall xinit
+killall xinit 2>/dev/null
+
+# kill dbus
+killall dbus-daemon 2>/dev/null
# kill nginx cache warmer scripts
for pid in `ps uaxww|grep warmer|grep zsh|awk '{print $2}'`;do
From 691394607907a1057020ff322548e7350790201d Mon Sep 17 00:00:00 2001
From: wiz
Date: Tue, 14 Nov 2023 17:46:52 +0900
Subject: [PATCH 164/446] ops: Upgrade mariadb to 10.11.x
---
production/install | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/production/install b/production/install
index 18ee06233..4f1280a05 100755
--- a/production/install
+++ b/production/install
@@ -394,7 +394,7 @@ FREEBSD_PKG=()
FREEBSD_PKG+=(zsh sudo git git-lfs screen curl wget calc neovim)
FREEBSD_PKG+=(openssh-portable py39-pip rust llvm10 jq base64 libzmq4)
FREEBSD_PKG+=(boost-libs autoconf automake gmake gcc libevent libtool pkgconf)
-FREEBSD_PKG+=(nginx rsync py39-certbot-nginx mariadb105-server keybase)
+FREEBSD_PKG+=(nginx rsync py39-certbot-nginx mariadb1011-server keybase)
FREEBSD_PKG+=(geoipupdate)
FREEBSD_UNFURL_PKG=()
From 85091e1f3a72b67d4f825d3a6749da7ab0504f19 Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Tue, 14 Nov 2023 09:55:02 +0000
Subject: [PATCH 165/446] =?UTF-8?q?=F0=9F=A5=A9=F0=9F=99=82=F0=9F=AA=B5?=
=?UTF-8?q?=F0=9F=93=9D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/src/api/bitcoin/esplora-api.ts | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/backend/src/api/bitcoin/esplora-api.ts b/backend/src/api/bitcoin/esplora-api.ts
index 19800cd21..2ad72f728 100644
--- a/backend/src/api/bitcoin/esplora-api.ts
+++ b/backend/src/api/bitcoin/esplora-api.ts
@@ -8,8 +8,9 @@ import logger from '../../logger';
interface FailoverHost {
host: string,
rtts: number[],
- rtt: number
+ rtt: number,
failures: number,
+ latestHeight?: number,
socket?: boolean,
outOfSync?: boolean,
unreachable?: boolean,
@@ -92,6 +93,7 @@ class FailoverRouter {
host.rtts.unshift(rtt);
host.rtts.slice(0, 5);
host.rtt = host.rtts.reduce((acc, l) => acc + l, 0) / host.rtts.length;
+ host.latestHeight = height;
if (height == null || isNaN(height) || (maxHeight - height > 2)) {
host.outOfSync = true;
} else {
@@ -105,16 +107,16 @@ class FailoverRouter {
this.sortHosts();
- logger.debug(`Tomahawk ranking: ${this.hosts.map(host => '\navg rtt ' + Math.round(host.rtt).toString().padStart(5, ' ') + ' | reachable? ' + (!host.unreachable || false).toString().padStart(5, ' ') + ' | in sync? ' + (!host.outOfSync || false).toString().padStart(5, ' ') + ` | ${host.host}`).join('')}`);
+ logger.debug(`Tomahawk ranking:\n${this.hosts.map((host, index) => this.formatRanking(index, host, this.activeHost, maxHeight)).join('\n')}`);
// switch if the current host is out of sync or significantly slower than the next best alternative
if (this.activeHost.outOfSync || this.activeHost.unreachable || (this.activeHost !== this.hosts[0] && this.hosts[0].preferred) || (!this.activeHost.preferred && this.activeHost.rtt > (this.hosts[0].rtt * 2) + 50)) {
if (this.activeHost.unreachable) {
- logger.warn(`Unable to reach ${this.activeHost.host}, failing over to next best alternative`);
+ logger.warn(`🚨🚨🚨 Unable to reach ${this.activeHost.host}, failing over to next best alternative 🚨🚨🚨`);
} else if (this.activeHost.outOfSync) {
- logger.warn(`${this.activeHost.host} has fallen behind, failing over to next best alternative`);
+ logger.warn(`🚨🚨🚨 ${this.activeHost.host} has fallen behind, failing over to next best alternative 🚨🚨🚨`);
} else {
- logger.debug(`${this.activeHost.host} is no longer the best esplora host`);
+ logger.debug(`🛠️ ${this.activeHost.host} is no longer the best esplora host 🛠️`);
}
this.electHost();
}
@@ -122,6 +124,11 @@ class FailoverRouter {
this.pollTimer = setTimeout(() => { this.pollHosts(); }, this.pollInterval);
}
+ private formatRanking(index: number, host: FailoverHost, active: FailoverHost, maxHeight: number): string {
+ const heightStatus = host.outOfSync ? '🚫' : (host.latestHeight && host.latestHeight < maxHeight ? '🟧' : '✅');
+ return `${host === active ? '⭐️' : ' '} ${host.rtt < Infinity ? Math.round(host.rtt).toString().padStart(5, ' ') + 'ms' : ' - '} ${host.unreachable ? '🔥' : '✅'} | block: ${host.latestHeight || '??????'} ${heightStatus} | ${host.host} ${host === active ? '⭐️' : ' '}`;
+ }
+
// sort hosts by connection quality, and update default fallback
private sortHosts(): void {
// sort by connection quality
@@ -156,7 +163,7 @@ class FailoverRouter {
private addFailure(host: FailoverHost): FailoverHost {
host.failures++;
if (host.failures > 5 && this.multihost) {
- logger.warn(`Too many esplora failures on ${this.activeHost.host}, falling back to next best alternative`);
+ logger.warn(`🚨🚨🚨 Too many esplora failures on ${this.activeHost.host}, falling back to next best alternative 🚨🚨🚨`);
this.electHost();
return this.activeHost;
} else {
From 5cfd599018b90b33759cddc58f238f3f187c310c Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Tue, 14 Nov 2023 10:17:02 +0000
Subject: [PATCH 166/446] Set fallback server out-of-symc when unreachable
---
backend/src/api/bitcoin/esplora-api.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/backend/src/api/bitcoin/esplora-api.ts b/backend/src/api/bitcoin/esplora-api.ts
index 2ad72f728..fe152e2a3 100644
--- a/backend/src/api/bitcoin/esplora-api.ts
+++ b/backend/src/api/bitcoin/esplora-api.ts
@@ -101,6 +101,7 @@ class FailoverRouter {
}
host.unreachable = false;
} else {
+ host.outOfSync = true;
host.unreachable = true;
}
}
From 2d30c0b588038caec287cac5faa96e8122f81e67 Mon Sep 17 00:00:00 2001
From: nymkappa <1612910616@pm.me>
Date: Wed, 15 Nov 2023 14:08:44 +0900
Subject: [PATCH 167/446] [graph] use echart echart yaxis `max` property
instead of modifying the data itself
---
.../incoming-transactions-graph.component.ts | 26 +++++++++++++-
.../statistics/statistics.component.html | 2 +-
.../statistics/statistics.component.ts | 34 +------------------
3 files changed, 27 insertions(+), 35 deletions(-)
diff --git a/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts b/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts
index 667ad1e28..82e3e77e7 100644
--- a/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts
+++ b/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts
@@ -7,6 +7,8 @@ import { formatNumber } from '@angular/common';
import { StateService } from '../../services/state.service';
import { Subscription } from 'rxjs';
+const OUTLIERS_MEDIAN_MULTIPLIER = 4;
+
@Component({
selector: 'app-incoming-transactions-graph',
templateUrl: './incoming-transactions-graph.component.html',
@@ -29,6 +31,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
@Input() left: number | string = '0';
@Input() template: ('widget' | 'advanced') = 'widget';
@Input() windowPreferenceOverride: string;
+ @Input() outlierCappingEnabled: boolean = false;
isLoading = true;
mempoolStatsChartOption: EChartsOption = {};
@@ -40,6 +43,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
MA: number[][] = [];
weightMode: boolean = false;
rateUnitSub: Subscription;
+ medianVbytesPerSecond: number | undefined;
constructor(
@Inject(LOCALE_ID) private locale: string,
@@ -65,16 +69,35 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
this.windowPreference = this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference');
const windowSize = Math.max(10, Math.floor(this.data.series[0].length / 8));
this.MA = this.calculateMA(this.data.series[0], windowSize);
+ if (this.outlierCappingEnabled === true) {
+ this.computeMedianVbytesPerSecond(this.data.series[0]);
+ }
this.mountChart();
}
rendered() {
if (!this.data) {
- return;
+ return;
}
this.isLoading = false;
}
+ /**
+ * Calculate the median value of the vbytes per second chart to hide outliers
+ */
+ computeMedianVbytesPerSecond(data: number[][]): void {
+ const vBytes: number[] = [];
+ for (const value of data) {
+ vBytes.push(value[1]);
+ }
+ const sorted = vBytes.slice().sort((a, b) => a - b);
+ const middle = Math.floor(sorted.length / 2);
+ this.medianVbytesPerSecond = sorted[middle];
+ if (sorted.length % 2 === 0) {
+ this.medianVbytesPerSecond = (sorted[middle - 1] + sorted[middle]) / 2;
+ }
+ }
+
/// calculate the moving average of the provided data based on windowSize
calculateMA(data: number[][], windowSize: number = 100): number[][] {
//update const variables that are not changed
@@ -232,6 +255,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
}
],
yAxis: {
+ max: this.outlierCappingEnabled ? Math.round(this.medianVbytesPerSecond * OUTLIERS_MEDIAN_MULTIPLIER) : undefined,
type: 'value',
axisLabel: {
fontSize: 11,
diff --git a/frontend/src/app/components/statistics/statistics.component.html b/frontend/src/app/components/statistics/statistics.component.html
index 3f0ea61f5..c726e354e 100644
--- a/frontend/src/app/components/statistics/statistics.component.html
+++ b/frontend/src/app/components/statistics/statistics.component.html
@@ -128,7 +128,7 @@
+ [data]="mempoolTransactionsWeightPerSecondData" [outlierCappingEnabled]="outlierCappingEnabled">
diff --git a/frontend/src/app/components/statistics/statistics.component.ts b/frontend/src/app/components/statistics/statistics.component.ts
index e80ab83ad..8e01e068b 100644
--- a/frontend/src/app/components/statistics/statistics.component.ts
+++ b/frontend/src/app/components/statistics/statistics.component.ts
@@ -160,10 +160,6 @@ export class StatisticsComponent implements OnInit {
labels: labels,
series: [mempoolStats.map((stats) => [stats.added * 1000, stats.vbytes_per_second])],
};
-
- if (this.outlierCappingEnabled) {
- this.capExtremeVbytesValues();
- }
}
saveGraphPreference() {
@@ -214,36 +210,8 @@ export class StatisticsComponent implements OnInit {
});
}
- /**
- * All value higher that "median * capRatio" are capped
- */
- onOutlierToggleChange(e) {
+ onOutlierToggleChange(e): void {
this.outlierCappingEnabled = e.target.checked;
- this.handleNewMempoolData(this.mempoolStats);
- }
- capExtremeVbytesValues() {
- if (this.stateService.network.length !== 0) {
- return; // Only cap on Bitcoin mainnet
- }
-
- let capRatio = 4;
-
- // Find median value
- const vBytes: number[] = [];
- for (const stat of this.mempoolTransactionsWeightPerSecondData.series[0]) {
- vBytes.push(stat[1]);
- }
- const sorted = vBytes.slice().sort((a, b) => a - b);
- const middle = Math.floor(sorted.length / 2);
- let median = sorted[middle];
- if (sorted.length % 2 === 0) {
- median = (sorted[middle - 1] + sorted[middle]) / 2;
- }
-
- // Cap
- for (const stat of this.mempoolTransactionsWeightPerSecondData.series[0]) {
- stat[1] = Math.min(median * capRatio, stat[1]);
- }
}
onSaveChart(name) {
From d4568b631dea57d0cfb11e8b38f1b54993d5f6c0 Mon Sep 17 00:00:00 2001
From: softsimon
Date: Mon, 13 Nov 2023 17:45:15 +0900
Subject: [PATCH 168/446] Make search and select work for truncated text
fixes #4367
---
.../app/shared/components/truncate/truncate.component.html | 2 ++
.../app/shared/components/truncate/truncate.component.scss | 5 +++++
2 files changed, 7 insertions(+)
diff --git a/frontend/src/app/shared/components/truncate/truncate.component.html b/frontend/src/app/shared/components/truncate/truncate.component.html
index 94208f3a4..d75b683f2 100644
--- a/frontend/src/app/shared/components/truncate/truncate.component.html
+++ b/frontend/src/app/shared/components/truncate/truncate.component.html
@@ -11,9 +11,11 @@
+ {{ text }}
{{text.slice(0,-lastChars)}} {{text.slice(-lastChars)}}
+ {{ text }}
{{text.slice(lastChars)}} {{text.slice(0,lastChars)}}
\ No newline at end of file
diff --git a/frontend/src/app/shared/components/truncate/truncate.component.scss b/frontend/src/app/shared/components/truncate/truncate.component.scss
index 315ce4e12..97233e5d6 100644
--- a/frontend/src/app/shared/components/truncate/truncate.component.scss
+++ b/frontend/src/app/shared/components/truncate/truncate.component.scss
@@ -27,4 +27,9 @@
&.inline {
display: inline-flex;
}
+}
+
+.hidden {
+ color: transparent;
+ position: absolute;
}
\ No newline at end of file
From 86fe6a802b2bb069cd32bdb62f079fad961e90de Mon Sep 17 00:00:00 2001
From: softsimon
Date: Wed, 15 Nov 2023 15:07:14 +0900
Subject: [PATCH 169/446] Fixing mobile overflow
---
.../shared/components/truncate/truncate.component.html | 4 ++--
.../shared/components/truncate/truncate.component.scss | 10 +++++++++-
2 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/frontend/src/app/shared/components/truncate/truncate.component.html b/frontend/src/app/shared/components/truncate/truncate.component.html
index d75b683f2..0ec579dad 100644
--- a/frontend/src/app/shared/components/truncate/truncate.component.html
+++ b/frontend/src/app/shared/components/truncate/truncate.component.html
@@ -1,4 +1,5 @@
+ {{ text }}
@@ -11,11 +12,10 @@
- {{ text }}
+
{{text.slice(0,-lastChars)}} {{text.slice(-lastChars)}}
- {{ text }}
{{text.slice(lastChars)}} {{text.slice(0,lastChars)}}
\ No newline at end of file
diff --git a/frontend/src/app/shared/components/truncate/truncate.component.scss b/frontend/src/app/shared/components/truncate/truncate.component.scss
index 97233e5d6..c5179384f 100644
--- a/frontend/src/app/shared/components/truncate/truncate.component.scss
+++ b/frontend/src/app/shared/components/truncate/truncate.component.scss
@@ -32,4 +32,12 @@
.hidden {
color: transparent;
position: absolute;
-}
\ No newline at end of file
+ max-width: 300px;
+ overflow: hidden;
+}
+
+@media (max-width: 567px) {
+ .hidden {
+ max-width: 150px;
+ }
+}
From 35d7c55c1de902d4d9b40a78b5bddff0aba41a93 Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Wed, 15 Nov 2023 06:10:00 +0000
Subject: [PATCH 170/446] Configurable esplora batch sizes
---
backend/mempool-config.sample.json | 6 ++++++
backend/src/__fixtures__/mempool-config.template.json | 4 +++-
backend/src/__tests__/config.test.ts | 4 +++-
backend/src/api/mempool.ts | 2 +-
backend/src/api/rbf-cache.ts | 2 +-
backend/src/api/redis-cache.ts | 5 +++--
backend/src/config.ts | 4 ++++
backend/src/tasks/lightning/forensics.service.ts | 2 +-
backend/src/tasks/lightning/network-sync.service.ts | 2 +-
docker/backend/mempool-config.json | 4 +++-
docker/backend/start.sh | 4 ++++
11 files changed, 30 insertions(+), 9 deletions(-)
diff --git a/backend/mempool-config.sample.json b/backend/mempool-config.sample.json
index 80c2b4e28..76b27d630 100644
--- a/backend/mempool-config.sample.json
+++ b/backend/mempool-config.sample.json
@@ -52,6 +52,7 @@
"ESPLORA": {
"REST_API_URL": "http://127.0.0.1:3000",
"UNIX_SOCKET_PATH": "/tmp/esplora-bitcoin-mainnet",
+ "BATCH_QUERY_BASE_SIZE": 1000,
"RETRY_UNIX_SOCKET_AFTER": 30000,
"REQUEST_TIMEOUT": 10000,
"FALLBACK_TIMEOUT": 5000,
@@ -132,6 +133,11 @@
"BISQ_URL": "https://bisq.markets/api",
"BISQ_ONION": "http://bisqmktse2cabavbr2xjq7xw3h6g5ottemo5rolfcwt6aly6tp5fdryd.onion/api"
},
+ "REDIS": {
+ "ENABLED": false,
+ "UNIX_SOCKET_PATH": "/tmp/redis.sock",
+ "BATCH_QUERY_BASE_SIZE": 5000
+ },
"REPLICATION": {
"ENABLED": false,
"AUDIT": false,
diff --git a/backend/src/__fixtures__/mempool-config.template.json b/backend/src/__fixtures__/mempool-config.template.json
index 7d5a14de6..0c30651ce 100644
--- a/backend/src/__fixtures__/mempool-config.template.json
+++ b/backend/src/__fixtures__/mempool-config.template.json
@@ -53,6 +53,7 @@
"ESPLORA": {
"REST_API_URL": "__ESPLORA_REST_API_URL__",
"UNIX_SOCKET_PATH": "__ESPLORA_UNIX_SOCKET_PATH__",
+ "BATCH_QUERY_BASE_SIZE": 1000,
"RETRY_UNIX_SOCKET_AFTER": 888,
"REQUEST_TIMEOUT": 10000,
"FALLBACK_TIMEOUT": 5000,
@@ -140,6 +141,7 @@
},
"REDIS": {
"ENABLED": false,
- "UNIX_SOCKET_PATH": "/tmp/redis.sock"
+ "UNIX_SOCKET_PATH": "/tmp/redis.sock",
+ "BATCH_QUERY_BASE_SIZE": 5000
}
}
diff --git a/backend/src/__tests__/config.test.ts b/backend/src/__tests__/config.test.ts
index a565c64af..2991162e9 100644
--- a/backend/src/__tests__/config.test.ts
+++ b/backend/src/__tests__/config.test.ts
@@ -55,6 +55,7 @@ describe('Mempool Backend Config', () => {
expect(config.ESPLORA).toStrictEqual({
REST_API_URL: 'http://127.0.0.1:3000',
UNIX_SOCKET_PATH: null,
+ BATCH_QUERY_BASE_SIZE: 1000,
RETRY_UNIX_SOCKET_AFTER: 30000,
REQUEST_TIMEOUT: 10000,
FALLBACK_TIMEOUT: 5000,
@@ -144,7 +145,8 @@ describe('Mempool Backend Config', () => {
expect(config.REDIS).toStrictEqual({
ENABLED: false,
- UNIX_SOCKET_PATH: ''
+ UNIX_SOCKET_PATH: '',
+ BATCH_QUERY_BASE_SIZE: 5000,
});
});
});
diff --git a/backend/src/api/mempool.ts b/backend/src/api/mempool.ts
index f5e788f98..a11e0d504 100644
--- a/backend/src/api/mempool.ts
+++ b/backend/src/api/mempool.ts
@@ -235,7 +235,7 @@ class Mempool {
if (!loaded) {
const remainingTxids = transactions.filter(txid => !this.mempoolCache[txid]);
- const sliceLength = 10000;
+ const sliceLength = config.ESPLORA.BATCH_QUERY_BASE_SIZE;
for (let i = 0; i < Math.ceil(remainingTxids.length / sliceLength); i++) {
const slice = remainingTxids.slice(i * sliceLength, (i + 1) * sliceLength);
const txs = await transactionUtils.$getMempoolTransactionsExtended(slice, false, false, false);
diff --git a/backend/src/api/rbf-cache.ts b/backend/src/api/rbf-cache.ts
index c573d3291..d2b7ddf6c 100644
--- a/backend/src/api/rbf-cache.ts
+++ b/backend/src/api/rbf-cache.ts
@@ -480,7 +480,7 @@ class RbfCache {
};
if (config.MEMPOOL.BACKEND === 'esplora') {
- const sliceLength = 250;
+ const sliceLength = Math.floor(config.ESPLORA.BATCH_QUERY_BASE_SIZE / 40);
for (let i = 0; i < Math.ceil(txids.length / sliceLength); i++) {
const slice = txids.slice(i * sliceLength, (i + 1) * sliceLength);
try {
diff --git a/backend/src/api/redis-cache.ts b/backend/src/api/redis-cache.ts
index 00b280274..82ce34ad1 100644
--- a/backend/src/api/redis-cache.ts
+++ b/backend/src/api/redis-cache.ts
@@ -122,8 +122,9 @@ class RedisCache {
async $removeTransactions(transactions: string[]) {
try {
await this.$ensureConnected();
- for (let i = 0; i < Math.ceil(transactions.length / 10000); i++) {
- const slice = transactions.slice(i * 10000, (i + 1) * 10000);
+ const sliceLength = config.REDIS.BATCH_QUERY_BASE_SIZE;
+ for (let i = 0; i < Math.ceil(transactions.length / sliceLength); i++) {
+ const slice = transactions.slice(i * sliceLength, (i + 1) * sliceLength);
await this.client.unlink(slice.map(txid => `mempool:tx:${txid}`));
logger.debug(`Deleted ${slice.length} transactions from the Redis cache`);
}
diff --git a/backend/src/config.ts b/backend/src/config.ts
index 37d5a2de9..4115149e6 100644
--- a/backend/src/config.ts
+++ b/backend/src/config.ts
@@ -43,6 +43,7 @@ interface IConfig {
ESPLORA: {
REST_API_URL: string;
UNIX_SOCKET_PATH: string | void | null;
+ BATCH_QUERY_BASE_SIZE: number;
RETRY_UNIX_SOCKET_AFTER: number;
REQUEST_TIMEOUT: number;
FALLBACK_TIMEOUT: number;
@@ -151,6 +152,7 @@ interface IConfig {
REDIS: {
ENABLED: boolean;
UNIX_SOCKET_PATH: string;
+ BATCH_QUERY_BASE_SIZE: number;
},
}
@@ -195,6 +197,7 @@ const defaults: IConfig = {
'ESPLORA': {
'REST_API_URL': 'http://127.0.0.1:3000',
'UNIX_SOCKET_PATH': null,
+ 'BATCH_QUERY_BASE_SIZE': 1000,
'RETRY_UNIX_SOCKET_AFTER': 30000,
'REQUEST_TIMEOUT': 10000,
'FALLBACK_TIMEOUT': 5000,
@@ -303,6 +306,7 @@ const defaults: IConfig = {
'REDIS': {
'ENABLED': false,
'UNIX_SOCKET_PATH': '',
+ 'BATCH_QUERY_BASE_SIZE': 5000,
},
};
diff --git a/backend/src/tasks/lightning/forensics.service.ts b/backend/src/tasks/lightning/forensics.service.ts
index 584dd3c79..a21c4c280 100644
--- a/backend/src/tasks/lightning/forensics.service.ts
+++ b/backend/src/tasks/lightning/forensics.service.ts
@@ -79,7 +79,7 @@ class ForensicsService {
}
let progress = 0;
- const sliceLength = 1000;
+ const sliceLength = Math.floor(config.ESPLORA.BATCH_QUERY_BASE_SIZE / 10);
// process batches of 1000 channels
for (let i = 0; i < Math.ceil(allChannels.length / sliceLength); i++) {
const channels = allChannels.slice(i * sliceLength, (i + 1) * sliceLength);
diff --git a/backend/src/tasks/lightning/network-sync.service.ts b/backend/src/tasks/lightning/network-sync.service.ts
index dc0d609fa..09c8b912e 100644
--- a/backend/src/tasks/lightning/network-sync.service.ts
+++ b/backend/src/tasks/lightning/network-sync.service.ts
@@ -290,7 +290,7 @@ class NetworkSyncService {
const allChannels = await channelsApi.$getChannelsByStatus([0, 1]);
- const sliceLength = 5000;
+ const sliceLength = Math.floor(config.ESPLORA.BATCH_QUERY_BASE_SIZE / 2);
// process batches of 5000 channels
for (let i = 0; i < Math.ceil(allChannels.length / sliceLength); i++) {
const channels = allChannels.slice(i * sliceLength, (i + 1) * sliceLength);
diff --git a/docker/backend/mempool-config.json b/docker/backend/mempool-config.json
index a1359db97..e73fa1929 100644
--- a/docker/backend/mempool-config.json
+++ b/docker/backend/mempool-config.json
@@ -53,6 +53,7 @@
"ESPLORA": {
"REST_API_URL": "__ESPLORA_REST_API_URL__",
"UNIX_SOCKET_PATH": "__ESPLORA_UNIX_SOCKET_PATH__",
+ "BATCH_QUERY_BASE_SIZE": __ESPLORA_BATCH_QUERY_BASE_SIZE__,
"RETRY_UNIX_SOCKET_AFTER": __ESPLORA_RETRY_UNIX_SOCKET_AFTER__,
"REQUEST_TIMEOUT": __ESPLORA_REQUEST_TIMEOUT__,
"FALLBACK_TIMEOUT": __ESPLORA_FALLBACK_TIMEOUT__,
@@ -146,6 +147,7 @@
},
"REDIS": {
"ENABLED": __REDIS_ENABLED__,
- "UNIX_SOCKET_PATH": "__REDIS_UNIX_SOCKET_PATH__"
+ "UNIX_SOCKET_PATH": "__REDIS_UNIX_SOCKET_PATH__",
+ "BATCH_QUERY_BASE_SIZE": __REDIS_BATCH_QUERY_BASE_SIZE__
}
}
diff --git a/docker/backend/start.sh b/docker/backend/start.sh
index 23c578efe..232cf7284 100755
--- a/docker/backend/start.sh
+++ b/docker/backend/start.sh
@@ -54,6 +54,7 @@ __ELECTRUM_TLS_ENABLED__=${ELECTRUM_TLS_ENABLED:=false}
# ESPLORA
__ESPLORA_REST_API_URL__=${ESPLORA_REST_API_URL:=http://127.0.0.1:3000}
__ESPLORA_UNIX_SOCKET_PATH__=${ESPLORA_UNIX_SOCKET_PATH:="null"}
+__ESPLORA_BATCH_QUERY_BASE_SIZE__=${ESPLORA_BATCH_QUERY_BASE_SIZE:=1000}
__ESPLORA_RETRY_UNIX_SOCKET_AFTER__=${ESPLORA_RETRY_UNIX_SOCKET_AFTER:=30000}
__ESPLORA_REQUEST_TIMEOUT__=${ESPLORA_REQUEST_TIMEOUT:=5000}
__ESPLORA_FALLBACK_TIMEOUT__=${ESPLORA_FALLBACK_TIMEOUT:=5000}
@@ -148,6 +149,7 @@ __MEMPOOL_SERVICES_ACCELERATIONS__=${MEMPOOL_SERVICES_ACCELERATIONS:=false}
# REDIS
__REDIS_ENABLED__=${REDIS_ENABLED:=false}
__REDIS_UNIX_SOCKET_PATH__=${REDIS_UNIX_SOCKET_PATH:=true}
+__REDIS_BATCH_QUERY_BASE_SIZE__=${REDIS_BATCH_QUERY_BASE_SIZE:=5000}
mkdir -p "${__MEMPOOL_CACHE_DIR__}"
@@ -201,6 +203,7 @@ sed -i "s!__ELECTRUM_TLS_ENABLED__!${__ELECTRUM_TLS_ENABLED__}!g" mempool-config
sed -i "s!__ESPLORA_REST_API_URL__!${__ESPLORA_REST_API_URL__}!g" mempool-config.json
sed -i "s!__ESPLORA_UNIX_SOCKET_PATH__!${__ESPLORA_UNIX_SOCKET_PATH__}!g" mempool-config.json
+sed -i "s!__ESPLORA_BATCH_QUERY_BASE_SIZE__!${__ESPLORA_BATCH_QUERY_BASE_SIZE__}!g" mempool-config.json
sed -i "s!__ESPLORA_RETRY_UNIX_SOCKET_AFTER__!${__ESPLORA_RETRY_UNIX_SOCKET_AFTER__}!g" mempool-config.json
sed -i "s!__ESPLORA_REQUEST_TIMEOUT__!${__ESPLORA_REQUEST_TIMEOUT__}!g" mempool-config.json
sed -i "s!__ESPLORA_FALLBACK_TIMEOUT__!${__ESPLORA_FALLBACK_TIMEOUT__}!g" mempool-config.json
@@ -288,5 +291,6 @@ sed -i "s!__MEMPOOL_SERVICES_ACCELERATIONS__!${__MEMPOOL_SERVICES_ACCELERATIONS_
# REDIS
sed -i "s!__REDIS_ENABLED__!${__REDIS_ENABLED__}!g" mempool-config.json
sed -i "s!__REDIS_UNIX_SOCKET_PATH__!${__REDIS_UNIX_SOCKET_PATH__}!g" mempool-config.json
+sed -i "s!__REDIS_BATCH_QUERY_BASE_SIZE__!${__REDIS_BATCH_QUERY_BASE_SIZE__}!g" mempool-config.json
node /backend/package/index.js
From 6454892d48cd94dd69d83adfae2543749cca1afc Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Wed, 15 Nov 2023 06:57:31 +0000
Subject: [PATCH 171/446] Use mempool/txs max_txs parameter
---
backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts | 2 +-
backend/src/api/bitcoin/bitcoin-api.ts | 2 +-
backend/src/api/bitcoin/esplora-api.ts | 4 ++--
backend/src/api/mempool.ts | 2 +-
4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts
index f008e5ed8..5bd961e23 100644
--- a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts
+++ b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts
@@ -5,7 +5,7 @@ export interface AbstractBitcoinApi {
$getRawTransaction(txId: string, skipConversion?: boolean, addPrevout?: boolean, lazyPrevouts?: boolean): Promise;
$getRawTransactions(txids: string[]): Promise;
$getMempoolTransactions(txids: string[]): Promise;
- $getAllMempoolTransactions(lastTxid: string);
+ $getAllMempoolTransactions(lastTxid?: string, max_txs?: number);
$getTransactionHex(txId: string): Promise;
$getBlockHeightTip(): Promise;
$getBlockHashTip(): Promise;
diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts
index 1722334df..f54c836f8 100644
--- a/backend/src/api/bitcoin/bitcoin-api.ts
+++ b/backend/src/api/bitcoin/bitcoin-api.ts
@@ -77,7 +77,7 @@ class BitcoinApi implements AbstractBitcoinApi {
throw new Error('Method getMempoolTransactions not supported by the Bitcoin RPC API.');
}
- $getAllMempoolTransactions(lastTxid: string): Promise {
+ $getAllMempoolTransactions(lastTxid?: string, max_txs?: number): Promise {
throw new Error('Method getAllMempoolTransactions not supported by the Bitcoin RPC API.');
}
diff --git a/backend/src/api/bitcoin/esplora-api.ts b/backend/src/api/bitcoin/esplora-api.ts
index fe152e2a3..c0b548b9a 100644
--- a/backend/src/api/bitcoin/esplora-api.ts
+++ b/backend/src/api/bitcoin/esplora-api.ts
@@ -233,8 +233,8 @@ class ElectrsApi implements AbstractBitcoinApi {
return this.failoverRouter.$post('/internal/mempool/txs', txids, 'json');
}
- async $getAllMempoolTransactions(lastSeenTxid?: string): Promise {
- return this.failoverRouter.$get('/internal/mempool/txs' + (lastSeenTxid ? '/' + lastSeenTxid : ''));
+ async $getAllMempoolTransactions(lastSeenTxid?: string, max_txs?: number): Promise {
+ return this.failoverRouter.$get('/internal/mempool/txs' + (lastSeenTxid ? '/' + lastSeenTxid : ''), 'json', max_txs ? { max_txs } : null);
}
$getTransactionHex(txId: string): Promise {
diff --git a/backend/src/api/mempool.ts b/backend/src/api/mempool.ts
index a11e0d504..60bcd2f99 100644
--- a/backend/src/api/mempool.ts
+++ b/backend/src/api/mempool.ts
@@ -126,7 +126,7 @@ class Mempool {
loadingIndicators.setProgress('mempool', count / expectedCount * 100);
while (!done) {
try {
- const result = await bitcoinApi.$getAllMempoolTransactions(last_txid);
+ const result = await bitcoinApi.$getAllMempoolTransactions(last_txid, config.ESPLORA.BATCH_QUERY_BASE_SIZE);
if (result) {
for (const tx of result) {
const extendedTransaction = transactionUtils.extendMempoolTransaction(tx);
From 20f61fc6a08358b91c72874229aba4b7fc0b92e7 Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Wed, 15 Nov 2023 06:58:00 +0000
Subject: [PATCH 172/446] Round batch sizes up
---
backend/src/api/rbf-cache.ts | 2 +-
backend/src/tasks/lightning/forensics.service.ts | 2 +-
backend/src/tasks/lightning/network-sync.service.ts | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/backend/src/api/rbf-cache.ts b/backend/src/api/rbf-cache.ts
index d2b7ddf6c..708393083 100644
--- a/backend/src/api/rbf-cache.ts
+++ b/backend/src/api/rbf-cache.ts
@@ -480,7 +480,7 @@ class RbfCache {
};
if (config.MEMPOOL.BACKEND === 'esplora') {
- const sliceLength = Math.floor(config.ESPLORA.BATCH_QUERY_BASE_SIZE / 40);
+ const sliceLength = Math.ceil(config.ESPLORA.BATCH_QUERY_BASE_SIZE / 40);
for (let i = 0; i < Math.ceil(txids.length / sliceLength); i++) {
const slice = txids.slice(i * sliceLength, (i + 1) * sliceLength);
try {
diff --git a/backend/src/tasks/lightning/forensics.service.ts b/backend/src/tasks/lightning/forensics.service.ts
index a21c4c280..aa88f5bb4 100644
--- a/backend/src/tasks/lightning/forensics.service.ts
+++ b/backend/src/tasks/lightning/forensics.service.ts
@@ -79,7 +79,7 @@ class ForensicsService {
}
let progress = 0;
- const sliceLength = Math.floor(config.ESPLORA.BATCH_QUERY_BASE_SIZE / 10);
+ const sliceLength = Math.ceil(config.ESPLORA.BATCH_QUERY_BASE_SIZE / 10);
// process batches of 1000 channels
for (let i = 0; i < Math.ceil(allChannels.length / sliceLength); i++) {
const channels = allChannels.slice(i * sliceLength, (i + 1) * sliceLength);
diff --git a/backend/src/tasks/lightning/network-sync.service.ts b/backend/src/tasks/lightning/network-sync.service.ts
index 09c8b912e..7d6a40571 100644
--- a/backend/src/tasks/lightning/network-sync.service.ts
+++ b/backend/src/tasks/lightning/network-sync.service.ts
@@ -290,7 +290,7 @@ class NetworkSyncService {
const allChannels = await channelsApi.$getChannelsByStatus([0, 1]);
- const sliceLength = Math.floor(config.ESPLORA.BATCH_QUERY_BASE_SIZE / 2);
+ const sliceLength = Math.ceil(config.ESPLORA.BATCH_QUERY_BASE_SIZE / 2);
// process batches of 5000 channels
for (let i = 0; i < Math.ceil(allChannels.length / sliceLength); i++) {
const channels = allChannels.slice(i * sliceLength, (i + 1) * sliceLength);
From 756f6d8abebb5d85a0b15533b81502355315cc1e Mon Sep 17 00:00:00 2001
From: nymkappa <1612910616@pm.me>
Date: Tue, 14 Nov 2023 15:53:37 +0900
Subject: [PATCH 173/446] [frontend] export MasterPageComponent for re-use in
hybrid build
---
frontend/src/app/master-page.module.ts | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/frontend/src/app/master-page.module.ts b/frontend/src/app/master-page.module.ts
index bfc1aed53..f1c0e2efa 100644
--- a/frontend/src/app/master-page.module.ts
+++ b/frontend/src/app/master-page.module.ts
@@ -109,12 +109,9 @@ export class MasterPageRoutingModule { }
],
declarations: [
MasterPageComponent,
+ ],
+ exports: [
+ MasterPageComponent,
]
})
export class MasterPageModule { }
-
-
-
-
-
-
From d9966143c19b2e26a8f2a109a5c3ce52fc4661d6 Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Wed, 15 Nov 2023 08:49:22 +0000
Subject: [PATCH 174/446] Minimum acceleration preview bar height
---
.../accelerate-fee-graph.component.scss | 1 +
.../accelerate-fee-graph.component.ts | 16 +++++++++-------
2 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.scss b/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.scss
index 6137b53ee..795d11df6 100644
--- a/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.scss
+++ b/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.scss
@@ -18,6 +18,7 @@
bottom: 0;
left: 0;
right: 0;
+ min-height: 30px;
display: flex;
flex-direction: column;
justify-content: center;
diff --git a/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.ts b/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.ts
index 4d746a0d9..b5aebd35f 100644
--- a/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.ts
+++ b/frontend/src/app/components/accelerate-preview/accelerate-fee-graph.component.ts
@@ -58,13 +58,15 @@ export class AccelerateFeeGraphComponent implements OnInit, OnChanges {
fee: option.fee,
}
});
- bars.push({
- rate: this.estimate.targetFeeRate,
- style: this.getStyle(this.estimate.targetFeeRate, maxRate, baseHeight),
- class: 'target',
- label: 'next block',
- fee: this.estimate.nextBlockFee - this.estimate.txSummary.effectiveFee
- });
+ if (this.estimate.nextBlockFee > this.estimate.txSummary.effectiveFee) {
+ bars.push({
+ rate: this.estimate.targetFeeRate,
+ style: this.getStyle(this.estimate.targetFeeRate, maxRate, baseHeight),
+ class: 'target',
+ label: 'next block',
+ fee: this.estimate.nextBlockFee - this.estimate.txSummary.effectiveFee
+ });
+ }
bars.push({
rate: baseRate,
style: this.getStyle(baseRate, maxRate, 0),
From dc26c6f105c666cbce9aa1248d95ce850f3558fb Mon Sep 17 00:00:00 2001
From: nymkappa <1612910616@pm.me>
Date: Wed, 15 Nov 2023 18:46:33 +0900
Subject: [PATCH 175/446] [graph] don't change yaxis scale if no outliers -
save state in localstorage
---
.../incoming-transactions-graph.component.ts | 10 +++++++++-
.../components/statistics/statistics.component.html | 2 +-
.../app/components/statistics/statistics.component.ts | 2 ++
3 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts b/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts
index 82e3e77e7..f6d2de5df 100644
--- a/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts
+++ b/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts
@@ -255,7 +255,15 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
}
],
yAxis: {
- max: this.outlierCappingEnabled ? Math.round(this.medianVbytesPerSecond * OUTLIERS_MEDIAN_MULTIPLIER) : undefined,
+ max: (value) => {
+ if (!this.outlierCappingEnabled) {
+ return undefined;
+ }
+ if (value.max < this.medianVbytesPerSecond * OUTLIERS_MEDIAN_MULTIPLIER) {
+ return undefined;
+ }
+ return Math.round(this.medianVbytesPerSecond * OUTLIERS_MEDIAN_MULTIPLIER);
+ },
type: 'value',
axisLabel: {
fontSize: 11,
diff --git a/frontend/src/app/components/statistics/statistics.component.html b/frontend/src/app/components/statistics/statistics.component.html
index c726e354e..0bb10a1c3 100644
--- a/frontend/src/app/components/statistics/statistics.component.html
+++ b/frontend/src/app/components/statistics/statistics.component.html
@@ -117,7 +117,7 @@