Compare commits

..

10 Commits

Author SHA1 Message Date
natsoni
428cc9eacd Analytic form for ETA algorithm 2024-09-09 16:03:40 +02:00
natsoni
638acffbad Consider incoming transactions flow into ETA calculation 2024-09-07 23:07:16 +02:00
natsoni
ae9125d316 statistics: class incoming transactions by fee rate 2024-09-07 17:49:21 +02:00
wiz
07fd3d3409 ops: Bump some FreeBSD install packages 2024-09-04 22:26:08 +09:00
wiz
f7360433a1 Merge pull request #5497 from mempool/nymkappa/nofaucet-error-message
[faucet] add missing error message for suspicious twitter accounts
2024-09-04 21:06:47 +09:00
nymkappa
f6fac92180 [faucet] add missing error message for suspicious twitter accounts 2024-09-04 13:52:48 +02:00
wiz
82d1502bfa Merge pull request #5487 from mempool/orangeusurf/about-enterprise-update
Update about page enterprise sponsors
2024-09-03 13:40:06 +09:00
orangesurf
8ab104d191 switch to alternate logo 2024-09-02 13:47:41 +02:00
orangesurf
263742132c Merge branch 'master' into orangeusurf/about-enterprise-update 2024-09-02 12:28:52 +01:00
orangesurf
fad39e0bea Update about page enterprise sponsors 2024-08-29 13:02:32 +02:00
31 changed files with 603 additions and 303 deletions

View File

