Skip to content

Commit

Permalink
General cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
mrfelton committed Jan 12, 2024
1 parent 55d302e commit ef3a617
Showing 1 changed file with 56 additions and 59 deletions.
115 changes: 56 additions & 59 deletions src/server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,45 +32,26 @@ console.info(`Using cache stdTTL: ${stdTTL}`);
console.info(`Using cache checkperiod: ${checkperiod}`);
console.info('---');

// Constants
const MEMPOOL_TIP_HASH_URL = `${mempoolBaseUrl}/api/blocks/tip/hash`;
const ESPLORA_TIP_HASH_URL = `${esploraBaseUrl}/api/blocks/tip/hash`;
const MEMPOOL_FEES_URL = `${mempoolBaseUrl}/api/v1/fees/recommended`;
const ESPLORA_FEE_ESTIMATES_URL = `${esploraBaseUrl}/api/fee-estimates`;

// Initialize the cache.
const cache = new NodeCache({ stdTTL: stdTTL, checkperiod: checkperiod });
const CACHE_KEY = 'estimates';

/**
* Fetches data from the given URL with a timeout.
*/
// async function fetchWithTimeout(url: string, timeout: number = TIMEOUT): Promise<Response> {
// const controller = new AbortController();
// const id = setTimeout(() => controller.abort(), timeout);

// console.debug(`Starting fetch request to ${url}`);

// try {
// const response = await fetch(url, { signal: controller.signal });
// clearTimeout(id);

// console.debug(`Successfully fetched data from ${url}`);
// return response;
// } catch (error: any) {
// if (error.name === 'AbortError') {
// console.error(`Fetch request to ${url} timed out after ${timeout} ms`);
// throw new Error(`Request timed out after ${timeout} ms`);
// } else {
// console.error(`Error fetching data from ${url}:`, error);
// throw error;
// }
// }
// }

