diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b2d34bb03..51ddb7855 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -9,7 +9,7 @@ jobs:
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
strategy:
matrix:
- node: ["18", "20"]
+ node: ["20", "21"]
flavor: ["dev", "prod"]
fail-fast: false
runs-on: "ubuntu-latest"
@@ -160,7 +160,7 @@ jobs:
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
strategy:
matrix:
- node: ["18", "20"]
+ node: ["20", "21"]
flavor: ["dev", "prod"]
fail-fast: false
runs-on: "ubuntu-latest"
diff --git a/.github/workflows/on-tag.yml b/.github/workflows/on-tag.yml
index 55a5585cc..919130c53 100644
--- a/.github/workflows/on-tag.yml
+++ b/.github/workflows/on-tag.yml
@@ -100,6 +100,5 @@ jobs:
--cache-to "type=local,dest=/tmp/.buildx-cache" \
--platform linux/amd64,linux/arm64 \
--tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG \
- --tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:latest \
--output "type=registry" ./${{ matrix.service }}/ \
--build-arg commitHash=$SHORT_SHA
diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts
index 4ccf58645..28ca38152 100644
--- a/backend/src/api/blocks.ts
+++ b/backend/src/api/blocks.ts
@@ -40,6 +40,7 @@ class Blocks {
private quarterEpochBlockTime: number | null = null;
private newBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: TransactionExtended[]) => void)[] = [];
private newAsyncBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: MempoolTransactionExtended[]) => Promise)[] = [];
+ private classifyingBlocks: boolean = false;
private mainLoopTimeout: number = 120000;
@@ -568,6 +569,11 @@ class Blocks {
* [INDEXING] Index transaction classification flags for Goggles
*/
public async $classifyBlocks(): Promise {
+ if (this.classifyingBlocks) {
+ return;
+ }
+ this.classifyingBlocks = true;
+
// classification requires an esplora backend
if (!Common.gogglesIndexingEnabled() || config.MEMPOOL.BACKEND !== 'esplora') {
return;
@@ -679,6 +685,8 @@ class Blocks {
indexedThisRun = 0;
}
}
+
+ this.classifyingBlocks = false;
}
/**
diff --git a/backend/src/api/redis-cache.ts b/backend/src/api/redis-cache.ts
index edfd2142b..d19d73a7f 100644
--- a/backend/src/api/redis-cache.ts
+++ b/backend/src/api/redis-cache.ts
@@ -19,45 +19,90 @@ class RedisCache {
private client;
private connected = false;
private schemaVersion = 1;
+ private redisConfig: any;
+ private pauseFlush: boolean = false;
private cacheQueue: MempoolTransactionExtended[] = [];
+ private removeQueue: string[] = [];
+ private rbfCacheQueue: { type: string, txid: string, value: any }[] = [];
+ private rbfRemoveQueue: { type: string, txid: string }[] = [];
private txFlushLimit: number = 10000;
constructor() {
if (config.REDIS.ENABLED) {
- const redisConfig = {
+ this.redisConfig = {
socket: {
path: config.REDIS.UNIX_SOCKET_PATH
},
database: NetworkDB[config.MEMPOOL.NETWORK],
};
- this.client = createClient(redisConfig);
- this.client.on('error', (e) => {
- logger.err(`Error in Redis client: ${e instanceof Error ? e.message : e}`);
- });
this.$ensureConnected();
+ setInterval(() => { this.$ensureConnected(); }, 10000);
}
}
- private async $ensureConnected(): Promise {
+ private async $ensureConnected(): Promise {
if (!this.connected && config.REDIS.ENABLED) {
- return this.client.connect().then(async () => {
- this.connected = true;
- logger.info(`Redis client connected`);
- const version = await this.client.get('schema_version');
- if (version !== this.schemaVersion) {
- // schema changed
- // perform migrations or flush DB if necessary
- logger.info(`Redis schema version changed from ${version} to ${this.schemaVersion}`);
- await this.client.set('schema_version', this.schemaVersion);
- }
- });
+ try {
+ this.client = createClient(this.redisConfig);
+ this.client.on('error', async (e) => {
+ logger.err(`Error in Redis client: ${e instanceof Error ? e.message : e}`);
+ this.connected = false;
+ await this.client.disconnect();
+ });
+ await this.client.connect().then(async () => {
+ try {
+ const version = await this.client.get('schema_version');
+ this.connected = true;
+ if (version !== this.schemaVersion) {
+ // schema changed
+ // perform migrations or flush DB if necessary
+ logger.info(`Redis schema version changed from ${version} to ${this.schemaVersion}`);
+ await this.client.set('schema_version', this.schemaVersion);
+ }
+ logger.info(`Redis client connected`);
+ return true;
+ } catch (e) {
+ this.connected = false;
+ logger.warn('Failed to connect to Redis');
+ return false;
+ }
+ });
+ await this.$onConnected();
+ return true;
+ } catch (e) {
+ logger.warn('Error connecting to Redis: ' + (e instanceof Error ? e.message : e));
+ return false;
+ }
+ } else {
+ try {
+ // test connection
+ await this.client.get('schema_version');
+ return true;
+ } catch (e) {
+ logger.warn('Lost connection to Redis: ' + (e instanceof Error ? e.message : e));
+ logger.warn('Attempting to reconnect in 10 seconds');
+ this.connected = false;
+ return false;
+ }
}
}
- async $updateBlocks(blocks: BlockExtended[]) {
+ private async $onConnected(): Promise {
+ await this.$flushTransactions();
+ await this.$removeTransactions([]);
+ await this.$flushRbfQueues();
+ }
+
+ async $updateBlocks(blocks: BlockExtended[]): Promise {
+ if (!config.REDIS.ENABLED) {
+ return;
+ }
+ if (!this.connected) {
+ logger.warn(`Failed to update blocks in Redis cache: Redis is not connected`);
+ return;
+ }
try {
- await this.$ensureConnected();
await this.client.set('blocks', JSON.stringify(blocks));
logger.debug(`Saved latest blocks to Redis cache`);
} catch (e) {
@@ -65,9 +110,15 @@ class RedisCache {
}
}
- async $updateBlockSummaries(summaries: BlockSummary[]) {
+ async $updateBlockSummaries(summaries: BlockSummary[]): Promise {
+ if (!config.REDIS.ENABLED) {
+ return;
+ }
+ if (!this.connected) {
+ logger.warn(`Failed to update block summaries in Redis cache: Redis is not connected`);
+ return;
+ }
try {
- await this.$ensureConnected();
await this.client.set('block-summaries', JSON.stringify(summaries));
logger.debug(`Saved latest block summaries to Redis cache`);
} catch (e) {
@@ -75,30 +126,35 @@ class RedisCache {
}
}
- async $addTransaction(tx: MempoolTransactionExtended) {
+ async $addTransaction(tx: MempoolTransactionExtended): Promise {
+ if (!config.REDIS.ENABLED) {
+ return;
+ }
this.cacheQueue.push(tx);
if (this.cacheQueue.length >= this.txFlushLimit) {
- await this.$flushTransactions();
+ if (!this.pauseFlush) {
+ await this.$flushTransactions();
+ }
}
}
- async $flushTransactions() {
- const success = await this.$addTransactions(this.cacheQueue);
- if (success) {
- logger.debug(`Saved ${this.cacheQueue.length} transactions to Redis cache`);
- this.cacheQueue = [];
- } else {
- logger.err(`Failed to save ${this.cacheQueue.length} transactions to Redis cache`);
+ async $flushTransactions(): Promise {
+ if (!config.REDIS.ENABLED) {
+ return;
+ }
+ if (!this.cacheQueue.length) {
+ return;
+ }
+ if (!this.connected) {
+ logger.warn(`Failed to add ${this.cacheQueue.length} transactions to Redis cache: Redis not connected`);
+ return;
}
- }
- private async $addTransactions(newTransactions: MempoolTransactionExtended[]): Promise {
- if (!newTransactions.length) {
- return true;
- }
+ this.pauseFlush = false;
+
+ const toAdd = this.cacheQueue.slice(0, this.txFlushLimit);
try {
- await this.$ensureConnected();
- const msetData = newTransactions.map(tx => {
+ const msetData = toAdd.map(tx => {
const minified: any = { ...tx };
delete minified.hex;
for (const vin of minified.vin) {
@@ -112,30 +168,53 @@ class RedisCache {
return [`mempool:tx:${tx.txid}`, JSON.stringify(minified)];
});
await this.client.MSET(msetData);
- return true;
+ // successful, remove transactions from cache queue
+ this.cacheQueue = this.cacheQueue.slice(toAdd.length);
+ logger.debug(`Saved ${toAdd.length} transactions to Redis cache, ${this.cacheQueue.length} left in queue`);
} catch (e) {
- logger.warn(`Failed to add ${newTransactions.length} transactions to Redis cache: ${e instanceof Error ? e.message : e}`);
- return false;
+ logger.warn(`Failed to add ${toAdd.length} transactions to Redis cache: ${e instanceof Error ? e.message : e}`);
+ this.pauseFlush = true;
}
}
- async $removeTransactions(transactions: string[]) {
- try {
- await this.$ensureConnected();
+ async $removeTransactions(transactions: string[]): Promise {
+ if (!config.REDIS.ENABLED) {
+ return;
+ }
+ const toRemove = this.removeQueue.concat(transactions);
+ this.removeQueue = [];
+ let failed: string[] = [];
+ let numRemoved = 0;
+ if (this.connected) {
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`);
+ for (let i = 0; i < Math.ceil(toRemove.length / sliceLength); i++) {
+ const slice = toRemove.slice(i * sliceLength, (i + 1) * sliceLength);
+ try {
+ await this.client.unlink(slice.map(txid => `mempool:tx:${txid}`));
+ numRemoved+= sliceLength;
+ logger.debug(`Deleted ${slice.length} transactions from the Redis cache`);
+ } catch (e) {
+ logger.warn(`Failed to remove ${slice.length} transactions from Redis cache: ${e instanceof Error ? e.message : e}`);
+ failed = failed.concat(slice);
+ }
}
- } catch (e) {
- logger.warn(`Failed to remove ${transactions.length} transactions from Redis cache: ${e instanceof Error ? e.message : e}`);
+ // concat instead of replace, in case more txs have been added in the meantime
+ this.removeQueue = this.removeQueue.concat(failed);
+ } else {
+ this.removeQueue = this.removeQueue.concat(toRemove);
}
}
async $setRbfEntry(type: string, txid: string, value: any): Promise {
+ if (!config.REDIS.ENABLED) {
+ return;
+ }
+ if (!this.connected) {
+ this.rbfCacheQueue.push({ type, txid, value });
+ logger.warn(`Failed to set RBF ${type} in Redis cache: Redis is not connected`);
+ return;
+ }
try {
- await this.$ensureConnected();
await this.client.set(`rbf:${type}:${txid}`, JSON.stringify(value));
} catch (e) {
logger.warn(`Failed to set RBF ${type} in Redis cache: ${e instanceof Error ? e.message : e}`);
@@ -143,17 +222,55 @@ class RedisCache {
}
async $removeRbfEntry(type: string, txid: string): Promise {
+ if (!config.REDIS.ENABLED) {
+ return;
+ }
+ if (!this.connected) {
+ this.rbfRemoveQueue.push({ type, txid });
+ logger.warn(`Failed to remove RBF ${type} from Redis cache: Redis is not connected`);
+ return;
+ }
try {
- await this.$ensureConnected();
await this.client.unlink(`rbf:${type}:${txid}`);
} catch (e) {
logger.warn(`Failed to remove RBF ${type} from Redis cache: ${e instanceof Error ? e.message : e}`);
}
}
- async $getBlocks(): Promise {
+ private async $flushRbfQueues(): Promise {
+ if (!config.REDIS.ENABLED) {
+ return;
+ }
+ if (!this.connected) {
+ return;
+ }
+ try {
+ const toAdd = this.rbfCacheQueue;
+ this.rbfCacheQueue = [];
+ for (const { type, txid, value } of toAdd) {
+ await this.$setRbfEntry(type, txid, value);
+ }
+ logger.debug(`Saved ${toAdd.length} queued RBF entries to the Redis cache`);
+ const toRemove = this.rbfRemoveQueue;
+ this.rbfRemoveQueue = [];
+ for (const { type, txid } of toRemove) {
+ await this.$removeRbfEntry(type, txid);
+ }
+ logger.debug(`Removed ${toRemove.length} queued RBF entries from the Redis cache`);
+ } catch (e) {
+ logger.warn(`Failed to flush RBF cache event queues after reconnecting to Redis: ${e instanceof Error ? e.message : e}`);
+ }
+ }
+
+ async $getBlocks(): Promise {
+ if (!config.REDIS.ENABLED) {
+ return [];
+ }
+ if (!this.connected) {
+ logger.warn(`Failed to retrieve blocks from Redis cache: Redis is not connected`);
+ return [];
+ }
try {
- await this.$ensureConnected();
const json = await this.client.get('blocks');
return JSON.parse(json);
} catch (e) {
@@ -163,8 +280,14 @@ class RedisCache {
}
async $getBlockSummaries(): Promise {
+ if (!config.REDIS.ENABLED) {
+ return [];
+ }
+ if (!this.connected) {
+ logger.warn(`Failed to retrieve blocks from Redis cache: Redis is not connected`);
+ return [];
+ }
try {
- await this.$ensureConnected();
const json = await this.client.get('block-summaries');
return JSON.parse(json);
} catch (e) {
@@ -174,10 +297,16 @@ class RedisCache {
}
async $getMempool(): Promise<{ [txid: string]: MempoolTransactionExtended }> {
+ if (!config.REDIS.ENABLED) {
+ return {};
+ }
+ if (!this.connected) {
+ logger.warn(`Failed to retrieve mempool from Redis cache: Redis is not connected`);
+ return {};
+ }
const start = Date.now();
const mempool = {};
try {
- await this.$ensureConnected();
const mempoolList = await this.scanKeys('mempool:tx:*');
for (const tx of mempoolList) {
mempool[tx.key] = tx.value;
@@ -191,8 +320,14 @@ class RedisCache {
}
async $getRbfEntries(type: string): Promise {
+ if (!config.REDIS.ENABLED) {
+ return [];
+ }
+ if (!this.connected) {
+ logger.warn(`Failed to retrieve Rbf ${type}s from Redis cache: Redis is not connected`);
+ return [];
+ }
try {
- await this.$ensureConnected();
const rbfEntries = await this.scanKeys(`rbf:${type}:*`);
return rbfEntries;
} catch (e) {
@@ -201,7 +336,10 @@ class RedisCache {
}
}
- async $loadCache() {
+ async $loadCache(): Promise {
+ if (!config.REDIS.ENABLED) {
+ return;
+ }
logger.info('Restoring mempool and blocks data from Redis cache');
// Load block data
const loadedBlocks = await this.$getBlocks();
@@ -226,7 +364,7 @@ class RedisCache {
});
}
- private inflateLoadedTxs(mempool: { [txid: string]: MempoolTransactionExtended }) {
+ private inflateLoadedTxs(mempool: { [txid: string]: MempoolTransactionExtended }): void {
for (const tx of Object.values(mempool)) {
for (const vin of tx.vin) {
if (vin.scriptsig) {
diff --git a/backend/src/indexer.ts b/backend/src/indexer.ts
index 90b4a59e6..dcb91d010 100644
--- a/backend/src/indexer.ts
+++ b/backend/src/indexer.ts
@@ -185,7 +185,8 @@ class Indexer {
await blocks.$generateCPFPDatabase();
await blocks.$generateAuditStats();
await auditReplicator.$sync();
- await blocks.$classifyBlocks();
+ // do not wait for classify blocks to finish
+ blocks.$classifyBlocks();
} catch (e) {
this.indexerRunning = false;
logger.err(`Indexer failed, trying again in 10 seconds. Reason: ` + (e instanceof Error ? e.message : e));
diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html
index 054f26fe6..009040889 100644
--- a/frontend/src/app/components/about/about.component.html
+++ b/frontend/src/app/components/about/about.component.html
@@ -416,7 +416,7 @@
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the full license terms for more details.
- This program incorporates software and other components licensed from third parties. See the full list of Third-Party Licenses for legal notices from those projects.
+ This program incorporates software and other components licensed from third parties. See the full list of Third-Party Licenses for legal notices from those projects.
Trademark Notice
@@ -429,10 +429,6 @@
-
-
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 2d2c9c3f3..b4488d33d 100644
--- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html
+++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html
@@ -129,7 +129,7 @@
- mempool.space fee
+ Accelerator Service Fee
|
+{{ estimate.mempoolBaseFee | number }}
@@ -141,7 +141,7 @@
|
- Transaction vsize fee
+ Transaction Size Surcharge
|
+{{ estimate.vsizeFee | number }}
diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.scss b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.scss
index 9ad50fc30..81a79eb67 100644
--- a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.scss
+++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.scss
@@ -11,6 +11,7 @@
text-align: left;
min-width: 320px;
pointer-events: none;
+ z-index: 11;
&.clickable {
pointer-events: all;
diff --git a/frontend/src/app/components/difficulty-mining/difficulty-mining.component.html b/frontend/src/app/components/difficulty-mining/difficulty-mining.component.html
index 215b5c68a..1461b0c59 100644
--- a/frontend/src/app/components/difficulty-mining/difficulty-mining.component.html
+++ b/frontend/src/app/components/difficulty-mining/difficulty-mining.component.html
@@ -46,15 +46,13 @@
Next Halving
-
- {{ i }} blocks
- {{ i }} block
+ {{ timeUntilHalving | date }}
- {{ timeUntilHalving | date }}
+
diff --git a/frontend/src/app/components/pool-ranking/pool-ranking.component.ts b/frontend/src/app/components/pool-ranking/pool-ranking.component.ts
index f7b91e151..a6f43909a 100644
--- a/frontend/src/app/components/pool-ranking/pool-ranking.component.ts
+++ b/frontend/src/app/components/pool-ranking/pool-ranking.component.ts
@@ -163,7 +163,7 @@ export class PoolRankingComponent implements OnInit {
const i = pool.blockCount.toString();
if (this.miningWindowPreference === '24h') {
return ` ${pool.name} (${pool.share}%)` +
- pool.lastEstimatedHashrate.toString() + ' PH/s' +
+ pool.lastEstimatedHashrate.toString() + ' ' + miningStats.miningUnits.hashrateUnit +
` ` + $localize`${ i }:INTERPOLATION: blocks`;
} else {
return ` ${pool.name} (${pool.share}%)` +
@@ -201,7 +201,7 @@ export class PoolRankingComponent implements OnInit {
const i = totalBlockOther.toString();
if (this.miningWindowPreference === '24h') {
return ` ` + $localize`Other (${percentage})` + `` +
- totalEstimatedHashrateOther.toString() + ' PH/s' +
+ totalEstimatedHashrateOther.toString() + ' ' + miningStats.miningUnits.hashrateUnit +
` ` + $localize`${ i }:INTERPOLATION: blocks`;
} else {
return ` ` + $localize`Other (${percentage})` + `` +
diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts
index 589b48869..60797a9a1 100644
--- a/frontend/src/app/components/transaction/transaction.component.ts
+++ b/frontend/src/app/components/transaction/transaction.component.ts
@@ -26,6 +26,7 @@ import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pi
import { Price, PriceService } from '../../services/price.service';
import { isFeatureActive } from '../../bitcoin.utils';
import { ServicesApiServices } from '../../services/services-api.service';
+import { EnterpriseService } from '../../services/enterprise.service';
@Component({
selector: 'app-transaction',
@@ -116,12 +117,15 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
private servicesApiService: ServicesApiServices,
private seoService: SeoService,
private priceService: PriceService,
- private storageService: StorageService
+ private storageService: StorageService,
+ private enterpriseService: EnterpriseService,
) {}
ngOnInit() {
this.acceleratorAvailable = this.stateService.env.OFFICIAL_MEMPOOL_SPACE && this.stateService.env.ACCELERATOR && this.stateService.network === '';
+ this.enterpriseService.page();
+
this.websocketService.want(['blocks', 'mempool-blocks']);
this.stateService.networkChanged$.subscribe(
(network) => {
@@ -527,6 +531,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
if (!this.txId) {
return;
}
+ this.enterpriseService.goal(8);
this.showAccelerationSummary = true && this.acceleratorAvailable;
this.scrollIntoAccelPreview = !this.scrollIntoAccelPreview;
return false;
diff --git a/frontend/src/app/lightning/nodes-map/nodes-map.component.ts b/frontend/src/app/lightning/nodes-map/nodes-map.component.ts
index 5e655b584..bb9e21c4b 100644
--- a/frontend/src/app/lightning/nodes-map/nodes-map.component.ts
+++ b/frontend/src/app/lightning/nodes-map/nodes-map.component.ts
@@ -88,7 +88,7 @@ export class NodesMap implements OnInit, OnChanges {
node.public_key,
node.alias,
node.capacity,
- node.active_channel_count,
+ node.channels,
node.country,
node.iso_code,
]);
diff --git a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.html b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.html
index 9318e925b..be7737894 100644
--- a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.html
+++ b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.html
@@ -64,8 +64,8 @@
Channels |
Location |
-
-
+
+
{{ node.alias }}
|
@@ -116,5 +116,10 @@
+
+
diff --git a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.scss b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.scss
index 25e4cf7f3..97d42298c 100644
--- a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.scss
+++ b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.scss
@@ -22,14 +22,14 @@
.timestamp-first {
width: 20%;
- @media (max-width: 576px) {
+ @media (max-width: 1060px) {
display: none
}
}
.timestamp-update {
width: 16%;
- @media (max-width: 576px) {
+ @media (max-width: 1060px) {
display: none
}
}
@@ -50,7 +50,7 @@
.city {
max-width: 150px;
- @media (max-width: 576px) {
+ @media (max-width: 675px) {
display: none
}
}
\ No newline at end of file
diff --git a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.ts b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.ts
index 19dd999ee..4035b62d4 100644
--- a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.ts
+++ b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.ts
@@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
-import { map, Observable, share } from 'rxjs';
+import { BehaviorSubject, combineLatest, map, Observable, share, tap } from 'rxjs';
import { ApiService } from '../../services/api.service';
import { SeoService } from '../../services/seo.service';
import { getFlagEmoji } from '../../shared/common.utils';
@@ -15,6 +15,12 @@ import { GeolocationData } from '../../shared/components/geolocation/geolocation
export class NodesPerCountry implements OnInit {
nodes$: Observable;
country: {name: string, flag: string};
+ nodesPagination$: Observable;
+ startingIndexSubject: BehaviorSubject = new BehaviorSubject(0);
+ page = 1;
+ pageSize = 15;
+ maxSize = window.innerWidth <= 767.98 ? 3 : 5;
+ isLoading = true;
skeletonLines: number[] = [];
@@ -23,7 +29,7 @@ export class NodesPerCountry implements OnInit {
private seoService: SeoService,
private route: ActivatedRoute,
) {
- for (let i = 0; i < 20; ++i) {
+ for (let i = 0; i < this.pageSize; ++i) {
this.skeletonLines.push(i);
}
}
@@ -31,6 +37,7 @@ export class NodesPerCountry implements OnInit {
ngOnInit(): void {
this.nodes$ = this.apiService.getNodeForCountry$(this.route.snapshot.params.country)
.pipe(
+ tap(() => this.isLoading = true),
map(response => {
this.seoService.setTitle($localize`Lightning nodes in ${response.country.en}`);
this.seoService.setDescription($localize`:@@meta.description.lightning.nodes-country:Explore all the Lightning nodes hosted in ${response.country.en} and see an overview of each node's capacity, number of open channels, and more.`);
@@ -87,11 +94,21 @@ export class NodesPerCountry implements OnInit {
ispCount: Object.keys(isps).length
};
}),
+ tap(() => this.isLoading = false),
share()
);
+
+ this.nodesPagination$ = combineLatest([this.nodes$, this.startingIndexSubject]).pipe(
+ map(([response, startingIndex]) => response.nodes.slice(startingIndex, startingIndex + this.pageSize))
+ );
}
trackByPublicKey(index: number, node: any): string {
return node.public_key;
}
+
+ pageChange(page: number): void {
+ this.startingIndexSubject.next((page - 1) * this.pageSize);
+ this.page = page;
+ }
}
diff --git a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.html b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.html
index 3daafe4db..865d2d2dd 100644
--- a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.html
+++ b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.html
@@ -61,8 +61,8 @@
Channels |
Location |
-
-
+
+
{{ node.alias }}
|
@@ -113,5 +113,10 @@
+
+
diff --git a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.scss b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.scss
index b829c5b59..b043d36f8 100644
--- a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.scss
+++ b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.scss
@@ -24,7 +24,7 @@
.timestamp-first {
width: 20%;
- @media (max-width: 576px) {
+ @media (max-width: 1060px) {
display: none
}
}
@@ -32,7 +32,7 @@
.timestamp-update {
width: 16%;
- @media (max-width: 576px) {
+ @media (max-width: 1060px) {
display: none
}
}
@@ -56,7 +56,7 @@
.city {
max-width: 150px;
- @media (max-width: 576px) {
+ @media (max-width: 675px) {
display: none
}
}
diff --git a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.ts b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.ts
index d4f27975c..f6c61a9f6 100644
--- a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.ts
+++ b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.ts
@@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
-import { map, Observable, share } from 'rxjs';
+import { BehaviorSubject, combineLatest, map, Observable, share, tap } from 'rxjs';
import { ApiService } from '../../services/api.service';
import { SeoService } from '../../services/seo.service';
import { getFlagEmoji } from '../../shared/common.utils';
@@ -15,6 +15,12 @@ import { GeolocationData } from '../../shared/components/geolocation/geolocation
export class NodesPerISP implements OnInit {
nodes$: Observable;
isp: {name: string, id: number};
+ nodesPagination$: Observable;
+ startingIndexSubject: BehaviorSubject = new BehaviorSubject(0);
+ page = 1;
+ pageSize = 15;
+ maxSize = window.innerWidth <= 767.98 ? 3 : 5;
+ isLoading = true;
skeletonLines: number[] = [];
@@ -23,7 +29,7 @@ export class NodesPerISP implements OnInit {
private seoService: SeoService,
private route: ActivatedRoute,
) {
- for (let i = 0; i < 20; ++i) {
+ for (let i = 0; i < this.pageSize; ++i) {
this.skeletonLines.push(i);
}
}
@@ -31,6 +37,7 @@ export class NodesPerISP implements OnInit {
ngOnInit(): void {
this.nodes$ = this.apiService.getNodeForISP$(this.route.snapshot.params.isp)
.pipe(
+ tap(() => this.isLoading = true),
map(response => {
this.isp = {
name: response.isp,
@@ -77,11 +84,21 @@ export class NodesPerISP implements OnInit {
topCountry: topCountry,
};
}),
+ tap(() => this.isLoading = false),
share()
);
+
+ this.nodesPagination$ = combineLatest([this.nodes$, this.startingIndexSubject]).pipe(
+ map(([response, startingIndex]) => response.nodes.slice(startingIndex, startingIndex + this.pageSize))
+ );
}
trackByPublicKey(index: number, node: any): string {
return node.public_key;
}
+
+ pageChange(page: number): void {
+ this.startingIndexSubject.next((page - 1) * this.pageSize);
+ this.page = page;
+ }
}
diff --git a/frontend/src/app/services/enterprise.service.ts b/frontend/src/app/services/enterprise.service.ts
index 7e69af223..d1e3624f9 100644
--- a/frontend/src/app/services/enterprise.service.ts
+++ b/frontend/src/app/services/enterprise.service.ts
@@ -139,6 +139,14 @@ export class EnterpriseService {
this.getMatomo()?.trackGoal(id);
}
+ page() {
+ const matomo = this.getMatomo();
+ if (matomo) {
+ matomo.setCustomUrl(this.getCustomUrl());
+ matomo.trackPageView();
+ }
+ }
+
private getCustomUrl(): string {
let url = window.location.origin + '/';
let route = this.activatedRoute;
diff --git a/frontend/src/app/shared/components/global-footer/global-footer.component.html b/frontend/src/app/shared/components/global-footer/global-footer.component.html
index 47b5d9835..271279589 100644
--- a/frontend/src/app/shared/components/global-footer/global-footer.component.html
+++ b/frontend/src/app/shared/components/global-footer/global-footer.component.html
@@ -77,6 +77,7 @@
Terms of Service
Privacy Policy
Trademark Policy
+ Third-party Licenses
diff --git a/production/bitcoin.conf b/production/bitcoin.conf
index cdfb5715c..3ec72e2e2 100644
--- a/production/bitcoin.conf
+++ b/production/bitcoin.conf
@@ -7,7 +7,6 @@ discover=1
par=16
dbcache=8192
maxmempool=4096
-mempoolexpiry=999999
mempoolfullrbf=1
maxconnections=100
onion=127.0.0.1:9050
@@ -20,6 +19,7 @@ whitelist=2401:b140::/32
#uacomment=@wiz
[main]
+mempoolexpiry=999999
rpcbind=127.0.0.1:8332
rpcbind=[::1]:8332
bind=0.0.0.0:8333
diff --git a/production/mempool-kill-all b/production/mempool-kill-all
index b3f88c6da..adb56915b 100755
--- a/production/mempool-kill-all
+++ b/production/mempool-kill-all
@@ -20,5 +20,10 @@ for pid in `ps uaxww|grep warmer|grep zsh|awk '{print $2}'`;do
kill $pid
done
+# kill nginx cache heater scripts
+for pid in `ps uaxww|grep heater|grep zsh|awk '{print $2}'`;do
+ kill $pid
+done
+
# always exit successfully despite above errors
exit 0
diff --git a/unfurler/src/index.ts b/unfurler/src/index.ts
index aaa72b163..162525dca 100644
--- a/unfurler/src/index.ts
+++ b/unfurler/src/index.ts
@@ -251,7 +251,8 @@ class Server {
if (!img) {
// send local fallback image file
- res.sendFile(nodejsPath.join(__dirname, matchedRoute.fallbackFile));
+ res.set('Cache-control', 'no-cache');
+ res.sendFile(nodejsPath.join(__dirname, matchedRoute.fallbackImg));
} else {
res.contentType('image/png');
res.send(img);
diff --git a/unfurler/src/resources b/unfurler/src/resources
new file mode 120000
index 000000000..aadffa9d9
--- /dev/null
+++ b/unfurler/src/resources
@@ -0,0 +1 @@
+../../frontend/src/resources
\ No newline at end of file
diff --git a/unfurler/src/resources/img/bisq.png b/unfurler/src/resources/img/bisq.png
deleted file mode 100644
index 2b5e1250b..000000000
Binary files a/unfurler/src/resources/img/bisq.png and /dev/null differ
diff --git a/unfurler/src/resources/img/dashboard.png b/unfurler/src/resources/img/dashboard.png
deleted file mode 100644
index 60c8bcc6a..000000000
Binary files a/unfurler/src/resources/img/dashboard.png and /dev/null differ
diff --git a/unfurler/src/resources/img/lightning.png b/unfurler/src/resources/img/lightning.png
deleted file mode 100644
index 4686b0ef0..000000000
Binary files a/unfurler/src/resources/img/lightning.png and /dev/null differ
diff --git a/unfurler/src/resources/img/liquid.png b/unfurler/src/resources/img/liquid.png
deleted file mode 100644
index 72942110c..000000000
Binary files a/unfurler/src/resources/img/liquid.png and /dev/null differ
diff --git a/unfurler/src/resources/img/mempool.png b/unfurler/src/resources/img/mempool.png
deleted file mode 100644
index 60c8bcc6a..000000000
Binary files a/unfurler/src/resources/img/mempool.png and /dev/null differ
diff --git a/unfurler/src/resources/img/mining.png b/unfurler/src/resources/img/mining.png
deleted file mode 100644
index 6a2aa1b41..000000000
Binary files a/unfurler/src/resources/img/mining.png and /dev/null differ
diff --git a/unfurler/src/routes.ts b/unfurler/src/routes.ts
index 0c626c0c9..f9280369c 100644
--- a/unfurler/src/routes.ts
+++ b/unfurler/src/routes.ts
@@ -2,7 +2,6 @@ interface Match {
render: boolean;
title: string;
fallbackImg: string;
- fallbackFile: string;
staticImg?: string;
networkMode: string;
}
@@ -32,7 +31,6 @@ const routes = {
lightning: {
title: "Lightning",
fallbackImg: '/resources/previews/lightning.png',
- fallbackFile: '/resources/img/lightning.png',
routes: {
node: {
render: true,
@@ -71,7 +69,6 @@ const routes = {
mining: {
title: "Mining",
fallbackImg: '/resources/previews/mining.png',
- fallbackFile: '/resources/img/mining.png',
routes: {
pool: {
render: true,
@@ -87,14 +84,12 @@ const routes = {
const networks = {
bitcoin: {
fallbackImg: '/resources/previews/dashboard.png',
- fallbackFile: '/resources/img/dashboard.png',
routes: {
...routes // all routes supported
}
},
liquid: {
fallbackImg: '/resources/liquid/liquid-network-preview.png',
- fallbackFile: '/resources/img/liquid',
routes: { // only block, address & tx routes supported
block: routes.block,
address: routes.address,
@@ -103,7 +98,6 @@ const networks = {
},
bisq: {
fallbackImg: '/resources/bisq/bisq-markets-preview.png',
- fallbackFile: '/resources/img/bisq.png',
routes: {} // no routes supported
}
};
@@ -113,7 +107,6 @@ export function matchRoute(network: string, path: string): Match {
render: false,
title: '',
fallbackImg: '',
- fallbackFile: '',
networkMode: 'mainnet'
}
@@ -128,7 +121,6 @@ export function matchRoute(network: string, path: string): Match {
let route = networks[network] || networks.bitcoin;
match.fallbackImg = route.fallbackImg;
- match.fallbackFile = route.fallbackFile;
// traverse the route tree until we run out of route or tree, or hit a renderable match
while (!route.render && route.routes && parts.length && route.routes[parts[0]]) {
@@ -136,7 +128,6 @@ export function matchRoute(network: string, path: string): Match {
parts.shift();
if (route.fallbackImg) {
match.fallbackImg = route.fallbackImg;
- match.fallbackFile = route.fallbackFile;
}
}
|