"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CacheProvider = void 0;
var tslib_1 = require("tslib");
var lodash_1 = require("lodash");
var constants_1 = require("./constants");
var rateLimitedProvider_1 = require("./rateLimitedProvider");
var utils_1 = require("./utils");
var CacheProvider = (function (_super) {
    tslib_1.__extends(CacheProvider, _super);
    function CacheProvider(providerCacheNamespace, redisClient, standardTtlBlockDistance, noTtlBlockDistance, providerCacheTtl) {
        if (standardTtlBlockDistance === void 0) { standardTtlBlockDistance = Number.POSITIVE_INFINITY; }
        if (noTtlBlockDistance === void 0) { noTtlBlockDistance = Number.POSITIVE_INFINITY; }
        if (providerCacheTtl === void 0) { providerCacheTtl = constants_1.PROVIDER_CACHE_TTL; }
        var jsonRpcConstructorParams = [];
        for (var _i = 5; _i < arguments.length; _i++) {
            jsonRpcConstructorParams[_i - 5] = arguments[_i];
        }
        var _this = _super.apply(this, jsonRpcConstructorParams) || this;
        _this.redisClient = redisClient;
        _this.standardTtlBlockDistance = standardTtlBlockDistance;
        _this.noTtlBlockDistance = noTtlBlockDistance;
        _this.providerCacheTtl = providerCacheTtl;
        var chainId = _this.network.chainId;
        var cachePrefix = "".concat(providerCacheNamespace, ",").concat(new URL(_this.connection.url).hostname, ",").concat(chainId);
        _this.getBlockByNumberPrefix = "".concat(cachePrefix, ":getBlockByNumber,");
        _this.getLogsCachePrefix = "".concat(cachePrefix, ":eth_getLogs,");
        _this.callCachePrefix = "".concat(cachePrefix, ":eth_call,");
        _this.getTransactionReceiptPrefix = "".concat(cachePrefix, ":eth_getTransactionReceipt,");
        var _ttlVar = providerCacheTtl;
        var _ttl = Number(_ttlVar);
        if (isNaN(_ttl) || _ttl <= 0) {
            throw new Error("PROVIDER_CACHE_TTL (".concat(_ttlVar, ") must be numeric and > 0"));
        }
        _this.baseTTL = _ttl;
        return _this;
    }
    CacheProvider.prototype.send = function (method, params) {
        var _a, _b, _c;
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var cacheType, _d, redisKey, redisResult, result, blockNumber, _e, ttl;
            return tslib_1.__generator(this, function (_f) {
                switch (_f.label) {
                    case 0:
                        if (!this.redisClient) return [3, 2];
                        return [4, this.cacheType(method, params)];
                    case 1:
                        _d = _f.sent();
                        return [3, 3];
                    case 2:
                        _d = utils_1.CacheType.NONE;
                        _f.label = 3;
                    case 3:
                        cacheType = _d;
                        if (!(cacheType === utils_1.CacheType.NONE)) return [3, 5];
                        return [4, _super.prototype.send.call(this, method, params)];
                    case 4: return [2, _f.sent()];
                    case 5:
                        redisKey = this.buildRedisKey(method, params);
                        return [4, ((_a = this.redisClient) === null || _a === void 0 ? void 0 : _a.get(redisKey))];
                    case 6:
                        redisResult = _f.sent();
                        if (redisResult) {
                            return [2, JSON.parse(redisResult)];
                        }
                        return [4, _super.prototype.send.call(this, method, params)];
                    case 7:
                        result = _f.sent();
                        if (!(cacheType === utils_1.CacheType.DECIDE_TTL_POST_SEND)) return [3, 9];
                        blockNumber = this.getBlockNumberFromRpcResponse(method, result);
                        return [4, this.cacheTypeForBlock(blockNumber)];
                    case 8:
                        cacheType = _f.sent();
                        if (cacheType === utils_1.CacheType.NONE) {
                            return [2, result];
                        }
                        _f.label = 9;
                    case 9:
                        _e = cacheType;
                        switch (_e) {
                            case utils_1.CacheType.WITH_TTL: return [3, 10];
                            case utils_1.CacheType.NO_TTL: return [3, 12];
                        }
                        return [3, 14];
                    case 10:
                        ttl = this.baseTTL + Math.ceil((0, lodash_1.random)(-constants_1.PROVIDER_CACHE_TTL_MODIFIER, constants_1.PROVIDER_CACHE_TTL_MODIFIER, true) * this.baseTTL);
                        return [4, ((_b = this.redisClient) === null || _b === void 0 ? void 0 : _b.set(redisKey, JSON.stringify(result), ttl))];
                    case 11:
                        _f.sent();
                        return [3, 15];
                    case 12: return [4, ((_c = this.redisClient) === null || _c === void 0 ? void 0 : _c.set(redisKey, JSON.stringify(result), Number.POSITIVE_INFINITY))];
                    case 13:
                        _f.sent();
                        return [3, 15];
                    case 14: throw new Error("Unexpected Cache type: ".concat(cacheType));
                    case 15: return [2, result];
                }
            });
        });
    };
    CacheProvider.prototype.buildRedisKey = function (method, params) {
        switch (method) {
            case "eth_getBlockByNumber":
                return this.getBlockByNumberPrefix + JSON.stringify(params);
            case "eth_getLogs":
                return this.getLogsCachePrefix + JSON.stringify(params);
            case "eth_call":
                return this.callCachePrefix + JSON.stringify(params);
            case "eth_getTransactionReceipt":
                return this.getTransactionReceiptPrefix + JSON.stringify(params);
            default:
                throw new Error("CacheProvider::buildRedisKey: invalid JSON-RPC method ".concat(method));
        }
    };
    CacheProvider.prototype.cacheType = function (method, params) {
        if (method === "eth_getLogs") {
            var _a = params[0], fromBlock = _a.fromBlock, toBlock = _a.toBlock;
            var fromBlockNumber = parseInt(String(fromBlock), 16);
            var toBlockNumber = parseInt(String(toBlock), 16);
            if (Number.isNaN(fromBlockNumber) || Number.isNaN(toBlockNumber)) {
                return Promise.resolve(utils_1.CacheType.NONE);
            }
            if (toBlockNumber < fromBlockNumber) {
                throw new Error("CacheProvider::shouldCache toBlock cannot be smaller than fromBlock.");
            }
            return this.cacheTypeForBlock(toBlock);
        }
        else if ("eth_call" === method || "eth_getBlockByNumber" === method) {
            var idx = method === "eth_getBlockByNumber" ? 0 : 1;
            var blockNumber = parseInt(String(params[idx]), 16);
            if (Number.isNaN(blockNumber)) {
                return Promise.resolve(utils_1.CacheType.NONE);
            }
            return this.cacheTypeForBlock(blockNumber);
        }
        else if ("eth_getTransactionReceipt" === method) {
            return Promise.resolve(utils_1.CacheType.DECIDE_TTL_POST_SEND);
        }
        else {
            return Promise.resolve(utils_1.CacheType.NONE);
        }
    };
    CacheProvider.prototype.getBlockNumberFromRpcResponse = function (method, result) {
        if (method === "eth_getTransactionReceipt") {
            var receipt = result;
            return Number(receipt.blockNumber);
        }
        else {
            throw new Error("CacheProvider::getBlockNumberFromRpcResponse: unsupported JSON-RPC method ".concat(method));
        }
    };
    CacheProvider.prototype.cacheTypeForBlock = function (blockNumber) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var currentBlockNumber, headDistance;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4, _super.prototype._getInternalBlockNumber.call(this, constants_1.BLOCK_NUMBER_TTL * 1000)];
                    case 1:
                        currentBlockNumber = _a.sent();
                        headDistance = currentBlockNumber - blockNumber;
                        if (headDistance > this.noTtlBlockDistance) {
                            return [2, utils_1.CacheType.NO_TTL];
                        }
                        if (headDistance > this.standardTtlBlockDistance) {
                            return [2, utils_1.CacheType.WITH_TTL];
                        }
                        return [2, utils_1.CacheType.NONE];
                }
            });
        });
    };
    return CacheProvider;
}(rateLimitedProvider_1.RateLimitedProvider));
exports.CacheProvider = CacheProvider;
//# sourceMappingURL=cachedProvider.js.map