Merge pull request #3785 from mempool/mononaut/mined-block-rbf
Detect RBF by mined transactions
This commit is contained in:
commit
0703690190
@ -77,6 +77,24 @@ export class Common {
|
|||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static findMinedRbfTransactions(minedTransactions: TransactionExtended[], spendMap: Map<string, TransactionExtended>): { [txid: string]: { replaced: TransactionExtended[], replacedBy: TransactionExtended }} {
|
||||||
|
const matches: { [txid: string]: { replaced: TransactionExtended[], replacedBy: TransactionExtended }} = {};
|
||||||
|
for (const tx of minedTransactions) {
|
||||||
|
const replaced: Set<TransactionExtended> = new Set();
|
||||||
|
for (let i = 0; i < tx.vin.length; i++) {
|
||||||
|
const vin = tx.vin[i];
|
||||||
|
const match = spendMap.get(`${vin.txid}:${vin.vout}`);
|
||||||
|
if (match && match.txid !== tx.txid) {
|
||||||
|
replaced.add(match);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (replaced.size) {
|
||||||
|
matches[tx.txid] = { replaced: Array.from(replaced), replacedBy: tx };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
static stripTransaction(tx: TransactionExtended): TransactionStripped {
|
static stripTransaction(tx: TransactionExtended): TransactionStripped {
|
||||||
return {
|
return {
|
||||||
txid: tx.txid,
|
txid: tx.txid,
|
||||||
|
@ -14,6 +14,7 @@ class Mempool {
|
|||||||
private inSync: boolean = false;
|
private inSync: boolean = false;
|
||||||
private mempoolCacheDelta: number = -1;
|
private mempoolCacheDelta: number = -1;
|
||||||
private mempoolCache: { [txId: string]: TransactionExtended } = {};
|
private mempoolCache: { [txId: string]: TransactionExtended } = {};
|
||||||
|
private spendMap = new Map<string, TransactionExtended>();
|
||||||
private mempoolInfo: IBitcoinApi.MempoolInfo = { loaded: false, size: 0, bytes: 0, usage: 0, total_fee: 0,
|
private mempoolInfo: IBitcoinApi.MempoolInfo = { loaded: false, size: 0, bytes: 0, usage: 0, total_fee: 0,
|
||||||
maxmempool: 300000000, mempoolminfee: 0.00001000, minrelaytxfee: 0.00001000 };
|
maxmempool: 300000000, mempoolminfee: 0.00001000, minrelaytxfee: 0.00001000 };
|
||||||
private mempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[],
|
private mempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[],
|
||||||
@ -77,6 +78,10 @@ class Mempool {
|
|||||||
return this.mempoolCache;
|
return this.mempoolCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getSpendMap(): Map<string, TransactionExtended> {
|
||||||
|
return this.spendMap;
|
||||||
|
}
|
||||||
|
|
||||||
public async $setMempool(mempoolData: { [txId: string]: TransactionExtended }) {
|
public async $setMempool(mempoolData: { [txId: string]: TransactionExtended }) {
|
||||||
this.mempoolCache = mempoolData;
|
this.mempoolCache = mempoolData;
|
||||||
if (this.mempoolChangedCallback) {
|
if (this.mempoolChangedCallback) {
|
||||||
@ -85,6 +90,7 @@ class Mempool {
|
|||||||
if (this.$asyncMempoolChangedCallback) {
|
if (this.$asyncMempoolChangedCallback) {
|
||||||
await this.$asyncMempoolChangedCallback(this.mempoolCache, [], []);
|
await this.$asyncMempoolChangedCallback(this.mempoolCache, [], []);
|
||||||
}
|
}
|
||||||
|
this.addToSpendMap(Object.values(this.mempoolCache));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async $updateMemPoolInfo() {
|
public async $updateMemPoolInfo() {
|
||||||
@ -276,6 +282,34 @@ class Mempool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public handleMinedRbfTransactions(rbfTransactions: { [txid: string]: { replaced: TransactionExtended[], replacedBy: TransactionExtended }}): void {
|
||||||
|
for (const rbfTransaction in rbfTransactions) {
|
||||||
|
if (rbfTransactions[rbfTransaction].replacedBy && rbfTransactions[rbfTransaction]?.replaced?.length) {
|
||||||
|
// Store replaced transactions
|
||||||
|
rbfCache.add(rbfTransactions[rbfTransaction].replaced, rbfTransactions[rbfTransaction].replacedBy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public addToSpendMap(transactions: TransactionExtended[]): void {
|
||||||
|
for (const tx of transactions) {
|
||||||
|
for (const vin of tx.vin) {
|
||||||
|
this.spendMap.set(`${vin.txid}:${vin.vout}`, tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeFromSpendMap(transactions: TransactionExtended[]): void {
|
||||||
|
for (const tx of transactions) {
|
||||||
|
for (const vin of tx.vin) {
|
||||||
|
const key = `${vin.txid}:${vin.vout}`;
|
||||||
|
if (this.spendMap.get(key)?.txid === tx.txid) {
|
||||||
|
this.spendMap.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private updateTxPerSecond() {
|
private updateTxPerSecond() {
|
||||||
const nowMinusTimeSpan = new Date().getTime() - (1000 * config.STATISTICS.TX_PER_SECOND_SAMPLE_PERIOD);
|
const nowMinusTimeSpan = new Date().getTime() - (1000 * config.STATISTICS.TX_PER_SECOND_SAMPLE_PERIOD);
|
||||||
this.txPerSecondArray = this.txPerSecondArray.filter((unixTime) => unixTime > nowMinusTimeSpan);
|
this.txPerSecondArray = this.txPerSecondArray.filter((unixTime) => unixTime > nowMinusTimeSpan);
|
||||||
|
@ -31,7 +31,7 @@ class RbfCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public add(replaced: TransactionExtended[], newTxExtended: TransactionExtended): void {
|
public add(replaced: TransactionExtended[], newTxExtended: TransactionExtended): void {
|
||||||
if (!newTxExtended || !replaced?.length) {
|
if (!newTxExtended || !replaced?.length || this.txs.has(newTxExtended.txid)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,6 +332,8 @@ class WebsocketHandler {
|
|||||||
for (const deletedTx of deletedTransactions) {
|
for (const deletedTx of deletedTransactions) {
|
||||||
rbfCache.evict(deletedTx.txid);
|
rbfCache.evict(deletedTx.txid);
|
||||||
}
|
}
|
||||||
|
memPool.removeFromSpendMap(deletedTransactions);
|
||||||
|
memPool.addToSpendMap(newTransactions);
|
||||||
const recommendedFees = feeApi.getRecommendedFee();
|
const recommendedFees = feeApi.getRecommendedFee();
|
||||||
|
|
||||||
// update init data
|
// update init data
|
||||||
@ -600,6 +602,10 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rbfTransactions = Common.findMinedRbfTransactions(transactions, memPool.getSpendMap());
|
||||||
|
memPool.handleMinedRbfTransactions(rbfTransactions);
|
||||||
|
memPool.removeFromSpendMap(transactions);
|
||||||
|
|
||||||
// Update mempool to remove transactions included in the new block
|
// Update mempool to remove transactions included in the new block
|
||||||
for (const txId of txIds) {
|
for (const txId of txIds) {
|
||||||
delete _memPool[txId];
|
delete _memPool[txId];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user