Skip to content

Commit eb5e6a8

Browse files
committed
Merge PR #778 from 'pinheadmz/maxfee'
2 parents e07ba54 + 5489e31 commit eb5e6a8

File tree

9 files changed

+294
-37
lines changed

9 files changed

+294
-37
lines changed

lib/mempool/mempool.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1610,6 +1610,9 @@ class Mempool extends EventEmitter {
16101610
// Make sure this guy gave a decent fee.
16111611
const minFee = policy.getMinFee(entry.size, this.options.minRelay);
16121612

1613+
// Make sure this guy didn't overpay
1614+
const absurdFee = minFee * this.options.absurdFactor;
1615+
16131616
if (this.options.relayPriority && entry.fee < minFee) {
16141617
if (!entry.isFree(height)) {
16151618
throw new VerifyError(tx,
@@ -1641,7 +1644,7 @@ class Mempool extends EventEmitter {
16411644
}
16421645

16431646
// Important safety feature.
1644-
if (this.options.rejectAbsurdFees && entry.fee > minFee * 10000)
1647+
if (this.options.rejectAbsurdFees && entry.fee > absurdFee)
16451648
throw new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0);
16461649

16471650
// Why do we have this here? Nested transactions are cool.
@@ -2725,6 +2728,7 @@ class MempoolOptions {
27252728
this.maxAncestors = policy.MEMPOOL_MAX_ANCESTORS;
27262729
this.expiryTime = policy.MEMPOOL_EXPIRY_TIME;
27272730
this.minRelay = this.network.minRelay;
2731+
this.absurdFactor = policy.ABSURD_FEE_FACTOR;
27282732

27292733
this.prefix = null;
27302734
this.location = null;
@@ -2828,6 +2832,11 @@ class MempoolOptions {
28282832
this.minRelay = options.minRelay;
28292833
}
28302834

2835+
if (options.absurdFactor != null) {
2836+
assert((options.absurdFactor >>> 0) === options.absurdFactor);
2837+
this.absurdFactor = options.absurdFactor;
2838+
}
2839+
28312840
if (options.prefix != null) {
28322841
assert(typeof options.prefix === 'string');
28332842
this.prefix = options.prefix;

lib/primitives/mtx.js

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1662,13 +1662,10 @@ class CoinSelector {
16621662
// This is mostly here for testing.
16631663
// i.e. A fee rounded to the nearest
16641664
// kb is easier to predict ahead of time.
1665-
if (this.round) {
1666-
const fee = policy.getRoundFee(size, this.rate);
1667-
return Math.min(fee, CoinSelector.MAX_FEE);
1668-
}
1665+
if (this.round)
1666+
return policy.getRoundFee(size, this.rate);
16691667

1670-
const fee = policy.getMinFee(size, this.rate);
1671-
return Math.min(fee, CoinSelector.MAX_FEE);
1668+
return policy.getMinFee(size, this.rate);
16721669
}
16731670

16741671
/**
@@ -1803,7 +1800,7 @@ class CoinSelector {
18031800
*/
18041801

18051802
selectHard() {
1806-
this.fee = Math.min(this.hardFee, CoinSelector.MAX_FEE);
1803+
this.fee = this.hardFee;
18071804
this.fund();
18081805
}
18091806
}
@@ -1826,15 +1823,6 @@ CoinSelector.FEE_RATE = 10000;
18261823

18271824
CoinSelector.MIN_FEE = 10000;
18281825

1829-
/**
1830-
* Maximum fee to allow
1831-
* after coin selection.
1832-
* @const {Amount}
1833-
* @default
1834-
*/
1835-
1836-
CoinSelector.MAX_FEE = consensus.COIN / 10;
1837-
18381826
/**
18391827
* Funding Error
18401828
* An error thrown from the coin selector.

lib/protocol/policy.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ exports.BYTES_PER_SIGOP = 20;
6161

6262
exports.MIN_RELAY = 1000;
6363

64+
/**
65+
* Maximum relay fee FACTOR for safety (policy).
66+
* Multiplied by result of policy.getMinFee()
67+
* @const {Number}
68+
*/
69+
70+
exports.ABSURD_FEE_FACTOR = 10000;
71+
6472
/**
6573
* Priority threshold for
6674
* free transactions (policy).

lib/wallet/wallet.js

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ class Wallet extends EventEmitter {
8282
this.txdb = new TXDB(this.wdb);
8383

8484
this.maxAncestors = policy.MEMPOOL_MAX_ANCESTORS;
85+
this.absurdFactor = policy.ABSURD_FEE_FACTOR;
8586

8687
if (options)
8788
this.fromOptions(options);
@@ -155,6 +156,11 @@ class Wallet extends EventEmitter {
155156
this.maxAncestors = options.maxAncestors;
156157
}
157158

159+
if (options.absurdFactor != null) {
160+
assert((options.absurdFactor >>> 0) === options.absurdFactor);
161+
this.absurdFactor = options.absurdFactor;
162+
}
163+
158164
if (!id)
159165
id = this.getID();
160166

@@ -1186,8 +1192,6 @@ class Wallet extends EventEmitter {
11861192
maxFee: options.maxFee,
11871193
estimate: prev => this.estimateSize(prev)
11881194
});
1189-
1190-
assert(mtx.getFee() <= MTX.Selector.MAX_FEE, 'TX exceeds MAX_FEE.');
11911195
}
11921196

11931197
/**
@@ -3960,6 +3964,21 @@ class Wallet extends EventEmitter {
39603964
if (tx.getWeight() > policy.MAX_TX_WEIGHT)
39613965
throw new Error('TX exceeds policy weight.');
39623966

3967+
const minFee = policy.getMinFee(
3968+
mtx.getVirtualSize(),
3969+
this.network.minRelay
3970+
);
3971+
3972+
const absurdFee = minFee * this.absurdFactor;
3973+
3974+
const fee = mtx.getFee();
3975+
3976+
if (fee < minFee)
3977+
throw new Error('Fee is below minimum relay limit.');
3978+
3979+
if (fee > absurdFee)
3980+
throw new Error('Fee exceeds absurd limit.');
3981+
39633982
const ancestors = await this.getPendingAncestors(tx);
39643983
if (ancestors.size + 1 > this.maxAncestors)
39653984
throw new Error('TX exceeds maximum unconfirmed ancestors.');
@@ -4018,10 +4037,7 @@ class Wallet extends EventEmitter {
40184037

40194038
const oldFee = tx.getFee(view);
40204039

4021-
let fee = tx.getMinFee(null, rate);
4022-
4023-
if (fee > MTX.Selector.MAX_FEE)
4024-
fee = MTX.Selector.MAX_FEE;
4040+
const fee = tx.getMinFee(null, rate);
40254041

40264042
if (oldFee >= fee)
40274043
throw new Error('Fee is not increasing.');

test/mempool-test.js

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const Witness = require('../lib/script/witness');
2222
const CoinView = require('../lib/coins/coinview');
2323
const util = require('../lib/utils/util');
2424
const consensus = require('../lib/protocol/consensus');
25+
const policy = require('../lib/protocol/policy');
2526
const MemWallet = require('./util/memwallet');
2627
const ALL = Script.hashType.ALL;
2728
const common = require('../lib/blockchain/common');
@@ -63,7 +64,7 @@ const wallet = new MemWallet({ network });
6364

6465
let cachedTX = null;
6566

66-
function dummyInput(addr, hash) {
67+
function dummyInput(addr, hash, value = 70000) {
6768
const coin = new Coin();
6869
coin.height = 0;
6970
coin.value = 0;
@@ -73,7 +74,7 @@ function dummyInput(addr, hash) {
7374

7475
const fund = new MTX();
7576
fund.addCoin(coin);
76-
fund.addOutput(addr, 70000);
77+
fund.addOutput(addr, value);
7778

7879
const [tx, view] = fund.commit();
7980

@@ -465,6 +466,85 @@ describe('Mempool', function() {
465466
}
466467
});
467468

469+
it('should reject absurd fee', async () => {
470+
const wallet = new MemWallet({ network });
471+
const addr = wallet.getAddress();
472+
const funds = 10000e6;
473+
474+
const mtx = new MTX();
475+
mtx.addCoin(
476+
dummyInput(
477+
addr,
478+
random.randomBytes(32),
479+
funds
480+
)
481+
);
482+
mtx.addOutput(wallet.getAddress(), 0); // temp
483+
wallet.sign(mtx);
484+
485+
const vsize = mtx.getVirtualSize();
486+
const minFee = (vsize / 1000) * network.minRelay;
487+
const absurdFee = minFee * policy.ABSURD_FEE_FACTOR;
488+
489+
// Revise with exactly absurd fee
490+
mtx.outputs[0].value = funds - absurdFee - 1;
491+
mtx.inputs[0].witness.items.length = 0;
492+
wallet.sign(mtx);
493+
const tx1 = mtx.toTX();
494+
495+
await assert.rejects(
496+
mempool.addTX(tx1),
497+
{message: /absurdly-high-fee/}
498+
);
499+
500+
// Revise again with just under absurd fee
501+
mtx.outputs[0].value = funds - absurdFee;
502+
mtx.inputs[0].witness.items.length = 0;
503+
wallet.sign(mtx);
504+
const tx2 = mtx.toTX();
505+
506+
await mempool.addTX(tx2);
507+
});
508+
509+
it('should reject too-low fee', async () => {
510+
const wallet = new MemWallet({ network });
511+
const addr = wallet.getAddress();
512+
const funds = 10000e6;
513+
514+
const mtx = new MTX();
515+
mtx.addCoin(
516+
dummyInput(
517+
addr,
518+
random.randomBytes(32),
519+
funds
520+
)
521+
);
522+
mtx.addOutput(wallet.getAddress(), 0); // temp
523+
wallet.sign(mtx);
524+
525+
const vsize = mtx.getVirtualSize();
526+
const minFee = (vsize / 1000) * network.minRelay;
527+
528+
// Revise with just under minFee
529+
mtx.outputs[0].value = funds - minFee + 1;
530+
mtx.inputs[0].witness.items.length = 0;
531+
wallet.sign(mtx);
532+
const tx1 = mtx.toTX();
533+
534+
await assert.rejects(
535+
mempool.addTX(tx1),
536+
{message: /insufficient priority/}
537+
);
538+
539+
// Revise again with exactly minFee
540+
mtx.outputs[0].value = funds - minFee;
541+
mtx.inputs[0].witness.items.length = 0;
542+
wallet.sign(mtx);
543+
const tx2 = mtx.toTX();
544+
545+
await mempool.addTX(tx2);
546+
});
547+
468548
it('should destroy mempool', async () => {
469549
await mempool.close();
470550
await chain.close();

test/util/memwallet.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1669,8 +1669,6 @@ class MemWallet {
16691669
async _create(mtx, options) {
16701670
await this.fund(mtx, options);
16711671

1672-
assert(mtx.getFee() <= MTX.Selector.MAX_FEE, 'TX exceeds MAX_FEE.');
1673-
16741672
mtx.sortMembers();
16751673

16761674
if (options && options.locktime != null)

test/wallet-auction-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,7 @@ describe('Wallet Auction', function() {
587587
actions.push(['NONE', addr, 10000]);
588588
}
589589

590-
const batch = await wallet.sendBatch(actions, {hardFee: 10});
590+
const batch = await wallet.sendBatch(actions, {hardFee: 1000});
591591

592592
assert.strictEqual(batch.outputs.length, 11);
593593

@@ -597,7 +597,7 @@ describe('Wallet Auction', function() {
597597

598598
const newBal = await wallet.getBalance();
599599

600-
assert.strictEqual(oldBal.confirmed - newBal.confirmed, 100010);
600+
assert.strictEqual(oldBal.confirmed - newBal.confirmed, 101000);
601601
});
602602

603603
it('should verify expected name properties', async () => {

0 commit comments

Comments
 (0)