Add expected weight to audit table
This commit is contained in:
		
							parent
							
								
									aedaf53137
								
							
						
					
					
						commit
						3013386ca5
					
				@ -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);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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) {
 | 
				
			||||||
 | 
				
			|||||||
@ -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`
 | 
				
			||||||
 | 
				
			|||||||
@ -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]);
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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]="'‎' + (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]="'‎' + (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>
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
				
			|||||||
@ -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[],
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user