|
| 1 | +'use strict'; |
| 2 | + |
| 3 | +var path = require('path'); |
| 4 | +var indexer = require('./indexer'); |
| 5 | + |
| 6 | +function sleep(ms) { |
| 7 | + return new Promise(function(resolve) { setTimeout(resolve, ms); }); |
| 8 | +} |
| 9 | + |
| 10 | +function loadDaemonConfig() { |
| 11 | + var configPath = process.env.ARCHIVE_NODE_CONFIG || path.join(__dirname, 'config.json'); |
| 12 | + var cfg = indexer.loadConfig(configPath); |
| 13 | + cfg.pollIntervalMs = Number(process.env.ARCHIVE_NODE_POLL_MS || cfg.pollIntervalMs || 5000); |
| 14 | + cfg.maxBlocksPerTick = Number(process.env.ARCHIVE_NODE_MAX_BLOCKS_PER_TICK || cfg.maxBlocksPerTick || 500); |
| 15 | + return cfg; |
| 16 | +} |
| 17 | + |
| 18 | +function rpcCall(nodeUrl, method, params, timeoutMs) { |
| 19 | + return new Promise(function(resolve, reject) { |
| 20 | + var controller = typeof AbortController !== 'undefined' ? new AbortController() : null; |
| 21 | + var done = false; |
| 22 | + var timer = setTimeout(function() { |
| 23 | + if (controller) controller.abort(); |
| 24 | + if (!done) reject(new Error('timeout')); |
| 25 | + }, timeoutMs || 8000); |
| 26 | + fetch(nodeUrl, { |
| 27 | + method: 'POST', |
| 28 | + headers: { 'Content-Type': 'application/json' }, |
| 29 | + body: JSON.stringify({ jsonrpc: '2.0', method: 'call', params: ['database_api', method, params || []], id: 1 }), |
| 30 | + signal: controller ? controller.signal : undefined |
| 31 | + }).then(function(resp) { |
| 32 | + if (!resp.ok) throw new Error('HTTP ' + resp.status); |
| 33 | + return resp.json(); |
| 34 | + }).then(function(payload) { |
| 35 | + done = true; |
| 36 | + clearTimeout(timer); |
| 37 | + if (!payload || !payload.result) throw new Error('empty result'); |
| 38 | + resolve(payload.result); |
| 39 | + }).catch(function(err) { |
| 40 | + done = true; |
| 41 | + clearTimeout(timer); |
| 42 | + reject(err); |
| 43 | + }); |
| 44 | + }); |
| 45 | +} |
| 46 | + |
| 47 | +async function getHeadBlock(cfg) { |
| 48 | + var lastErr = null; |
| 49 | + for (var i = 0; i < cfg.sourceNodes.length; i += 1) { |
| 50 | + try { |
| 51 | + var dgp = await rpcCall(cfg.sourceNodes[i], 'get_dynamic_global_properties', [], cfg.timeoutMs); |
| 52 | + return Number(dgp.head_block_number || 0); |
| 53 | + } catch (err) { |
| 54 | + lastErr = err; |
| 55 | + } |
| 56 | + } |
| 57 | + throw lastErr || new Error('cannot read head block'); |
| 58 | +} |
| 59 | + |
| 60 | +async function runForever() { |
| 61 | + var cfg = loadDaemonConfig(); |
| 62 | + console.log('archive-node daemon started pollMs=' + cfg.pollIntervalMs + ' maxBlocksPerTick=' + cfg.maxBlocksPerTick); |
| 63 | + while (true) { |
| 64 | + try { |
| 65 | + var head = await getHeadBlock(cfg); |
| 66 | + var result = await indexer.indexRange({ config: cfg, to: head, maxBlocks: cfg.maxBlocksPerTick, once: true }); |
| 67 | + if (result.indexedBlocks || result.indexedEvents) { |
| 68 | + console.log('archive-node tick head=' + head + ' indexedBlocks=' + result.indexedBlocks + ' indexedEvents=' + result.indexedEvents + ' last=' + result.lastIndexedBlock); |
| 69 | + } |
| 70 | + } catch (err) { |
| 71 | + console.error('archive-node tick failed: ' + (err && err.stack || err)); |
| 72 | + } |
| 73 | + await sleep(cfg.pollIntervalMs); |
| 74 | + } |
| 75 | +} |
| 76 | + |
| 77 | +if (require.main === module) { |
| 78 | + runForever().catch(function(err) { |
| 79 | + console.error(err && err.stack || err); |
| 80 | + process.exit(1); |
| 81 | + }); |
| 82 | +} |
| 83 | + |
| 84 | +module.exports = { |
| 85 | + loadDaemonConfig: loadDaemonConfig, |
| 86 | + rpcCall: rpcCall, |
| 87 | + getHeadBlock: getHeadBlock, |
| 88 | + runForever: runForever |
| 89 | +}; |
0 commit comments