diff --git a/backend/mempool-config.sample.json b/backend/mempool-config.sample.json
index dbbc8412d..5eefb5b83 100644
--- a/backend/mempool-config.sample.json
+++ b/backend/mempool-config.sample.json
@@ -27,7 +27,8 @@
"POOLS_JSON_TREE_URL": "https://api.github.com/repos/mempool/mining-pools/git/trees/master",
"ADVANCED_GBT_AUDIT": false,
"ADVANCED_GBT_MEMPOOL": false,
- "TRANSACTION_INDEXING": false
+ "TRANSACTION_INDEXING": false,
+ "FIRST_SEEN_INDEXING_DAYS": 0
},
"CORE_RPC": {
"HOST": "127.0.0.1",
diff --git a/backend/src/__fixtures__/mempool-config.template.json b/backend/src/__fixtures__/mempool-config.template.json
index 2e9221c7a..bdc557a29 100644
--- a/backend/src/__fixtures__/mempool-config.template.json
+++ b/backend/src/__fixtures__/mempool-config.template.json
@@ -28,7 +28,8 @@
"POOLS_JSON_URL": "__POOLS_JSON_URL__",
"ADVANCED_GBT_AUDIT": "__ADVANCED_GBT_AUDIT__",
"ADVANCED_GBT_MEMPOOL": "__ADVANCED_GBT_MEMPOOL__",
- "TRANSACTION_INDEXING": "__TRANSACTION_INDEXING__"
+ "TRANSACTION_INDEXING": "__TRANSACTION_INDEXING__",
+ "FIRST_SEEN_INDEXING_DAYS": "__FIRST_SEEN_INDEXING_DAYS__"
},
"CORE_RPC": {
"HOST": "__CORE_RPC_HOST__",
diff --git a/backend/src/__tests__/config.test.ts b/backend/src/__tests__/config.test.ts
index 58cf3a214..30a286f8a 100644
--- a/backend/src/__tests__/config.test.ts
+++ b/backend/src/__tests__/config.test.ts
@@ -41,6 +41,7 @@ describe('Mempool Backend Config', () => {
ADVANCED_GBT_AUDIT: false,
ADVANCED_GBT_MEMPOOL: false,
TRANSACTION_INDEXING: false,
+ FIRST_SEEN_INDEXING_DAYS: 0,
});
expect(config.ELECTRUM).toStrictEqual({ HOST: '127.0.0.1', PORT: 3306, TLS_ENABLED: true });
diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts
index 621f021ba..5f7e4f6e9 100644
--- a/backend/src/api/common.ts
+++ b/backend/src/api/common.ts
@@ -194,6 +194,13 @@ export class Common {
);
}
+ static firstSeenIndexingEnabled(): boolean {
+ return (
+ Common.indexingEnabled() &&
+ config.MEMPOOL.FIRST_SEEN_INDEXING_DAYS !== 0
+ );
+ }
+
static setDateMidnight(date: Date): void {
date.setUTCHours(0);
date.setUTCMinutes(0);
diff --git a/backend/src/api/database-migration.ts b/backend/src/api/database-migration.ts
index 77203114d..a566fb211 100644
--- a/backend/src/api/database-migration.ts
+++ b/backend/src/api/database-migration.ts
@@ -444,8 +444,8 @@ class DatabaseMigration {
}
if (databaseSchemaVersion < 50 && isBitcoin === true) {
- await this.$executeQuery('ALTER TABLE `transactions` ADD first_seen datetime DEFAULT NULL');
- await this.updateToSchemaVersion(49);
+ await this.$executeQuery('ALTER TABLE `transactions` ADD first_seen datetime DEFAULT NULL, ADD INDEX (first_seen)');
+ await this.updateToSchemaVersion(50);
}
}
diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts
index c2897f38b..ec5966aa3 100644
--- a/backend/src/api/websocket-handler.ts
+++ b/backend/src/api/websocket-handler.ts
@@ -463,7 +463,7 @@ class WebsocketHandler {
}
}
- if (config.MEMPOOL.TRANSACTION_INDEXING) {
+ if (Common.firstSeenIndexingEnabled()) {
await mempool.$saveTxFirstSeenTimes(transactions, _memPool);
}
diff --git a/backend/src/config.ts b/backend/src/config.ts
index e97deb5e5..31ae719ff 100644
--- a/backend/src/config.ts
+++ b/backend/src/config.ts
@@ -32,6 +32,7 @@ interface IConfig {
ADVANCED_GBT_AUDIT: boolean;
ADVANCED_GBT_MEMPOOL: boolean;
TRANSACTION_INDEXING: boolean;
+ FIRST_SEEN_INDEXING_DAYS: number;
};
ESPLORA: {
REST_API_URL: string;
@@ -153,6 +154,7 @@ const defaults: IConfig = {
'ADVANCED_GBT_AUDIT': false,
'ADVANCED_GBT_MEMPOOL': false,
'TRANSACTION_INDEXING': false,
+ 'FIRST_SEEN_INDEXING_DAYS': 0,
},
'ESPLORA': {
'REST_API_URL': 'http://127.0.0.1:3000',
diff --git a/backend/src/indexer.ts b/backend/src/indexer.ts
index 22f3ce319..f7e159368 100644
--- a/backend/src/indexer.ts
+++ b/backend/src/indexer.ts
@@ -7,6 +7,7 @@ import HashratesRepository from './repositories/HashratesRepository';
import bitcoinClient from './api/bitcoin/bitcoin-client';
import priceUpdater from './tasks/price-updater';
import PricesRepository from './repositories/PricesRepository';
+import TransactionRepository from './repositories/TransactionRepository';
class Indexer {
runIndexer = true;
@@ -78,6 +79,7 @@ class Indexer {
await mining.$generatePoolHashrateHistory();
await blocks.$generateBlocksSummariesDatabase();
await blocks.$generateCPFPDatabase();
+ await TransactionRepository.$clearOldFirstSeen();
} catch (e) {
this.indexerRunning = false;
logger.err(`Indexer failed, trying again in 10 seconds. Reason: ` + (e instanceof Error ? e.message : e));
diff --git a/backend/src/repositories/TransactionRepository.ts b/backend/src/repositories/TransactionRepository.ts
index 474bea0c6..e7c5ee770 100644
--- a/backend/src/repositories/TransactionRepository.ts
+++ b/backend/src/repositories/TransactionRepository.ts
@@ -1,6 +1,7 @@
+import config from '../config';
import DB from '../database';
import logger from '../logger';
-import { Ancestor, CpfpInfo, TransactionExtended, TransactionExtras } from '../mempool.interfaces';
+import { Ancestor, CpfpInfo, TransactionExtras } from '../mempool.interfaces';
interface TxInfo {
txid: string;
@@ -95,6 +96,28 @@ class TransactionRepository {
}
}
+ public async $clearOldFirstSeen() {
+ if (config.MEMPOOL.FIRST_SEEN_INDEXING_DAYS > 0) {
+ const cutoff = Math.floor(Date.now() / 1000) - (config.MEMPOOL.FIRST_SEEN_INDEXING_DAYS * 86400);
+ await this.$clearFirstSeenBefore(cutoff);
+ }
+ }
+
+ private async $clearFirstSeenBefore(cutoff: number) {
+ try {
+ const result = await DB.query(
+ `
+ DELETE FROM transactions
+ WHERE cluster is null AND first_seen < FROM_UNIXTIME(?)
+ ;`,
+ [cutoff]
+ );
+ } catch (e: any) {
+ logger.err(`Cannot clear old tx first seen times from db. Reason: ` + (e instanceof Error ? e.message : e));
+ throw e;
+ }
+ }
+
private convertCpfp(info: TxInfo): TransactionExtras {
const descendants: Ancestor[] = [];
const ancestors: Ancestor[] = [];
diff --git a/frontend/src/app/components/time-span/time-span.component.ts b/frontend/src/app/components/time-span/time-span.component.ts
index 03a438164..91b7c244d 100644
--- a/frontend/src/app/components/time-span/time-span.component.ts
+++ b/frontend/src/app/components/time-span/time-span.component.ts
@@ -53,9 +53,6 @@ export class TimeSpanComponent implements OnInit, OnChanges, OnDestroy {
calculate() {
const seconds = Math.floor(this.time);
- if (seconds < 60) {
- return $localize`:@@date-base.just-now:Just now`;
- }
let counter: number;
for (const i in this.intervals) {
if (this.intervals.hasOwnProperty(i)) {
diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html
index 86ae7c40c..857c93ebe 100644
--- a/frontend/src/app/components/transaction/transaction.component.html
+++ b/frontend/src/app/components/transaction/transaction.component.html
@@ -75,10 +75,10 @@
Confirmed
-
+