mirror of
https://github.com/mempool/mempool.git
synced 2026-08-13 12:33:11 +02:00
Merge pull request #6089 from mempool/mononaut/fractional-fees
fractional fee API
This commit is contained in:
commit
ac17d28f6a
3 changed files with 64 additions and 26 deletions
|
|
@ -51,7 +51,7 @@ describe('Mempool Backend Config', () => {
|
|||
MAX_PUSH_TX_SIZE_WEIGHT: 400000,
|
||||
ALLOW_UNREACHABLE: true,
|
||||
PRICE_UPDATES_PER_HOUR: 1,
|
||||
MAX_TRACKED_ADDRESSES: 1,
|
||||
MAX_TRACKED_ADDRESSES: 1
|
||||
});
|
||||
|
||||
expect(config.ELECTRUM).toStrictEqual({ HOST: '127.0.0.1', PORT: 3306, TLS_ENABLED: true });
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ class BitcoinRoutes {
|
|||
.get(config.MEMPOOL.API_URL_PREFIX + 'cpfp/:txId', this.$getCpfpInfo)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'difficulty-adjustment', this.getDifficultyChange)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'fees/recommended', this.getRecommendedFees)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'fees/precise', this.getPreciseRecommendedFees)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'fees/mempool-blocks', this.getMempoolBlocks)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'backend-info', this.getBackendInfo)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'init-data', this.getInitData)
|
||||
|
|
@ -122,6 +123,26 @@ class BitcoinRoutes {
|
|||
res.json(result);
|
||||
}
|
||||
|
||||
private getPreciseRecommendedFees(req: Request, res: Response) {
|
||||
if (!mempool.isInSync()) {
|
||||
res.statusCode = 503;
|
||||
res.send('Service Unavailable');
|
||||
return;
|
||||
}
|
||||
let minFee = 0;
|
||||
if (req.query.min) {
|
||||
try {
|
||||
minFee = parseFloat(req.query.min as string);
|
||||
} catch (e) {
|
||||
res.statusCode = 400;
|
||||
res.send('Invalid minimum fee');
|
||||
return;
|
||||
}
|
||||
}
|
||||
const result = feeApi.getPreciseRecommendedFee(minFee);
|
||||
res.json(result);
|
||||
}
|
||||
|
||||
private getMempoolBlocks(req: Request, res: Response) {
|
||||
try {
|
||||
const result = mempoolBlocks.getMempoolBlocks();
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ interface RecommendedFees {
|
|||
class FeeApi {
|
||||
constructor() { }
|
||||
|
||||
defaultFee = isLiquid ? 0.1 : 1;
|
||||
minimumIncrement = isLiquid ? 0.1 : 1;
|
||||
|
||||
public getRecommendedFee(): RecommendedFees {
|
||||
|
|
@ -27,24 +26,36 @@ class FeeApi {
|
|||
return this.calculateRecommendedFee(pBlocks, mPool);
|
||||
}
|
||||
|
||||
public calculateRecommendedFee(pBlocks: MempoolBlock[], mPool: IBitcoinApi.MempoolInfo): RecommendedFees {
|
||||
const minimumFee = this.roundUpToNearest(mPool.mempoolminfee * 100000, this.minimumIncrement);
|
||||
const defaultMinFee = Math.max(minimumFee, this.defaultFee);
|
||||
public getPreciseRecommendedFee(minimum: number = 0): RecommendedFees {
|
||||
const pBlocks = projectedBlocks.getMempoolBlocks();
|
||||
const mPool = mempool.getMempoolInfo();
|
||||
|
||||
// minimum non-zero minrelaytxfee / incrementalrelayfee is 1 sat/kvB = 0.001 sat/vB
|
||||
return this.calculateRecommendedFee(pBlocks, mPool, minimum, 0.001);
|
||||
}
|
||||
|
||||
public calculateRecommendedFee(pBlocks: MempoolBlock[], mPool: IBitcoinApi.MempoolInfo, minimumRecommendation: number = 0, minIncrement: number = this.minimumIncrement): RecommendedFees {
|
||||
const purgeRate = this.roundUpToNearest(mPool.mempoolminfee * 100000, minIncrement);
|
||||
const minimumFee = Math.max(purgeRate, minimumRecommendation, minIncrement);
|
||||
|
||||
if (!pBlocks.length) {
|
||||
return {
|
||||
'fastestFee': defaultMinFee,
|
||||
'halfHourFee': defaultMinFee,
|
||||
'hourFee': defaultMinFee,
|
||||
'fastestFee': minimumFee,
|
||||
'halfHourFee': minimumFee,
|
||||
'hourFee': minimumFee,
|
||||
'economyFee': minimumFee,
|
||||
'minimumFee': minimumFee,
|
||||
};
|
||||
}
|
||||
|
||||
const firstMedianFee = this.optimizeMedianFee(pBlocks[0], pBlocks[1]);
|
||||
const secondMedianFee = pBlocks[1] ? this.optimizeMedianFee(pBlocks[1], pBlocks[2], firstMedianFee) : this.defaultFee;
|
||||
const thirdMedianFee = pBlocks[2] ? this.optimizeMedianFee(pBlocks[2], pBlocks[3], secondMedianFee) : this.defaultFee;
|
||||
const firstMedianFee = this.optimizeMedianFee(pBlocks[0], pBlocks[1], undefined, minimumFee, minIncrement);
|
||||
const secondMedianFee = pBlocks[1] ? this.optimizeMedianFee(pBlocks[1], pBlocks[2], firstMedianFee, minimumFee, minIncrement) : minimumFee;
|
||||
const thirdMedianFee = pBlocks[2] ? this.optimizeMedianFee(pBlocks[2], pBlocks[3], secondMedianFee, minimumFee, minIncrement) : minimumFee;
|
||||
|
||||
// explicitly enforce a minimum of ceil(mempoolminfee) on all recommendations.
|
||||
// simply rounding up recommended rates is insufficient, as the purging rate
|
||||
// can exceed the median rate of projected blocks in some extreme scenarios
|
||||
// (see https://bitcoin.stackexchange.com/a/120024)
|
||||
let fastestFee = Math.max(minimumFee, firstMedianFee);
|
||||
let halfHourFee = Math.max(minimumFee, secondMedianFee);
|
||||
let hourFee = Math.max(minimumFee, thirdMedianFee);
|
||||
|
|
@ -55,33 +66,39 @@ class FeeApi {
|
|||
halfHourFee = Math.max(halfHourFee, hourFee, economyFee);
|
||||
hourFee = Math.max(hourFee, economyFee);
|
||||
|
||||
// explicitly enforce a minimum of ceil(mempoolminfee) on all recommendations.
|
||||
// simply rounding up recommended rates is insufficient, as the purging rate
|
||||
// can exceed the median rate of projected blocks in some extreme scenarios
|
||||
// (see https://bitcoin.stackexchange.com/a/120024)
|
||||
return {
|
||||
'fastestFee': fastestFee,
|
||||
'halfHourFee': halfHourFee,
|
||||
'hourFee': hourFee,
|
||||
'economyFee': economyFee,
|
||||
'minimumFee': minimumFee,
|
||||
'fastestFee': this.roundToNearest(fastestFee, minIncrement),
|
||||
'halfHourFee': this.roundToNearest(halfHourFee, minIncrement),
|
||||
'hourFee': this.roundToNearest(hourFee, minIncrement),
|
||||
'economyFee': this.roundToNearest(economyFee, minIncrement),
|
||||
'minimumFee': this.roundToNearest(minimumFee, minIncrement),
|
||||
};
|
||||
}
|
||||
|
||||
private optimizeMedianFee(pBlock: MempoolBlock, nextBlock: MempoolBlock | undefined, previousFee?: number): number {
|
||||
private optimizeMedianFee(pBlock: MempoolBlock, nextBlock: MempoolBlock | undefined, previousFee: number | undefined, minFee: number, minIncrement: number = this.minimumIncrement): number {
|
||||
const useFee = previousFee ? (pBlock.medianFee + previousFee) / 2 : pBlock.medianFee;
|
||||
if (pBlock.blockVSize <= 500000 || pBlock.medianFee < 1) {
|
||||
return this.defaultFee;
|
||||
if (pBlock.blockVSize <= 500000 || pBlock.medianFee < minFee) {
|
||||
return minFee;
|
||||
}
|
||||
if (pBlock.blockVSize <= 950000 && !nextBlock) {
|
||||
const multiplier = (pBlock.blockVSize - 500000) / 500000;
|
||||
return Math.max(Math.round(useFee * multiplier), this.defaultFee);
|
||||
return Math.max(this.roundToNearest(useFee * multiplier, minIncrement), minFee);
|
||||
}
|
||||
return this.roundUpToNearest(useFee, this.minimumIncrement);
|
||||
return Math.max(this.roundUpToNearest(useFee, minIncrement), minFee);
|
||||
}
|
||||
|
||||
private roundUpToNearest(value: number, nearest: number): number {
|
||||
return Math.ceil(value / nearest) * nearest;
|
||||
if (nearest !== 0) {
|
||||
return Math.ceil(value / nearest) * nearest;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private roundToNearest(value: number, nearest: number): number {
|
||||
if (nearest !== 0) {
|
||||
return Math.round(value / nearest) * nearest;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue