eth-json-rpc-middleware
Version:
Ethereum-related json-rpc-engine middleware.
166 lines • 6.43 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.createBlockCacheMiddleware = void 0;
const json_rpc_engine_1 = require("json-rpc-engine");
const logging_utils_1 = require("./logging-utils");
const cache_1 = require("./utils/cache");
const log = logging_utils_1.createModuleLogger(logging_utils_1.projectLogger, 'block-cache');
// `<nil>` comes from https://github.com/ethereum/go-ethereum/issues/16925
const emptyValues = [undefined, null, '\u003cnil\u003e'];
//
// Cache Strategies
//
class BlockCacheStrategy {
constructor() {
this.cache = {};
}
getBlockCacheForPayload(_payload, blockNumberHex) {
const blockNumber = Number.parseInt(blockNumberHex, 16);
let blockCache = this.cache[blockNumber];
// create new cache if necesary
if (!blockCache) {
const newCache = {};
this.cache[blockNumber] = newCache;
blockCache = newCache;
}
return blockCache;
}
async get(payload, requestedBlockNumber) {
// lookup block cache
const blockCache = this.getBlockCacheForPayload(payload, requestedBlockNumber);
// lookup payload in block cache
const identifier = cache_1.cacheIdentifierForPayload(payload, true);
return identifier ? blockCache[identifier] : undefined;
}
async set(payload, requestedBlockNumber, result) {
// check if we can cached this result
const canCacheResult = this.canCacheResult(payload, result);
if (!canCacheResult) {
return;
}
// set the value in the cache
const identifier = cache_1.cacheIdentifierForPayload(payload, true);
if (!identifier) {
return;
}
const blockCache = this.getBlockCacheForPayload(payload, requestedBlockNumber);
blockCache[identifier] = result;
}
canCacheRequest(payload) {
// check request method
if (!cache_1.canCache(payload)) {
return false;
}
// check blockTag
const blockTag = cache_1.blockTagForPayload(payload);
if (blockTag === 'pending') {
return false;
}
// can be cached
return true;
}
canCacheResult(payload, result) {
// never cache empty values (e.g. undefined)
if (emptyValues.includes(result)) {
return false;
}
// check if transactions have block reference before caching
if (payload.method &&
['eth_getTransactionByHash', 'eth_getTransactionReceipt'].includes(payload.method)) {
if (!result ||
!result.blockHash ||
result.blockHash ===
'0x0000000000000000000000000000000000000000000000000000000000000000') {
return false;
}
}
// otherwise true
return true;
}
// removes all block caches with block number lower than `oldBlockHex`
clearBefore(oldBlockHex) {
const oldBlockNumber = Number.parseInt(oldBlockHex, 16);
// clear old caches
Object.keys(this.cache)
.map(Number)
.filter((num) => num < oldBlockNumber)
.forEach((num) => delete this.cache[num]);
}
}
function createBlockCacheMiddleware({ blockTracker, } = {}) {
// validate options
if (!blockTracker) {
throw new Error('createBlockCacheMiddleware - No PollingBlockTracker specified');
}
// create caching strategies
const blockCache = new BlockCacheStrategy();
const strategies = {
perma: blockCache,
block: blockCache,
fork: blockCache,
};
return json_rpc_engine_1.createAsyncMiddleware(async (req, res, next) => {
// allow cach to be skipped if so specified
if (req.skipCache) {
return next();
}
// check type and matching strategy
const type = cache_1.cacheTypeForPayload(req);
const strategy = strategies[type];
// If there's no strategy in place, pass it down the chain.
if (!strategy) {
return next();
}
// If the strategy can't cache this request, ignore it.
if (!strategy.canCacheRequest(req)) {
return next();
}
// get block reference (number or keyword)
let blockTag = cache_1.blockTagForPayload(req);
if (!blockTag) {
blockTag = 'latest';
}
log('blockTag = %o, req = %o', blockTag, req);
// get exact block number
let requestedBlockNumber;
if (blockTag === 'earliest') {
// this just exists for symmetry with "latest"
requestedBlockNumber = '0x00';
}
else if (blockTag === 'latest') {
// fetch latest block number
log('Fetching latest block number to determine cache key');
const latestBlockNumber = await blockTracker.getLatestBlock();
// clear all cache before latest block
log('Clearing values stored under block numbers before %o', latestBlockNumber);
blockCache.clearBefore(latestBlockNumber);
requestedBlockNumber = latestBlockNumber;
}
else {
// We have a hex number
requestedBlockNumber = blockTag;
}
// end on a hit, continue on a miss
const cacheResult = await strategy.get(req, requestedBlockNumber);
if (cacheResult === undefined) {
// cache miss
// wait for other middleware to handle request
log('No cache stored under block number %o, carrying request forward', requestedBlockNumber);
// eslint-disable-next-line node/callback-return
await next();
// add result to cache
// it's safe to cast res.result as Block, due to runtime type checks
// performed when strategy.set is called
log('Populating cache with', res);
await strategy.set(req, requestedBlockNumber, res.result);
}
else {
// fill in result from cache
log('Cache hit, reusing cache result stored under block number %o', requestedBlockNumber);
res.result = cacheResult;
}
return undefined;
});
}
exports.createBlockCacheMiddleware = createBlockCacheMiddleware;
//# sourceMappingURL=block-cache.js.map
;