Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mempool: log is rbf with hash and mempool size #12

Open
wants to merge 7 commits into
base: rbf1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 113 additions & 5 deletions lib/mempool/mempool.js
Original file line number Diff line number Diff line change
Expand Up @@ -844,7 +844,20 @@ class Mempool extends EventEmitter {
const entry = MempoolEntry.fromTX(tx, view, height);

// Contextual verification.
await this.verify(entry, view);
const conflicts = await this.verify(entry, view);

// RBF only: Remove conflicting TXs and their descendants
if (!this.options.replaceByFee) {
assert(conflicts.length === 0);
} else {
for (const conflict of conflicts) {
this.logger.debug(
'Replacing tx %h with %h',
conflict.tx.hash(),
tx.hash());
this.evictEntry(conflict);
}
}

// Add and index the entry.
await this.addEntry(entry, view);
Expand All @@ -862,10 +875,11 @@ class Mempool extends EventEmitter {

/**
* Verify a transaction with mempool standards.
* Returns an array of conflicting TXs
* @method
* @param {TX} tx
* @param {CoinView} view
* @returns {Promise}
* @returns {Promise} - Returns {@link MempoolEntry}[]
*/

async verify(entry, view) {
Expand Down Expand Up @@ -953,6 +967,72 @@ class Mempool extends EventEmitter {
0);
}

// If we reject RBF transactions altogether we can skip these checks,
// because incoming conflicts are already rejected as double spends.
let conflicts = [];
if (this.options.replaceByFee)
conflicts = this.getConflicts(tx);

if (conflicts.length) {
let conflictingFees = 0;
let totalEvictions = 0;

// Replacement TXs must pay a higher fee rate than each conflicting TX.
// We compare using deltaFee to account for prioritiseTransaction()
for (const conflict of conflicts) {
conflictingFees += conflict.descFee;
totalEvictions += this.countDescendants(conflict) + 1;

if (entry.getDeltaRate() <= conflict.getDeltaRate()) {
throw new VerifyError(tx,
'insufficientfee',
'insufficient fee',
0);
}
}

// Replacement TXs must not evict/replace more than 100 descendants
if (totalEvictions > 100) {
throw new VerifyError(tx,
'nonstandard',
'too many potential replacements',
0);
}

// Replacement TX must also pay for the total fees of all descendant
// transactions that will be evicted if an ancestor is replaced.
// Thus the replacement "pays for the bandwidth" of all the conflicts.
if (entry.deltaFee <= conflictingFees) {
throw new VerifyError(tx,
'insufficientfee',
'insufficient fee',
0);
}

// Once the conflicts are all paid for, the replacement TX fee
// still needs to cover it's own bandwidth.
const feeRemaining = entry.deltaFee - conflictingFees;
if (feeRemaining < minFee) {
throw new VerifyError(tx,
'insufficientfee',
'insufficient fee',
0);
}

// Replacement TXs can not consume any unconfirmed outputs that were not
// already included in the original transactions. They can only add
// confirmed UTXO, saving the trouble of checking new mempool ancestors.
for (const {prevout} of tx.inputs) {
if (!this.isSpent(prevout.hash, prevout.index) &&
this.map.has(prevout.hash)) {
throw new VerifyError(tx,
'nonstandard',
'replacement-adds-unconfirmed',
0);
}
}
}

// Contextual sanity checks.
const [fee, reason, score] = tx.checkInputs(view, height);

Expand Down Expand Up @@ -995,6 +1075,8 @@ class Mempool extends EventEmitter {
assert(await this.verifyResult(tx, view, flags),
'BUG: Verify failed for mandatory but not standard.');
}

return conflicts;
}

/**
Expand Down Expand Up @@ -1075,8 +1157,8 @@ class Mempool extends EventEmitter {
this.fees.processTX(entry, this.chain.synced);

this.logger.debug(
'Added %h to mempool (txs=%d).',
tx.hash(), this.map.size);
'Added %h to mempool (rbf=%s) (txs=%d).',
tx.hash(), tx.isRBF(), this.map.size);

this.cache.save(entry);

Expand Down Expand Up @@ -1659,13 +1741,39 @@ class Mempool extends EventEmitter {
isDoubleSpend(tx) {
for (const {prevout} of tx.inputs) {
const {hash, index} = prevout;
if (this.isSpent(hash, index))
const conflict = this.getSpent(hash, index);

if (conflict && !conflict.tx.isRBF())
return true;
}

return false;
}

/**
* Get an array of all transactions currently in the mempool that
* spend one or more of the same outputs as an incoming transaction.
* @param {TX} tx
* @returns {Promise} - Returns (@link MempoolEntry}[].
*/

getConflicts(tx) {
const conflicts = [];
const hashes = new BufferSet();

for (const {prevout} of tx.inputs) {
const {hash, index} = prevout;
const conflict = this.getSpent(hash, index);

if (conflict && !hashes.has(hash)) {
hashes.add(hash);
conflicts.push(conflict);
}
}

return conflicts;
}

/**
* Get coin viewpoint (lock).
* Note: this does not return
Expand Down
Loading