UNPKG

@drift-labs/sdk

Version:
223 lines (222 loc) • 8.35 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BulkAccountLoader = void 0; const uuid_1 = require("uuid"); const promiseTimeout_1 = require("../util/promiseTimeout"); const numericConstants_1 = require("../constants/numericConstants"); const oneMinute = 60 * 1000; class BulkAccountLoader { constructor(connection, commitment, pollingFrequency) { this.accountsToLoad = new Map(); this.bufferAndSlotMap = new Map(); this.errorCallbacks = new Map(); this.lastTimeLoadingPromiseCleared = Date.now(); this.mostRecentSlot = 0; this.connection = connection; this.commitment = commitment; this.pollingFrequency = pollingFrequency; } async addAccount(publicKey, callback) { if (!publicKey) { console.trace(`Caught adding blank publickey to bulkAccountLoader`); } const existingSize = this.accountsToLoad.size; const callbackId = (0, uuid_1.v4)(); const existingAccountToLoad = this.accountsToLoad.get(publicKey.toString()); if (existingAccountToLoad) { existingAccountToLoad.callbacks.set(callbackId, callback); } else { const callbacks = new Map(); callbacks.set(callbackId, callback); const newAccountToLoad = { publicKey, callbacks, }; this.accountsToLoad.set(publicKey.toString(), newAccountToLoad); } if (existingSize === 0) { this.startPolling(); } // resolve the current loadPromise in case client wants to call load await this.loadPromise; return callbackId; } removeAccount(publicKey, callbackId) { const existingAccountToLoad = this.accountsToLoad.get(publicKey.toString()); if (existingAccountToLoad) { existingAccountToLoad.callbacks.delete(callbackId); if (existingAccountToLoad.callbacks.size === 0) { this.bufferAndSlotMap.delete(publicKey.toString()); this.accountsToLoad.delete(existingAccountToLoad.publicKey.toString()); } } if (this.accountsToLoad.size === 0) { this.stopPolling(); } } addErrorCallbacks(callback) { const callbackId = (0, uuid_1.v4)(); this.errorCallbacks.set(callbackId, callback); return callbackId; } removeErrorCallbacks(callbackId) { this.errorCallbacks.delete(callbackId); } chunks(array, size) { return new Array(Math.ceil(array.length / size)) .fill(null) .map((_, index) => index * size) .map((begin) => array.slice(begin, begin + size)); } async load() { if (this.loadPromise) { const now = Date.now(); if (now - this.lastTimeLoadingPromiseCleared > oneMinute) { this.loadPromise = undefined; } else { return this.loadPromise; } } this.loadPromise = new Promise((resolver) => { this.loadPromiseResolver = resolver; }); this.lastTimeLoadingPromiseCleared = Date.now(); try { const chunks = this.chunks(this.chunks(Array.from(this.accountsToLoad.values()), numericConstants_1.GET_MULTIPLE_ACCOUNTS_CHUNK_SIZE), 10); await Promise.all(chunks.map((chunk) => { return this.loadChunk(chunk); })); } catch (e) { console.error(`Error in bulkAccountLoader.load()`); console.error(e); for (const [_, callback] of this.errorCallbacks) { callback(e); } } finally { this.loadPromiseResolver(); this.loadPromise = undefined; } } async loadChunk(accountsToLoadChunks) { if (accountsToLoadChunks.length === 0) { return; } const requests = new Array(); for (const accountsToLoadChunk of accountsToLoadChunks) { const args = [ accountsToLoadChunk .filter((accountToLoad) => accountToLoad.callbacks.size > 0) .map((accountToLoad) => { return accountToLoad.publicKey.toBase58(); }), { commitment: this.commitment }, ]; requests.push({ methodName: 'getMultipleAccounts', args, }); } const rpcResponses = await (0, promiseTimeout_1.promiseTimeout)( // @ts-ignore this.connection._rpcBatchRequest(requests), 10 * 1000 // 30 second timeout ); if (rpcResponses === null) { this.log('request to rpc timed out'); return; } rpcResponses.forEach((rpcResponse, i) => { if (!rpcResponse.result) { console.error('rpc response missing result:'); console.log(JSON.stringify(rpcResponse)); return; } const newSlot = rpcResponse.result.context.slot; if (newSlot > this.mostRecentSlot) { this.mostRecentSlot = newSlot; } const accountsToLoad = accountsToLoadChunks[i]; accountsToLoad.forEach((accountToLoad, j) => { if (accountToLoad.callbacks.size === 0) { return; } const key = accountToLoad.publicKey.toBase58(); const oldRPCResponse = this.bufferAndSlotMap.get(key); if (oldRPCResponse && newSlot < oldRPCResponse.slot) { return; } let newBuffer = undefined; if (rpcResponse.result.value[j]) { const raw = rpcResponse.result.value[j].data[0]; const dataType = rpcResponse.result.value[j].data[1]; newBuffer = Buffer.from(raw, dataType); } if (!oldRPCResponse) { this.bufferAndSlotMap.set(key, { slot: newSlot, buffer: newBuffer, }); this.handleAccountCallbacks(accountToLoad, newBuffer, newSlot); return; } const oldBuffer = oldRPCResponse.buffer; if (newBuffer && (!oldBuffer || !newBuffer.equals(oldBuffer))) { this.bufferAndSlotMap.set(key, { slot: newSlot, buffer: newBuffer, }); this.handleAccountCallbacks(accountToLoad, newBuffer, newSlot); } }); }); } handleAccountCallbacks(accountToLoad, buffer, slot) { for (const [_, callback] of accountToLoad.callbacks) { try { callback(buffer, slot); } catch (e) { console.log('Bulk account load: error in account callback'); console.log('accounto to load', accountToLoad.publicKey.toString()); console.log('buffer', buffer.toString('base64')); for (const callback of accountToLoad.callbacks.values()) { console.log('account to load cb', callback); } throw e; } } } getBufferAndSlot(publicKey) { return this.bufferAndSlotMap.get(publicKey.toString()); } getSlot() { return this.mostRecentSlot; } startPolling() { if (this.intervalId) { return; } if (this.pollingFrequency !== 0) this.intervalId = setInterval(this.load.bind(this), this.pollingFrequency); } stopPolling() { if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = undefined; } } log(msg) { console.log(msg); } updatePollingFrequency(pollingFrequency) { this.stopPolling(); this.pollingFrequency = pollingFrequency; if (this.accountsToLoad.size > 0) { this.startPolling(); } } } exports.BulkAccountLoader = BulkAccountLoader;