@@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
import { RowDataPacket } from 'mysql2';
class DatabaseMigration {
private static currentVersion = 82;
private static currentVersion = 83;
private queryTimeout = 3600_000;
private statisticsAddedIndexed = false;
private uniqueLogs: string[] = [];
@@ -705,6 +705,11 @@ class DatabaseMigration {
await this.$fixBadV1AuditBlocks();
await this.updateToSchemaVersion(82);
}
if (databaseSchemaVersion < 83) {
await this.$addPerSecondVsizeToStatistics();
await this.updateToSchemaVersion(83);
}
}
/**
@@ -1341,6 +1346,47 @@ class DatabaseMigration {
}
}
}
private async $addPerSecondVsizeToStatistics(): Promise<void> {
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_1` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_2` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_3` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_4` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_5` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_6` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_8` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_10` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_12` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_15` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_20` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_30` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_40` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_50` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_60` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_70` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_80` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_90` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_100` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_125` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_150` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_175` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_200` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_250` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_300` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_350` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_400` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_500` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_600` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_700` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_800` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_900` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_1000` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_1200` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_1400` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_1600` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_1800` int(11) NOT NULL DEFAULT 0');
await this.$executeQuery('ALTER TABLE `statistics` ADD `vsize_ps_2000` int(11) NOT NULL DEFAULT 0');
}
}
export default new DatabaseMigration();

View File

@@ -12,6 +12,7 @@ import rbfCache from './rbf-cache';
import { Acceleration } from './services/acceleration';
import redisCache from './redis-cache';
import blocks from './blocks';
import { logFees } from './statistics/statistics';
class Mempool {
private inSync: boolean = false;
@@ -34,6 +35,7 @@ class Mempool {
private vBytesPerSecondArray: VbytesPerSecond[] = [];
private vBytesPerSecond: number = 0;
private vBytesPerSecondByFeeRate: { [feePerWU: number]: number } = {};
private mempoolProtection = 0;
private latestTransactions: any[] = [];
@@ -193,6 +195,10 @@ class Mempool {
return this.vBytesPerSecond;
}
public getVBytesPerSecondByFeeRate(): { [feePerWU: number]: number } {
return this.vBytesPerSecondByFeeRate;
}
public getFirstSeenForTransactions(txIds: string[]): number[] {
const txTimes: number[] = [];
txIds.forEach((txId: string) => {
@@ -270,6 +276,7 @@ class Mempool {
this.vBytesPerSecondArray.push({
unixTime: new Date().getTime(),
vSize: transaction.vsize,
effectiveFeePerVsize: transaction.effectiveFeePerVsize
});
}
hasChange = true;
@@ -588,6 +595,21 @@ class Mempool {
this.vBytesPerSecond = Math.round(
this.vBytesPerSecondArray.map((data) => data.vSize).reduce((a, b) => a + b) / config.STATISTICS.TX_PER_SECOND_SAMPLE_PERIOD
);
if (!Common.isLiquid()) {
this.vBytesPerSecondByFeeRate = {};
for (const tx of this.vBytesPerSecondArray) {
for (let i = 0; i < logFees.length; i++) {
if (tx.effectiveFeePerVsize < logFees[i + 1] || i === logFees.length - 1) {
this.vBytesPerSecondByFeeRate[logFees[i]] = (this.vBytesPerSecondByFeeRate[logFees[i]] || 0) + tx.vSize;
break;
}
}
}
for (const feeRate of Object.keys(this.vBytesPerSecondByFeeRate)) {
this.vBytesPerSecondByFeeRate[feeRate] = Math.round(this.vBytesPerSecondByFeeRate[feeRate] / config.STATISTICS.TX_PER_SECOND_SAMPLE_PERIOD);
}
}
}
}

View File

@@ -30,7 +30,6 @@ class MiningRoutes {
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fee-rates/:interval', this.$getHistoricalBlockFeeRates)
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/sizes-weights/:interval', this.$getHistoricalBlockSizeAndWeight)
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustments/:interval', this.$getDifficultyAdjustments)
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustment/:height', this.$getDifficultyAdjustment)
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/predictions/:interval', this.$getHistoricalBlocksHealth)
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/scores', this.$getBlockAuditScores)
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/scores/:height', this.$getBlockAuditScores)
@@ -298,18 +297,6 @@ class MiningRoutes {
}
}
private async $getDifficultyAdjustment(req: Request, res: Response) {
try {
const adjustment = await DifficultyAdjustmentsRepository.$getAdjustmentAtHeight(parseInt(req.params.height, 10));
res.header('Pragma', 'public');
res.header('Cache-control', 'public');
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
res.json(adjustment);
} catch (e) {
res.status(e instanceof Error && e.message === 'not found' ? 204 : 500).send(e instanceof Error ? e.message : e);
}
}
private async $getRewardStats(req: Request, res: Response) {
try {
const response = await mining.$getRewardStats(parseInt(req.params.blockCount, 10));

View File

@@ -53,7 +53,45 @@ class StatisticsApi {
vsize_1400,
vsize_1600,
vsize_1800,
vsize_2000
vsize_2000,
vsize_ps_1,
vsize_ps_2,
vsize_ps_3,
vsize_ps_4,
vsize_ps_5,
vsize_ps_6,
vsize_ps_8,
vsize_ps_10,
vsize_ps_12,
vsize_ps_15,
vsize_ps_20,
vsize_ps_30,
vsize_ps_40,
vsize_ps_50,
vsize_ps_60,
vsize_ps_70,
vsize_ps_80,
vsize_ps_90,
vsize_ps_100,
vsize_ps_125,
vsize_ps_150,
vsize_ps_175,
vsize_ps_200,
vsize_ps_250,
vsize_ps_300,
vsize_ps_350,
vsize_ps_400,
vsize_ps_500,
vsize_ps_600,
vsize_ps_700,
vsize_ps_800,
vsize_ps_900,
vsize_ps_1000,
vsize_ps_1200,
vsize_ps_1400,
vsize_ps_1600,
vsize_ps_1800,
vsize_ps_2000
)
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)`;
@@ -67,56 +105,94 @@ class StatisticsApi {
public async $create(statistics: Statistic, convertToDatetime = false): Promise<number | undefined> {
try {
const query = `INSERT INTO statistics(
added,
unconfirmed_transactions,
tx_per_second,
vbytes_per_second,
mempool_byte_weight,
fee_data,
total_fee,
min_fee,
vsize_1,
vsize_2,
vsize_3,
vsize_4,
vsize_5,
vsize_6,
vsize_8,
vsize_10,
vsize_12,
vsize_15,
vsize_20,
vsize_30,
vsize_40,
vsize_50,
vsize_60,
vsize_70,
vsize_80,
vsize_90,
vsize_100,
vsize_125,
vsize_150,
vsize_175,
vsize_200,
vsize_250,
vsize_300,
vsize_350,
vsize_400,
vsize_500,
vsize_600,
vsize_700,
vsize_800,
vsize_900,
vsize_1000,
vsize_1200,
vsize_1400,
vsize_1600,
vsize_1800,
vsize_2000
)
VALUES (${convertToDatetime ? `FROM_UNIXTIME(${statistics.added})` : statistics.added}, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
added,
unconfirmed_transactions,
tx_per_second,
vbytes_per_second,
mempool_byte_weight,
fee_data,
total_fee,
min_fee,
vsize_1,
vsize_2,
vsize_3,
vsize_4,
vsize_5,
vsize_6,
vsize_8,
vsize_10,
vsize_12,
vsize_15,
vsize_20,
vsize_30,
vsize_40,
vsize_50,
vsize_60,
vsize_70,
vsize_80,
vsize_90,
vsize_100,
vsize_125,
vsize_150,
vsize_175,
vsize_200,
vsize_250,
vsize_300,
vsize_350,
vsize_400,
vsize_500,
vsize_600,
vsize_700,
vsize_800,
vsize_900,
vsize_1000,
vsize_1200,
vsize_1400,
vsize_1600,
vsize_1800,
vsize_2000,
vsize_ps_1,
vsize_ps_2,
vsize_ps_3,
vsize_ps_4,
vsize_ps_5,
vsize_ps_6,
vsize_ps_8,
vsize_ps_10,
vsize_ps_12,
vsize_ps_15,
vsize_ps_20,
vsize_ps_30,
vsize_ps_40,
vsize_ps_50,
vsize_ps_60,
vsize_ps_70,
vsize_ps_80,
vsize_ps_90,
vsize_ps_100,
vsize_ps_125,
vsize_ps_150,
vsize_ps_175,
vsize_ps_200,
vsize_ps_250,
vsize_ps_300,
vsize_ps_350,
vsize_ps_400,
vsize_ps_500,
vsize_ps_600,
vsize_ps_700,
vsize_ps_800,
vsize_ps_900,
vsize_ps_1000,
vsize_ps_1200,
vsize_ps_1400,
vsize_ps_1600,
vsize_ps_1800,
vsize_ps_2000
)
VALUES (${convertToDatetime ? `FROM_UNIXTIME(${statistics.added})` : statistics.added}, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
const params: (string | number)[] = [
statistics.unconfirmed_transactions,
statistics.tx_per_second,
@@ -163,6 +239,44 @@ class StatisticsApi {
statistics.vsize_1600,
statistics.vsize_1800,
statistics.vsize_2000,
statistics.vsize_ps_1,
statistics.vsize_ps_2,
statistics.vsize_ps_3,
statistics.vsize_ps_4,
statistics.vsize_ps_5,
statistics.vsize_ps_6,
statistics.vsize_ps_8,
statistics.vsize_ps_10,
statistics.vsize_ps_12,
statistics.vsize_ps_15,
statistics.vsize_ps_20,
statistics.vsize_ps_30,
statistics.vsize_ps_40,
statistics.vsize_ps_50,
statistics.vsize_ps_60,
statistics.vsize_ps_70,
statistics.vsize_ps_80,
statistics.vsize_ps_90,
statistics.vsize_ps_100,
statistics.vsize_ps_125,
statistics.vsize_ps_150,
statistics.vsize_ps_175,
statistics.vsize_ps_200,
statistics.vsize_ps_250,
statistics.vsize_ps_300,
statistics.vsize_ps_350,
statistics.vsize_ps_400,
statistics.vsize_ps_500,
statistics.vsize_ps_600,
statistics.vsize_ps_700,
statistics.vsize_ps_800,
statistics.vsize_ps_900,
statistics.vsize_ps_1000,
statistics.vsize_ps_1200,
statistics.vsize_ps_1400,
statistics.vsize_ps_1600,
statistics.vsize_ps_1800,
statistics.vsize_ps_2000,
];
const [result]: any = await DB.query(query, params);
return result.insertId;
@@ -214,7 +328,45 @@ class StatisticsApi {
CAST(avg(vsize_1400) as DOUBLE) as vsize_1400,
CAST(avg(vsize_1600) as DOUBLE) as vsize_1600,
CAST(avg(vsize_1800) as DOUBLE) as vsize_1800,
CAST(avg(vsize_2000) as DOUBLE) as vsize_2000 \
CAST(avg(vsize_2000) as DOUBLE) as vsize_2000,
CAST(avg(vsize_ps_1) as DOUBLE) as vsize_ps_1,
CAST(avg(vsize_ps_2) as DOUBLE) as vsize_ps_2,
CAST(avg(vsize_ps_3) as DOUBLE) as vsize_ps_3,
CAST(avg(vsize_ps_4) as DOUBLE) as vsize_ps_4,
CAST(avg(vsize_ps_5) as DOUBLE) as vsize_ps_5,
CAST(avg(vsize_ps_6) as DOUBLE) as vsize_ps_6,
CAST(avg(vsize_ps_8) as DOUBLE) as vsize_ps_8,
CAST(avg(vsize_ps_10) as DOUBLE) as vsize_ps_10,
CAST(avg(vsize_ps_12) as DOUBLE) as vsize_ps_12,
CAST(avg(vsize_ps_15) as DOUBLE) as vsize_ps_15,
CAST(avg(vsize_ps_20) as DOUBLE) as vsize_ps_20,
CAST(avg(vsize_ps_30) as DOUBLE) as vsize_ps_30,
CAST(avg(vsize_ps_40) as DOUBLE) as vsize_ps_40,
CAST(avg(vsize_ps_50) as DOUBLE) as vsize_ps_50,
CAST(avg(vsize_ps_60) as DOUBLE) as vsize_ps_60,
CAST(avg(vsize_ps_70) as DOUBLE) as vsize_ps_70,
CAST(avg(vsize_ps_80) as DOUBLE) as vsize_ps_80,
CAST(avg(vsize_ps_90) as DOUBLE) as vsize_ps_90,
CAST(avg(vsize_ps_100) as DOUBLE) as vsize_ps_100,
CAST(avg(vsize_ps_125) as DOUBLE) as vsize_ps_125,
CAST(avg(vsize_ps_150) as DOUBLE) as vsize_ps_150,
CAST(avg(vsize_ps_175) as DOUBLE) as vsize_ps_175,
CAST(avg(vsize_ps_200) as DOUBLE) as vsize_ps_200,
CAST(avg(vsize_ps_250) as DOUBLE) as vsize_ps_250,
CAST(avg(vsize_ps_300) as DOUBLE) as vsize_ps_300,
CAST(avg(vsize_ps_350) as DOUBLE) as vsize_ps_350,
CAST(avg(vsize_ps_400) as DOUBLE) as vsize_ps_400,
CAST(avg(vsize_ps_500) as DOUBLE) as vsize_ps_500,
CAST(avg(vsize_ps_600) as DOUBLE) as vsize_ps_600,
CAST(avg(vsize_ps_700) as DOUBLE) as vsize_ps_700,
CAST(avg(vsize_ps_800) as DOUBLE) as vsize_ps_800,
CAST(avg(vsize_ps_900) as DOUBLE) as vsize_ps_900,
CAST(avg(vsize_ps_1000) as DOUBLE) as vsize_ps_1000,
CAST(avg(vsize_ps_1200) as DOUBLE) as vsize_ps_1200,
CAST(avg(vsize_ps_1400) as DOUBLE) as vsize_ps_1400,
CAST(avg(vsize_ps_1600) as DOUBLE) as vsize_ps_1600,
CAST(avg(vsize_ps_1800) as DOUBLE) as vsize_ps_1800,
CAST(avg(vsize_ps_2000) as DOUBLE) as vsize_ps_2000 \
FROM statistics \
${interval === 'all' ? '' : `WHERE added BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`} \
GROUP BY UNIX_TIMESTAMP(added) DIV ${div} \
@@ -264,7 +416,45 @@ class StatisticsApi {
vsize_1400,
vsize_1600,
vsize_1800,
vsize_2000 \
vsize_2000,
vsize_ps_1,
vsize_ps_2,
vsize_ps_3,
vsize_ps_4,
vsize_ps_5,
vsize_ps_6,
vsize_ps_8,
vsize_ps_10,
vsize_ps_12,
vsize_ps_15,
vsize_ps_20,
vsize_ps_30,
vsize_ps_40,
vsize_ps_50,
vsize_ps_60,
vsize_ps_70,
vsize_ps_80,
vsize_ps_90,
vsize_ps_100,
vsize_ps_125,
vsize_ps_150,
vsize_ps_175,
vsize_ps_200,
vsize_ps_250,
vsize_ps_300,
vsize_ps_350,
vsize_ps_400,
vsize_ps_500,
vsize_ps_600,
vsize_ps_700,
vsize_ps_800,
vsize_ps_900,
vsize_ps_1000,
vsize_ps_1200,
vsize_ps_1400,
vsize_ps_1600,
vsize_ps_1800,
vsize_ps_2000 \
FROM statistics \
${interval === 'all' ? '' : `WHERE added BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`} \
GROUP BY UNIX_TIMESTAMP(added) DIV ${div} \
@@ -452,7 +642,47 @@ class StatisticsApi {
s.vsize_1600,
s.vsize_1800,
s.vsize_2000,
]
],
vsizes_ps: [
s.vsize_ps_1,
s.vsize_ps_2,
s.vsize_ps_3,
s.vsize_ps_4,
s.vsize_ps_5,
s.vsize_ps_6,
s.vsize_ps_8,
s.vsize_ps_10,
s.vsize_ps_12,
s.vsize_ps_15,
s.vsize_ps_20,
s.vsize_ps_30,
s.vsize_ps_40,
s.vsize_ps_50,
s.vsize_ps_60,
s.vsize_ps_70,
s.vsize_ps_80,
s.vsize_ps_90,
s.vsize_ps_100,
s.vsize_ps_125,
s.vsize_ps_150,
s.vsize_ps_175,
s.vsize_ps_200,
s.vsize_ps_250,
s.vsize_ps_300,
s.vsize_ps_350,
s.vsize_ps_400,
s.vsize_ps_500,
s.vsize_ps_600,
s.vsize_ps_700,
s.vsize_ps_800,
s.vsize_ps_900,
s.vsize_ps_1000,
s.vsize_ps_1200,
s.vsize_ps_1400,
s.vsize_ps_1600,
s.vsize_ps_1800,
s.vsize_ps_2000,
],
};
});
}
@@ -506,6 +736,44 @@ class StatisticsApi {
vsize_1600: s.vsizes[35],
vsize_1800: s.vsizes[36],
vsize_2000: s.vsizes[37],
vsize_ps_1: s.vsizes_ps?.[0] || 0,
vsize_ps_2: s.vsizes_ps?.[1] || 0,
vsize_ps_3: s.vsizes_ps?.[2] || 0,
vsize_ps_4: s.vsizes_ps?.[3] || 0,
vsize_ps_5: s.vsizes_ps?.[4] || 0,
vsize_ps_6: s.vsizes_ps?.[5] || 0,
vsize_ps_8: s.vsizes_ps?.[6] || 0,
vsize_ps_10: s.vsizes_ps?.[7] || 0,
vsize_ps_12: s.vsizes_ps?.[8] || 0,
vsize_ps_15: s.vsizes_ps?.[9] || 0,
vsize_ps_20: s.vsizes_ps?.[10] || 0,
vsize_ps_30: s.vsizes_ps?.[11] || 0,
vsize_ps_40: s.vsizes_ps?.[12] || 0,
vsize_ps_50: s.vsizes_ps?.[13] || 0,
vsize_ps_60: s.vsizes_ps?.[14] || 0,
vsize_ps_70: s.vsizes_ps?.[15] || 0,
vsize_ps_80: s.vsizes_ps?.[16] || 0,
vsize_ps_90: s.vsizes_ps?.[17] || 0,
vsize_ps_100: s.vsizes_ps?.[18] || 0,
vsize_ps_125: s.vsizes_ps?.[19] || 0,
vsize_ps_150: s.vsizes_ps?.[20] || 0,
vsize_ps_175: s.vsizes_ps?.[21] || 0,
vsize_ps_200: s.vsizes_ps?.[22] || 0,
vsize_ps_250: s.vsizes_ps?.[23] || 0,
vsize_ps_300: s.vsizes_ps?.[24] || 0,
vsize_ps_350: s.vsizes_ps?.[25] || 0,
vsize_ps_400: s.vsizes_ps?.[26] || 0,
vsize_ps_500: s.vsizes_ps?.[27] || 0,
vsize_ps_600: s.vsizes_ps?.[28] || 0,
vsize_ps_700: s.vsizes_ps?.[29] || 0,
vsize_ps_800: s.vsizes_ps?.[30] || 0,
vsize_ps_900: s.vsizes_ps?.[31] || 0,
vsize_ps_1000: s.vsizes_ps?.[32] || 0,
vsize_ps_1200: s.vsizes_ps?.[33] || 0,
vsize_ps_1400: s.vsizes_ps?.[34] || 0,
vsize_ps_1600: s.vsizes_ps?.[35] || 0,
vsize_ps_1800: s.vsizes_ps?.[36] || 0,
vsize_ps_2000: s.vsizes_ps?.[37] || 0,
}
});
}

View File

@@ -4,6 +4,9 @@ import { TransactionExtended, OptimizedStatistic } from '../../mempool.interface
import { Common } from '../common';
import statisticsApi from './statistics-api';
export const logFees = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
class Statistics {
protected intervalTimer: NodeJS.Timer | undefined;
protected lastRun: number = 0;
@@ -42,6 +45,7 @@ class Statistics {
const currentMempool = memPool.getMempool();
const txPerSecond = memPool.getTxPerSecond();
const vBytesPerSecond = memPool.getVBytesPerSecond();
const vBytesPerSecondByFeeRate = memPool.getVBytesPerSecondByFeeRate();
logger.debug('Running statistics');
@@ -73,9 +77,6 @@ class Statistics {
const totalWeight = memPoolArray.map((tx) => tx.vsize).reduce((acc, curr) => acc + curr) * 4;
const totalFee = memPoolArray.map((tx) => tx.fee).reduce((acc, curr) => acc + curr);
const logFees = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
const weightVsizeFees: { [feePerWU: number]: number } = {};
const lastItem = logFees.length - 1;
@@ -147,6 +148,44 @@ class Statistics {
vsize_1600: weightVsizeFees['1600'] || 0,
vsize_1800: weightVsizeFees['1800'] || 0,
vsize_2000: weightVsizeFees['2000'] || 0,
vsize_ps_1: vBytesPerSecondByFeeRate['1'] || 0,
vsize_ps_2: vBytesPerSecondByFeeRate['2'] || 0,
vsize_ps_3: vBytesPerSecondByFeeRate['3'] || 0,
vsize_ps_4: vBytesPerSecondByFeeRate['4'] || 0,
vsize_ps_5: vBytesPerSecondByFeeRate['5'] || 0,
vsize_ps_6: vBytesPerSecondByFeeRate['6'] || 0,
vsize_ps_8: vBytesPerSecondByFeeRate['8'] || 0,
vsize_ps_10: vBytesPerSecondByFeeRate['10'] || 0,
vsize_ps_12: vBytesPerSecondByFeeRate['12'] || 0,
vsize_ps_15: vBytesPerSecondByFeeRate['15'] || 0,
vsize_ps_20: vBytesPerSecondByFeeRate['20'] || 0,
vsize_ps_30: vBytesPerSecondByFeeRate['30'] || 0,
vsize_ps_40: vBytesPerSecondByFeeRate['40'] || 0,
vsize_ps_50: vBytesPerSecondByFeeRate['50'] || 0,
vsize_ps_60: vBytesPerSecondByFeeRate['60'] || 0,
vsize_ps_70: vBytesPerSecondByFeeRate['70'] || 0,
vsize_ps_80: vBytesPerSecondByFeeRate['80'] || 0,
vsize_ps_90: vBytesPerSecondByFeeRate['90'] || 0,
vsize_ps_100: vBytesPerSecondByFeeRate['100'] || 0,
vsize_ps_125: vBytesPerSecondByFeeRate['125'] || 0,
vsize_ps_150: vBytesPerSecondByFeeRate['150'] || 0,
vsize_ps_175: vBytesPerSecondByFeeRate['175'] || 0,
vsize_ps_200: vBytesPerSecondByFeeRate['200'] || 0,
vsize_ps_250: vBytesPerSecondByFeeRate['250'] || 0,
vsize_ps_300: vBytesPerSecondByFeeRate['300'] || 0,
vsize_ps_350: vBytesPerSecondByFeeRate['350'] || 0,
vsize_ps_400: vBytesPerSecondByFeeRate['400'] || 0,
vsize_ps_500: vBytesPerSecondByFeeRate['500'] || 0,
vsize_ps_600: vBytesPerSecondByFeeRate['600'] || 0,
vsize_ps_700: vBytesPerSecondByFeeRate['700'] || 0,
vsize_ps_800: vBytesPerSecondByFeeRate['800'] || 0,
vsize_ps_900: vBytesPerSecondByFeeRate['900'] || 0,
vsize_ps_1000: vBytesPerSecondByFeeRate['1000'] || 0,
vsize_ps_1200: vBytesPerSecondByFeeRate['1200'] || 0,
vsize_ps_1400: vBytesPerSecondByFeeRate['1400'] || 0,
vsize_ps_1600: vBytesPerSecondByFeeRate['1600'] || 0,
vsize_ps_1800: vBytesPerSecondByFeeRate['1800'] || 0,
vsize_ps_2000: vBytesPerSecondByFeeRate['2000'] || 0,
});
if (this.newStatisticsEntryCallback && insertId) {

View File

@@ -439,6 +439,46 @@ export interface Statistic {
vsize_1600: number;
vsize_1800: number;
vsize_2000: number;
vsize_ps_1: number;
vsize_ps_2: number;
vsize_ps_3: number;
vsize_ps_4: number;
vsize_ps_5: number;
vsize_ps_6: number;
vsize_ps_8: number;
vsize_ps_10: number;
vsize_ps_12: number;
vsize_ps_15: number;
vsize_ps_20: number;
vsize_ps_30: number;
vsize_ps_40: number;
vsize_ps_50: number;
vsize_ps_60: number;
vsize_ps_70: number;
vsize_ps_80: number;
vsize_ps_90: number;
vsize_ps_100: number;
vsize_ps_125: number;
vsize_ps_150: number;
vsize_ps_175: number;
vsize_ps_200: number;
vsize_ps_250: number;
vsize_ps_300: number;
vsize_ps_350: number;
vsize_ps_400: number;
vsize_ps_500: number;
vsize_ps_600: number;
vsize_ps_700: number;
vsize_ps_800: number;
vsize_ps_900: number;
vsize_ps_1000: number;
vsize_ps_1200: number;
vsize_ps_1400: number;
vsize_ps_1600: number;
vsize_ps_1800: number;
vsize_ps_2000: number;
}
export interface OptimizedStatistic {
@@ -449,6 +489,7 @@ export interface OptimizedStatistic {
mempool_byte_weight: number;
min_fee: number;
vsizes: number[];
vsizes_ps: number[];
}
export interface TxTrackingInfo {
@@ -481,6 +522,7 @@ export interface WebsocketResponse {
export interface VbytesPerSecond {
unixTime: number;
vSize: number;
effectiveFeePerVsize: number;
}
export interface RequiredSpec { [name: string]: RequiredParams; }

View File

@@ -88,22 +88,6 @@ class DifficultyAdjustmentsRepository {
}
}
public async $getAdjustmentAtHeight(height: number): Promise<IndexedDifficultyAdjustment> {
try {
if (isNaN(height)) {
throw new Error(`argument must be a number`);
}
const [rows] = await DB.query(`SELECT * FROM difficulty_adjustments WHERE height = ?`, [height]);
if (!rows[0]) {
throw new Error(`not found`);
}
return rows[0] as IndexedDifficultyAdjustment;
} catch (e: any) {
logger.err(`Cannot get difficulty adjustment from the database. Reason: ${e instanceof Error ? e.message : e}`, logger.tags.mining);
throw e;
}
}
public async $getAdjustmentsHeights(): Promise<number[]> {
try {
const [rows]: any[] = await DB.query(`SELECT height FROM difficulty_adjustments`);

View File

@@ -53,7 +53,7 @@
<span>Spiral</span>
</a>
<a href="https://foundrydigital.com/" target="_blank" title="Foundry">
<svg xmlns="http://www.w3.org/2000/svg" id="b" data-name="Layer 2" style="zoom: 1;" width="32" height="76" viewBox="0 0 32 76" class="image">
<svg xmlns="http://www.w3.org/2000/svg" id="b" data-name="Layer 2" style="zoom: 1;" width="32" height="90" viewBox="0 -5 32 90" class="image">
<defs>
<style>
.d {
@@ -130,14 +130,9 @@
</svg>
<span>Unchained</span>
</a>
<a href="https://gemini.com/" target="_blank" title="Gemini">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="360" height="360" viewBox="0 0 360 360" class="image">
<rect style="fill: black" width="360" height="360" />
<g transform="matrix(0.62 0 0 0.62 180 180)">
<path style="fill: rgb(0,220,250)" transform=" translate(-162, -162)" d="M 211.74 0 C 154.74 0 106.35 43.84 100.25 100.25 C 43.84 106.35 1.4210854715202004e-14 154.76 1.4210854715202004e-14 211.74 C 0.044122601308501076 273.7212006364817 50.27879936351834 323.95587739869154 112.26 324 C 169.26 324 217.84 280.15999999999997 223.75 223.75 C 280.15999999999997 217.65 324 169.24 324 112.26 C 323.95587739869154 50.278799363518324 273.72120063648174 0.04412260130848722 211.74 -1.4210854715202004e-14 z M 297.74 124.84 C 291.9644950552469 162.621439649343 262.2969457716857 192.26062994820046 224.51 198 L 224.51 124.84 z M 26.3 199.16 C 31.986912917108594 161.30935034910615 61.653433460549415 131.56986937804106 99.48999999999998 125.78999999999999 L 99.49 199 L 26.3 199 z M 198.21 224.51 C 191.87736076583954 267.0991541201681 155.312384597087 298.62923417787493 112.255 298.62923417787493 C 69.19761540291302 298.62923417787493 32.63263923416048 267.0991541201682 26.3 224.51 z M 199.16 124.83999999999999 L 199.16 199 L 124.84 199 L 124.84 124.84 z M 297.7 99.48999999999998 L 125.78999999999999 99.48999999999998 C 132.12263923416046 56.90084587983182 168.687615402913 25.37076582212505 211.745 25.37076582212505 C 254.80238459708698 25.37076582212505 291.3673607658395 56.900845879831834 297.7 99.49 z" stroke-linecap="round" />
</g>
</svg>
<span>Gemini</span>
<a href="https://bitkey.world/" target="_blank" title="Bitkey">
<img class="image" src="/resources/profile/bitkey.svg" />
<span>Bitkey</span>
</a>
<a href="https://bullbitcoin.com/" target="_blank" title="Bull Bitcoin">
<svg aria-hidden="true" class="image" viewBox="0 -5 40 40" xmlns="http://www.w3.org/2000/svg">
@@ -193,6 +188,19 @@
</svg>
<span>Exodus</span>
</a>
<a href="https://gemini.com/" target="_blank" title="Gemini">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="360" height="360" viewBox="0 0 360 360" class="image">
<rect style="fill: black" width="360" height="360" />
<g transform="matrix(0.62 0 0 0.62 180 180)">
<path style="fill: rgb(0,220,250)" transform=" translate(-162, -162)" d="M 211.74 0 C 154.74 0 106.35 43.84 100.25 100.25 C 43.84 106.35 1.4210854715202004e-14 154.76 1.4210854715202004e-14 211.74 C 0.044122601308501076 273.7212006364817 50.27879936351834 323.95587739869154 112.26 324 C 169.26 324 217.84 280.15999999999997 223.75 223.75 C 280.15999999999997 217.65 324 169.24 324 112.26 C 323.95587739869154 50.278799363518324 273.72120063648174 0.04412260130848722 211.74 -1.4210854715202004e-14 z M 297.74 124.84 C 291.9644950552469 162.621439649343 262.2969457716857 192.26062994820046 224.51 198 L 224.51 124.84 z M 26.3 199.16 C 31.986912917108594 161.30935034910615 61.653433460549415 131.56986937804106 99.48999999999998 125.78999999999999 L 99.49 199 L 26.3 199 z M 198.21 224.51 C 191.87736076583954 267.0991541201681 155.312384597087 298.62923417787493 112.255 298.62923417787493 C 69.19761540291302 298.62923417787493 32.63263923416048 267.0991541201682 26.3 224.51 z M 199.16 124.83999999999999 L 199.16 199 L 124.84 199 L 124.84 124.84 z M 297.7 99.48999999999998 L 125.78999999999999 99.48999999999998 C 132.12263923416046 56.90084587983182 168.687615402913 25.37076582212505 211.745 25.37076582212505 C 254.80238459708698 25.37076582212505 291.3673607658395 56.900845879831834 297.7 99.49 z" stroke-linecap="round" />
</g>
</svg>
<span>Gemini</span>
</a>
<a href="https://leather.io/" target="_blank" title="Leather">
<img class="image" src="/resources/profile/leather.svg" />
<span>Leather</span>
</a>
</div>
</div>

View File

@@ -251,3 +251,12 @@
width: 64px;
height: 64px;
}
.enterprise-sponsor {
.wrapper {
display: flex;
flex-wrap: wrap;
justify-content: center;
max-width: 800px;
}
}

View File

@@ -78,25 +78,6 @@
</ng-template>
</td>
</tr>
<tr *ngIf="this.stateService.network === '' && block.height % 2016 === 0">
<td i18n="mining.difficulty-adjustment">Adjustment</td>
<td>
<ng-container *ngIf="cacheService.daCache[block.height]?.adjustment > 0; else loadingAdjustment">
<div [style.color]="cacheService.daCache[block.height].adjustment > 1 ? 'var(--green)' : (cacheService.daCache[block.height].adjustment < 1 ? 'var(--red)' : '')">
@if (cacheService.daCache[block.height].adjustment > 1) {
<fa-icon class="retarget-sign up" [icon]="['fas', 'caret-up']" [fixedWidth]="true"></fa-icon>
} @else if (cacheService.daCache[block.height].adjustment < 1) {
<fa-icon class="retarget-sign" [icon]="['fas', 'caret-down']" [fixedWidth]="true"></fa-icon>
}
{{ (cacheService.daCache[block.height].adjustment - 1) * 100 | absolute | number: '1.2-2' }}
<span class="symbol">%</span>
</div>
</ng-container>
<ng-template #loadingAdjustment>
<span class="skeleton-loader" style="max-width: 60px"></span>
</ng-template>
</td>
</tr>
</ng-container>
<ng-template #skeletonRows>
<tr>
@@ -212,10 +193,6 @@
</span>
</td>
</tr>
<tr *ngIf="this.stateService.network === '' && block.height % 2016 === 0">
<td i18n="block.difficulty">Difficulty</td>
<td>{{ block.difficulty | amountShortener: 2 }}</td>
</tr>
</ng-container>
<ng-template #loadingRest>
<tr>

View File

@@ -280,11 +280,3 @@ h1 {
top: -1px;
margin-right: 2px;
}
.retarget-sign {
margin-right: -3px;
&.up {
position: relative;
top: 2px;
}
}

View File

@@ -99,7 +99,7 @@ export class BlockComponent implements OnInit, OnDestroy {
private relativeUrlPipe: RelativeUrlPipe,
private apiService: ApiService,
private priceService: PriceService,
public cacheService: CacheService,
private cacheService: CacheService,
private servicesApiService: ServicesApiServices,
private cd: ChangeDetectorRef,
private preloadService: PreloadService,

View File

@@ -1,12 +1,10 @@
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Observable, Subscription, delay, filter, of, retryWhen, switchMap, take, tap, throwError } from 'rxjs';
import { Observable, Subscription, delay, filter, tap } from 'rxjs';
import { StateService } from '../../services/state.service';
import { specialBlocks } from '../../app.constants';
import { BlockExtended } from '../../interfaces/node-api.interface';
import { Location } from '@angular/common';
import { CacheService } from '../../services/cache.service';
import { ApiService } from '../../services/api.service';
import { colorFromRetarget } from '../../shared/common.utils';
interface BlockchainBlock extends BlockExtended {
placeholder?: boolean;
@@ -79,7 +77,6 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
constructor(
public stateService: StateService,
public cacheService: CacheService,
public apiService: ApiService,
private cd: ChangeDetectorRef,
private location: Location,
) {
@@ -337,31 +334,6 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
return this.specialBlocks[height]?.networks.includes(this.stateService.network || 'mainnet') ? true : false;
}
isDA(height: number): boolean {
const isDA = height % 2016 === 0 && this.stateService.network === '';
if (isDA && !this.cacheService.daCache[height]?.exact) {
const estimatedAdjustment = this.cacheService.daCache[height]?.adjustment || 0;
this.cacheService.daCache[height] = { adjustment: estimatedAdjustment, exact: true };
this.apiService.getDifficultyAdjustmentByHeight$(height).pipe(
switchMap(da => {
const blocksAvailable = (this.height || this.chainTip) && this.blockStyles[(this.height || this.chainTip) - height];
return blocksAvailable ? of(da) : throwError(() => new Error());
}),
retryWhen(errors =>
errors.pipe(
delay(1000),
take(3)
)
),
tap((da) => {
this.cacheService.daCache[height] = { adjustment: da?.adjustment || 1, exact: true };
this.blockStyles[(this.height || this.chainTip) - height].background = colorFromRetarget(da?.adjustment);
})
).subscribe();
}
return isDA;
}
getStyleForBlock(block: BlockchainBlock, index: number, animateEnterFrom: number = 0) {
if (!block || block.placeholder) {
return this.getStyleForPlaceholderBlock(index, animateEnterFrom);
@@ -377,8 +349,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
return {
left: addLeft + this.blockOffset * index + 'px',
background: this.isDA(block.height) ? colorFromRetarget(this.cacheService.daCache[block.height]?.adjustment || 1) :
`repeating-linear-gradient(
background: `repeating-linear-gradient(
var(--secondary),
var(--secondary) ${greenBackgroundHeight}%,
${this.gradientColors[this.network][0]} ${Math.max(greenBackgroundHeight, 0)}%,

View File

@@ -5,7 +5,6 @@ import { ApiService } from '../../services/api.service';
import { formatNumber } from '@angular/common';
import { selectPowerOfTen } from '../../bitcoin.utils';
import { StateService } from '../../services/state.service';
import { CacheService } from '../../services/cache.service';
@Component({
selector: 'app-difficulty-adjustments-table',
@@ -28,8 +27,7 @@ export class DifficultyAdjustmentsTable implements OnInit {
constructor(
@Inject(LOCALE_ID) public locale: string,
private apiService: ApiService,
public stateService: StateService,
private cacheService: CacheService,
public stateService: StateService
) {
}
@@ -54,7 +52,6 @@ export class DifficultyAdjustmentsTable implements OnInit {
adjustment[2] / selectedPowerOfTen.divider,
this.locale, `1.${decimals}-${decimals}`) + selectedPowerOfTen.unit
});
this.cacheService.daCache[adjustment[1]] = { adjustment: adjustment[3], exact: true };
}
this.isLoading = false;
return tableData.slice(0, 6);

View File

@@ -5,7 +5,7 @@
</div>
<div class="faucet-container text-center">
@if (txid) {
<div class="alert alert-success w-100 text-truncate">
<fa-icon [icon]="['fas', 'circle-check']"></fa-icon>
@@ -36,6 +36,13 @@
<app-twitter-login customClass="btn btn-sm" width="180px" redirectTo="/testnet4/faucet" buttonString="Link your Twitter"></app-twitter-login>
</div>
}
@else if (error === 'account_limited') {
<div class="alert alert-mempool d-block text-center w-100">
<div class="d-inline align-middle">
<span class="mb-2 mr-2">Your Twitter account does not allow you to access the faucet</span>
</div>
</div>
}
@else if (error) {
<!-- User can request -->
<app-mempool-error class="w-100" [error]="error"></app-mempool-error>
@@ -81,7 +88,7 @@
}
<!-- Send back coins -->
@if (status?.address) {
@if (status?.address) {
<div class="mt-4 alert alert-info w-100">If you no longer need your testnet4 coins, please consider <a class="text-primary" [routerLink]="['/address/' | relativeUrl, status.address]"><u>sending them back</u></a> to replenish the faucet.</div>
}

View File

@@ -19,7 +19,7 @@ export class FaucetComponent implements OnInit, OnDestroy {
error: string = '';
user: any = undefined;
txid: string = '';
faucetStatusSubscription: Subscription;
status: {
min: number; // minimum amount to request at once (in sats)

View File

@@ -41,25 +41,6 @@
</div>
</td>
</tr>
<tr *ngIf="this.stateService.network === '' && mempoolBlock.height % 2016 === 0">
<td i18n="mining.difficulty-adjustment">Adjustment</td>
<td>
<ng-container *ngIf="cacheService.daCache[mempoolBlock.height]?.adjustment > 0; else loadingAdjustment">
<div [style.color]="cacheService.daCache[mempoolBlock.height].adjustment > 1 ? 'var(--green)' : (cacheService.daCache[mempoolBlock.height].adjustment < 1 ? 'var(--red)' : '')">
@if (cacheService.daCache[mempoolBlock.height].adjustment > 1) {
<fa-icon class="retarget-sign up" [icon]="['fas', 'caret-up']" [fixedWidth]="true"></fa-icon>
} @else if (cacheService.daCache[mempoolBlock.height].adjustment < 1) {
<fa-icon class="retarget-sign" [icon]="['fas', 'caret-down']" [fixedWidth]="true"></fa-icon>
}
{{ (cacheService.daCache[mempoolBlock.height].adjustment - 1) * 100 | absolute | number: '1.2-2' }}
<span class="symbol">%</span>
</div>
</ng-container>
<ng-template #loadingAdjustment>
<span class="skeleton-loader" style="max-width: 60px"></span>
</ng-template>
</td>
</tr>
</tbody>
</table>
<app-fee-distribution-graph *ngIf="webGlEnabled" [transactions]="mempoolBlockTransactions$ | async" [feeRange]="mempoolBlock.isStack ? mempoolBlock.feeRange : []" [vsize]="mempoolBlock.blockVSize" ></app-fee-distribution-graph>

View File

@@ -36,11 +36,3 @@ h1 {
margin: auto;
}
}
.retarget-sign {
margin-right: -3px;
&.up {
position: relative;
top: 2px;
}
}

View File

@@ -9,7 +9,6 @@ import { Observable, BehaviorSubject } from 'rxjs';
import { SeoService } from '../../services/seo.service';
import { seoDescriptionNetwork } from '../../shared/common.utils';
import { WebsocketService } from '../../services/websocket.service';
import { CacheService } from '../../services/cache.service';
@Component({
selector: 'app-mempool-block',
@@ -31,7 +30,6 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
public stateService: StateService,
private seoService: SeoService,
private websocketService: WebsocketService,
public cacheService: CacheService,
private cd: ChangeDetectorRef,
@Inject(PLATFORM_ID) private platformId: Object,
) {

View File

@@ -1,10 +1,10 @@
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, HostListener, Input, OnChanges, SimpleChanges, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core';
import { Subscription, Observable, of, combineLatest, throwError } from 'rxjs';
import { Subscription, Observable, of, combineLatest } from 'rxjs';
import { MempoolBlock } from '../../interfaces/websocket.interface';
import { StateService } from '../../services/state.service';
import { EtaService } from '../../services/eta.service';
import { Router } from '@angular/router';
import { delay, filter, map, retryWhen, switchMap, take, tap } from 'rxjs/operators';
import { delay, filter, map, switchMap, tap } from 'rxjs/operators';
import { feeLevels } from '../../app.constants';
import { specialBlocks } from '../../app.constants';
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
@@ -12,8 +12,6 @@ import { Location } from '@angular/common';
import { DifficultyAdjustment, MempoolPosition } from '../../interfaces/node-api.interface';
import { animate, style, transition, trigger } from '@angular/animations';
import { ThemeService } from '../../services/theme.service';
import { CacheService } from '../../services/cache.service';
import { colorFromRetarget } from '../../shared/common.utils';
@Component({
selector: 'app-mempool-blocks',
@@ -95,7 +93,6 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
constructor(
private router: Router,
public stateService: StateService,
public cacheService: CacheService,
private etaService: EtaService,
private themeService: ThemeService,
private cd: ChangeDetectorRef,
@@ -390,37 +387,6 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
this.mempoolBlocksFull.forEach((block, i) => this.mempoolBlockStyles.push(this.getStyleForMempoolBlock(block, i)));
}
isDA(height: number): boolean {
if (this.chainTip === -1) {
return false;
}
const isDA = height % 2016 === 0 && this.stateService.network === '';
if (isDA && !this.cacheService.daCache[height]) {
this.cacheService.daCache[height] = { adjustment: 0 };
this.difficultyAdjustments$.pipe(
filter(da => !!da),
switchMap(da => {
const mempoolBlocksAvailable = this.chainTip && this.mempoolBlockStyles[height - this.chainTip - 1];
return mempoolBlocksAvailable ? of(da) : throwError(() => new Error());
}),
retryWhen(errors =>
errors.pipe(
delay(100),
take(3)
)
),
tap(da => {
const adjustment = parseFloat((1 + da.difficultyChange / 100).toFixed(4));
if (adjustment !== this.cacheService.daCache[height].adjustment) {
this.cacheService.daCache[height].adjustment = adjustment;
this.mempoolBlockStyles[height - this.chainTip - 1].background = colorFromRetarget(adjustment);
}
})
).subscribe();
}
return isDA;
}
getStyleForMempoolBlock(mempoolBlock: MempoolBlock, index: number) {
const emptyBackgroundSpacePercentage = Math.max(100 - mempoolBlock.blockVSize / this.stateService.blockVSize * 100, 0);
const usedBlockSpace = 100 - emptyBackgroundSpacePercentage;
@@ -444,7 +410,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
return {
'right': this.containerOffset + index * this.blockOffset + 'px',
'background': this.isDA(mempoolBlock.height) ? colorFromRetarget(this.cacheService.daCache[mempoolBlock.height]?.adjustment || 1) : backgroundGradients.join(',') + ')'
'background': backgroundGradients.join(',') + ')'
};
}

View File

@@ -27,7 +27,7 @@ import { StorageService } from '../../services/storage.service';
import { seoDescriptionNetwork } from '../../shared/common.utils';
import { getTransactionFlags, getUnacceleratedFeeRate } from '../../shared/transaction.utils';
import { Filter, TransactionFlags, toFilters } from '../../shared/filters.utils';
import { BlockExtended, CpfpInfo, RbfTree, MempoolPosition, DifficultyAdjustment, Acceleration, AccelerationPosition } from '../../interfaces/node-api.interface';
import { BlockExtended, CpfpInfo, RbfTree, MempoolPosition, DifficultyAdjustment, Acceleration, AccelerationPosition, OptimizedMempoolStats } from '../../interfaces/node-api.interface';
import { LiquidUnblinding } from './liquid-ublinding';
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
import { PriceService } from '../../services/price.service';
@@ -139,6 +139,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
firstLoad = true;
waitingForAccelerationInfo: boolean = false;
isLoadingFirstSeen = false;
mempoolStats: OptimizedMempoolStats[] = null;
featuresEnabled: boolean;
segwitEnabled: boolean;
@@ -196,7 +197,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
});
}
this.websocketService.want(['blocks', 'mempool-blocks']);
this.websocketService.want(['blocks', 'mempool-blocks', 'live-2h-chart']);
this.stateService.networkChanged$.subscribe(
(network) => {
this.network = network;
@@ -769,8 +770,20 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.stateService.difficultyAdjustment$.pipe(startWith(null)),
this.isAccelerated$,
this.txChanged$,
this.apiService.list2HStatistics$(),
this.stateService.live2Chart$.pipe(startWith(null)),
]).pipe(
map(([position, mempoolBlocks, da, isAccelerated]) => {
map(([position, mempoolBlocks, da, isAccelerated, _, mempoolStats, mempoolStat]) => {
if (this.mempoolStats === null) {
this.mempoolStats = mempoolStats;
}
if (this.mempoolStats.length && mempoolStat && this.mempoolStats[0].added !== mempoolStat.added) {
this.mempoolStats.pop();
this.mempoolStats.unshift(mempoolStat);
}
return this.etaService.calculateETA(
this.network,
this.tx,
@@ -780,6 +793,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.miningStats,
isAccelerated,
this.accelerationPositions,
this.mempoolStats,
);
})
);
@@ -977,6 +991,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.isAcceleration = false;
this.isAccelerated$.next(this.isAcceleration);
this.eligibleForAcceleration = false;
this.mempoolStats = null;
this.leaveTransaction();
}

View File

@@ -3670,39 +3670,6 @@ export const restApiDocsData = [
}
}
},
{
type: "endpoint",
category: "mining",
httpRequestMethod: "GET",
fragment: "get-difficulty-adjustment-by-height",
title: "GET Difficulty Adjustment",
description: {
default: "<p>Returns difficulty adjustment for the block at the specified <code>:blockHeight</code>. If no adjustment happened at that height, an empty response is returned.</p>"
},
urlString: "/v1/mining/difficulty-adjustment/:blockHeight",
showConditions: [""],
showJsExamples: showJsExamplesDefaultFalse,
codeExample: {
default: {
codeTemplate: {
curl: `/api/v1/mining/difficulty-adjustment/%{1}`,
commonJS: ``,
esModule: ``
},
codeSampleMainnet: {
esModule: [],
commonJS: [],
curl: [`756000`],
response: `{
"time": "2022-09-28T02:56:34.000Z",
"height": 756000,
"difficulty": 31360548173144.9,
"adjustment": 0.97863
}`
},
}
}
},
{
type: "endpoint",
category: "mining",

View File

@@ -7,6 +7,7 @@ export interface OptimizedMempoolStats {
total_fee: number;
mempool_byte_weight: number;
vsizes: number[];
vsizes_ps: number[];
}
interface Ancestor {

View File

@@ -315,12 +315,6 @@ export class ApiService {
);
}
getDifficultyAdjustmentByHeight$(height: number): Observable<any> {
return this.httpClient.get<any>(
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/difficulty-adjustment/${height}`
);
}
getHistoricalHashrate$(interval: string | undefined): Observable<any> {
return this.httpClient.get<any[]>(
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/hashrate` +

View File

@@ -23,7 +23,6 @@ export class CacheService {
blockLoading: { [height: number]: boolean } = {};
copiesInBlockQueue: { [height: number]: number } = {};
blockPriorities: number[] = [];
daCache: { [height: number]: { adjustment: number, exact?: boolean } } = {};
constructor(
private stateService: StateService,
@@ -129,7 +128,6 @@ export class CacheService {
this.blockLoading = {};
this.copiesInBlockQueue = {};
this.blockPriorities = [];
this.daCache = {};
}
getCachedBlock(height) {

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { AccelerationPosition, CpfpInfo, DifficultyAdjustment, MempoolPosition, SinglePoolStats } from '../interfaces/node-api.interface';
import { AccelerationPosition, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, SinglePoolStats } from '../interfaces/node-api.interface';
import { StateService } from './state.service';
import { MempoolBlock } from '../interfaces/websocket.interface';
import { Transaction } from '../interfaces/electrs.interface';
@@ -7,6 +7,7 @@ import { MiningService, MiningStats } from './mining.service';
import { getUnacceleratedFeeRate } from '../shared/transaction.utils';
import { AccelerationEstimate } from '../components/accelerate-checkout/accelerate-checkout.component';
import { Observable, combineLatest, map, of, share, shareReplay, tap } from 'rxjs';
import { feeLevels } from '../app.constants';
export interface ETA {
now: number, // time at which calculation performed
@@ -113,6 +114,7 @@ export class EtaService {
miningStats: MiningStats,
isAccelerated: boolean,
accelerationPositions: AccelerationPosition[],
mempoolStats: OptimizedMempoolStats[] = [],
): ETA | null {
// return this.calculateETA(tx, this.accelerationPositions, position, mempoolBlocks, da, isAccelerated)
if (!tx || !mempoolBlocks) {
@@ -143,7 +145,17 @@ export class EtaService {
if (!isAccelerated) {
const blocks = mempoolPosition.block + 1;
const wait = da.adjustedTimeAvg * (mempoolPosition.block + 1);
const vsizeAhead = mempoolPosition.vsize + this.stateService.blockVSize * mempoolPosition.block;
const incomingVsizePerBlock = this.estimateVsizePerSecond(tx, mempoolStats) * da.adjustedTimeAvg / 1000;
const vsizeConsumedPerBlock = Math.max(
this.stateService.blockVSize - incomingVsizePerBlock,
0.05 * this.stateService.blockVSize // So that we don't return infinite ETA
)
const blocksUntilMined = Math.ceil(vsizeAhead / vsizeConsumedPerBlock);
const wait = blocksUntilMined * da.adjustedTimeAvg;
return {
now,
time: wait + now + da.timeOffset,
@@ -279,4 +291,46 @@ export class EtaService {
return tx.fee / (tx.weight / 4);
}
estimateVsizePerSecond(tx: Transaction, mempoolStats: OptimizedMempoolStats[], timeWindow: number = 15 * 60 * 1000): number {
const nowMinusTimeSpan = (new Date().getTime() - timeWindow) / 1000;
const vsizeAboveTransaction = mempoolStats
// Remove datapoints older than now - timeWindow
.filter(stat => stat.added > nowMinusTimeSpan)
// Remove datapoints less than 45 seconds apart from the previous one
.filter((el, i, arr) => {
if (i === 0) {
return true;
}
return arr[i - 1].added - el.added > 45;
})
// For each datapoint, compute the total vsize of transactions with higher fee rate
.map(stat => {
let vsizeAbove = 0;
for (let i = feeLevels.length - 1; i >= 0; i--) {
if (feeLevels[i] > tx.effectiveFeePerVsize) {
vsizeAbove += stat.vsizes_ps[i];
} else {
break;
}
}
return vsizeAbove;
});
// vsizeAboveTransaction is a temporal series of past vsize values above the transaction's fee rate
// From this array we need to estimate the future vsize per second
// Naive first approach: take the median of the series
if (!vsizeAboveTransaction.length) {
return 0;
}
const sorted = Array.from(vsizeAboveTransaction).sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);
if (sorted.length % 2 === 0) {
return (sorted[middle - 1] + sorted[middle]) / 2;
}
return sorted[middle];
}
}

View File

@@ -240,25 +240,3 @@ export function md5(inputString): string {
}
return rh(a)+rh(b)+rh(c)+rh(d);
}
export function colorFromRetarget(da: number): string {
const minDA = 0.95;
const maxDA = 1.05;
const midDA = 1;
const red = { r: 220, g: 53, b: 69 };
const grey = { r: 108, g: 117, b: 125 };
const green = { r: 59, g: 204, b: 73 };
const interpolateColor = (color1, color2, ratio) => {
ratio = Math.min(1, Math.max(0, ratio));
const r = Math.round(color1.r + ratio * (color2.r - color1.r));
const g = Math.round(color1.g + ratio * (color2.g - color1.g));
const b = Math.round(color1.b + ratio * (color2.b - color1.b));
return `rgba(${r}, ${g}, ${b}, 0.7)`;
}
return da <= midDA ?
interpolateColor(red, grey, (da - minDA) / (midDA - minDA)) :
interpolateColor(grey, green, (da - midDA) / (maxDA - midDA));
}

View File

@@ -4,7 +4,7 @@ import { NgbCollapseModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstra
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle,
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faClock, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown,
faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline, faCircleXmark} from '@fortawesome/free-solid-svg-icons';
faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline, faCircleXmark, faCalendarCheck } from '@fortawesome/free-solid-svg-icons';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { MenuComponent } from '../components/menu/menu.component';
import { PreviewTitleComponent } from '../components/master-page-preview/preview-title.component';
@@ -440,5 +440,6 @@ export class SharedModule {
library.addIcons(faFaucetDrip);
library.addIcons(faTimeline);
library.addIcons(faCircleXmark);
library.addIcons(faCalendarCheck);
}
}

View File

@@ -0,0 +1,3 @@
<svg width="212" height="450" viewBox="0 0 212 450" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M114.005 2.14659C109.052 -0.71553 102.948 -0.715531 97.9948 2.14659L7.99481 54.1531C3.04732 57.012 0 62.2924 0 68.0065V172.043C0 177.758 3.04733 183.038 7.99482 185.897L69.0247 221.163C73.9722 224.022 77.0195 229.302 77.0195 235.016V433.998V437.998C77.0195 444.625 82.3921 449.998 89.0195 449.998H93.0195H118.981H123.031C129.658 449.998 135.031 444.625 135.031 437.998V233.751C135.444 228.531 138.395 223.809 142.975 221.163L204.005 185.897C208.953 183.038 212 177.758 212 172.043V68.0065C212 62.2924 208.953 57.012 204.005 54.1531L114.005 2.14659ZM112.07 68.1162C108.334 65.938 103.716 65.938 99.9803 68.1162L63.9718 89.1129C60.2841 91.2631 58.0164 95.2105 58.0164 99.4793V141.68C58.0164 145.948 60.2841 149.896 63.9718 152.046L99.9803 173.043C103.716 175.221 108.334 175.221 112.07 173.043L148.078 152.046C151.766 149.896 154.034 145.948 154.034 141.68V99.4793C154.034 95.2105 151.766 91.2631 148.078 89.1129L112.07 68.1162ZM172.046 338.527C164.047 333.861 154 339.631 154 348.892L154 356L154 412V419.108C154 428.369 164.047 434.14 172.046 429.473L196.046 415.473C199.733 413.322 202 409.376 202 405.108V362.892C202 358.624 199.733 354.678 196.046 352.527L172.046 338.527Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,3 @@
<svg width="387" height="377" viewBox="-58.05 -56.55 503.1 490.1" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M249.327 137.966C283.745 132.618 333.52 96.2553 333.52 67.9135C333.52 59.3574 326.637 53.4752 316.576 53.4752C297.513 53.4752 265.212 82.3518 249.327 137.966ZM89.9409 278.606C44.9317 278.606 41.225 323.525 86.2343 323.525C106.356 323.525 130.714 315.504 143.422 301.065C124.889 285.023 109.533 278.606 89.9409 278.606ZM376.411 259.355C379.059 334.755 340.934 377 276.332 377C238.207 377 219.144 362.562 178.371 335.824C157.19 359.353 116.946 377 83.5867 377C-31.3192 377 -26.5536 229.943 90.4704 229.943C114.828 229.943 135.48 236.36 161.956 252.938L179.43 191.441C107.415 171.655 71.4077 116.041 106.886 36.3631H164.074C132.303 89.3035 154.013 133.153 194.256 137.966C215.967 60.4269 262.565 0 324.518 0C359.467 0 387.002 22.9943 387.002 64.705C387.002 131.549 300.161 186.094 234.5 191.441L207.494 287.162C238.207 322.99 323.459 357.749 323.459 259.355H376.411Z" fill="#F5F1ED"/>
</svg>

After

Width:  |  Height:  |  Size: 1011 B

View File

@@ -392,9 +392,9 @@ DEBIAN_UNFURL_PKG+=(libxdamage-dev libxrandr-dev libgbm-dev libpango1.0-dev liba
# packages needed for mempool ecosystem
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+=(openssh-portable py311-pip rust llvm18 jq base64 libzmq4)
FREEBSD_PKG+=(boost-libs autoconf automake gmake gcc libevent libtool pkgconf)
FREEBSD_PKG+=(nginx rsync py39-certbot-nginx mariadb1011-server keybase)
FREEBSD_PKG+=(nginx rsync py311-certbot-nginx mariadb1011-server)
FREEBSD_PKG+=(geoipupdate redis)
FREEBSD_UNFURL_PKG=()