// FIXME: fetch signal abortcontroller does not work on Bun. See https://github.com/oven-sh/bun/issues/2489
// NOTE: fetch signal abortcontroller does not work on Bun.
// See https://github.com/oven-sh/bun/issues/2489
async function fetchWithTimeout(url: string, timeout: number = TIMEOUT): Promise<Response> {
console.debug(`Starting fetch request to ${url}`);
const fetchPromise = fetch(url);
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Request timed out after ${timeout} ms`)), timeout)
);

return Promise.race([fetchPromise, timeoutPromise]);
return Promise.race([fetchPromise, timeoutPromise]) as Promise<Response>;
}

/**
Expand Down Expand Up @@ -122,10 +103,10 @@ app.use('/static/*', serveStatic({ root: './' }))
*/
async function fetchData() {
const tasks = [
fetchAndHandle(`${mempoolBaseUrl}/api/blocks/tip/hash`),
fetchAndHandle(`${esploraBaseUrl}/api/blocks/tip/hash`),
fetchAndHandle(`${mempoolBaseUrl}/api/v1/fees/recommended`),
fetchAndHandle(`${esploraBaseUrl}/api/fee-estimates`)
fetchAndHandle(MEMPOOL_TIP_HASH_URL),
fetchAndHandle(ESPLORA_TIP_HASH_URL),
fetchAndHandle(MEMPOOL_FEES_URL),
fetchAndHandle(ESPLORA_FEE_ESTIMATES_URL)
];

return await Promise.allSettled(tasks);
Expand Down Expand Up @@ -156,27 +137,24 @@ async function getEstimates() : Promise<Estimates> {
return estimates;
}

/**
* Helper function to extract value from a fulfilled promise.
*/
function getValueFromFulfilledPromise(result: PromiseSettledResult<any>) {
return result.status === "fulfilled" && result.value ? result.value : null;
}

/**
* Assigns the results of the fetch tasks to variables.
*/
function assignResults(results: PromiseSettledResult<any>[]) {
let blocksTipHash, mempoolFeeEstimates, esploraFeeEstimates;

if (results[0].status === "fulfilled" && results[0].value) {
blocksTipHash = results[0].value;
} else if (results[1].status === "fulfilled" && results[1].value) {
blocksTipHash = results[1].value;
}
const [result1, result2, result3, result4] = results;

if (results[2].status === "fulfilled" && results[2].value) {
mempoolFeeEstimates = results[2].value as MempoolFeeEstimates;
}

if (results[3].status === "fulfilled" && results[3].value) {
esploraFeeEstimates = results[3].value as EsploraFeeEstimates;
}
const blocksTipHash = getValueFromFulfilledPromise(result1) || getValueFromFulfilledPromise(result2);
const mempoolFeeEstimates = getValueFromFulfilledPromise(result3) as MempoolFeeEstimates;
const esploraFeeEstimates = getValueFromFulfilledPromise(result4) as EsploraFeeEstimates;

return { blocksTipHash, mempoolFeeEstimates, esploraFeeEstimates: esploraFeeEstimates };
return { blocksTipHash, mempoolFeeEstimates, esploraFeeEstimates };
}

/**
Expand All @@ -186,6 +164,14 @@ function calculateFees(mempoolFeeEstimates: MempoolFeeEstimates | null | undefin
let feeByBlockTarget: FeeByBlockTarget = {};
const minFee = mempoolFeeEstimates?.minimumFee;

feeByBlockTarget = calculateMempoolFees(mempoolFeeEstimates, feeByBlockTarget);
const minMempoolFee = calculateMinMempoolFee(feeByBlockTarget);
feeByBlockTarget = calculateEsploraFees(esploraFeeEstimates, feeByBlockTarget, minMempoolFee, minFee);

return feeByBlockTarget;
}

function calculateMempoolFees(mempoolFeeEstimates: MempoolFeeEstimates | null | undefined, feeByBlockTarget: FeeByBlockTarget) {
if (mempoolFeeEstimates) {
const blockTargetMapping: BlockTargetMapping = {
1: 'fastestFee',
Expand All @@ -200,24 +186,35 @@ function calculateFees(mempoolFeeEstimates: MempoolFeeEstimates | null | undefin
}
}
}
return feeByBlockTarget;
}

function calculateMinMempoolFee(feeByBlockTarget: FeeByBlockTarget) {
const values = Object.values(feeByBlockTarget);
const minMempoolFee = values.length > 0 ? Math.min(...values) : undefined;
return values.length > 0 ? Math.min(...values) : undefined;
}

function shouldSkipFee(blockTarget: string, adjustedFee: number, feeByBlockTarget: FeeByBlockTarget, minMempoolFee: number | undefined, minFee: number | undefined): boolean {
const blockTargetInt = parseInt(blockTarget);

if (feeByBlockTarget.hasOwnProperty(blockTarget)) return true;
if (minMempoolFee && adjustedFee >= minMempoolFee) return true;
if (minFee && adjustedFee <= minFee) return true;
if (blockTargetInt <= mempoolDepth) return true;

return false;
}

function calculateEsploraFees(esploraFeeEstimates: EsploraFeeEstimates | null | undefined, feeByBlockTarget: FeeByBlockTarget, minMempoolFee: number | undefined, minFee: number | undefined) {
if (esploraFeeEstimates) {
for (const [blockTarget, fee] of Object.entries(esploraFeeEstimates)) {
const blockTargetInt = parseInt(blockTarget);
const adjustedFee = Math.round(fee * 1000 * feeMultiplier);

if (feeByBlockTarget.hasOwnProperty(blockTarget)) continue;
if (minMempoolFee && adjustedFee >= minMempoolFee) continue;
if (minFee && adjustedFee <= minFee) continue;
if (blockTargetInt <= mempoolDepth) continue;
if (shouldSkipFee(blockTarget, adjustedFee, feeByBlockTarget, minMempoolFee, minFee)) continue;

feeByBlockTarget[blockTarget] = adjustedFee;
}
}

return feeByBlockTarget;
}

Expand Down Expand Up @@ -246,8 +243,8 @@ const Content = (props: { siteData: SiteData; estimates: Estimates }) => (
<Layout {...props.siteData}>

<div style="display: flex; justify-content: center; margin-top: 26px;">
<svg width="20" height="20" viewBox="0 0 155 120" fill="none" xmlns="http://www.w3.org/2000/svg" data-theme="dark" focusable="false">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.06565 43.2477C1.90963 41.2685 -0.665684 35.4843 1.31353 30.3283C3.29274 25.1722 9.07699 22.5969 14.233 24.5761L51.4526 38.8634C51.4937 38.8798 51.535 38.896 51.5765 38.9119L70.2481 46.0792C75.4041 48.0584 81.1883 45.4831 83.1675 40.3271C85.1468 35.1711 82.5714 29.3868 77.4154 27.4076L77.4132 27.4068C77.4139 27.4064 77.4145 27.406 77.4151 27.4056L58.7436 20.2383C53.5876 18.2591 51.0123 12.4749 52.9915 7.31885C54.9707 2.16283 60.755 -0.412485 65.911 1.56673L120.828 22.6473C120.959 22.6977 121.089 22.7506 121.217 22.8059C121.453 22.8928 121.69 22.9815 121.926 23.0721C147.706 32.9681 160.583 61.8894 150.686 87.6695C140.79 113.45 111.869 126.326 86.089 116.43C85.5927 116.24 85.1011 116.042 84.6144 115.838C84.3783 115.766 84.1431 115.686 83.9091 115.596L30.0742 94.9308C24.9182 92.9516 22.3428 87.1673 24.3221 82.0113C26.3013 76.8553 32.0855 74.2799 37.2415 76.2592L55.9106 83.4256C55.9103 83.4242 55.9099 83.4229 55.9095 83.4215L55.9133 83.423C61.0694 85.4022 66.8536 82.8269 68.8328 77.6709C70.812 72.5148 68.2367 66.7306 63.0807 64.7514L54.6786 61.5261C54.6787 61.5257 54.6788 61.5252 54.6789 61.5247L7.06565 43.2477Z" fill="currentColor"></path>
<svg width="20" height="20" viewBox="0 0 155 120" fill="none" xmlns="http://www.w3.org/2000/svg" focusable="false">
<path fillRule="evenodd" clipRule="evenodd" d="M7.06565 43.2477C1.90963 41.2685 -0.665684 35.4843 1.31353 30.3283C3.29274 25.1722 9.07699 22.5969 14.233 24.5761L51.4526 38.8634C51.4937 38.8798 51.535 38.896 51.5765 38.9119L70.2481 46.0792C75.4041 48.0584 81.1883 45.4831 83.1675 40.3271C85.1468 35.1711 82.5714 29.3868 77.4154 27.4076L77.4132 27.4068C77.4139 27.4064 77.4145 27.406 77.4151 27.4056L58.7436 20.2383C53.5876 18.2591 51.0123 12.4749 52.9915 7.31885C54.9707 2.16283 60.755 -0.412485 65.911 1.56673L120.828 22.6473C120.959 22.6977 121.089 22.7506 121.217 22.8059C121.453 22.8928 121.69 22.9815 121.926 23.0721C147.706 32.9681 160.583 61.8894 150.686 87.6695C140.79 113.45 111.869 126.326 86.089 116.43C85.5927 116.24 85.1011 116.042 84.6144 115.838C84.3783 115.766 84.1431 115.686 83.9091 115.596L30.0742 94.9308C24.9182 92.9516 22.3428 87.1673 24.3221 82.0113C26.3013 76.8553 32.0855 74.2799 37.2415 76.2592L55.9106 83.4256C55.9103 83.4242 55.9099 83.4229 55.9095 83.4215L55.9133 83.423C61.0694 85.4022 66.8536 82.8269 68.8328 77.6709C70.812 72.5148 68.2367 66.7306 63.0807 64.7514L54.6786 61.5261C54.6787 61.5257 54.6788 61.5252 54.6789 61.5247L7.06565 43.2477Z" fill="currentColor"></path>
</svg>
</div>

Expand All @@ -266,13 +263,13 @@ const Content = (props: { siteData: SiteData; estimates: Estimates }) => (
overflowX: 'auto'
}}>

<div class="header">
<div className="header">
<h1>{props.siteData.title}</h1>
<p>{props.siteData.subtitle}</p>
</div>

<pre>
<span class="blue">curl</span> -L -X GET <span class="green">'{baseUrl}/v1/fee-estimates'</span> -H <span class="green">'Accept: application/json'</span>
<span className="blue">curl</span> -L -X GET <span className="green">'{baseUrl}/v1/fee-estimates'</span> -H <span class="green">'Accept: application/json'</span>
</pre>

<pre>
Expand Down Expand Up @@ -320,7 +317,7 @@ app.get('/', async (c) => {
*/
app.get('/v1/fee-estimates', async (c) => {
try {
var estimates = await getEstimates();
let estimates = await getEstimates();

// Set cache headers.
c.res.headers.set('Cache-Control', `public, max-age=${stdTTL}`)
Expand Down

0 comments on commit ef3a617

Please sign in to comment.