@defillama/sdk 5.0.240 → 5.0.244
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/Balances.d.ts +139 -0
- package/build/Balances.js +363 -0
- package/build/ChainApi.d.ts +142 -0
- package/build/ChainApi.js +310 -0
- package/build/abi/abi2.d.ts +5 -0
- package/build/abi/abi2.js +215 -0
- package/build/abi/cachedABIs.d.ts +4 -0
- package/build/abi/cachedABIs.js +8 -0
- package/build/abi/convertResults.d.ts +5 -0
- package/build/abi/convertResults.js +60 -0
- package/build/abi/index.d.ts +31 -0
- package/build/abi/index.js +327 -0
- package/build/abi/multicall.d.ts +23 -0
- package/build/abi/multicall.js +383 -0
- package/build/abi/multicall3.d.ts +7 -0
- package/build/abi/multicall3.js +405 -0
- package/build/abi/rpcCall.d.ts +4 -0
- package/build/abi/rpcCall.js +62 -0
- package/build/abi/tron.d.ts +33 -0
- package/build/abi/tron.js +118 -0
- package/build/api.d.ts +10 -0
- package/build/api.js +45 -0
- package/build/api2.d.ts +5 -0
- package/build/api2.js +41 -0
- package/build/chains/algorand.d.ts +277 -0
- package/build/chains/algorand.js +618 -0
- package/build/chains/aptos.d.ts +269 -0
- package/build/chains/aptos.js +700 -0
- package/build/chains/cardano.d.ts +242 -0
- package/build/chains/cardano.js +407 -0
- package/build/chains/cosmos.d.ts +252 -0
- package/build/chains/cosmos.js +623 -0
- package/build/chains/icp.d.ts +235 -0
- package/build/chains/icp.js +1040 -0
- package/build/chains/index.d.ts +23 -0
- package/build/chains/index.js +59 -0
- package/build/chains/near.d.ts +192 -0
- package/build/chains/near.js +446 -0
- package/build/chains/rpc.d.ts +125 -0
- package/build/chains/rpc.js +332 -0
- package/build/chains/starknet.d.ts +205 -0
- package/build/chains/starknet.js +886 -0
- package/build/chains/stellar.d.ts +410 -0
- package/build/chains/stellar.js +1082 -0
- package/build/chains/substrate.d.ts +333 -0
- package/build/chains/substrate.js +1042 -0
- package/build/chains/sui.d.ts +392 -0
- package/build/chains/sui.js +1213 -0
- package/build/chains/svm.d.ts +340 -0
- package/build/chains/svm.js +764 -0
- package/build/chains/tezos.d.ts +225 -0
- package/build/chains/tezos.js +412 -0
- package/build/chains/ton.d.ts +353 -0
- package/build/chains/ton.js +788 -0
- package/build/chains/tron.d.ts +45 -0
- package/build/chains/tron.js +110 -0
- package/build/chains/utxo.d.ts +210 -0
- package/build/chains/utxo.js +678 -0
- package/build/chains/xrpl.d.ts +179 -0
- package/build/chains/xrpl.js +467 -0
- package/build/computeTVL/blocks.d.ts +1 -0
- package/build/computeTVL/blocks.js +12 -0
- package/build/computeTVL/humanizeNumber.d.ts +1 -0
- package/build/computeTVL/humanizeNumber.js +19 -0
- package/build/erc20/index.d.ts +31 -0
- package/build/erc20/index.js +56 -0
- package/build/eth/index.d.ts +22 -0
- package/build/eth/index.js +118 -0
- package/build/general.d.ts +5 -0
- package/build/general.js +20 -0
- package/build/generalUtil.d.ts +8 -0
- package/build/generalUtil.js +73 -0
- package/build/index.d.ts +33 -0
- package/build/index.js +75 -0
- package/build/providers.json +1 -0
- package/build/types.d.ts +76 -0
- package/build/types.js +2 -0
- package/build/util/LlamaProvider.d.ts +46 -0
- package/build/util/LlamaProvider.js +343 -0
- package/build/util/blocks.d.ts +36 -0
- package/build/util/blocks.js +500 -0
- package/build/util/cache.d.ts +45 -0
- package/build/util/cache.js +309 -0
- package/build/util/callStats.d.ts +74 -0
- package/build/util/callStats.js +385 -0
- package/build/util/chainUtils/data.json +1459 -0
- package/build/util/chainUtils/index.d.ts +11 -0
- package/build/util/chainUtils/index.js +136 -0
- package/build/util/coins.d.ts +19 -0
- package/build/util/coins.js +124 -0
- package/build/util/common.d.ts +34 -0
- package/build/util/common.js +376 -0
- package/build/util/computeTVL.d.ts +19 -0
- package/build/util/computeTVL.js +102 -0
- package/build/util/cosmos.d.ts +1 -0
- package/build/util/cosmos.js +8 -0
- package/build/util/debugLog.d.ts +4 -0
- package/build/util/debugLog.js +18 -0
- package/build/util/discord.d.ts +1 -0
- package/build/util/discord.js +33 -0
- package/build/util/elastic.d.ts +43 -0
- package/build/util/elastic.js +113 -0
- package/build/util/env.d.ts +20 -0
- package/build/util/env.js +176 -0
- package/build/util/graph.d.ts +8 -0
- package/build/util/graph.js +50 -0
- package/build/util/index.d.ts +32 -0
- package/build/util/index.js +227 -0
- package/build/util/indexer.compatibility.d.ts +6 -0
- package/build/util/indexer.compatibility.js +64 -0
- package/build/util/indexer.d.ts +92 -0
- package/build/util/indexer.js +854 -0
- package/build/util/indexerSql.d.ts +9 -0
- package/build/util/indexerSql.js +102 -0
- package/build/util/internal-cache.d.ts +11 -0
- package/build/util/internal-cache.js +77 -0
- package/build/util/logs.d.ts +58 -0
- package/build/util/logs.decode.shared.d.ts +8 -0
- package/build/util/logs.decode.shared.js +289 -0
- package/build/util/logs.js +432 -0
- package/build/util/promisePool.d.ts +8 -0
- package/build/util/promisePool.js +31 -0
- package/build/util/r2.d.ts +4 -0
- package/build/util/r2.js +52 -0
- package/build/util/removeRedundantRPCs.d.ts +1 -0
- package/build/util/removeRedundantRPCs.js +49 -0
- package/build/util/transactions.d.ts +35 -0
- package/build/util/transactions.js +22 -0
- package/build/util/updateChainLabels.d.ts +1 -0
- package/build/util/updateChainLabels.js +5 -0
- package/build/util/updateProviderList.d.ts +1 -0
- package/build/util/updateProviderList.js +275 -0
- package/package.json +3 -2
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getLatestBlock = exports.chainsForBlocks = void 0;
|
|
7
|
+
exports.getChainBlocks = getChainBlocks;
|
|
8
|
+
exports.getBlocks = getBlocks;
|
|
9
|
+
exports.getCurrentBlocks = getCurrentBlocks;
|
|
10
|
+
exports.getBlock = getBlock;
|
|
11
|
+
exports.getBlockNumber = getBlockNumber;
|
|
12
|
+
exports.getCurrentChainBlock = getCurrentChainBlock;
|
|
13
|
+
exports.lookupBlock = lookupBlock;
|
|
14
|
+
exports.getTimestamp = getTimestamp;
|
|
15
|
+
const general_1 = require("../general");
|
|
16
|
+
const common_1 = require("./common");
|
|
17
|
+
const debugLog_1 = require("./debugLog");
|
|
18
|
+
const cosmos_1 = require("./cosmos");
|
|
19
|
+
const cache_1 = require("./cache");
|
|
20
|
+
const p_limit_1 = __importDefault(require("p-limit"));
|
|
21
|
+
const env_1 = require("./env");
|
|
22
|
+
const defaultChains = ["avax", "bsc", "polygon", "arbitrum"];
|
|
23
|
+
exports.chainsForBlocks = defaultChains;
|
|
24
|
+
const blockRetries = 3;
|
|
25
|
+
async function getChainBlocks(timestamp, chains = defaultChains) {
|
|
26
|
+
const chainBlocks = {};
|
|
27
|
+
const setBlock = async (chain) => chainBlocks[chain] = await getBlockNumber(chain, timestamp);
|
|
28
|
+
await Promise.all(chains.map(setBlock));
|
|
29
|
+
return chainBlocks;
|
|
30
|
+
}
|
|
31
|
+
async function getBlocks(timestamp, chains = undefined) {
|
|
32
|
+
chains = chains === null || chains === void 0 ? void 0 : chains.filter(i => i !== 'ethereum');
|
|
33
|
+
const [ethBlock, chainBlocks] = await Promise.all([getBlockNumber('ethereum', timestamp), getChainBlocks(timestamp, chains)]);
|
|
34
|
+
chainBlocks['ethereum'] = ethBlock;
|
|
35
|
+
return {
|
|
36
|
+
ethereumBlock: ethBlock,
|
|
37
|
+
chainBlocks,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async function getCurrentBlocks(chains = undefined) {
|
|
41
|
+
if (chains)
|
|
42
|
+
chains = chains.filter(i => i !== "ethereum");
|
|
43
|
+
const block = await getBlock('ethereum');
|
|
44
|
+
const chainBlocks = await getChainBlocks(undefined, chains);
|
|
45
|
+
chainBlocks['ethereum'] = block.block;
|
|
46
|
+
return {
|
|
47
|
+
timestamp: block.timestamp,
|
|
48
|
+
ethereumBlock: block.block,
|
|
49
|
+
chainBlocks,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
async function getBlock(chain, timestamp, options = {}) {
|
|
53
|
+
if (!timestamp)
|
|
54
|
+
return getCurrentChainBlock(chain);
|
|
55
|
+
let error;
|
|
56
|
+
for (let i = 0; i < blockRetries; i++) {
|
|
57
|
+
try {
|
|
58
|
+
let res = await lookupBlock(timestamp, { ...options, chain, });
|
|
59
|
+
return res;
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
error = e;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
async function getBlockNumber(chain, timestamp) {
|
|
68
|
+
return (await getBlock(chain, timestamp)).block;
|
|
69
|
+
}
|
|
70
|
+
const refreshInterval = 1000 * 60 * 1; // 1 minute
|
|
71
|
+
function getCurrentChainBlock(chain = 'ethereum') {
|
|
72
|
+
const { timestamp, } = currentChainBlockCache[chain] || {};
|
|
73
|
+
const currentTimestamp = Date.now();
|
|
74
|
+
if (!timestamp || currentTimestamp - timestamp > refreshInterval) {
|
|
75
|
+
currentChainBlockCache[chain] = {
|
|
76
|
+
timestamp: currentTimestamp,
|
|
77
|
+
promise: _getCurrentChainBlock()
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return currentChainBlockCache[chain].promise;
|
|
81
|
+
async function _getCurrentChainBlock() {
|
|
82
|
+
var _a, _b;
|
|
83
|
+
try {
|
|
84
|
+
const provider = getExtraProvider(chain);
|
|
85
|
+
const { number, timestamp, } = await provider.getBlock('latest');
|
|
86
|
+
const response = { block: number, timestamp, number };
|
|
87
|
+
validateCurrentBlock(response, chain);
|
|
88
|
+
currentChainBlockCache[chain] = { timestamp: currentTimestamp, promise: response };
|
|
89
|
+
return response; // we are interested only in these fields
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
delete currentChainBlockCache[chain];
|
|
93
|
+
if ((_b = (_a = e === null || e === void 0 ? void 0 : e.message) === null || _a === void 0 ? void 0 : _a.includes) === null || _b === void 0 ? void 0 : _b.call(_a, "Last block of chain"))
|
|
94
|
+
throw e;
|
|
95
|
+
throw (0, common_1.formError)(e);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const algorandBlockProvider = {
|
|
100
|
+
getBlock: async (height) => {
|
|
101
|
+
if (height !== 'latest')
|
|
102
|
+
return (0, common_1.fetchJson)(`https://algoindexer.algoexplorerapi.io/v2/blocks/${height}`)
|
|
103
|
+
.then((block) => ({
|
|
104
|
+
number: block.round,
|
|
105
|
+
timestamp: block.timestamp
|
|
106
|
+
}));
|
|
107
|
+
return (0, common_1.fetchJson)('https://algoindexer.algoexplorerapi.io/health')
|
|
108
|
+
.then((block) => algorandBlockProvider.getBlock(block.round));
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
async function fetchBlockFromProvider(height, chain) {
|
|
112
|
+
const provider = getExtraProvider(chain !== null && chain !== void 0 ? chain : "ethereum");
|
|
113
|
+
const block = await provider.getBlock(height);
|
|
114
|
+
if (block === null) {
|
|
115
|
+
throw new Error(`Can't get block of chain ${chain !== null && chain !== void 0 ? chain : "ethereum"}`);
|
|
116
|
+
}
|
|
117
|
+
return block;
|
|
118
|
+
}
|
|
119
|
+
function getExtraProvider(chain = "ethereum") {
|
|
120
|
+
if ((0, cosmos_1.isCosmosChain)(chain)) // maybe check if it is also an evm chain?
|
|
121
|
+
return (0, cosmos_1.getCosmosProvider)(chain);
|
|
122
|
+
if (chain === "algorand")
|
|
123
|
+
return algorandBlockProvider;
|
|
124
|
+
return (0, general_1.getProvider)(chain);
|
|
125
|
+
}
|
|
126
|
+
exports.getLatestBlock = getCurrentChainBlock;
|
|
127
|
+
const intialBlocks = {
|
|
128
|
+
planq: 14000000,
|
|
129
|
+
sei: 150023881,
|
|
130
|
+
terra: 4724001,
|
|
131
|
+
crab: 4969901
|
|
132
|
+
};
|
|
133
|
+
// pruned nodes don't serve old blocks, we find the earliest available block per chain and remember it for this process
|
|
134
|
+
const earliestAvailableBlock = {};
|
|
135
|
+
const prunedBlockBuffer = 1000; // pruning keeps moving forward, so stay a bit ahead of the reported height
|
|
136
|
+
// known formats:
|
|
137
|
+
// sei: "requested height 150022713 has been pruned; earliest available is 229666772"
|
|
138
|
+
// sei (pre evm): "EVM is only supported from block 79123881 onwards"
|
|
139
|
+
// publicnode: "pruned history unavailable: requested 100, earliest available 51000000"
|
|
140
|
+
// cosmos/tendermint/evm: "height 100 is not available, lowest height is 47180386" (often in error.data)
|
|
141
|
+
// both patterns need a block/height/available word next to the number, so unrelated numbers in error pages don't match
|
|
142
|
+
const earliestBlockPatterns = [
|
|
143
|
+
/(?:earliest|lowest|oldest)\s+(?:available\s+(?:block\s+|height\s+)?|(?:block|height)\s+)(?:number\s+)?(?:is\s+|:\s*|=\s*)?#?(\d+)/i,
|
|
144
|
+
/(?:supported|available)\s+(?:from|since|after)\s+(?:block|height)\s+#?(\d+)/i,
|
|
145
|
+
];
|
|
146
|
+
// the node's message sits in different fields depending on who wrapped the error (ethers, axios, formError, llamaRPCError)
|
|
147
|
+
const errorMessageFields = ['message', 'shortMessage', 'reason', 'data', '_underlyingError', 'error', 'info', 'response', 'errors'];
|
|
148
|
+
function getErrorText(e, depth = 0) {
|
|
149
|
+
if (typeof e === 'string')
|
|
150
|
+
return e;
|
|
151
|
+
if (!e || typeof e !== 'object' || depth > 5)
|
|
152
|
+
return ''; // llamaRPCError: errors[i].error.message sits 4 deep
|
|
153
|
+
if (Array.isArray(e))
|
|
154
|
+
return e.map(i => getErrorText(i, depth + 1)).filter(Boolean).join(' ');
|
|
155
|
+
return errorMessageFields
|
|
156
|
+
.map(key => getErrorText(e[key], depth + 1))
|
|
157
|
+
.filter(Boolean)
|
|
158
|
+
.join(' ');
|
|
159
|
+
}
|
|
160
|
+
function getEarliestAvailableFromError(e, requested) {
|
|
161
|
+
const text = getErrorText(e);
|
|
162
|
+
const found = earliestBlockPatterns
|
|
163
|
+
.map(pattern => { var _a; return (_a = text.match(pattern)) === null || _a === void 0 ? void 0 : _a[1]; })
|
|
164
|
+
.filter(Boolean)
|
|
165
|
+
.map(Number)
|
|
166
|
+
.filter(block => block > requested);
|
|
167
|
+
return found.length ? Math.max(...found) : undefined;
|
|
168
|
+
}
|
|
169
|
+
// true only when the node says the block doesn't exist for it, transient errors (timeouts, 429s) are not pruning
|
|
170
|
+
function isMissingBlockError(e) {
|
|
171
|
+
const text = getErrorText(e);
|
|
172
|
+
return /Can't get block of chain|not found|not available|pruned|missing trie node|does not have this data|unknown block|no block/i.test(text);
|
|
173
|
+
}
|
|
174
|
+
// last resort for nodes that return null/not found without telling where the history starts
|
|
175
|
+
async function searchEarliestAvailableBlock(low, chain) {
|
|
176
|
+
const { number: latest } = await (0, exports.getLatestBlock)(chain);
|
|
177
|
+
let hi = latest - prunedBlockBuffer;
|
|
178
|
+
if (hi <= low || !(await isBlockAvailable(hi)))
|
|
179
|
+
return; // node is broken, not pruned
|
|
180
|
+
while (hi - low > prunedBlockBuffer) {
|
|
181
|
+
const mid = Math.floor((low + hi) / 2);
|
|
182
|
+
if (await isBlockAvailable(mid))
|
|
183
|
+
hi = mid;
|
|
184
|
+
else
|
|
185
|
+
low = mid;
|
|
186
|
+
}
|
|
187
|
+
return hi;
|
|
188
|
+
// we only get here after the node confirmed pruning, so a failure in the pruned region is most likely pruning too
|
|
189
|
+
// (sei answers "method handler crashed" for blocks just above its reported horizon); retry once to rule out a blip
|
|
190
|
+
async function isBlockAvailable(block, retry = true) {
|
|
191
|
+
try {
|
|
192
|
+
await fetchBlockFromProvider(block, chain);
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
const earliest = getEarliestAvailableFromError(e, block);
|
|
197
|
+
if (earliest)
|
|
198
|
+
low = Math.max(low, earliest - 1); // node told us, skip ahead
|
|
199
|
+
else if (!isMissingBlockError(e) && retry)
|
|
200
|
+
return isBlockAvailable(block, false);
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// fetches the given block, or the earliest available block if the node has pruned it
|
|
206
|
+
async function fetchAvailableBlock(low, chain) {
|
|
207
|
+
var _a, _b;
|
|
208
|
+
low = Math.max(low, (_a = earliestAvailableBlock[chain]) !== null && _a !== void 0 ? _a : 0); // already known to be pruned, don't ask again
|
|
209
|
+
let originalError;
|
|
210
|
+
// a few hops: sei first reports the evm start block, then the pruned height
|
|
211
|
+
for (let i = 0; i < 3; i++) {
|
|
212
|
+
try {
|
|
213
|
+
return await fetchBlockFromProvider(low, chain);
|
|
214
|
+
}
|
|
215
|
+
catch (e) {
|
|
216
|
+
originalError = originalError !== null && originalError !== void 0 ? originalError : e;
|
|
217
|
+
const earliest = getEarliestAvailableFromError(e, low);
|
|
218
|
+
if (!earliest)
|
|
219
|
+
break;
|
|
220
|
+
low = earliestAvailableBlock[chain] = earliest + prunedBlockBuffer;
|
|
221
|
+
(0, debugLog_1.debugLog)(`chain: ${chain} block is pruned, using earliest available block ${low}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// only search when the node says the block is missing, anything else is a transient failure
|
|
225
|
+
if (!isMissingBlockError(originalError))
|
|
226
|
+
throw originalError;
|
|
227
|
+
// parallel probes can all land in the pruned range, run a single search per chain
|
|
228
|
+
if (!earliestBlockSearches[chain]) {
|
|
229
|
+
earliestBlockSearches[chain] = searchEarliestAvailableBlock(low, chain)
|
|
230
|
+
.catch(() => undefined)
|
|
231
|
+
.finally(() => delete earliestBlockSearches[chain]);
|
|
232
|
+
}
|
|
233
|
+
const earliest = await earliestBlockSearches[chain];
|
|
234
|
+
if (!earliest)
|
|
235
|
+
throw originalError;
|
|
236
|
+
if (((_b = earliestAvailableBlock[chain]) !== null && _b !== void 0 ? _b : 0) < earliest + prunedBlockBuffer) {
|
|
237
|
+
earliestAvailableBlock[chain] = earliest + prunedBlockBuffer;
|
|
238
|
+
(0, debugLog_1.debugLog)(`chain: ${chain} block is pruned, found earliest available block ${earliestAvailableBlock[chain]} by searching`);
|
|
239
|
+
}
|
|
240
|
+
return fetchBlockFromProvider(earliestAvailableBlock[chain], chain);
|
|
241
|
+
}
|
|
242
|
+
const earliestBlockSearches = {};
|
|
243
|
+
let lastBlockCacheSave = Date.now();
|
|
244
|
+
const blockCacheSaveInterval = 1000 * 60 * 5; // 5 minutes
|
|
245
|
+
const { data: blockTimeCache, saveCacheFile: saveBlockCacheFile } = (0, cache_1.getTempLocalCache)({ file: 'BlockCache-v1.json', defaultData: {}, clearAfter: cache_1.ONE_WEEK, returnWithSaveFunction: true });
|
|
246
|
+
function validateCurrentBlock(block, chain = 'ethereum') {
|
|
247
|
+
// sometimes the chain is halted or facing issues, so we skip the validation for those chains
|
|
248
|
+
if ((0, env_1.shouldSkipBlockValidation)(chain))
|
|
249
|
+
return;
|
|
250
|
+
const provider = getExtraProvider(chain);
|
|
251
|
+
const now = Math.floor(Date.now() / 1000);
|
|
252
|
+
const minutesDiff = Math.floor((now - block.timestamp) / 60);
|
|
253
|
+
if (minutesDiff > 60)
|
|
254
|
+
throw new Error(`Last block for ${chain} is ${minutesDiff} minutes behind (${new Date(block.timestamp * 1000)}). Provider is "${(0, common_1.getProviderUrl)(provider)}"`);
|
|
255
|
+
}
|
|
256
|
+
const chainLimiters = {};
|
|
257
|
+
function getChainLimiter(chain) {
|
|
258
|
+
if (!chainLimiters[chain])
|
|
259
|
+
chainLimiters[chain] = createLimiter(chain);
|
|
260
|
+
return chainLimiters[chain];
|
|
261
|
+
function createLimiter(chain) {
|
|
262
|
+
return (0, p_limit_1.default)((0, env_1.getParallelGetBlocksLimit)(chain));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// concurrent lookups for the same chain & timestamp share one search
|
|
266
|
+
const inflightLookups = {};
|
|
267
|
+
async function lookupBlock(timestamp, extraParams) {
|
|
268
|
+
const { chain = "ethereum", allowedTimeRange, acceptableBlockImprecision } = extraParams !== null && extraParams !== void 0 ? extraParams : {};
|
|
269
|
+
if (timestamp > Date.now() / 1000)
|
|
270
|
+
throw new Error(`Requesting for block in the future ${timestamp} is in the future, current time is ${Math.floor(Date.now() / 1000)}`);
|
|
271
|
+
const key = [chain, timestamp, allowedTimeRange, acceptableBlockImprecision].join(':');
|
|
272
|
+
if (!inflightLookups[key]) {
|
|
273
|
+
const limiter = getChainLimiter(chain);
|
|
274
|
+
inflightLookups[key] = lookupBlockFromService(timestamp, extraParams)
|
|
275
|
+
.then(block => block !== null && block !== void 0 ? block : limiter(() => _lookupBlock(timestamp, extraParams)))
|
|
276
|
+
.finally(() => delete inflightLookups[key]);
|
|
277
|
+
}
|
|
278
|
+
return inflightLookups[key];
|
|
279
|
+
}
|
|
280
|
+
const blockServiceRoundSeconds = 300; // the service's default route rounds timestamps down to this
|
|
281
|
+
const blockServiceTimeout = 5000;
|
|
282
|
+
const blockServiceMaxFailures = 3;
|
|
283
|
+
const blockServicePauseDuration = 1000 * 60 * 5; // after repeated failures, go straight to the local search for a while
|
|
284
|
+
let blockServiceFailures = 0;
|
|
285
|
+
let blockServicePausedUntil = 0;
|
|
286
|
+
// ask the block-service when BLOCK_SERVICE_URL is set, any failure falls back to the local search
|
|
287
|
+
async function lookupBlockFromService(timestamp, extraParams) {
|
|
288
|
+
var _a;
|
|
289
|
+
const serviceUrl = (0, env_1.getBlockServiceUrl)();
|
|
290
|
+
if (!serviceUrl || Date.now() < blockServicePausedUntil)
|
|
291
|
+
return;
|
|
292
|
+
const { chain = "ethereum", allowedTimeRange = 5 * 60 } = extraParams !== null && extraParams !== void 0 ? extraParams : {};
|
|
293
|
+
if (chain === 'waves')
|
|
294
|
+
return;
|
|
295
|
+
const route = allowedTimeRange >= blockServiceRoundSeconds ? '' : '/precise';
|
|
296
|
+
try {
|
|
297
|
+
const { block, timestamp: blockTimestamp } = await (0, common_1.fetchJson)(`${serviceUrl}/block/${chain}/${timestamp}${route}`, { timeout: blockServiceTimeout });
|
|
298
|
+
if (!Number.isInteger(block) || !Number.isInteger(blockTimestamp))
|
|
299
|
+
throw new Error('bad response');
|
|
300
|
+
// same guard as the local search: a wildly off answer is worse than no answer
|
|
301
|
+
if (blockTimestamp > timestamp || timestamp - blockTimestamp > 3600)
|
|
302
|
+
throw new Error(`block ${block} (${blockTimestamp}) is too far from ${timestamp}`);
|
|
303
|
+
blockServiceFailures = 0;
|
|
304
|
+
addBlockToCache(chain, { number: block, timestamp: blockTimestamp });
|
|
305
|
+
return { block, number: block, timestamp: blockTimestamp };
|
|
306
|
+
}
|
|
307
|
+
catch (e) {
|
|
308
|
+
if (++blockServiceFailures >= blockServiceMaxFailures) {
|
|
309
|
+
blockServicePausedUntil = Date.now() + blockServicePauseDuration;
|
|
310
|
+
blockServiceFailures = 0;
|
|
311
|
+
}
|
|
312
|
+
(0, debugLog_1.debugLog)(`block-service lookup failed for ${chain} ${timestamp}, falling back to local search: ${(_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : e}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function getChainBlockTimeCache(chain) {
|
|
316
|
+
if (!blockTimeCache[chain])
|
|
317
|
+
blockTimeCache[chain] = {};
|
|
318
|
+
return blockTimeCache[chain];
|
|
319
|
+
}
|
|
320
|
+
function addBlockToCache(chain, block) {
|
|
321
|
+
const { number, timestamp } = block;
|
|
322
|
+
getChainBlockTimeCache(chain)[number] = { number, timestamp, };
|
|
323
|
+
// save the block cache every 5 minutes
|
|
324
|
+
if (Date.now() - lastBlockCacheSave > blockCacheSaveInterval) {
|
|
325
|
+
lastBlockCacheSave = Date.now();
|
|
326
|
+
saveBlockCacheFile();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const earliestAvailableBlockRange = {
|
|
330
|
+
nibiru: 4 * 1e5, // nibiru hold only the last 400k block data
|
|
331
|
+
evmos: 2 * 1e5,
|
|
332
|
+
};
|
|
333
|
+
// block time measured over the most recent blocks, a far better predictor of the near past than the all time average
|
|
334
|
+
const blockTimeSampleSize = 10000;
|
|
335
|
+
const blockTimeCacheDuration = 1000 * 60 * 60; // 1 hour
|
|
336
|
+
const recentBlockTimes = {};
|
|
337
|
+
async function getRecentBlockTime(chain, anchor, floor, onBlock) {
|
|
338
|
+
const cached = recentBlockTimes[chain];
|
|
339
|
+
if (cached && Date.now() - cached.updatedAt < blockTimeCacheDuration)
|
|
340
|
+
return cached.blockTime;
|
|
341
|
+
const sample = await fetchAvailableBlock(Math.max(floor, anchor.number - blockTimeSampleSize), chain);
|
|
342
|
+
if (sample.number >= anchor.number)
|
|
343
|
+
return 2; // pruning horizon is above the anchor, nothing to measure, rough guess, don't cache
|
|
344
|
+
onBlock(sample);
|
|
345
|
+
const blockTime = (anchor.timestamp - sample.timestamp) / (anchor.number - sample.number);
|
|
346
|
+
if (!(blockTime > 0) || !isFinite(blockTime))
|
|
347
|
+
return 2; // not enough history to measure, rough guess, don't cache
|
|
348
|
+
recentBlockTimes[chain] = { blockTime, updatedAt: Date.now() };
|
|
349
|
+
return blockTime;
|
|
350
|
+
}
|
|
351
|
+
async function _lookupBlock(timestamp, extraParams) {
|
|
352
|
+
var _a, _b;
|
|
353
|
+
const { allowedTimeRange = 5 * 60, acceptableBlockImprecision = 10 } = extraParams !== null && extraParams !== void 0 ? extraParams : {};
|
|
354
|
+
const chain = (_a = extraParams === null || extraParams === void 0 ? void 0 : extraParams.chain) !== null && _a !== void 0 ? _a : "ethereum";
|
|
355
|
+
let time = Date.now();
|
|
356
|
+
if (chain === 'waves') {
|
|
357
|
+
const api = `https://nodes.wavesnodes.com/blocks/heightByTimestamp/${timestamp * 1000}`;
|
|
358
|
+
const data = await (0, common_1.fetchJson)(api);
|
|
359
|
+
return {
|
|
360
|
+
timestamp,
|
|
361
|
+
block: +data.height,
|
|
362
|
+
number: +data.height,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
const maxCalls = 30;
|
|
366
|
+
let calls = 0;
|
|
367
|
+
let lowBlock; // closest known block at or before the timestamp
|
|
368
|
+
let highBlock; // closest known block after the timestamp
|
|
369
|
+
let bestBlock;
|
|
370
|
+
let blockTime;
|
|
371
|
+
let lastBracketWidth = Infinity;
|
|
372
|
+
try {
|
|
373
|
+
let floor = getFloorBlock();
|
|
374
|
+
const cachedLow = getLowBlock();
|
|
375
|
+
if (cachedLow && cachedLow.number >= floor)
|
|
376
|
+
updateBounds(cachedLow, false);
|
|
377
|
+
const cachedHigh = getHighBlock();
|
|
378
|
+
if (cachedHigh)
|
|
379
|
+
updateBounds(cachedHigh, false);
|
|
380
|
+
// anchor on the chain head unless the cache already has a block after the timestamp
|
|
381
|
+
if (!highBlock || earliestAvailableBlockRange[chain]) {
|
|
382
|
+
const head = await (0, exports.getLatestBlock)(chain);
|
|
383
|
+
// Short-circuit in case we are trying to get the current block, or the rpc head lags behind the requested time
|
|
384
|
+
if (Math.abs(head.timestamp - timestamp) < 60 * 5 || head.timestamp <= timestamp)
|
|
385
|
+
return { block: head.number, number: head.number, timestamp: head.timestamp };
|
|
386
|
+
floor = getFloorBlock(head);
|
|
387
|
+
if (!highBlock || head.number < highBlock.number)
|
|
388
|
+
updateBounds(head, false);
|
|
389
|
+
}
|
|
390
|
+
while (!isPreciseEnough() && calls < maxCalls) {
|
|
391
|
+
floor = Math.max(floor, (_b = earliestAvailableBlock[chain]) !== null && _b !== void 0 ? _b : 0);
|
|
392
|
+
if (lowBlock && lowBlock.number < floor)
|
|
393
|
+
lowBlock = undefined; // pruned away, guesses below the floor would just repeat the horizon block
|
|
394
|
+
let guesses;
|
|
395
|
+
if (lowBlock) {
|
|
396
|
+
// bracketed: interpolate between the bounds, bisect when the previous step didn't at least halve the bracket
|
|
397
|
+
const width = highBlock.number - lowBlock.number;
|
|
398
|
+
if (width <= 1)
|
|
399
|
+
break;
|
|
400
|
+
let guess = lowBlock.number + Math.round((timestamp - lowBlock.timestamp) * width / (highBlock.timestamp - lowBlock.timestamp));
|
|
401
|
+
if (width > lastBracketWidth / 2)
|
|
402
|
+
guess = Math.floor((lowBlock.number + highBlock.number) / 2);
|
|
403
|
+
lastBracketWidth = width;
|
|
404
|
+
guesses = [clamp(guess, lowBlock.number + 1, highBlock.number - 1)];
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
// only an upper bound: extrapolate back from it using the recent block time
|
|
408
|
+
if (highBlock.number <= floor)
|
|
409
|
+
break; // requested time is before the available history
|
|
410
|
+
if (!blockTime) {
|
|
411
|
+
const recentBlockTime = await getRecentBlockTime(chain, highBlock, floor, block => { calls++; updateBounds(block); });
|
|
412
|
+
blockTime = blockTime !== null && blockTime !== void 0 ? blockTime : recentBlockTime; // updateBounds may have set a more local slope meanwhile, keep that
|
|
413
|
+
}
|
|
414
|
+
if (lowBlock)
|
|
415
|
+
continue; // the block time sample already bracketed the timestamp
|
|
416
|
+
const guess = clamp(Math.round(highBlock.number - (highBlock.timestamp - timestamp) / blockTime), floor, highBlock.number - 1);
|
|
417
|
+
// probe around the guess too, so a slightly off block time still brackets the timestamp in one round trip
|
|
418
|
+
const spread = Math.max(acceptableBlockImprecision, Math.round((highBlock.number - guess) * 0.02));
|
|
419
|
+
guesses = [...new Set([guess - spread, guess, guess + spread].map(b => clamp(b, floor, highBlock.number - 1)))];
|
|
420
|
+
}
|
|
421
|
+
const blocks = await Promise.all(guesses.map(b => fetchAvailableBlock(b, chain)));
|
|
422
|
+
calls += blocks.length;
|
|
423
|
+
blocks.forEach(block => updateBounds(block));
|
|
424
|
+
}
|
|
425
|
+
if (calls > 2 && env_1.ENV_CONSTANTS.GET_BLOCK_STATS)
|
|
426
|
+
(0, debugLog_1.debugLog)(`chain: ${chain} block: ${bestBlock.number} #calls: ${calls} imprecision: ${Number(getImprecision(bestBlock) / 60).toFixed(2)} (min) Time Taken: ${Number((Date.now() - time) / 1000).toFixed(2)} (in sec)`);
|
|
427
|
+
if (getImprecision(bestBlock) > 3600
|
|
428
|
+
&& chain !== "bsc" // this check is there because bsc halted the chain for few days
|
|
429
|
+
&& !(0, env_1.shouldSkipBlockValidation)(chain)) {
|
|
430
|
+
if (!lowBlock && highBlock.number <= floor)
|
|
431
|
+
throw new Error(`Requested timestamp ${timestamp} (${new Date(timestamp * 1000).toISOString()}) is before the earliest available block ${highBlock.number} (${new Date(highBlock.timestamp * 1000).toISOString()}) of ${chain}`);
|
|
432
|
+
throw new Error("Block selected is more than 1 hour away from the requested timestamp");
|
|
433
|
+
}
|
|
434
|
+
return {
|
|
435
|
+
number: bestBlock.number,
|
|
436
|
+
block: bestBlock.number,
|
|
437
|
+
timestamp: bestBlock.timestamp
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
catch (e) {
|
|
441
|
+
throw (0, common_1.formError)(e);
|
|
442
|
+
}
|
|
443
|
+
function getFloorBlock(head) {
|
|
444
|
+
var _a, _b;
|
|
445
|
+
let floor = (_a = intialBlocks[chain]) !== null && _a !== void 0 ? _a : 100;
|
|
446
|
+
const envLowValue = process.env[`${chain.toUpperCase()}_BLOCK_LOW`];
|
|
447
|
+
if (envLowValue)
|
|
448
|
+
floor = parseInt(envLowValue);
|
|
449
|
+
floor = Math.max(floor, (_b = earliestAvailableBlock[chain]) !== null && _b !== void 0 ? _b : 0);
|
|
450
|
+
if (head && earliestAvailableBlockRange[chain])
|
|
451
|
+
floor = Math.max(floor, head.number - earliestAvailableBlockRange[chain]);
|
|
452
|
+
return floor;
|
|
453
|
+
}
|
|
454
|
+
function getImprecision(block) {
|
|
455
|
+
return Math.abs(block.timestamp - timestamp);
|
|
456
|
+
}
|
|
457
|
+
function isPreciseEnough() {
|
|
458
|
+
if (bestBlock && getImprecision(bestBlock) <= allowedTimeRange)
|
|
459
|
+
return true;
|
|
460
|
+
return !!lowBlock && !!highBlock && highBlock.number - lowBlock.number <= acceptableBlockImprecision;
|
|
461
|
+
}
|
|
462
|
+
function updateBounds(block, saveToCache = true) {
|
|
463
|
+
if (saveToCache)
|
|
464
|
+
addBlockToCache(chain, block);
|
|
465
|
+
if (block.timestamp <= timestamp) {
|
|
466
|
+
if (!lowBlock || block.number > lowBlock.number)
|
|
467
|
+
lowBlock = block;
|
|
468
|
+
}
|
|
469
|
+
else if (!highBlock || block.number < highBlock.number) {
|
|
470
|
+
// while unbracketed, the slope between consecutive upper bounds is the most local block time we have
|
|
471
|
+
if (highBlock && !lowBlock) {
|
|
472
|
+
const localBlockTime = (highBlock.timestamp - block.timestamp) / (highBlock.number - block.number);
|
|
473
|
+
if (localBlockTime > 0)
|
|
474
|
+
blockTime = localBlockTime;
|
|
475
|
+
}
|
|
476
|
+
highBlock = block;
|
|
477
|
+
}
|
|
478
|
+
if (!bestBlock || getImprecision(block) < getImprecision(bestBlock))
|
|
479
|
+
bestBlock = block;
|
|
480
|
+
}
|
|
481
|
+
function clamp(value, min, max) {
|
|
482
|
+
return Math.min(Math.max(value, min), max);
|
|
483
|
+
}
|
|
484
|
+
function getLowBlock() {
|
|
485
|
+
return Object.values(getChainBlockTimeCache(chain))
|
|
486
|
+
.filter(i => i.timestamp < timestamp)
|
|
487
|
+
.sort((a, b) => b.timestamp - a.timestamp)[0];
|
|
488
|
+
}
|
|
489
|
+
function getHighBlock() {
|
|
490
|
+
return Object.values(getChainBlockTimeCache(chain))
|
|
491
|
+
.filter(i => i.timestamp > timestamp)
|
|
492
|
+
.sort((a, b) => a.timestamp - b.timestamp)[0];
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
async function getTimestamp(height, chain) {
|
|
496
|
+
const provider = getExtraProvider(chain);
|
|
497
|
+
const block = await provider.getBlock(height);
|
|
498
|
+
return block.timestamp;
|
|
499
|
+
}
|
|
500
|
+
const currentChainBlockCache = {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export declare const currentVersion = "zlib-1.0";
|
|
2
|
+
export declare function readCache(file: string, options?: ReadCacheOptions): Promise<any>;
|
|
3
|
+
interface WriteCacheOptions {
|
|
4
|
+
skipR2CacheWrite?: boolean;
|
|
5
|
+
alreadyCompressed?: boolean;
|
|
6
|
+
skipCompression?: boolean;
|
|
7
|
+
}
|
|
8
|
+
interface ReadCacheOptions {
|
|
9
|
+
checkIfRecent?: boolean;
|
|
10
|
+
readFromR2Cache?: boolean;
|
|
11
|
+
skipR2Cache?: boolean;
|
|
12
|
+
skipCompression?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function writeCache(file: string, data: any, options?: WriteCacheOptions): Promise<string | undefined>;
|
|
15
|
+
export declare function parseCache(dataString: string | Buffer, options?: ReadCacheOptions): Promise<any>;
|
|
16
|
+
export declare function compressCache(data: any, options?: WriteCacheOptions): Promise<string | Buffer<ArrayBufferLike>>;
|
|
17
|
+
export declare const ONE_WEEK: number;
|
|
18
|
+
export declare function getTempLocalCache({ file, defaultData, clearAfter, returnWithSaveFunction }: {
|
|
19
|
+
file: string;
|
|
20
|
+
defaultData?: any;
|
|
21
|
+
clearAfter?: number;
|
|
22
|
+
returnWithSaveFunction?: boolean;
|
|
23
|
+
}): any | ({
|
|
24
|
+
data: any;
|
|
25
|
+
saveCacheFile: () => void;
|
|
26
|
+
});
|
|
27
|
+
export declare function deleteCache(file: string): Promise<void>;
|
|
28
|
+
export declare function readExpiringJsonCache(file: string): Promise<any>;
|
|
29
|
+
export declare function writeExpiringJsonCache(file: string, data: any, { expireAfter, // cache for 1 day by default
|
|
30
|
+
expiryTimestamp, }?: {
|
|
31
|
+
expireAfter?: number;
|
|
32
|
+
expiryTimestamp?: number;
|
|
33
|
+
}): Promise<void>;
|
|
34
|
+
export declare function cachedFetch({ key, endpoint, postData, fetcher, fetchOptions, errorOnNoCache, cacheMaxAge, writeCacheOptions, readCacheOptions, }: {
|
|
35
|
+
key: string;
|
|
36
|
+
endpoint?: string;
|
|
37
|
+
postData?: any;
|
|
38
|
+
fetcher?: () => Promise<any>;
|
|
39
|
+
fetchOptions?: any;
|
|
40
|
+
errorOnNoCache?: boolean;
|
|
41
|
+
cacheMaxAge?: number;
|
|
42
|
+
writeCacheOptions?: WriteCacheOptions;
|
|
43
|
+
readCacheOptions?: ReadCacheOptions;
|
|
44
|
+
}): Promise<any>;
|
|
45
|
+
export {};
|