From 509e658c8c8ad71cf6ed7125cff4a427b1296ae6 Mon Sep 17 00:00:00 2001 From: Tom Kirkpatrick Date: Mon, 19 Feb 2024 19:24:04 +0000 Subject: [PATCH] Ensure mempool estimates take precedence --- src/lib/DataProviderManager.ts | 28 ++++++++++++++----- src/providers/esplora.ts | 6 ++--- src/providers/mempool.ts | 4 +-- test/DataProviderManager.test.ts | 46 ++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 11 deletions(-) diff --git a/src/lib/DataProviderManager.ts b/src/lib/DataProviderManager.ts index 976286a..fbfc824 100644 --- a/src/lib/DataProviderManager.ts +++ b/src/lib/DataProviderManager.ts @@ -1,6 +1,7 @@ import NodeCache from "node-cache"; import { LOGLEVEL } from "./util"; import { logger } from "./logger"; +import { MempoolProvider } from "../providers/mempool"; const log = logger(LOGLEVEL); @@ -13,7 +14,7 @@ export class DataProviderManager { private cacheKey: string = "data"; constructor( - cacheConfig: CacheConfig, + cacheConfig: CacheConfig = { stdTTL: 0, checkperiod: 0 }, maxHeightDelta: number = 1, feeMultiplier: number = 1, feeMinimum: number = 1, @@ -113,13 +114,28 @@ export class DataProviderManager { * * @returns A promise that resolves to an array of sorted data points. */ - private async getSortedDataPoints(): Promise { + public async getSortedDataPoints(): Promise { const dataPoints = await this.fetchDataPoints(); - dataPoints.sort( - (a, b) => + dataPoints.sort((a, b) => { + // Prioritize mempool-based estimates + if ( + a.provider instanceof MempoolProvider && + !(b.provider instanceof MempoolProvider) + ) { + return -1; + } else if ( + !(a.provider instanceof MempoolProvider) && + b.provider instanceof MempoolProvider + ) { + return 1; + } + + // If both are the same type, sort by block height and then by provider order + return ( b.blockHeight - a.blockHeight || - this.providers.indexOf(a.provider) - this.providers.indexOf(b.provider), - ); + this.providers.indexOf(a.provider) - this.providers.indexOf(b.provider) + ); + }); return dataPoints; } diff --git a/src/providers/esplora.ts b/src/providers/esplora.ts index cc6f53f..0c41560 100644 --- a/src/providers/esplora.ts +++ b/src/providers/esplora.ts @@ -1,4 +1,4 @@ -import { fetchData, LOGLEVEL } from "../lib/util"; +import { fetchData, LOGLEVEL, TIMEOUT } from "../lib/util"; import { logger } from "../lib/logger"; const log = logger(LOGLEVEL); @@ -14,7 +14,7 @@ const log = logger(LOGLEVEL); * methods and properties for a data provider. * * @example - * const provider = new EsploraProvider('https://blockstream.info/api/'); + * const provider = new EsploraProvider('https://blockstream.info'); * const data = await provider.getAllData(); */ export class EsploraProvider implements Provider { @@ -32,7 +32,7 @@ export class EsploraProvider implements Provider { constructor( url: string, defaultDepth: number, - defaultTimeout: number = 5000, + defaultTimeout: number = TIMEOUT, ) { this.url = url; this.depth = defaultDepth; diff --git a/src/providers/mempool.ts b/src/providers/mempool.ts index e42d6cb..a953f63 100644 --- a/src/providers/mempool.ts +++ b/src/providers/mempool.ts @@ -1,4 +1,4 @@ -import { fetchData, LOGLEVEL } from "../lib/util"; +import { fetchData, LOGLEVEL, TIMEOUT } from "../lib/util"; import { logger } from "../lib/logger"; const log = logger(LOGLEVEL); @@ -29,7 +29,7 @@ export class MempoolProvider implements Provider { constructor( url: string, defaultDepth: number, - defaultTimeout: number = 5000, + defaultTimeout: number = TIMEOUT, ) { this.url = url; this.depth = defaultDepth; diff --git a/test/DataProviderManager.test.ts b/test/DataProviderManager.test.ts index e91338f..6c834fc 100644 --- a/test/DataProviderManager.test.ts +++ b/test/DataProviderManager.test.ts @@ -1,5 +1,7 @@ import { expect, test } from "bun:test"; import { DataProviderManager } from "../src/lib/DataProviderManager"; +import { MempoolProvider } from "../src/providers/mempool"; +import { EsploraProvider } from "../src/providers/esplora"; class MockProvider1 implements Provider { getBlockHeight = () => Promise.resolve(998); @@ -78,3 +80,47 @@ test("should merge fee estimates from multiple providers correctly", async () => "5": 3000, }); }); + +test("sorts providers correctly", async () => { + const mempoolProvider = new MempoolProvider("https://mempool.space", 6); + const esploraProvider = new EsploraProvider("https://blockstream.info", 6); + + // Register the providers in the correct order + const manager = new DataProviderManager(); + manager.registerProvider(mempoolProvider); + manager.registerProvider(esploraProvider); + const dataPoints = await manager.getSortedDataPoints(); + expect(dataPoints[0].provider).toBe(mempoolProvider); + expect(dataPoints[1].provider).toBe(esploraProvider); + + // Register the providers in reverse order + const manager2 = new DataProviderManager(); + manager2.registerProvider(esploraProvider); + manager2.registerProvider(mempoolProvider); + const dataPoints2 = await manager.getSortedDataPoints(); + expect(dataPoints2[0].provider).toBe(mempoolProvider); + expect(dataPoints2[1].provider).toBe(esploraProvider); + + // Register multiple mempool providers with different block heights + class mempool1 extends MempoolProvider { + getBlockHeight = () => Promise.resolve(999); + getBlockHash = () => Promise.resolve("hash1"); + getFeeEstimates = () => Promise.resolve({}); + } + class mempool2 extends MempoolProvider { + getBlockHeight = () => Promise.resolve(1000); + getBlockHash = () => Promise.resolve("hash1"); + getFeeEstimates = () => Promise.resolve({}); + } + class mempool3 extends MempoolProvider { + getBlockHeight = () => Promise.resolve(999); + getBlockHash = () => Promise.resolve("hash1"); + getFeeEstimates = () => Promise.resolve({}); + } + const manager3 = new DataProviderManager(); + manager3.registerProvider(new mempool1("https://mempool.space", 6)); + manager3.registerProvider(new mempool2("https://mempool.space", 6)); + manager3.registerProvider(new mempool3("https://mempool.space", 6)); + const dataPoints3 = await manager3.getSortedDataPoints(); + expect(dataPoints3[0].blockHeight).toBe(1000); +});