Add expected weight to audit table

This commit is contained in:
Mononaut 2023-06-09 13:46:14 -04:00
parent aedaf53137
commit 3013386ca5
No known key found for this signature in database
GPG key ID: A3F058E41374C04E
9 changed files with 98 additions and 22 deletions

View file

@ -534,7 +534,8 @@ class DatabaseMigration {
} }
if (databaseSchemaVersion < 62 && isBitcoin === true) { if (databaseSchemaVersion < 62 && isBitcoin === true) {
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD expected_fees BIGINT UNSIGNED NOT NULL DEFAULT "0"'); await this.$executeQuery('ALTER TABLE `blocks_audits` ADD expected_fees BIGINT UNSIGNED DEFAULT NULL');
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD expected_weight BIGINT UNSIGNED DEFAULT NULL');
await this.updateToSchemaVersion(62); await this.updateToSchemaVersion(62);
} }

View file

@ -559,8 +559,6 @@ class WebsocketHandler {
} }
if (Common.indexingEnabled() && memPool.isInSync()) { if (Common.indexingEnabled() && memPool.isInSync()) {
logger.debug(`Auditing block ${block.height} (${block.id})`);
const { censored, added, fresh, sigop, score, similarity } = Audit.auditBlock(transactions, projectedBlocks, auditMempool); const { censored, added, fresh, sigop, score, similarity } = Audit.auditBlock(transactions, projectedBlocks, auditMempool);
const matchRate = Math.round(score * 100 * 100) / 100; const matchRate = Math.round(score * 100 * 100) / 100;
@ -573,8 +571,12 @@ class WebsocketHandler {
}; };
}) : []; }) : [];
const totalFees = stripped.reduce((total, transaction) => total + transaction.fee, 0); let totalFees = 0;
logger.debug(`Projected block fees: ${totalFees} sats`); let totalWeight = 0;
for (const tx of stripped) {
totalFees += tx.fee;
totalWeight += (tx.vsize * 4);
}
BlocksSummariesRepository.$saveTemplate({ BlocksSummariesRepository.$saveTemplate({
height: block.height, height: block.height,
@ -593,7 +595,8 @@ class WebsocketHandler {
freshTxs: fresh, freshTxs: fresh,
sigopTxs: sigop, sigopTxs: sigop,
matchRate: matchRate, matchRate: matchRate,
expectedFees: totalFees expectedFees: totalFees,
expectedWeight: totalWeight,
}); });
if (block.extras) { if (block.extras) {

View file

@ -35,13 +35,15 @@ export interface BlockAudit {
sigopTxs: string[], sigopTxs: string[],
addedTxs: string[], addedTxs: string[],
matchRate: number, matchRate: number,
expectedFees: number; expectedFees?: number,
expectedWeight?: number,
} }
export interface AuditScore { export interface AuditScore {
hash: string, hash: string,
matchRate?: number, matchRate?: number,
expectedFees?: number expectedFees?: number
expectedWeight?: number
} }
export interface MempoolBlock { export interface MempoolBlock {
@ -185,6 +187,7 @@ export interface BlockExtension {
reward: number; reward: number;
matchRate: number | null; matchRate: number | null;
expectedFees: number | null; expectedFees: number | null;
expectedWeight: number | null;
similarity?: number; similarity?: number;
pool: { pool: {
id: number; // Note - This is the `unique_id`, not to mix with the auto increment `id` id: number; // Note - This is the `unique_id`, not to mix with the auto increment `id`

View file

@ -6,9 +6,9 @@ import { BlockAudit, AuditScore } from '../mempool.interfaces';
class BlocksAuditRepositories { class BlocksAuditRepositories {
public async $saveAudit(audit: BlockAudit): Promise<void> { public async $saveAudit(audit: BlockAudit): Promise<void> {
try { try {
await DB.query(`INSERT INTO blocks_audits(time, height, hash, missing_txs, added_txs, fresh_txs, sigop_txs, match_rate, expected_fees) await DB.query(`INSERT INTO blocks_audits(time, height, hash, missing_txs, added_txs, fresh_txs, sigop_txs, match_rate, expected_fees, expected_weight)
VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?, ?)`, [audit.time, audit.height, audit.hash, JSON.stringify(audit.missingTxs), VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [audit.time, audit.height, audit.hash, JSON.stringify(audit.missingTxs),
JSON.stringify(audit.addedTxs), JSON.stringify(audit.freshTxs), JSON.stringify(audit.sigopTxs), audit.matchRate, audit.expectedFees]); JSON.stringify(audit.addedTxs), JSON.stringify(audit.freshTxs), JSON.stringify(audit.sigopTxs), audit.matchRate, audit.expectedFees, audit.expectedWeight]);
} catch (e: any) { } catch (e: any) {
if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
logger.debug(`Cannot save block audit for block ${audit.hash} because it has already been indexed, ignoring`); logger.debug(`Cannot save block audit for block ${audit.hash} because it has already been indexed, ignoring`);
@ -51,7 +51,15 @@ class BlocksAuditRepositories {
const [rows]: any[] = await DB.query( const [rows]: any[] = await DB.query(
`SELECT blocks.height, blocks.hash as id, UNIX_TIMESTAMP(blocks.blockTimestamp) as timestamp, blocks.size, `SELECT blocks.height, blocks.hash as id, UNIX_TIMESTAMP(blocks.blockTimestamp) as timestamp, blocks.size,
blocks.weight, blocks.tx_count, blocks.weight, blocks.tx_count,
transactions, template, missing_txs as missingTxs, added_txs as addedTxs, fresh_txs as freshTxs, sigop_txs as sigopTxs, match_rate as matchRate, expected_fees as expectedFees transactions,
template,
missing_txs as missingTxs,
added_txs as addedTxs,
fresh_txs as freshTxs,
sigop_txs as sigopTxs,
match_rate as matchRate,
expected_fees as expectedFees,
expected_weight as expectedWeight
FROM blocks_audits FROM blocks_audits
JOIN blocks ON blocks.hash = blocks_audits.hash JOIN blocks ON blocks.hash = blocks_audits.hash
JOIN blocks_templates ON blocks_templates.id = blocks_audits.hash JOIN blocks_templates ON blocks_templates.id = blocks_audits.hash
@ -81,7 +89,7 @@ class BlocksAuditRepositories {
public async $getBlockAuditScore(hash: string): Promise<AuditScore> { public async $getBlockAuditScore(hash: string): Promise<AuditScore> {
try { try {
const [rows]: any[] = await DB.query( const [rows]: any[] = await DB.query(
`SELECT hash, match_rate as matchRate, expected_fees as expectedFees `SELECT hash, match_rate as matchRate, expected_fees as expectedFees, expected_weight as expectedWeight
FROM blocks_audits FROM blocks_audits
WHERE blocks_audits.hash = "${hash}" WHERE blocks_audits.hash = "${hash}"
`); `);
@ -95,7 +103,7 @@ class BlocksAuditRepositories {
public async $getBlockAuditScores(maxHeight: number, minHeight: number): Promise<AuditScore[]> { public async $getBlockAuditScores(maxHeight: number, minHeight: number): Promise<AuditScore[]> {
try { try {
const [rows]: any[] = await DB.query( const [rows]: any[] = await DB.query(
`SELECT hash, match_rate as matchRate, expected_fees as expectedFees `SELECT hash, match_rate as matchRate, expected_fees as expectedFees, expected_weight as expectedWeight
FROM blocks_audits FROM blocks_audits
WHERE blocks_audits.height BETWEEN ? AND ? WHERE blocks_audits.height BETWEEN ? AND ?
`, [minHeight, maxHeight]); `, [minHeight, maxHeight]);

View file

@ -1038,6 +1038,7 @@ class BlocksRepository {
if (auditScore != null) { if (auditScore != null) {
extras.matchRate = auditScore.matchRate; extras.matchRate = auditScore.matchRate;
extras.expectedFees = auditScore.expectedFees; extras.expectedFees = auditScore.expectedFees;
extras.expectedWeight = auditScore.expectedWeight;
} }
} }

View file

@ -72,15 +72,6 @@
</ng-template> </ng-template>
</td> </td>
</tr> </tr>
<tr *ngIf="auditAvailable">
<td i18n="latest-blocks.expected_fees">Expected total fees</td>
<td>
<app-amount [satoshis]="blockAudit?.expectedFees" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
<span class="fiat">
<app-fiat [blockConversion]="blockConversion" [value]="blockAudit?.expectedFees" digitsInfo="1.0-0"></app-fiat>
</span>
</td>
</tr>
</ng-container> </ng-container>
<ng-template #skeletonRows> <ng-template #skeletonRows>
<tr> <tr>
@ -235,6 +226,9 @@
(txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="!isMobile && !showAudit"></app-block-overview-graph> (txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="!isMobile && !showAudit"></app-block-overview-graph>
<ng-container *ngIf="!isMobile || mode !== 'actual'; else emptyBlockInfo"></ng-container> <ng-container *ngIf="!isMobile || mode !== 'actual'; else emptyBlockInfo"></ng-container>
</div> </div>
<ng-container *ngIf="network !== 'liquid'">
<ng-container *ngTemplateOutlet="isMobile && mode === 'actual' ? actualDetails : expectedDetails"></ng-container>
</ng-container>
</div> </div>
<div class="col-sm" *ngIf="!isMobile"> <div class="col-sm" *ngIf="!isMobile">
<h3 class="block-subtitle actual" *ngIf="!isMobile"><ng-container i18n="block.actual-block">Actual Block</ng-container> <a class="info-link" [routerLink]="['/docs/faq' | relativeUrl ]" fragment="how-do-block-audits-work"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></a></h3> <h3 class="block-subtitle actual" *ngIf="!isMobile"><ng-container i18n="block.actual-block">Actual Block</ng-container> <a class="info-link" [routerLink]="['/docs/faq' | relativeUrl ]" fragment="how-do-block-audits-work"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></a></h3>
@ -244,6 +238,9 @@
(txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="isMobile && !showAudit"></app-block-overview-graph> (txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="isMobile && !showAudit"></app-block-overview-graph>
<ng-container *ngTemplateOutlet="emptyBlockInfo"></ng-container> <ng-container *ngTemplateOutlet="emptyBlockInfo"></ng-container>
</div> </div>
<ng-container *ngIf="network !== 'liquid'">
<ng-container *ngTemplateOutlet="actualDetails"></ng-container>
</ng-container>
</div> </div>
</div> </div>
</div> </div>
@ -394,5 +391,54 @@
</a> </a>
</ng-template> </ng-template>
<ng-template #expectedDetails>
<table *ngIf="block && blockAudit" class="table table-borderless table-striped audit-details-table">
<tbody>
<tr>
<td i18n="block.expected-total-fees|Expected total fees in a block">Expected fees</td>
<td>
<app-amount [satoshis]="blockAudit.expectedFees" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
</td>
</tr>
<tr>
<td i18n="block.expected-weight">Expected weight</td>
<td [innerHTML]="'&lrm;' + (blockAudit.expectedWeight | wuBytes: 2)"></td>
</tr>
<tr>
<td i18n="block.expected_transaction_count">Expected transactions</td>
<td>{{ blockAudit.template?.length || 0 }}</td>
</tr>
</tbody>
</table>
</ng-template>
<ng-template #actualDetails>
<table *ngIf="block && blockAudit" class="table table-borderless table-striped audit-details-table">
<tbody>
<tr>
<td i18n="block.actual-total-fees|Actual total fees in a block">Actual fees</td>
<td>
<app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
<span *ngIf="blockAudit.feeDelta" class="difference" [class.positive]="blockAudit.feeDelta <= 0" [class.negative]="blockAudit.feeDelta > 0">
{{ blockAudit.feeDelta < 0 ? '+' : '' }}{{ (-blockAudit.feeDelta * 100) | amountShortener: 2 }}%
</span>
</td>
</tr>
<tr>
<td i18n="block.actual-weight">Actual weight</td>
<td [innerHTML]>
<span [innerHTML]="'&lrm;' + (block.weight | wuBytes: 2)"></span>
</td>
</tr>
<tr>
<td i18n="block.actual_transaction_count">Actual transactions</td>
<td>
{{ block.tx_count }}
</td>
</tr>
</tbody>
</table>
</ng-template>
<br> <br>
<br> <br>

View file

@ -38,6 +38,17 @@
color: rgba(255, 255, 255, 0.4); color: rgba(255, 255, 255, 0.4);
margin-left: 5px; margin-left: 5px;
} }
.difference {
margin-left: 0.5em;
&.positive {
color: rgb(66, 183, 71);
}
&.negative {
color: rgb(183, 66, 66);
}
}
} }
} }

View file

@ -388,6 +388,7 @@ export class BlockComponent implements OnInit, OnDestroy {
for (const tx of blockAudit.transactions) { for (const tx of blockAudit.transactions) {
inBlock[tx.txid] = true; inBlock[tx.txid] = true;
} }
blockAudit.feeDelta = (blockAudit.expectedFees - this.block.extras.totalFees) / blockAudit.expectedFees;
this.setAuditAvailable(true); this.setAuditAvailable(true);
} else { } else {
this.setAuditAvailable(false); this.setAuditAvailable(false);

View file

@ -151,6 +151,8 @@ export interface BlockAudit extends BlockExtended {
addedTxs: string[], addedTxs: string[],
matchRate: number, matchRate: number,
expectedFees: number, expectedFees: number,
expectedWeight: number,
feeDelta?: number,
template: TransactionStripped[], template: TransactionStripped[],
transactions: TransactionStripped[], transactions: TransactionStripped[],
} }