Source: mempool/fees.js

  1. /*!
  2. * fees.js - fee estimation for bcoin
  3. * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
  4. * https://github.com/bcoin-org/bcoin
  5. * Ported from:
  6. * https://github.com/bitcoin/bitcoin/blob/master/src/policy/fees.cpp
  7. */
  8. 'use strict';
  9. const assert = require('bsert');
  10. const bio = require('bufio');
  11. const Logger = require('blgr');
  12. const {BufferMap} = require('buffer-map');
  13. const binary = require('../utils/binary');
  14. const consensus = require('../protocol/consensus');
  15. const policy = require('../protocol/policy');
  16. const {encoding} = bio;
  17. /*
  18. * Constants
  19. */
  20. const MAX_BLOCK_CONFIRMS = 15; /* 25 */
  21. const DEFAULT_DECAY = 0.998;
  22. const MIN_SUCCESS_PCT = 0.95;
  23. const UNLIKELY_PCT = 0.5;
  24. const SUFFICIENT_FEETXS = 1;
  25. const SUFFICIENT_PRITXS = 0.2;
  26. const MIN_FEERATE = 10;
  27. const MAX_FEERATE = 1e6; /* 1e7 */
  28. const INF_FEERATE = consensus.MAX_MONEY;
  29. const MIN_PRIORITY = 10;
  30. const MAX_PRIORITY = 1e16;
  31. const INF_PRIORITY = 1e9 * consensus.MAX_MONEY;
  32. const FEE_SPACING = 1.1;
  33. const PRI_SPACING = 2;
  34. /**
  35. * Confirmation stats.
  36. * @alias module:mempool.ConfirmStats
  37. */
  38. class ConfirmStats {
  39. /**
  40. * Create confirmation stats.
  41. * @constructor
  42. * @param {String} type
  43. * @param {Logger?} logger
  44. */
  45. constructor(type, logger) {
  46. this.logger = Logger.global;
  47. this.type = type;
  48. this.decay = 0;
  49. this.maxConfirms = 0;
  50. this.buckets = new Float64Array(0);
  51. this.bucketMap = new DoubleMap();
  52. this.confAvg = [];
  53. this.curBlockConf = [];
  54. this.unconfTX = [];
  55. this.oldUnconfTX = new Int32Array(0);
  56. this.curBlockTX = new Int32Array(0);
  57. this.txAvg = new Float64Array(0);
  58. this.curBlockVal = new Float64Array(0);
  59. this.avg = new Float64Array(0);
  60. if (logger) {
  61. assert(typeof logger === 'object');
  62. this.logger = logger.context('fees');
  63. }
  64. }
  65. /**
  66. * Initialize stats.
  67. * @param {Array} buckets
  68. * @param {Number} maxConfirms
  69. * @param {Number} decay
  70. * @private
  71. */
  72. init(buckets, maxConfirms, decay) {
  73. this.maxConfirms = maxConfirms;
  74. this.decay = decay;
  75. this.buckets = new Float64Array(buckets.length);
  76. this.bucketMap = new DoubleMap();
  77. for (let i = 0; i < buckets.length; i++) {
  78. this.buckets[i] = buckets[i];
  79. this.bucketMap.insert(buckets[i], i);
  80. }
  81. this.confAvg = new Array(maxConfirms);
  82. this.curBlockConf = new Array(maxConfirms);
  83. this.unconfTX = new Array(maxConfirms);
  84. for (let i = 0; i < maxConfirms; i++) {
  85. this.confAvg[i] = new Float64Array(buckets.length);
  86. this.curBlockConf[i] = new Int32Array(buckets.length);
  87. this.unconfTX[i] = new Int32Array(buckets.length);
  88. }
  89. this.oldUnconfTX = new Int32Array(buckets.length);
  90. this.curBlockTX = new Int32Array(buckets.length);
  91. this.txAvg = new Float64Array(buckets.length);
  92. this.curBlockVal = new Float64Array(buckets.length);
  93. this.avg = new Float64Array(buckets.length);
  94. }
  95. /**
  96. * Clear data for the current block.
  97. * @param {Number} height
  98. */
  99. clearCurrent(height) {
  100. for (let i = 0; i < this.buckets.length; i++) {
  101. this.oldUnconfTX[i] = this.unconfTX[height % this.unconfTX.length][i];
  102. this.unconfTX[height % this.unconfTX.length][i] = 0;
  103. for (let j = 0; j < this.curBlockConf.length; j++)
  104. this.curBlockConf[j][i] = 0;
  105. this.curBlockTX[i] = 0;
  106. this.curBlockVal[i] = 0;
  107. }
  108. }
  109. /**
  110. * Record a rate or priority based on number of blocks to confirm.
  111. * @param {Number} blocks - Blocks to confirm.
  112. * @param {Rate|Number} val - Rate or priority.
  113. */
  114. record(blocks, val) {
  115. if (blocks < 1)
  116. return;
  117. const bucketIndex = this.bucketMap.search(val);
  118. for (let i = blocks; i <= this.curBlockConf.length; i++)
  119. this.curBlockConf[i - 1][bucketIndex]++;
  120. this.curBlockTX[bucketIndex]++;
  121. this.curBlockVal[bucketIndex] += val;
  122. }
  123. /**
  124. * Update moving averages.
  125. */
  126. updateAverages() {
  127. for (let i = 0; i < this.buckets.length; i++) {
  128. for (let j = 0; j < this.confAvg.length; j++) {
  129. this.confAvg[j][i] =
  130. this.confAvg[j][i] * this.decay + this.curBlockConf[j][i];
  131. }
  132. this.avg[i] = this.avg[i] * this.decay + this.curBlockVal[i];
  133. this.txAvg[i] = this.txAvg[i] * this.decay + this.curBlockTX[i];
  134. }
  135. }
  136. /**
  137. * Estimate the median value for rate or priority.
  138. * @param {Number} target - Confirmation target.
  139. * @param {Number} needed - Sufficient tx value.
  140. * @param {Number} breakpoint - Success break point.
  141. * @param {Boolean} greater - Whether to look for lowest value.
  142. * @param {Number} height - Block height.
  143. * @returns {Rate|Number} Returns -1 on error.
  144. */
  145. estimateMedian(target, needed, breakpoint, greater, height) {
  146. const max = this.buckets.length - 1;
  147. const start = greater ? max : 0;
  148. const step = greater ? -1 : 1;
  149. const bins = this.unconfTX.length;
  150. let conf = 0;
  151. let total = 0;
  152. let extra = 0;
  153. let near = start;
  154. let far = start;
  155. let bestNear = start;
  156. let bestFar = start;
  157. let found = false;
  158. let median = -1;
  159. let sum = 0;
  160. for (let i = start; i >= 0 && i <= max; i += step) {
  161. far = i;
  162. conf += this.confAvg[target - 1][i];
  163. total += this.txAvg[i];
  164. for (let j = target; j < this.maxConfirms; j++)
  165. extra += this.unconfTX[Math.max(height - j, 0) % bins][i];
  166. extra += this.oldUnconfTX[i];
  167. if (total >= needed / (1 - this.decay)) {
  168. const perc = conf / (total + extra);
  169. if (greater && perc < breakpoint)
  170. break;
  171. if (!greater && perc > breakpoint)
  172. break;
  173. found = true;
  174. conf = 0;
  175. total = 0;
  176. extra = 0;
  177. bestNear = near;
  178. bestFar = far;
  179. near = i + step;
  180. }
  181. }
  182. const minBucket = bestNear < bestFar ? bestNear : bestFar;
  183. const maxBucket = bestNear > bestFar ? bestNear : bestFar;
  184. for (let i = minBucket; i <= maxBucket; i++)
  185. sum += this.txAvg[i];
  186. if (found && sum !== 0) {
  187. sum = sum / 2;
  188. for (let j = minBucket; j <= maxBucket; j++) {
  189. if (this.txAvg[j] < sum) {
  190. sum -= this.txAvg[j];
  191. } else {
  192. median = this.avg[j] / this.txAvg[j];
  193. break;
  194. }
  195. }
  196. }
  197. return median;
  198. }
  199. /**
  200. * Add a transaction's rate/priority to be tracked.
  201. * @param {Number} height - Block height.
  202. * @param {Number} val
  203. * @returns {Number} Bucket index.
  204. */
  205. addTX(height, val) {
  206. const bucketIndex = this.bucketMap.search(val);
  207. const blockIndex = height % this.unconfTX.length;
  208. this.unconfTX[blockIndex][bucketIndex]++;
  209. this.logger.spam('Adding tx to %s.', this.type);
  210. return bucketIndex;
  211. }
  212. /**
  213. * Remove a transaction from tracking.
  214. * @param {Number} entryHeight
  215. * @param {Number} bestHeight
  216. * @param {Number} bucketIndex
  217. */
  218. removeTX(entryHeight, bestHeight, bucketIndex) {
  219. let blocksAgo = bestHeight - entryHeight;
  220. if (bestHeight === 0)
  221. blocksAgo = 0;
  222. if (blocksAgo < 0) {
  223. this.logger.debug('Blocks ago is negative for mempool tx.');
  224. return;
  225. }
  226. if (blocksAgo >= this.unconfTX.length) {
  227. if (this.oldUnconfTX[bucketIndex] > 0) {
  228. this.oldUnconfTX[bucketIndex]--;
  229. } else {
  230. this.logger.debug('Mempool tx removed >25 blocks (bucket=%d).',
  231. bucketIndex);
  232. }
  233. } else {
  234. const blockIndex = entryHeight % this.unconfTX.length;
  235. if (this.unconfTX[blockIndex][bucketIndex] > 0) {
  236. this.unconfTX[blockIndex][bucketIndex]--;
  237. } else {
  238. this.logger.debug('Mempool tx removed (block=%d, bucket=%d).',
  239. blockIndex, bucketIndex);
  240. }
  241. }
  242. }
  243. /**
  244. * Get serialization size.
  245. * @returns {Number}
  246. */
  247. getSize() {
  248. let size = 0;
  249. size += 8;
  250. size += sizeArray(this.buckets);
  251. size += sizeArray(this.avg);
  252. size += sizeArray(this.txAvg);
  253. size += encoding.sizeVarint(this.maxConfirms);
  254. for (let i = 0; i < this.maxConfirms; i++)
  255. size += sizeArray(this.confAvg[i]);
  256. return size;
  257. }
  258. /**
  259. * Serialize confirm stats.
  260. * @returns {Buffer}
  261. */
  262. toRaw() {
  263. const size = this.getSize();
  264. const bw = bio.write(size);
  265. bw.writeDouble(this.decay);
  266. writeArray(bw, this.buckets);
  267. writeArray(bw, this.avg);
  268. writeArray(bw, this.txAvg);
  269. bw.writeVarint(this.maxConfirms);
  270. for (let i = 0; i < this.maxConfirms; i++)
  271. writeArray(bw, this.confAvg[i]);
  272. return bw.render();
  273. }
  274. /**
  275. * Inject properties from serialized data.
  276. * @private
  277. * @param {Buffer} data
  278. * @returns {ConfirmStats}
  279. */
  280. fromRaw(data) {
  281. const br = bio.read(data);
  282. const decay = br.readDouble();
  283. const buckets = readArray(br);
  284. const avg = readArray(br);
  285. const txAvg = readArray(br);
  286. const maxConfirms = br.readVarint();
  287. const confAvg = new Array(maxConfirms);
  288. for (let i = 0; i < maxConfirms; i++)
  289. confAvg[i] = readArray(br);
  290. if (decay <= 0 || decay >= 1)
  291. throw new Error('Decay must be between 0 and 1 (non-inclusive).');
  292. if (buckets.length <= 1 || buckets.length > 1000)
  293. throw new Error('Must have between 2 and 1000 fee/pri buckets.');
  294. if (avg.length !== buckets.length)
  295. throw new Error('Mismatch in fee/pri average bucket count.');
  296. if (txAvg.length !== buckets.length)
  297. throw new Error('Mismatch in tx count bucket count.');
  298. if (maxConfirms <= 0 || maxConfirms > 6 * 24 * 7)
  299. throw new Error('Must maintain estimates for between 1-1008 confirms.');
  300. for (let i = 0; i < maxConfirms; i++) {
  301. if (confAvg[i].length !== buckets.length)
  302. throw new Error('Mismatch in fee/pri conf average bucket count.');
  303. }
  304. this.init(buckets, maxConfirms, decay);
  305. this.avg = avg;
  306. this.txAvg = txAvg;
  307. this.confAvg = confAvg;
  308. return this;
  309. }
  310. /**
  311. * Instantiate confirm stats from serialized data.
  312. * @param {Buffer} data
  313. * @param {String} type
  314. * @param {Logger?} logger
  315. * @returns {ConfirmStats}
  316. */
  317. static fromRaw(data, type, logger) {
  318. return new this(type, logger).fromRaw(data);
  319. }
  320. }
  321. /**
  322. * Policy Estimator
  323. * Estimator for fees and priority.
  324. * @alias module:mempool.PolicyEstimator
  325. */
  326. class PolicyEstimator {
  327. /**
  328. * Create an estimator.
  329. * @constructor
  330. * @param {Logger?} logger
  331. */
  332. constructor(logger) {
  333. this.logger = Logger.global;
  334. this.minTrackedFee = MIN_FEERATE;
  335. this.minTrackedPri = MIN_PRIORITY;
  336. this.feeStats = new ConfirmStats('FeeRate');
  337. this.priStats = new ConfirmStats('Priority');
  338. this.feeUnlikely = 0;
  339. this.feeLikely = INF_FEERATE;
  340. this.priUnlikely = 0;
  341. this.priLikely = INF_PRIORITY;
  342. this.map = new BufferMap();
  343. this.bestHeight = 0;
  344. if (policy.MIN_RELAY >= MIN_FEERATE)
  345. this.minTrackedFee = policy.MIN_RELAY;
  346. if (policy.FREE_THRESHOLD >= MIN_PRIORITY)
  347. this.minTrackedPri = policy.FREE_THRESHOLD;
  348. if (logger) {
  349. assert(typeof logger === 'object');
  350. this.logger = logger.context('fees');
  351. this.feeStats.logger = this.logger;
  352. this.priStats.logger = this.logger;
  353. }
  354. }
  355. /**
  356. * Initialize the estimator.
  357. * @private
  358. */
  359. init() {
  360. const minFee = this.minTrackedFee;
  361. const minPri = this.minTrackedPri;
  362. const fee = [];
  363. for (let b = minFee; b <= MAX_FEERATE; b *= FEE_SPACING)
  364. fee.push(b);
  365. fee.push(INF_FEERATE);
  366. const priority = [];
  367. for (let b = minPri; b <= MAX_PRIORITY; b *= PRI_SPACING)
  368. priority.push(b);
  369. priority.push(INF_PRIORITY);
  370. this.feeStats.init(fee, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY);
  371. this.priStats.init(priority, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY);
  372. }
  373. /**
  374. * Reset the estimator.
  375. */
  376. reset() {
  377. this.feeUnlikely = 0;
  378. this.feeLikely = INF_FEERATE;
  379. this.priUnlikely = 0;
  380. this.priLikely = INF_PRIORITY;
  381. this.map.clear();
  382. this.bestHeight = 0;
  383. this.init();
  384. }
  385. /**
  386. * Stop tracking a tx. Remove from map.
  387. * @param {Hash} hash
  388. */
  389. removeTX(hash) {
  390. const item = this.map.get(hash);
  391. if (!item) {
  392. this.logger.spam('Mempool tx %h not found.', hash);
  393. return;
  394. }
  395. this.feeStats.removeTX(item.blockHeight, this.bestHeight, item.bucketIndex);
  396. this.map.delete(hash);
  397. }
  398. /**
  399. * Test whether a fee should be used for calculation.
  400. * @param {Amount} fee
  401. * @param {Number} priority
  402. * @returns {Boolean}
  403. */
  404. isFeePoint(fee, priority) {
  405. if ((priority < this.minTrackedPri && fee >= this.minTrackedFee)
  406. || (priority < this.priUnlikely && fee > this.feeLikely)) {
  407. return true;
  408. }
  409. return false;
  410. }
  411. /**
  412. * Test whether a priority should be used for calculation.
  413. * @param {Amount} fee
  414. * @param {Number} priority
  415. * @returns {Boolean}
  416. */
  417. isPriPoint(fee, priority) {
  418. if ((fee < this.minTrackedFee && priority >= this.minTrackedPri)
  419. || (fee < this.feeUnlikely && priority > this.priLikely)) {
  420. return true;
  421. }
  422. return false;
  423. }
  424. /**
  425. * Process a mempool entry.
  426. * @param {MempoolEntry} entry
  427. * @param {Boolean} current - Whether the chain is synced.
  428. */
  429. processTX(entry, current) {
  430. const height = entry.height;
  431. const hash = entry.hash();
  432. if (this.map.has(hash)) {
  433. this.logger.debug('Mempool tx %h already tracked.', entry.hash());
  434. return;
  435. }
  436. // Ignore reorgs.
  437. if (height < this.bestHeight)
  438. return;
  439. // Wait for chain to sync.
  440. if (!current)
  441. return;
  442. // Requires other mempool txs in order to be confirmed. Ignore.
  443. if (entry.dependencies)
  444. return;
  445. const fee = entry.getFee();
  446. const rate = entry.getRate();
  447. const priority = entry.getPriority(height);
  448. this.logger.spam('Processing mempool tx %h.', entry.hash());
  449. if (fee === 0 || this.isPriPoint(rate, priority)) {
  450. const item = new StatEntry();
  451. item.blockHeight = height;
  452. item.bucketIndex = this.priStats.addTX(height, priority);
  453. this.map.set(hash, item);
  454. } else if (this.isFeePoint(rate, priority)) {
  455. const item = new StatEntry();
  456. item.blockHeight = height;
  457. item.bucketIndex = this.feeStats.addTX(height, rate);
  458. this.map.set(hash, item);
  459. } else {
  460. this.logger.spam('Not adding tx %h.', entry.hash());
  461. }
  462. }
  463. /**
  464. * Process an entry being removed from the mempool.
  465. * @param {Number} height - Block height.
  466. * @param {MempoolEntry} entry
  467. */
  468. processBlockTX(height, entry) {
  469. // Requires other mempool txs in order to be confirmed. Ignore.
  470. if (entry.dependencies)
  471. return;
  472. const blocks = height - entry.height;
  473. if (blocks <= 0) {
  474. this.logger.debug(
  475. 'Block tx %h had negative blocks to confirm (%d, %d).',
  476. entry.hash(),
  477. height,
  478. entry.height);
  479. return;
  480. }
  481. const fee = entry.getFee();
  482. const rate = entry.getRate();
  483. const priority = entry.getPriority(height);
  484. if (fee === 0 || this.isPriPoint(rate, priority))
  485. this.priStats.record(blocks, priority);
  486. else if (this.isFeePoint(rate, priority))
  487. this.feeStats.record(blocks, rate);
  488. }
  489. /**
  490. * Process a block of transaction entries being removed from the mempool.
  491. * @param {Number} height - Block height.
  492. * @param {MempoolEntry[]} entries
  493. * @param {Boolean} current - Whether the chain is synced.
  494. */
  495. processBlock(height, entries, current) {
  496. // Ignore reorgs.
  497. if (height <= this.bestHeight)
  498. return;
  499. this.bestHeight = height;
  500. if (entries.length === 0)
  501. return;
  502. // Wait for chain to sync.
  503. if (!current)
  504. return;
  505. this.logger.debug('Recalculating dynamic cutoffs.');
  506. this.feeLikely = this.feeStats.estimateMedian(
  507. 2, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT,
  508. true, height);
  509. if (this.feeLikely === -1)
  510. this.feeLikely = INF_FEERATE;
  511. this.feeUnlikely = this.feeStats.estimateMedian(
  512. 10, SUFFICIENT_FEETXS, UNLIKELY_PCT,
  513. false, height);
  514. if (this.feeUnlikely === -1)
  515. this.feeUnlikely = 0;
  516. this.priLikely = this.priStats.estimateMedian(
  517. 2, SUFFICIENT_PRITXS, MIN_SUCCESS_PCT,
  518. true, height);
  519. if (this.priLikely === -1)
  520. this.priLikely = INF_PRIORITY;
  521. this.priUnlikely = this.priStats.estimateMedian(
  522. 10, SUFFICIENT_PRITXS, UNLIKELY_PCT,
  523. false, height);
  524. if (this.priUnlikely === -1)
  525. this.priUnlikely = 0;
  526. this.feeStats.clearCurrent(height);
  527. this.priStats.clearCurrent(height);
  528. for (const entry of entries)
  529. this.processBlockTX(height, entry);
  530. this.feeStats.updateAverages();
  531. this.priStats.updateAverages();
  532. this.logger.debug('Done updating estimates'
  533. + ' for %d confirmed entries. New mempool map size %d.',
  534. entries.length, this.map.size);
  535. this.logger.debug('New fee rate: %d.', this.estimateFee());
  536. }
  537. /**
  538. * Estimate a fee rate.
  539. * @param {Number} [target=1] - Confirmation target.
  540. * @param {Boolean} [smart=true] - Smart estimation.
  541. * @returns {Rate}
  542. */
  543. estimateFee(target, smart) {
  544. if (!target)
  545. target = 1;
  546. if (smart == null)
  547. smart = true;
  548. assert((target >>> 0) === target, 'Target must be a number.');
  549. assert(target <= this.feeStats.maxConfirms,
  550. 'Too many confirmations for estimate.');
  551. if (!smart) {
  552. const rate = this.feeStats.estimateMedian(
  553. target, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT,
  554. true, this.bestHeight);
  555. if (rate < 0)
  556. return 0;
  557. return Math.floor(rate);
  558. }
  559. let rate = -1;
  560. while (rate < 0 && target <= this.feeStats.maxConfirms) {
  561. rate = this.feeStats.estimateMedian(
  562. target++, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT,
  563. true, this.bestHeight);
  564. }
  565. target -= 1;
  566. if (rate < 0)
  567. return 0;
  568. return Math.floor(rate);
  569. }
  570. /**
  571. * Estimate a priority.
  572. * @param {Number} [target=1] - Confirmation target.
  573. * @param {Boolean} [smart=true] - Smart estimation.
  574. * @returns {Number}
  575. */
  576. estimatePriority(target, smart) {
  577. if (!target)
  578. target = 1;
  579. if (smart == null)
  580. smart = true;
  581. assert((target >>> 0) === target, 'Target must be a number.');
  582. assert(target <= this.priStats.maxConfirms,
  583. 'Too many confirmations for estimate.');
  584. if (!smart) {
  585. const priority = this.priStats.estimateMedian(
  586. target, SUFFICIENT_PRITXS, MIN_SUCCESS_PCT,
  587. true, this.bestHeight);
  588. return Math.floor(priority);
  589. }
  590. let priority = -1;
  591. while (priority < 0 && target <= this.priStats.maxConfirms) {
  592. priority = this.priStats.estimateMedian(
  593. target++, SUFFICIENT_PRITXS, MIN_SUCCESS_PCT,
  594. true, this.bestHeight);
  595. }
  596. target -= 1;
  597. if (priority < 0)
  598. return 0;
  599. return Math.floor(priority);
  600. }
  601. /**
  602. * Get serialization size.
  603. * @returns {Number}
  604. */
  605. getSize() {
  606. let size = 0;
  607. size += 5;
  608. size += encoding.sizeVarlen(this.feeStats.getSize());
  609. return size;
  610. }
  611. /**
  612. * Serialize the estimator.
  613. * @returns {Buffer}
  614. */
  615. toRaw() {
  616. const size = this.getSize();
  617. const bw = bio.write(size);
  618. bw.writeU8(PolicyEstimator.VERSION);
  619. bw.writeU32(this.bestHeight);
  620. bw.writeVarBytes(this.feeStats.toRaw());
  621. return bw.render();
  622. }
  623. /**
  624. * Inject properties from serialized data.
  625. * @private
  626. * @param {Buffer} data
  627. * @returns {PolicyEstimator}
  628. */
  629. fromRaw(data) {
  630. const br = bio.read(data);
  631. if (br.readU8() !== PolicyEstimator.VERSION)
  632. throw new Error('Bad serialization version for estimator.');
  633. this.bestHeight = br.readU32();
  634. this.feeStats.fromRaw(br.readVarBytes());
  635. return this;
  636. }
  637. /**
  638. * Instantiate a policy estimator from serialized data.
  639. * @param {Buffer} data
  640. * @param {Logger?} logger
  641. * @returns {PolicyEstimator}
  642. */
  643. static fromRaw(data, logger) {
  644. return new this(logger).fromRaw(data);
  645. }
  646. /**
  647. * Inject properties from estimator.
  648. * @param {PolicyEstimator} estimator
  649. * @returns {PolicyEstimator}
  650. */
  651. inject(estimator) {
  652. this.bestHeight = estimator.bestHeight;
  653. this.feeStats = estimator.feeStats;
  654. return this;
  655. }
  656. }
  657. /**
  658. * Serialization version.
  659. * @const {Number}
  660. * @default
  661. */
  662. PolicyEstimator.VERSION = 0;
  663. /**
  664. * Stat Entry
  665. * @alias module:mempool.StatEntry
  666. * @ignore
  667. */
  668. class StatEntry {
  669. /**
  670. * StatEntry
  671. * @constructor
  672. */
  673. constructor() {
  674. this.blockHeight = -1;
  675. this.bucketIndex = -1;
  676. }
  677. }
  678. /**
  679. * Double Map
  680. * @alias module:mempool.DoubleMap
  681. * @ignore
  682. */
  683. class DoubleMap {
  684. /**
  685. * DoubleMap
  686. * @constructor
  687. */
  688. constructor() {
  689. this.buckets = [];
  690. }
  691. insert(key, value) {
  692. const i = binary.search(this.buckets, key, compare, true);
  693. this.buckets.splice(i, 0, [key, value]);
  694. }
  695. search(key) {
  696. assert(this.buckets.length !== 0, 'Cannot search.');
  697. const i = binary.search(this.buckets, key, compare, true);
  698. return this.buckets[i][1];
  699. }
  700. }
  701. /*
  702. * Helpers
  703. */
  704. function compare(a, b) {
  705. return a[0] - b;
  706. }
  707. function sizeArray(buckets) {
  708. const size = encoding.sizeVarint(buckets.length);
  709. return size + buckets.length * 8;
  710. }
  711. function writeArray(bw, buckets) {
  712. bw.writeVarint(buckets.length);
  713. for (let i = 0; i < buckets.length; i++)
  714. bw.writeDouble(buckets[i]);
  715. }
  716. function readArray(br) {
  717. const buckets = new Float64Array(br.readVarint());
  718. for (let i = 0; i < buckets.length; i++)
  719. buckets[i] = br.readDouble();
  720. return buckets;
  721. }
  722. /*
  723. * Expose
  724. */
  725. module.exports = PolicyEstimator;