Compare commits
2 Commits
master
...
mononaut/m
Author | SHA1 | Date | |
---|---|---|---|
|
e2a5b90b38 | ||
|
ccc127c84a |
@ -6,6 +6,8 @@ import config from '../config';
|
|||||||
import { Worker } from 'worker_threads';
|
import { Worker } from 'worker_threads';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import mempool from './mempool';
|
import mempool from './mempool';
|
||||||
|
import { Acceleration } from './services/acceleration';
|
||||||
|
import PoolsRepository from '../repositories/PoolsRepository';
|
||||||
|
|
||||||
const MAX_UINT32 = Math.pow(2, 32) - 1;
|
const MAX_UINT32 = Math.pow(2, 32) - 1;
|
||||||
|
|
||||||
@ -19,6 +21,17 @@ class MempoolBlocks {
|
|||||||
private nextUid: number = 1;
|
private nextUid: number = 1;
|
||||||
private uidMap: Map<number, string> = new Map(); // map short numerical uids to full txids
|
private uidMap: Map<number, string> = new Map(); // map short numerical uids to full txids
|
||||||
|
|
||||||
|
private pools: { [id: number]: PoolTag } = {};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
PoolsRepository.$getPools().then(allPools => {
|
||||||
|
this.pools = {};
|
||||||
|
for (const pool of allPools) {
|
||||||
|
this.pools[pool.uniqueId] = pool;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public getMempoolBlocks(): MempoolBlock[] {
|
public getMempoolBlocks(): MempoolBlock[] {
|
||||||
return this.mempoolBlocks.map((block) => {
|
return this.mempoolBlocks.map((block) => {
|
||||||
return {
|
return {
|
||||||
@ -452,7 +465,7 @@ class MempoolBlocks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private processBlockTemplates(mempool: { [txid: string]: MempoolTransactionExtended }, blocks: string[][], blockWeights: number[] | null, rates: [string, number][], clusters: string[][], accelerations, accelerationPool, saveResults): MempoolBlockWithTransactions[] {
|
private processBlockTemplates(mempool: { [txid: string]: MempoolTransactionExtended }, blocks: string[][], blockWeights: number[] | null, rates: [string, number][], clusters: string[][], accelerations: { [txid: string]: Acceleration }, accelerationPool, saveResults): MempoolBlockWithTransactions[] {
|
||||||
for (const [txid, rate] of rates) {
|
for (const [txid, rate] of rates) {
|
||||||
if (txid in mempool) {
|
if (txid in mempool) {
|
||||||
mempool[txid].cpfpDirty = (rate !== mempool[txid].effectiveFeePerVsize);
|
mempool[txid].cpfpDirty = (rate !== mempool[txid].effectiveFeePerVsize);
|
||||||
@ -586,7 +599,7 @@ class MempoolBlocks {
|
|||||||
const deltas = this.calculateMempoolDeltas(this.mempoolBlocks, mempoolBlocks);
|
const deltas = this.calculateMempoolDeltas(this.mempoolBlocks, mempoolBlocks);
|
||||||
this.mempoolBlocks = mempoolBlocks;
|
this.mempoolBlocks = mempoolBlocks;
|
||||||
this.mempoolBlockDeltas = deltas;
|
this.mempoolBlockDeltas = deltas;
|
||||||
|
this.updateAccelerationPositions(mempool, accelerations, mempoolBlocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
return mempoolBlocks;
|
return mempoolBlocks;
|
||||||
@ -691,6 +704,124 @@ class MempoolBlocks {
|
|||||||
});
|
});
|
||||||
return { blocks: convertedBlocks, blockWeights, rates: convertedRates, clusters: convertedClusters, overflow: convertedOverflow };
|
return { blocks: convertedBlocks, blockWeights, rates: convertedRates, clusters: convertedClusters, overflow: convertedOverflow };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// estimates and saves positions of accelerations in mining partner mempools
|
||||||
|
private updateAccelerationPositions(mempoolCache: { [txid: string]: MempoolTransactionExtended }, accelerations: { [txid: string]: Acceleration }, mempoolBlocks: MempoolBlockWithTransactions[]): void {
|
||||||
|
const accelerationPositions: { [txid: string]: { poolId: number, pool: string, block: number, vsize: number }[] } = {};
|
||||||
|
// keep track of simulated mempool blocks for each active pool
|
||||||
|
const pools: {
|
||||||
|
[pool: string]: { name: string, block: number, vsize: number, accelerations: string[], complete: boolean };
|
||||||
|
} = {};
|
||||||
|
// prepare a list of accelerations in ascending order (we'll pop items off the end of the list)
|
||||||
|
const accQueue: { acceleration: Acceleration, rate: number, vsize: number }[] = Object.values(accelerations).map(acc => {
|
||||||
|
let vsize = mempoolCache[acc.txid].vsize;
|
||||||
|
for (const ancestor of mempoolCache[acc.txid].ancestors || []) {
|
||||||
|
vsize += (ancestor.weight / 4);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
acceleration: acc,
|
||||||
|
rate: mempoolCache[acc.txid].effectiveFeePerVsize,
|
||||||
|
vsize
|
||||||
|
};
|
||||||
|
}).sort((a, b) => a.rate - b.rate);
|
||||||
|
// initialize the pool tracker
|
||||||
|
for (const { acceleration } of accQueue) {
|
||||||
|
accelerationPositions[acceleration.txid] = [];
|
||||||
|
for (const pool of acceleration.pools) {
|
||||||
|
if (!pools[pool]) {
|
||||||
|
pools[pool] = {
|
||||||
|
name: this.pools[pool]?.name || 'unknown',
|
||||||
|
block: 0,
|
||||||
|
vsize: 0,
|
||||||
|
accelerations: [],
|
||||||
|
complete: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pools[pool].accelerations.push(acceleration.txid);
|
||||||
|
}
|
||||||
|
for (const ancestor of mempoolCache[acceleration.txid].ancestors || []) {
|
||||||
|
accelerationPositions[ancestor.txid] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const pool of Object.keys(pools)) {
|
||||||
|
// if any pools accepted *every* acceleration, we can just use the GBT result positions directly
|
||||||
|
if (pools[pool].accelerations.length === Object.keys(accelerations).length) {
|
||||||
|
pools[pool].complete = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let block = 0;
|
||||||
|
let index = 0;
|
||||||
|
let next = accQueue.pop();
|
||||||
|
// build simulated blocks for each pool by taking the best option from
|
||||||
|
// either the mempool or the list of accelerations.
|
||||||
|
while (next && block < mempoolBlocks.length) {
|
||||||
|
while (next && index < mempoolBlocks[block].transactions.length) {
|
||||||
|
const nextTx = mempoolBlocks[block].transactions[index];
|
||||||
|
if (next.rate >= (nextTx.rate || (nextTx.fee / nextTx.vsize))) {
|
||||||
|
for (const pool of next.acceleration.pools) {
|
||||||
|
if (pools[pool].vsize + next.vsize <= 999_000) {
|
||||||
|
pools[pool].vsize += next.vsize;
|
||||||
|
} else {
|
||||||
|
pools[pool].block++;
|
||||||
|
pools[pool].vsize = next.vsize;
|
||||||
|
}
|
||||||
|
// insert the acceleration into matching pool's blocks
|
||||||
|
if (pools[pool].complete && mempoolCache[next.acceleration.txid]?.position !== undefined) {
|
||||||
|
accelerationPositions[next.acceleration.txid].push({
|
||||||
|
...mempoolCache[next.acceleration.txid].position as { block: number, vsize: number },
|
||||||
|
poolId: pool,
|
||||||
|
pool: pools[pool].name
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
accelerationPositions[next.acceleration.txid].push({
|
||||||
|
poolId: pool,
|
||||||
|
pool: pools[pool].name,
|
||||||
|
block: pools[pool].block,
|
||||||
|
vsize: pools[pool].vsize - (next.vsize / 2),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// and any accelerated ancestors
|
||||||
|
for (const ancestor of mempoolCache[next.acceleration.txid].ancestors || []) {
|
||||||
|
if (pools[pool].complete && mempoolCache[ancestor.txid]?.position !== undefined) {
|
||||||
|
accelerationPositions[ancestor.txid].push({
|
||||||
|
...mempoolCache[ancestor.txid].position as { block: number, vsize: number },
|
||||||
|
poolId: pool,
|
||||||
|
pool: pools[pool].name,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
accelerationPositions[ancestor.txid].push({
|
||||||
|
poolId: pool,
|
||||||
|
pool: pools[pool].name,
|
||||||
|
block: pools[pool].block,
|
||||||
|
vsize: pools[pool].vsize - (next.vsize / 2),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next = accQueue.pop();
|
||||||
|
} else {
|
||||||
|
// skip accelerated transactions and their CPFP ancestors
|
||||||
|
if (accelerationPositions[nextTx.txid] == null) {
|
||||||
|
// insert into all pools' blocks
|
||||||
|
for (const pool of Object.keys(pools)) {
|
||||||
|
if (pools[pool].vsize + nextTx.vsize <= 999_000) {
|
||||||
|
pools[pool].vsize += nextTx.vsize;
|
||||||
|
} else {
|
||||||
|
pools[pool].block++;
|
||||||
|
pools[pool].vsize = nextTx.vsize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
block++;
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
mempool.setAccelerationPositions(accelerationPositions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new MempoolBlocks();
|
export default new MempoolBlocks();
|
||||||
|
@ -25,6 +25,7 @@ class Mempool {
|
|||||||
deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[]) => Promise<void>) | undefined;
|
deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[]) => Promise<void>) | undefined;
|
||||||
|
|
||||||
private accelerations: { [txId: string]: Acceleration } = {};
|
private accelerations: { [txId: string]: Acceleration } = {};
|
||||||
|
private accelerationPositions: { [txid: string]: { poolId: number, pool: string, block: number, vsize: number }[] } = {};
|
||||||
|
|
||||||
private txPerSecondArray: number[] = [];
|
private txPerSecondArray: number[] = [];
|
||||||
private txPerSecond: number = 0;
|
private txPerSecond: number = 0;
|
||||||
@ -431,6 +432,14 @@ class Mempool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setAccelerationPositions(positions: { [txid: string]: { poolId: number, pool: string, block: number, vsize: number }[] }): void {
|
||||||
|
this.accelerationPositions = positions;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccelerationPositions(txid: string): { [pool: number]: { poolId: number, pool: string, block: number, vsize: number } } | undefined {
|
||||||
|
return this.accelerationPositions[txid];
|
||||||
|
}
|
||||||
|
|
||||||
private startTimer() {
|
private startTimer() {
|
||||||
const state: any = {
|
const state: any = {
|
||||||
start: Date.now(),
|
start: Date.now(),
|
||||||
|
@ -7,6 +7,14 @@ export interface Acceleration {
|
|||||||
txid: string,
|
txid: string,
|
||||||
feeDelta: number,
|
feeDelta: number,
|
||||||
pools: number[],
|
pools: number[],
|
||||||
|
effectiveFee: number;
|
||||||
|
effectiveVsize: number;
|
||||||
|
positions?: {
|
||||||
|
[pool: number]: {
|
||||||
|
block: number,
|
||||||
|
vbytes: number,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
class AccelerationApi {
|
class AccelerationApi {
|
||||||
|
@ -192,7 +192,8 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
response['txPosition'] = JSON.stringify({
|
response['txPosition'] = JSON.stringify({
|
||||||
txid: trackTxid,
|
txid: trackTxid,
|
||||||
position
|
position,
|
||||||
|
accelerationPositions: memPool.getAccelerationPositions(tx.txid),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -674,7 +675,8 @@ class WebsocketHandler {
|
|||||||
position: {
|
position: {
|
||||||
...mempoolTx.position,
|
...mempoolTx.position,
|
||||||
accelerated: mempoolTx.acceleration || undefined,
|
accelerated: mempoolTx.acceleration || undefined,
|
||||||
}
|
},
|
||||||
|
accelerationPositions: memPool.getAccelerationPositions(mempoolTx.txid),
|
||||||
};
|
};
|
||||||
if (mempoolTx.cpfpDirty) {
|
if (mempoolTx.cpfpDirty) {
|
||||||
positionData['cpfp'] = {
|
positionData['cpfp'] = {
|
||||||
@ -684,7 +686,7 @@ class WebsocketHandler {
|
|||||||
effectiveFeePerVsize: mempoolTx.effectiveFeePerVsize || null,
|
effectiveFeePerVsize: mempoolTx.effectiveFeePerVsize || null,
|
||||||
sigops: mempoolTx.sigops,
|
sigops: mempoolTx.sigops,
|
||||||
adjustedVsize: mempoolTx.adjustedVsize,
|
adjustedVsize: mempoolTx.adjustedVsize,
|
||||||
acceleration: mempoolTx.acceleration
|
acceleration: mempoolTx.acceleration,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
response['txPosition'] = JSON.stringify(positionData);
|
response['txPosition'] = JSON.stringify(positionData);
|
||||||
@ -896,7 +898,8 @@ class WebsocketHandler {
|
|||||||
position: {
|
position: {
|
||||||
...mempoolTx.position,
|
...mempoolTx.position,
|
||||||
accelerated: mempoolTx.acceleration || undefined,
|
accelerated: mempoolTx.acceleration || undefined,
|
||||||
}
|
},
|
||||||
|
accelerationPositions: memPool.getAccelerationPositions(mempoolTx.txid),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="arrowVisible" id="arrow-up" [ngStyle]="{'right': rightPosition + 75 + 'px', transition: transition }" [class.blink]="txPosition?.accelerated"></div>
|
<div *ngIf="arrowVisible" id="arrow-up" [ngStyle]="{'right': rightPosition + 75 + 'px', transition: transition }"></div>
|
||||||
|
<ng-container *ngFor="let pool of accelerationPositions; trackBy: accTrackByFn">
|
||||||
|
<div class="acceleration-arrow blink" [ngStyle]="{'right': pool.offset + 75 + 'px', transition: 'background 2s, right 2s, transform 1s' }"></div>
|
||||||
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
@ -122,6 +122,17 @@
|
|||||||
border-bottom: 35px solid #FFF;
|
border-bottom: 35px solid #FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.acceleration-arrow {
|
||||||
|
position: relative;
|
||||||
|
right: 75px;
|
||||||
|
top: 105px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 35px solid transparent;
|
||||||
|
border-right: 35px solid transparent;
|
||||||
|
border-bottom: 35px solid #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
.blockLink {
|
.blockLink {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -154,7 +165,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:host-context(.rtl-layout) {
|
:host-context(.rtl-layout) {
|
||||||
#arrow-up {
|
#arrow-up, .acceleration-arrow {
|
||||||
transform: translateX(70px);
|
transform: translateX(70px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -172,8 +183,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.blink{
|
.blink{
|
||||||
width:400px;
|
|
||||||
height:400px;
|
|
||||||
border-bottom: 35px solid #FFF;
|
border-bottom: 35px solid #FFF;
|
||||||
animation: blink 0.2s infinite;
|
animation: blink 0.2s infinite;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import { feeLevels, mempoolFeeColors } from '../../app.constants';
|
|||||||
import { specialBlocks } from '../../app.constants';
|
import { specialBlocks } from '../../app.constants';
|
||||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { DifficultyAdjustment, MempoolPosition } from '../../interfaces/node-api.interface';
|
import { AccelerationPosition, DifficultyAdjustment, MempoolPosition } from '../../interfaces/node-api.interface';
|
||||||
import { animate, style, transition, trigger } from '@angular/animations';
|
import { animate, style, transition, trigger } from '@angular/animations';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -66,13 +66,19 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
blockPadding: number = 30;
|
blockPadding: number = 30;
|
||||||
containerOffset: number = 40;
|
containerOffset: number = 40;
|
||||||
arrowVisible = false;
|
arrowVisible = false;
|
||||||
|
accelerationArrow = true;
|
||||||
tabHidden = false;
|
tabHidden = false;
|
||||||
feeRounding = '1.0-0';
|
feeRounding = '1.0-0';
|
||||||
|
|
||||||
rightPosition = 0;
|
rightPosition = 0;
|
||||||
transition = 'background 2s, right 2s, transform 1s';
|
transition = 'background 2s, right 2s, transform 1s';
|
||||||
|
accelerationPositions: AccelerationPosition[] = [];
|
||||||
|
accTransition = 'background 2s, right 2s, transform 1s';
|
||||||
|
animatingAcceleration: boolean = false;
|
||||||
|
|
||||||
markIndex: number;
|
markIndex: number;
|
||||||
|
markedTxid: string;
|
||||||
|
|
||||||
txPosition: MempoolPosition;
|
txPosition: MempoolPosition;
|
||||||
txFeePerVSize: number;
|
txFeePerVSize: number;
|
||||||
|
|
||||||
@ -160,7 +166,6 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.now = Date.now();
|
this.now = Date.now();
|
||||||
|
|
||||||
this.updateMempoolBlockStyles();
|
this.updateMempoolBlockStyles();
|
||||||
this.calculateTransactionPosition();
|
|
||||||
|
|
||||||
return this.mempoolBlocks;
|
return this.mempoolBlocks;
|
||||||
}),
|
}),
|
||||||
@ -188,6 +193,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.markIndex = undefined;
|
this.markIndex = undefined;
|
||||||
this.txPosition = undefined;
|
this.txPosition = undefined;
|
||||||
this.txFeePerVSize = undefined;
|
this.txFeePerVSize = undefined;
|
||||||
|
this.accelerationPositions = [];
|
||||||
if (state.mempoolBlockIndex !== undefined) {
|
if (state.mempoolBlockIndex !== undefined) {
|
||||||
this.markIndex = state.mempoolBlockIndex;
|
this.markIndex = state.mempoolBlockIndex;
|
||||||
}
|
}
|
||||||
@ -197,7 +203,20 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
if (state.txFeePerVSize) {
|
if (state.txFeePerVSize) {
|
||||||
this.txFeePerVSize = state.txFeePerVSize;
|
this.txFeePerVSize = state.txFeePerVSize;
|
||||||
}
|
}
|
||||||
this.calculateTransactionPosition();
|
if (state.accelerationPositions) {
|
||||||
|
this.accelerationPositions = state.accelerationPositions;
|
||||||
|
}
|
||||||
|
if (this.txPosition && this.txPosition.accelerated) {
|
||||||
|
const newlyAccelerated = (!this.accelerationArrow && state.txid === this.markedTxid);
|
||||||
|
this.calculateTransactionPosition(true);
|
||||||
|
if (newlyAccelerated || !this.animatingAcceleration) {
|
||||||
|
this.calculateAccelerationPositions(newlyAccelerated);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.accelerationArrow = false;
|
||||||
|
this.calculateTransactionPosition();
|
||||||
|
}
|
||||||
|
this.markedTxid = state.txid;
|
||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -289,6 +308,10 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
return (block.isStack) ? `stack-${block.index}` : block.index;
|
return (block.isStack) ? `stack-${block.index}` : block.index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accTrackByFn(index: number, pool: AccelerationPosition) {
|
||||||
|
return pool.pool;
|
||||||
|
}
|
||||||
|
|
||||||
reduceEmptyBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
|
reduceEmptyBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
|
||||||
const innerWidth = this.containerWidth || (this.stateService.env.BASE_MODULE !== 'liquid' && window.innerWidth <= 767.98 ? window.innerWidth : window.innerWidth / 2);
|
const innerWidth = this.containerWidth || (this.stateService.env.BASE_MODULE !== 'liquid' && window.innerWidth <= 767.98 ? window.innerWidth : window.innerWidth / 2);
|
||||||
let blocksAmount = this.stateService.env.MEMPOOL_BLOCKS_AMOUNT;
|
let blocksAmount = this.stateService.env.MEMPOOL_BLOCKS_AMOUNT;
|
||||||
@ -389,7 +412,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateTransactionPosition() {
|
calculateTransactionPosition(fromFee: boolean = false) {
|
||||||
if ((!this.txPosition && !this.txFeePerVSize && (this.markIndex === undefined || this.markIndex === -1)) || !this.mempoolBlocks) {
|
if ((!this.txPosition && !this.txFeePerVSize && (this.markIndex === undefined || this.markIndex === -1)) || !this.mempoolBlocks) {
|
||||||
this.arrowVisible = false;
|
this.arrowVisible = false;
|
||||||
return;
|
return;
|
||||||
@ -408,7 +431,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
|
|
||||||
this.arrowVisible = true;
|
this.arrowVisible = true;
|
||||||
|
|
||||||
if (this.txPosition) {
|
if (this.txPosition && !fromFee) {
|
||||||
if (this.txPosition.block >= this.mempoolBlocks.length) {
|
if (this.txPosition.block >= this.mempoolBlocks.length) {
|
||||||
this.rightPosition = ((this.mempoolBlocks.length - 1) * (this.blockWidth + this.blockPadding)) + this.blockWidth;
|
this.rightPosition = ((this.mempoolBlocks.length - 1) * (this.blockWidth + this.blockPadding)) + this.blockWidth;
|
||||||
} else {
|
} else {
|
||||||
@ -418,9 +441,9 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let found = false;
|
let found = false;
|
||||||
for (let txInBlockIndex = 0; txInBlockIndex < this.mempoolBlocks.length && !found; txInBlockIndex++) {
|
for (let txInBlockIndex = this.mempoolBlocks.length - 1; txInBlockIndex >= 0 && !found; txInBlockIndex--) {
|
||||||
const block = this.mempoolBlocks[txInBlockIndex];
|
const block = this.mempoolBlocks[txInBlockIndex];
|
||||||
for (let i = 0; i < block.feeRange.length - 1 && !found; i++) {
|
for (let i = block.feeRange.length - 2; i >= 0 && !found; i--) {
|
||||||
if (this.txFeePerVSize < block.feeRange[i + 1] && this.txFeePerVSize >= block.feeRange[i]) {
|
if (this.txFeePerVSize < block.feeRange[i + 1] && this.txFeePerVSize >= block.feeRange[i]) {
|
||||||
const feeRangeIndex = i;
|
const feeRangeIndex = i;
|
||||||
const feeRangeChunkSize = 1 / (block.feeRange.length - 1);
|
const feeRangeChunkSize = 1 / (block.feeRange.length - 1);
|
||||||
@ -448,6 +471,39 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
calculateAccelerationPositions(animate: boolean = false) {
|
||||||
|
if (!this.accelerationPositions || !this.mempoolBlocks) {
|
||||||
|
this.accelerationArrow = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.accelerationArrow = true;
|
||||||
|
|
||||||
|
const applyPositions = () => {
|
||||||
|
for (const accelerationPosition of this.accelerationPositions) {
|
||||||
|
if (accelerationPosition.block >= this.mempoolBlocks.length) {
|
||||||
|
accelerationPosition.offset = ((this.mempoolBlocks.length - 1) * (this.blockWidth + this.blockPadding)) + this.blockWidth;
|
||||||
|
} else {
|
||||||
|
const positionInBlock = Math.min(1, this.txPosition.vsize / this.stateService.blockVSize) * this.blockWidth;
|
||||||
|
const positionOfBlock = accelerationPosition.block * (this.blockWidth + this.blockPadding);
|
||||||
|
accelerationPosition.offset = positionOfBlock + positionInBlock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (animate) {
|
||||||
|
this.animatingAcceleration = true;
|
||||||
|
for (const accelerationPosition of this.accelerationPositions) {
|
||||||
|
accelerationPosition.offset = this.rightPosition;
|
||||||
|
}
|
||||||
|
setTimeout(applyPositions, 100);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.animatingAcceleration = false;
|
||||||
|
}, 200);
|
||||||
|
} else {
|
||||||
|
applyPositions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mountEmptyBlocks() {
|
mountEmptyBlocks() {
|
||||||
const emptyBlocks = [];
|
const emptyBlocks = [];
|
||||||
const numberOfBlocks = this.stateService.env.MEMPOOL_BLOCKS_AMOUNT;
|
const numberOfBlocks = this.stateService.env.MEMPOOL_BLOCKS_AMOUNT;
|
||||||
|
@ -21,7 +21,7 @@ import { ApiService } from '../../services/api.service';
|
|||||||
import { SeoService } from '../../services/seo.service';
|
import { SeoService } from '../../services/seo.service';
|
||||||
import { StorageService } from '../../services/storage.service';
|
import { StorageService } from '../../services/storage.service';
|
||||||
import { seoDescriptionNetwork } from '../../shared/common.utils';
|
import { seoDescriptionNetwork } from '../../shared/common.utils';
|
||||||
import { BlockExtended, CpfpInfo, RbfTree, MempoolPosition, DifficultyAdjustment, Acceleration } from '../../interfaces/node-api.interface';
|
import { BlockExtended, CpfpInfo, RbfTree, MempoolPosition, DifficultyAdjustment, Acceleration, AccelerationPosition } from '../../interfaces/node-api.interface';
|
||||||
import { LiquidUnblinding } from './liquid-ublinding';
|
import { LiquidUnblinding } from './liquid-ublinding';
|
||||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||||
import { Price, PriceService } from '../../services/price.service';
|
import { Price, PriceService } from '../../services/price.service';
|
||||||
@ -38,6 +38,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
txId: string;
|
txId: string;
|
||||||
txInBlockIndex: number;
|
txInBlockIndex: number;
|
||||||
mempoolPosition: MempoolPosition;
|
mempoolPosition: MempoolPosition;
|
||||||
|
accelerationPositions: AccelerationPosition[];
|
||||||
isLoadingTx = true;
|
isLoadingTx = true;
|
||||||
error: any = undefined;
|
error: any = undefined;
|
||||||
errorUnblinded: any = undefined;
|
errorUnblinded: any = undefined;
|
||||||
@ -265,10 +266,14 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.now = Date.now();
|
this.now = Date.now();
|
||||||
if (txPosition && txPosition.txid === this.txId && txPosition.position) {
|
if (txPosition && txPosition.txid === this.txId && txPosition.position) {
|
||||||
this.mempoolPosition = txPosition.position;
|
this.mempoolPosition = txPosition.position;
|
||||||
|
this.accelerationPositions = txPosition.accelerationPositions;
|
||||||
if (this.tx && !this.tx.status.confirmed) {
|
if (this.tx && !this.tx.status.confirmed) {
|
||||||
|
const txFeePerVSize = this.getUnacceleratedFeeRate(this.tx, this.tx.acceleration || this.mempoolPosition?.accelerated);
|
||||||
this.stateService.markBlock$.next({
|
this.stateService.markBlock$.next({
|
||||||
txid: txPosition.txid,
|
txid: txPosition.txid,
|
||||||
mempoolPosition: this.mempoolPosition
|
txFeePerVSize,
|
||||||
|
mempoolPosition: this.mempoolPosition,
|
||||||
|
accelerationPositions: this.accelerationPositions,
|
||||||
});
|
});
|
||||||
this.txInBlockIndex = this.mempoolPosition.block;
|
this.txInBlockIndex = this.mempoolPosition.block;
|
||||||
|
|
||||||
@ -278,6 +283,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.mempoolPosition = null;
|
this.mempoolPosition = null;
|
||||||
|
this.accelerationPositions = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -400,11 +406,13 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
this.fetchCpfp$.next(this.tx.txid);
|
this.fetchCpfp$.next(this.tx.txid);
|
||||||
} else {
|
} else {
|
||||||
|
const txFeePerVSize = this.getUnacceleratedFeeRate(this.tx, this.tx.acceleration || this.mempoolPosition?.accelerated);
|
||||||
if (tx.cpfpChecked) {
|
if (tx.cpfpChecked) {
|
||||||
this.stateService.markBlock$.next({
|
this.stateService.markBlock$.next({
|
||||||
txid: tx.txid,
|
txid: tx.txid,
|
||||||
txFeePerVSize: tx.effectiveFeePerVsize,
|
txFeePerVSize,
|
||||||
mempoolPosition: this.mempoolPosition,
|
mempoolPosition: this.mempoolPosition,
|
||||||
|
accelerationPositions: this.accelerationPositions,
|
||||||
});
|
});
|
||||||
this.cpfpInfo = {
|
this.cpfpInfo = {
|
||||||
ancestors: tx.ancestors,
|
ancestors: tx.ancestors,
|
||||||
@ -619,6 +627,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.accelerationInfo = null;
|
this.accelerationInfo = null;
|
||||||
this.txInBlockIndex = null;
|
this.txInBlockIndex = null;
|
||||||
this.mempoolPosition = null;
|
this.mempoolPosition = null;
|
||||||
|
this.accelerationPositions = null;
|
||||||
document.body.scrollTo(0, 0);
|
document.body.scrollTo(0, 0);
|
||||||
this.leaveTransaction();
|
this.leaveTransaction();
|
||||||
}
|
}
|
||||||
@ -632,6 +641,20 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
return +(cpfpTx.fee / (cpfpTx.weight / 4)).toFixed(1);
|
return +(cpfpTx.fee / (cpfpTx.weight / 4)).toFixed(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUnacceleratedFeeRate(tx: Transaction, accelerated: boolean): number {
|
||||||
|
if (accelerated) {
|
||||||
|
let ancestorVsize = tx.weight / 4;
|
||||||
|
let ancestorFee = tx.fee;
|
||||||
|
for (const ancestor of tx.ancestors || []) {
|
||||||
|
ancestorVsize += (ancestor.weight / 4);
|
||||||
|
ancestorFee += ancestor.fee;
|
||||||
|
}
|
||||||
|
return Math.min(tx.fee / (tx.weight / 4), (ancestorFee / ancestorVsize));
|
||||||
|
} else {
|
||||||
|
return tx.effectiveFeePerVsize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setupGraph() {
|
setupGraph() {
|
||||||
this.maxInOut = Math.min(this.inOutLimit, Math.max(this.tx?.vin?.length || 1, this.tx?.vout?.length + 1 || 1));
|
this.maxInOut = Math.min(this.inOutLimit, Math.max(this.tx?.vin?.length || 1, this.tx?.vout?.length + 1 || 1));
|
||||||
this.graphHeight = this.graphExpanded ? this.maxInOut * 15 : Math.min(360, this.maxInOut * 80);
|
this.graphHeight = this.graphExpanded ? this.maxInOut * 15 : Math.min(360, this.maxInOut * 80);
|
||||||
|
@ -196,6 +196,11 @@ export interface MempoolPosition {
|
|||||||
accelerated?: boolean
|
accelerated?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AccelerationPosition extends MempoolPosition {
|
||||||
|
pool: string;
|
||||||
|
offset?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface RewardStats {
|
export interface RewardStats {
|
||||||
startBlock: number;
|
startBlock: number;
|
||||||
endBlock: number;
|
endBlock: number;
|
||||||
|
@ -2,7 +2,7 @@ import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
|
|||||||
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs';
|
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs';
|
||||||
import { Transaction } from '../interfaces/electrs.interface';
|
import { Transaction } from '../interfaces/electrs.interface';
|
||||||
import { IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, TransactionStripped } from '../interfaces/websocket.interface';
|
import { IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, TransactionStripped } from '../interfaces/websocket.interface';
|
||||||
import { BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface';
|
import { AccelerationPosition, BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface';
|
||||||
import { Router, NavigationStart } from '@angular/router';
|
import { Router, NavigationStart } from '@angular/router';
|
||||||
import { isPlatformBrowser } from '@angular/common';
|
import { isPlatformBrowser } from '@angular/common';
|
||||||
import { filter, map, scan, shareReplay } from 'rxjs/operators';
|
import { filter, map, scan, shareReplay } from 'rxjs/operators';
|
||||||
@ -16,6 +16,7 @@ export interface MarkBlockState {
|
|||||||
mempoolBlockIndex?: number;
|
mempoolBlockIndex?: number;
|
||||||
txFeePerVSize?: number;
|
txFeePerVSize?: number;
|
||||||
mempoolPosition?: MempoolPosition;
|
mempoolPosition?: MempoolPosition;
|
||||||
|
accelerationPositions?: AccelerationPosition[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILoadingIndicators { [name: string]: number; }
|
export interface ILoadingIndicators { [name: string]: number; }
|
||||||
@ -116,7 +117,7 @@ export class StateService {
|
|||||||
utxoSpent$ = new Subject<object>();
|
utxoSpent$ = new Subject<object>();
|
||||||
difficultyAdjustment$ = new ReplaySubject<DifficultyAdjustment>(1);
|
difficultyAdjustment$ = new ReplaySubject<DifficultyAdjustment>(1);
|
||||||
mempoolTransactions$ = new Subject<Transaction>();
|
mempoolTransactions$ = new Subject<Transaction>();
|
||||||
mempoolTxPosition$ = new Subject<{ txid: string, position: MempoolPosition, cpfp: CpfpInfo | null}>();
|
mempoolTxPosition$ = new Subject<{ txid: string, position: MempoolPosition, cpfp: CpfpInfo | null, accelerationPositions?: AccelerationPosition[] }>();
|
||||||
mempoolRemovedTransactions$ = new Subject<Transaction>();
|
mempoolRemovedTransactions$ = new Subject<Transaction>();
|
||||||
blockTransactions$ = new Subject<Transaction>();
|
blockTransactions$ = new Subject<Transaction>();
|
||||||
isLoadingWebSocket$ = new ReplaySubject<boolean>(1);
|
isLoadingWebSocket$ = new ReplaySubject<boolean>(1);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user