new page listing recent RBF events

This commit is contained in:
Mononaut
2022-12-14 16:51:53 -06:00
parent 7b2a1cfd10
commit f46296a2bb
16 changed files with 333 additions and 7 deletions

View File

@@ -34,6 +34,8 @@ class BitcoinRoutes {
.get(config.MEMPOOL.API_URL_PREFIX + 'validate-address/:address', this.validateAddress)
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/rbf', this.getRbfHistory)
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/cached', this.getCachedTx)
.get(config.MEMPOOL.API_URL_PREFIX + 'replacements', this.getRbfReplacements)
.get(config.MEMPOOL.API_URL_PREFIX + 'fullrbf/replacements', this.getFullRbfReplacements)
.post(config.MEMPOOL.API_URL_PREFIX + 'tx/push', this.$postTransactionForm)
.get(config.MEMPOOL.API_URL_PREFIX + 'donations', async (req, res) => {
try {
@@ -653,6 +655,24 @@ class BitcoinRoutes {
}
}
private async getRbfReplacements(req: Request, res: Response) {
try {
const result = rbfCache.getRbfChains(false);
res.json(result);
} catch (e) {
res.status(500).send(e instanceof Error ? e.message : e);
}
}
private async getFullRbfReplacements(req: Request, res: Response) {
try {
const result = rbfCache.getRbfChains(true);
res.json(result);
} catch (e) {
res.status(500).send(e instanceof Error ? e.message : e);
}
}
private async getCachedTx(req: Request, res: Response) {
try {
const result = rbfCache.getTx(req.params.txId);

View File

@@ -73,6 +73,33 @@ class RbfCache {
return this.rbfChains.get(this.chainMap.get(txId) || '') || [];
}
// get a paginated list of RbfChains
// ordered by most recent replacement time
public getRbfChains(onlyFullRbf: boolean, after?: string): RbfChain[] {
const limit = 25;
const chains: RbfChain[] = [];
const used = new Set<string>();
const replacements: string[][] = Array.from(this.replacedBy).reverse();
const afterChain = after ? this.chainMap.get(after) : null;
let ready = !afterChain;
for (let i = 0; i < replacements.length && chains.length <= limit - 1; i++) {
const txid = replacements[i][1];
const chainRoot = this.chainMap.get(txid) || '';
if (chainRoot === afterChain) {
ready = true;
} else if (ready) {
if (!used.has(chainRoot)) {
const chain = this.rbfChains.get(chainRoot);
used.add(chainRoot);
if (chain && (!onlyFullRbf || chain.slice(0, -1).some(entry => !entry.tx.rbf))) {
chains.push(chain);
}
}
}
}
return chains;
}
// get map of rbf chains that have been updated since the last call
public getRbfChanges(): { chains: {[root: string]: RbfChain }, map: { [txid: string]: string }} {
const changes: { chains: {[root: string]: RbfChain }, map: { [txid: string]: string }} = {
@@ -92,6 +119,20 @@ class RbfCache {
return changes;
}
public mined(txid): void {
const chainRoot = this.chainMap.get(txid)
if (chainRoot && this.rbfChains.has(chainRoot)) {
const chain = this.rbfChains.get(chainRoot);
if (chain) {
const chainEntry = chain.find(entry => entry.tx.txid === txid);
if (chainEntry) {
chainEntry.mined = true;
}
this.dirtyChains.add(chainRoot);
}
}
this.evict(txid);
}
// flag a transaction as removed from the mempool
public evict(txid): void {

View File

@@ -140,6 +140,14 @@ class WebsocketHandler {
}
}
if (parsedMessage && parsedMessage['track-rbf'] !== undefined) {
if (['all', 'fullRbf'].includes(parsedMessage['track-rbf'])) {
client['track-rbf'] = parsedMessage['track-rbf'];
} else {
client['track-rbf'] = false;
}
}
if (parsedMessage.action === 'init') {
const _blocks = blocks.getBlocks().slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT);
if (!_blocks) {
@@ -279,6 +287,12 @@ class WebsocketHandler {
const da = difficultyAdjustment.getDifficultyAdjustment();
memPool.handleRbfTransactions(rbfTransactions);
const rbfChanges = rbfCache.getRbfChanges();
let rbfReplacements;
let fullRbfReplacements;
if (Object.keys(rbfChanges.chains).length) {
rbfReplacements = rbfCache.getRbfChains(false);
fullRbfReplacements = rbfCache.getRbfChains(true);
}
const recommendedFees = feeApi.getRecommendedFee();
this.wss.clients.forEach(async (client) => {
@@ -428,6 +442,13 @@ class WebsocketHandler {
}
}
console.log(client['track-rbf']);
if (client['track-rbf'] === 'all' && rbfReplacements) {
response['rbfLatest'] = rbfReplacements;
} else if (client['track-rbf'] === 'fullRbf' && fullRbfReplacements) {
response['rbfLatest'] = fullRbfReplacements;
}
if (Object.keys(response).length) {
client.send(JSON.stringify(response));
}
@@ -506,7 +527,7 @@ class WebsocketHandler {
// Update mempool to remove transactions included in the new block
for (const txId of txIds) {
delete _memPool[txId];
rbfCache.evict(txId);
rbfCache.mined(txId);
}
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {