parent
f0b0fc3f4b
commit
8b6a681614
@ -25,4 +25,26 @@ export class Common {
|
||||
arr.push(transactions[lastindex].feePerVsize);
|
||||
return arr;
|
||||
}
|
||||
|
||||
static findRbfTransactions(added: TransactionExtended[], deleted: TransactionExtended[]): { [txid: string]: TransactionExtended } {
|
||||
const matches: { [txid: string]: TransactionExtended } = {};
|
||||
deleted
|
||||
// The replaced tx must have at least one input with nSequence < maxint-1 (That’s the opt-in)
|
||||
.filter((tx) => tx.vin.some((vin) => vin.sequence < 0xfffffffe))
|
||||
.forEach((deletedTx) => {
|
||||
const foundMatches = added.find((addedTx) => {
|
||||
// The new tx must, absolutely speaking, pay at least as much fee as the replaced tx.
|
||||
return addedTx.fee > deletedTx.fee
|
||||
// The new transaction must pay more fee per kB than the replaced tx.
|
||||
&& addedTx.feePerVsize > deletedTx.feePerVsize
|
||||
// Spends one or more of the same inputs
|
||||
&& deletedTx.vin.some((deletedVin) =>
|
||||
addedTx.vin.some((vin) => vin.txid === deletedVin.txid));
|
||||
});
|
||||
if (foundMatches) {
|
||||
matches[deletedTx.txid] = foundMatches;
|
||||
}
|
||||
});
|
||||
return matches;
|
||||
}
|
||||
}
|
||||
|
@ -49,9 +49,9 @@ class MempoolBlocks {
|
||||
transactions.push(tx);
|
||||
} else {
|
||||
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockVSize, mempoolBlocks.length));
|
||||
blockVSize = 0;
|
||||
blockSize = 0;
|
||||
transactions = [];
|
||||
blockVSize = tx.vsize;
|
||||
blockSize = tx.size;
|
||||
transactions = [tx];
|
||||
}
|
||||
});
|
||||
if (transactions.length) {
|
||||
|
@ -123,31 +123,30 @@ class Mempool {
|
||||
}
|
||||
}
|
||||
|
||||
// Replace mempool to clear already confirmed transactions
|
||||
// Replace mempool to clear deleted transactions
|
||||
const newMempool = {};
|
||||
transactions.forEach((tx) => {
|
||||
if (this.mempoolCache[tx]) {
|
||||
const deletedTransactions: Transaction[] = [];
|
||||
for (const tx in this.mempoolCache) {
|
||||
if (transactions.indexOf(tx) > -1) {
|
||||
newMempool[tx] = this.mempoolCache[tx];
|
||||
} else {
|
||||
hasChange = true;
|
||||
deletedTransactions.push(this.mempoolCache[tx]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.inSync && transactions.length === Object.keys(newMempool).length) {
|
||||
this.inSync = true;
|
||||
console.log('The mempool is now in sync!');
|
||||
}
|
||||
|
||||
console.log(`New mempool size: ${Object.keys(newMempool).length} Change: ${diff}`);
|
||||
|
||||
this.mempoolCache = newMempool;
|
||||
|
||||
if (hasChange && this.mempoolChangedCallback) {
|
||||
this.mempoolChangedCallback(this.mempoolCache, newTransactions);
|
||||
if (this.mempoolChangedCallback && (hasChange || deletedTransactions.length)) {
|
||||
this.mempoolCache = newMempool;
|
||||
this.mempoolChangedCallback(this.mempoolCache, newTransactions, deletedTransactions);
|
||||
}
|
||||
|
||||
const end = new Date().getTime();
|
||||
const time = end - start;
|
||||
console.log(`New mempool size: ${Object.keys(newMempool).length} Change: ${diff}`);
|
||||
console.log('Mempool updated in ' + time / 1000 + ' seconds');
|
||||
} catch (err) {
|
||||
console.log('getRawMempool error.', err);
|
||||
|
@ -8,6 +8,7 @@ import backendInfo from './backend-info';
|
||||
import mempoolBlocks from './mempool-blocks';
|
||||
import fiatConversion from './fiat-conversion';
|
||||
import * as os from 'os';
|
||||
import { Common } from './common';
|
||||
|
||||
class WebsocketHandler {
|
||||
private wss: WebSocket.Server | undefined;
|
||||
@ -121,7 +122,8 @@ class WebsocketHandler {
|
||||
});
|
||||
}
|
||||
|
||||
handleMempoolChange(newMempool: { [txid: string]: TransactionExtended }, newTransactions: TransactionExtended[]) {
|
||||
handleMempoolChange(newMempool: { [txid: string]: TransactionExtended },
|
||||
newTransactions: TransactionExtended[], deletedTransactions: TransactionExtended[]) {
|
||||
if (!this.wss) {
|
||||
throw new Error('WebSocket.Server is not set');
|
||||
}
|
||||
@ -130,6 +132,7 @@ class WebsocketHandler {
|
||||
const mBlocks = mempoolBlocks.getMempoolBlocks();
|
||||
const mempoolInfo = memPool.getMempoolInfo();
|
||||
const vBytesPerSecond = memPool.getVBytesPerSecond();
|
||||
const rbfTransactions = Common.findRbfTransactions(newTransactions, deletedTransactions);
|
||||
|
||||
this.wss.clients.forEach((client: WebSocket) => {
|
||||
if (client.readyState !== WebSocket.OPEN) {
|
||||
@ -204,6 +207,15 @@ class WebsocketHandler {
|
||||
}
|
||||
}
|
||||
|
||||
if (client['track-tx'] && rbfTransactions[client['track-tx']]) {
|
||||
for (const rbfTransaction in rbfTransactions) {
|
||||
if (client['track-tx'] === rbfTransaction) {
|
||||
response['rbfTransaction'] = rbfTransactions[rbfTransaction];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(response).length) {
|
||||
client.send(JSON.stringify(response));
|
||||
}
|
||||
@ -228,7 +240,7 @@ class WebsocketHandler {
|
||||
}
|
||||
}
|
||||
|
||||
matchRate = Math.ceil((matches.length / txIds.length) * 100);
|
||||
matchRate = Math.round((matches.length / (txIds.length - 1)) * 100);
|
||||
if (matchRate > 0) {
|
||||
const currentMemPool = memPool.getMempool();
|
||||
for (const txId of matches) {
|
||||
|
@ -35,9 +35,6 @@ export interface Transaction {
|
||||
}
|
||||
|
||||
export interface TransactionExtended extends Transaction {
|
||||
txid: string;
|
||||
fee: number;
|
||||
size: number;
|
||||
vsize: number;
|
||||
feePerVsize: number;
|
||||
firstSeen: number;
|
||||
|
@ -1,6 +1,14 @@
|
||||
<div class="container-xl">
|
||||
|
||||
<div class="title-block">
|
||||
<div *ngIf="rbfTransaction" class="alert alert-mempool" role="alert">
|
||||
This transaction has been replaced by:
|
||||
<a class="alert-link" [routerLink]="['/tx/' | relativeUrl, rbfTransaction.txid]" [state]="{ data: rbfTransaction }">
|
||||
<span class="d-inline d-lg-none">{{ rbfTransaction.txid | shortenString : 24 }}</span>
|
||||
<span class="d-none d-lg-inline">{{ rbfTransaction.txid }}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h1 class="float-left mr-3 mb-md-3">Transaction</h1>
|
||||
|
||||
<ng-template [ngIf]="tx?.status?.confirmed">
|
||||
|
@ -36,6 +36,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
||||
potentialP2shGains: 0,
|
||||
};
|
||||
isRbfTransaction: boolean;
|
||||
rbfTransaction: undefined | Transaction;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
@ -126,6 +127,9 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
||||
this.audioService.playSound('magic');
|
||||
this.findBlockAndSetFeeRating();
|
||||
});
|
||||
|
||||
this.stateService.txReplaced$
|
||||
.subscribe((rbfTransaction) => this.rbfTransaction = rbfTransaction);
|
||||
}
|
||||
|
||||
handleLoadElectrsTransactionError(error: any): Observable<any> {
|
||||
@ -198,6 +202,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
||||
this.feeRating = undefined;
|
||||
this.waitingForTransaction = false;
|
||||
this.isLoadingTx = true;
|
||||
this.rbfTransaction = undefined;
|
||||
this.transactionTime = -1;
|
||||
document.body.scrollTo(0, 0);
|
||||
this.leaveTransaction();
|
||||
|
@ -11,6 +11,7 @@ export interface WebsocketResponse {
|
||||
action?: string;
|
||||
data?: string[];
|
||||
tx?: Transaction;
|
||||
rbfTransaction?: Transaction;
|
||||
'track-tx'?: string;
|
||||
'track-address'?: string;
|
||||
'track-asset'?: string;
|
||||
|
@ -24,6 +24,7 @@ export class StateService {
|
||||
mempoolStats$ = new ReplaySubject<MemPoolState>(1);
|
||||
mempoolBlocks$ = new ReplaySubject<MempoolBlock[]>(1);
|
||||
txConfirmed$ = new Subject<Block>();
|
||||
txReplaced$ = new Subject<Transaction>();
|
||||
mempoolTransactions$ = new Subject<Transaction>();
|
||||
blockTransactions$ = new Subject<Transaction>();
|
||||
|
||||
|
@ -89,6 +89,10 @@ export class WebsocketService {
|
||||
this.stateService.conversions$.next(response.conversions);
|
||||
}
|
||||
|
||||
if (response.rbfTransaction) {
|
||||
this.stateService.txReplaced$.next(response.rbfTransaction);
|
||||
}
|
||||
|
||||
if (response['mempool-blocks']) {
|
||||
this.stateService.mempoolBlocks$.next(response['mempool-blocks']);
|
||||
}
|
||||
|
@ -410,3 +410,9 @@ h1, h2, h3 {
|
||||
.tooltip-inner {
|
||||
max-width: inherit;
|
||||
}
|
||||
|
||||
.alert-mempool {
|
||||
color: #ffffff;
|
||||
background-color: #653b9c;
|
||||
border-color: #3a1c61;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user