mirror of
https://github.com/mempool/mempool.git
synced 2026-08-13 12:33:11 +02:00
enforce no un-awaited unsafe async methods
This commit is contained in:
parent
538f92abaf
commit
5c87ffc257
19 changed files with 56 additions and 11 deletions
|
|
@ -21,6 +21,8 @@ module.exports = {
|
|||
messages: {
|
||||
unhandled:
|
||||
'await of non-@asyncSafe callee in @asyncSafe context; use try/catch or annotate callee (@asyncSafe) or context (@asyncUnsafe)',
|
||||
unhandledVoid:
|
||||
'void of non-@asyncSafe callee; annotate callee with @asyncSafe or handle the promise properly',
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -308,8 +310,8 @@ module.exports = {
|
|||
return fnHasTag(m, opt.safeTag);
|
||||
}
|
||||
|
||||
function calleeIsAnnotatedSafe(awaitNode) {
|
||||
const arg = unwrapChain(awaitNode.argument);
|
||||
function calleeIsAnnotatedSafe(callExpr) {
|
||||
const arg = unwrapChain(callExpr);
|
||||
if (!arg) return false;
|
||||
|
||||
if (arg.type === 'CallExpression') {
|
||||
|
|
@ -368,7 +370,7 @@ module.exports = {
|
|||
if (inTryBlock(node) || isHandledAwaitArg(node)) return;
|
||||
|
||||
// callee carries @asyncSafe? → ok anywhere
|
||||
if (calleeIsAnnotatedSafe(node)) return;
|
||||
if (calleeIsAnnotatedSafe(node.argument)) return;
|
||||
|
||||
// context explicitly @asyncUnsafe? → ok to bubble
|
||||
if (contextIsAnnotatedUnsafe(node)) return;
|
||||
|
|
@ -376,6 +378,20 @@ module.exports = {
|
|||
// default: context is safe, callee is unsafe → error
|
||||
context.report({ node, messageId: 'unhandled' });
|
||||
},
|
||||
|
||||
// void someAsyncCall() — only allowed if callee is @asyncSafe
|
||||
UnaryExpression(node) {
|
||||
if (node.operator !== 'void') return;
|
||||
|
||||
const arg = unwrapChain(node.argument);
|
||||
if (!arg || arg.type !== 'CallExpression') return;
|
||||
|
||||
// callee carries @asyncSafe? → ok
|
||||
if (calleeIsAnnotatedSafe(node.argument)) return;
|
||||
|
||||
// void of non-safe callee → error
|
||||
context.report({ node, messageId: 'unhandledVoid' });
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ class BackendInfo {
|
|||
void this.$updateCoreVersion(); // starting immediately
|
||||
}
|
||||
|
||||
/** @asyncSafe */
|
||||
private async $updateCoreVersion(): Promise<void> {
|
||||
try {
|
||||
const networkInfo = await bitcoinClient.getNetworkInfo();
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ class FailoverRouter {
|
|||
}
|
||||
|
||||
// start polling hosts to measure availability & rtt
|
||||
/** @asyncSafe */
|
||||
private async pollHosts(): Promise<void> {
|
||||
if (this.pollTimer) {
|
||||
clearTimeout(this.pollTimer);
|
||||
|
|
|
|||
|
|
@ -660,7 +660,7 @@ class Blocks {
|
|||
/**
|
||||
* [INDEXING] Index transaction classification flags for Goggles
|
||||
*
|
||||
* @asyncUnsafe
|
||||
* @asyncSafe
|
||||
*/
|
||||
public async $classifyBlocks(): Promise<void> {
|
||||
if (this.classifyingBlocks) {
|
||||
|
|
@ -1615,6 +1615,7 @@ class Blocks {
|
|||
}
|
||||
}
|
||||
|
||||
/** @asyncSafe */
|
||||
public async $saveCpfp(hash: string, height: number, cpfpSummary: CpfpSummary): Promise<void> {
|
||||
try {
|
||||
const result = await cpfpRepository.$batchSaveClusters(cpfpSummary.clusters);
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ class ChainTips {
|
|||
}
|
||||
}
|
||||
|
||||
/** @asyncSafe */
|
||||
private async $indexOrphanedBlocks(): Promise<void> {
|
||||
if (this.indexingOrphanedBlocks) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ class DiskCache {
|
|||
});
|
||||
}
|
||||
|
||||
/** @asyncSafe */
|
||||
async $saveCacheToDisk(sync: boolean = false): Promise<void> {
|
||||
if (!cluster.isPrimary || !config.MEMPOOL.CACHE_ENABLED) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ class RedisCache {
|
|||
}
|
||||
}
|
||||
|
||||
/* @asyncSafe */
|
||||
private async $ensureConnected(): Promise<boolean> {
|
||||
if (!this.connected && config.REDIS.ENABLED) {
|
||||
try {
|
||||
|
|
@ -95,6 +96,7 @@ class RedisCache {
|
|||
await this.$flushRbfQueues();
|
||||
}
|
||||
|
||||
/** @asyncSafe */
|
||||
async $updateBlocks(blocks: BlockExtended[]): Promise<void> {
|
||||
if (!config.REDIS.ENABLED) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -240,6 +240,7 @@ class AccelerationApi {
|
|||
}
|
||||
}
|
||||
|
||||
/** @asyncSafe */
|
||||
public async connectWebsocket(): Promise<void> {
|
||||
if (this.startedWebsocketLoop) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ class StratumApi {
|
|||
}
|
||||
}
|
||||
|
||||
/** @asyncSafe */
|
||||
public async connectWebsocket(): Promise<void> {
|
||||
if (!config.STRATUM.ENABLED) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ class WalletApi {
|
|||
}
|
||||
}
|
||||
|
||||
/** @asyncSafe */
|
||||
private async $loadCache(): Promise<void> {
|
||||
try {
|
||||
const cacheData = await fsPromises.readFile(WalletApi.FILE_NAME, 'utf8');
|
||||
|
|
@ -148,6 +149,7 @@ class WalletApi {
|
|||
}
|
||||
|
||||
// resync wallet addresses from the services backend
|
||||
/** @asyncSafe */
|
||||
async $syncWallets(): Promise<void> {
|
||||
if (!config.WALLETS.ENABLED || this.syncing) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -182,7 +182,8 @@ import { execSync } from 'child_process';
|
|||
if (this.pool === null) {
|
||||
this.pool = createPool(this.poolConfig);
|
||||
this.pool.on('connection', function (newConnection: PoolConnection) {
|
||||
void newConnection.query(`SET time_zone='+00:00'`);
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- callback API, not a promise despite types
|
||||
newConnection.query(`SET time_zone='+00:00'`);
|
||||
});
|
||||
}
|
||||
return this.pool;
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ class Server {
|
|||
}
|
||||
}
|
||||
|
||||
/** @asyncSafe */
|
||||
async startServer(worker = false): Promise<void> {
|
||||
logger.notice(`Starting Mempool Server${worker ? ' (worker)' : ''}... (${backendInfo.getShortCommitHash()})`);
|
||||
|
||||
|
|
@ -237,6 +238,7 @@ class Server {
|
|||
void poolsUpdater.$startService();
|
||||
}
|
||||
|
||||
/** @asyncSafe */
|
||||
async runMainUpdateLoop(): Promise<void> {
|
||||
const start = Date.now();
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -127,6 +127,8 @@ class Indexer {
|
|||
* Runs a single task immediately
|
||||
*
|
||||
* (use `scheduleSingleTask` instead to queue a task to run after some timeout)
|
||||
*
|
||||
* @asyncSafe
|
||||
*/
|
||||
public async runSingleTask(task: TaskName): Promise<void> {
|
||||
if (!Common.indexingEnabled() || this.tasksRunning[task]) {
|
||||
|
|
@ -165,7 +167,7 @@ class Indexer {
|
|||
this.tasksRunning[task] = false;
|
||||
}
|
||||
|
||||
/** @asyncUnsafe */
|
||||
/** @asyncSafe */
|
||||
public async $run(): Promise<void> {
|
||||
if (!Common.indexingEnabled() || this.runIndexer === false ||
|
||||
this.indexerRunning === true || mempool.hasPriority()
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export interface PublicAcceleration {
|
|||
class AccelerationRepository {
|
||||
private bidBoostV2Activated = 831580;
|
||||
|
||||
/** @asyncSafe */
|
||||
public async $saveAcceleration(acceleration: AccelerationInfo, block: IEsploraApi.Block, pool_id: number, accelerationData: Acceleration[]): Promise<void> {
|
||||
const accelerationMap: { [txid: string]: Acceleration } = {};
|
||||
for (const acc of accelerationData) {
|
||||
|
|
|
|||
|
|
@ -112,7 +112,9 @@ class NetworkSyncService {
|
|||
await nodesApi.$setNodesInactive(graphNodesPubkeys);
|
||||
|
||||
if (config.MAXMIND.ENABLED) {
|
||||
void $lookupNodeLocation();
|
||||
$lookupNodeLocation().catch((e) => {
|
||||
logger.err(`Error in $lookupNodeLocation: ${e instanceof Error ? e.message : e}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,9 @@ class FundingTxFetcher {
|
|||
elapsedSeconds = Math.round((new Date().getTime() / 1000) - cacheTimer);
|
||||
if (elapsedSeconds > 60) {
|
||||
logger.debug(`Saving ${Object.keys(this.fundingTxCache).length} funding txs cache into disk`, logger.tags.ln);
|
||||
void fsPromises.writeFile(CACHE_FILE_NAME, JSON.stringify(this.fundingTxCache));
|
||||
fsPromises.writeFile(CACHE_FILE_NAME, JSON.stringify(this.fundingTxCache)).catch((e) => {
|
||||
logger.err(`Error saving funding txs cache to disk: ${e instanceof Error ? e.message : e}`, logger.tags.ln);
|
||||
});
|
||||
cacheTimer = new Date().getTime() / 1000;
|
||||
}
|
||||
}
|
||||
|
|
@ -66,7 +68,9 @@ class FundingTxFetcher {
|
|||
if (this.channelNewlyProcessed > 0) {
|
||||
logger.info(`Indexed ${this.channelNewlyProcessed} additional channels funding tx`, logger.tags.ln);
|
||||
logger.debug(`Saving ${Object.keys(this.fundingTxCache).length} funding txs cache into disk`, logger.tags.ln);
|
||||
void fsPromises.writeFile(CACHE_FILE_NAME, JSON.stringify(this.fundingTxCache));
|
||||
fsPromises.writeFile(CACHE_FILE_NAME, JSON.stringify(this.fundingTxCache)).catch((e) => {
|
||||
logger.err(`Error saving funding txs cache to disk: ${e instanceof Error ? e.message : e}`, logger.tags.ln);
|
||||
});
|
||||
}
|
||||
|
||||
this.running = false;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class PoolsUpdater {
|
|||
poolsUrl: string = config.MEMPOOL.POOLS_JSON_URL;
|
||||
treeUrl: string = config.MEMPOOL.POOLS_JSON_TREE_URL;
|
||||
|
||||
/** @asyncSafe */
|
||||
public async $startService(): Promise<void> {
|
||||
while ('Bitcoin is still alive') {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ class PriceUpdater {
|
|||
this.latestGoodPrices = JSON.parse(JSON.stringify(this.latestPrices));
|
||||
}
|
||||
|
||||
/** @asyncSafe */
|
||||
public async $run(): Promise<void> {
|
||||
if (['testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) {
|
||||
// Coins have no value on testnet/signet, so we want to always show 0
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFT
|
|||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import logger from "../logger";
|
||||
|
||||
/*
|
||||
How it works:
|
||||
`this._head` is an instance of `Node` which keeps track of its current value and nests
|
||||
|
|
@ -143,7 +145,7 @@ export default function pLimit(concurrency: number): LimitFunction {
|
|||
const enqueue = (fn, resolve, args) => {
|
||||
queue.enqueue(run.bind(undefined, fn, resolve, args));
|
||||
|
||||
void (
|
||||
(
|
||||
/** @asyncUnsafe */
|
||||
async () => {
|
||||
// This function needs to wait until the next microtask before comparing
|
||||
|
|
@ -155,7 +157,9 @@ export default function pLimit(concurrency: number): LimitFunction {
|
|||
if (activeCount < concurrency && queue.size > 0) {
|
||||
queue.dequeue()();
|
||||
}
|
||||
})();
|
||||
})().catch((e) => {
|
||||
logger.err(`Error in pLimit enqueue: ${e instanceof Error ? e.message : e}`);
|
||||
});
|
||||
};
|
||||
|
||||
const generator = (fn, ...args) =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue