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.
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 0f04f751e1f99827d556391a38239d917cae5bb1 Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Mon, 1 May 2023 17:59:48 -0600
Subject: [PATCH 02/26] Disconnect websocket clients on error
---
backend/src/api/websocket-handler.ts | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts
index 3a444701f..cdbf87719 100644
--- a/backend/src/api/websocket-handler.ts
+++ b/backend/src/api/websocket-handler.ts
@@ -96,7 +96,10 @@ class WebsocketHandler {
this.wss.on('connection', (client: WebSocket) => {
this.numConnected++;
- client.on('error', logger.info);
+ client.on('error', (e) => {
+ logger.info('websocket client error: ' + (e instanceof Error ? e.message : e));
+ client.close();
+ });
client.on('close', () => {
this.numDisconnected++;
});
@@ -283,6 +286,7 @@ class WebsocketHandler {
}
} catch (e) {
logger.debug('Error parsing websocket message: ' + (e instanceof Error ? e.message : e));
+ client.close();
}
});
});
From 652100f774813894b19af4444bcc87ba3bb8a3ea Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Sat, 25 Nov 2023 09:02:27 +0000
Subject: [PATCH 03/26] More verbose websocket error logs
---
backend/src/api/websocket-handler.ts | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts
index cdbf87719..b082573f1 100644
--- a/backend/src/api/websocket-handler.ts
+++ b/backend/src/api/websocket-handler.ts
@@ -94,10 +94,11 @@ class WebsocketHandler {
throw new Error('WebSocket.Server is not set');
}
- this.wss.on('connection', (client: WebSocket) => {
+ this.wss.on('connection', (client: WebSocket, req) => {
this.numConnected++;
+ client['remoteAddress'] = req.headers['x-forwarded-for'] || req.socket?.remoteAddress || 'unknown';
client.on('error', (e) => {
- logger.info('websocket client error: ' + (e instanceof Error ? e.message : e));
+ logger.info(`websocket client error from ${client['remoteAddress']}: ` + (e instanceof Error ? e.message : e));
client.close();
});
client.on('close', () => {
@@ -285,7 +286,7 @@ class WebsocketHandler {
client.send(serializedResponse);
}
} catch (e) {
- logger.debug('Error parsing websocket message: ' + (e instanceof Error ? e.message : e));
+ logger.debug(`Error parsing websocket message from ${client['remoteAddress']}: ` + (e instanceof Error ? e.message : e));
client.close();
}
});
From 85c9c79699ac0f6f70b2de445fb0ed6e50ad8883 Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Mon, 1 May 2023 18:10:51 -0600
Subject: [PATCH 04/26] reconnect websocket after closed by server
---
.../src/app/services/websocket.service.ts | 21 ++++++++++++-------
1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts
index 22da49f06..1e7f528eb 100644
--- a/frontend/src/app/services/websocket.service.ts
+++ b/frontend/src/app/services/websocket.service.ts
@@ -76,17 +76,22 @@ export class WebsocketService {
this.stateService.resetChainTip();
- this.websocketSubject.complete();
- this.subscription.unsubscribe();
- this.websocketSubject = webSocket(
- this.webSocketUrl.replace('{network}', this.network ? '/' + this.network : '')
- );
-
- this.startSubscription();
+ this.reconnectWebsocket();
});
}
}
+ reconnectWebsocket(retrying = false, hasInitData = false) {
+ console.log('reconnecting websocket');
+ this.websocketSubject.complete();
+ this.subscription.unsubscribe();
+ this.websocketSubject = webSocket(
+ this.webSocketUrl.replace('{network}', this.network ? '/' + this.network : '')
+ );
+
+ this.startSubscription(retrying, hasInitData);
+ }
+
startSubscription(retrying = false, hasInitData = false) {
if (!hasInitData) {
this.stateService.isLoadingWebSocket$.next(true);
@@ -237,7 +242,7 @@ export class WebsocketService {
this.goneOffline = true;
this.stateService.connectionState$.next(0);
window.setTimeout(() => {
- this.startSubscription(true);
+ this.reconnectWebsocket(true);
}, retryDelay);
}
From 66d88abdc585b9cb471c2f4dd4f72c58dd97af0f Mon Sep 17 00:00:00 2001
From: Mononaut
Date: Sun, 26 Nov 2023 06:59:43 +0000
Subject: [PATCH 05/26] Refactor difficulty reindexing to process blocks in
height order
---
backend/src/api/mining/mining.ts | 77 +++++++++++++-------
backend/src/repositories/BlocksRepository.ts | 2 +-
2 files changed, 51 insertions(+), 28 deletions(-)
diff --git a/backend/src/api/mining/mining.ts b/backend/src/api/mining/mining.ts
index d9d5995da..b23ad04c5 100644
--- a/backend/src/api/mining/mining.ts
+++ b/backend/src/api/mining/mining.ts
@@ -15,6 +15,13 @@ import bitcoinApi from '../bitcoin/bitcoin-api-factory';
import { IEsploraApi } from '../bitcoin/esplora-api.interface';
import database from '../../database';
+interface DifficultyBlock {
+ timestamp: number,
+ height: number,
+ bits: number,
+ difficulty: number,
+}
+
class Mining {
private blocksPriceIndexingRunning = false;
public lastHashrateIndexingDate: number | null = null;
@@ -421,6 +428,7 @@ class Mining {
indexedHeights[height] = true;
}
+ // gets {time, height, difficulty, bits} of blocks in ascending order of height
const blocks: any = await BlocksRepository.$getBlocksDifficulty();
const genesisBlock: IEsploraApi.Block = await bitcoinApi.$getBlock(await bitcoinApi.$getBlockHash(0));
let currentDifficulty = genesisBlock.difficulty;
@@ -436,41 +444,45 @@ class Mining {
});
}
- const oldestConsecutiveBlock = await BlocksRepository.$getOldestConsecutiveBlock();
- if (config.MEMPOOL.INDEXING_BLOCKS_AMOUNT !== -1) {
- currentBits = oldestConsecutiveBlock.bits;
- currentDifficulty = oldestConsecutiveBlock.difficulty;
+ if (!blocks?.length) {
+ // no blocks in database yet
+ return;
}
+ const oldestConsecutiveBlock = this.getOldestConsecutiveBlock(blocks);
+
+ currentBits = oldestConsecutiveBlock.bits;
+ currentDifficulty = oldestConsecutiveBlock.difficulty;
+
let totalBlockChecked = 0;
let timer = new Date().getTime() / 1000;
for (const block of blocks) {
+ // skip until the first block after the oldest consecutive block
+ if (block.height <= oldestConsecutiveBlock.height) {
+ continue;
+ }
+
+ // difficulty has changed between two consecutive blocks!
if (block.bits !== currentBits) {
- if (indexedHeights[block.height] === true) { // Already indexed
- if (block.height >= oldestConsecutiveBlock.height) {
- currentDifficulty = block.difficulty;
- currentBits = block.bits;
- }
- continue;
+ // skip if already indexed
+ if (indexedHeights[block.height] !== true) {
+ let adjustment = block.difficulty / currentDifficulty;
+ adjustment = Math.round(adjustment * 1000000) / 1000000; // Remove float point noise
+
+ await DifficultyAdjustmentsRepository.$saveAdjustments({
+ time: block.time,
+ height: block.height,
+ difficulty: block.difficulty,
+ adjustment: adjustment,
+ });
+
+ totalIndexed++;
}
-
- let adjustment = block.difficulty / currentDifficulty;
- adjustment = Math.round(adjustment * 1000000) / 1000000; // Remove float point noise
-
- await DifficultyAdjustmentsRepository.$saveAdjustments({
- time: block.time,
- height: block.height,
- difficulty: block.difficulty,
- adjustment: adjustment,
- });
-
- totalIndexed++;
- if (block.height >= oldestConsecutiveBlock.height) {
- currentDifficulty = block.difficulty;
- currentBits = block.bits;
- }
- }
+ // update the current difficulty
+ currentDifficulty = block.difficulty;
+ currentBits = block.bits;
+ }
totalBlockChecked++;
const elapsedSeconds = Math.max(1, Math.round((new Date().getTime() / 1000) - timer));
@@ -633,6 +645,17 @@ class Mining {
default: return 86400 * scale;
}
}
+
+ // Finds the oldest block in a consecutive chain back from the tip
+ // assumes `blocks` is sorted in ascending height order
+ private getOldestConsecutiveBlock(blocks: DifficultyBlock[]): DifficultyBlock {
+ for (let i = blocks.length - 1; i > 0; i--) {
+ if ((blocks[i].height - blocks[i - 1].height) > 1) {
+ return blocks[i];
+ }
+ }
+ return blocks[0];
+ }
}
export default new Mining();
diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts
index 0953f9b84..a798c40f8 100644
--- a/backend/src/repositories/BlocksRepository.ts
+++ b/backend/src/repositories/BlocksRepository.ts
@@ -541,7 +541,7 @@ class BlocksRepository {
*/
public async $getBlocksDifficulty(): Promise