1 | "use strict";
|
2 |
|
3 | const App = require('fib-app');
|
4 | const util = require("util");
|
5 | const fs = require("fs");
|
6 | const Config = require("./conf/conf.json");
|
7 | const chain = require("chain");
|
8 |
|
9 | BigInt.prototype.toJSON = function() { return this.toString(); }
|
10 |
|
11 | let block_caches = new util.LruCache(20);
|
12 |
|
13 | function Tracker() {
|
14 | console.notice(`==========chain-tracker==========\n\nDBconnString: ${Config.DBconnString.replace(/:[^:]*@/, ":*****@")}\n\n==========chain-tracker==========`);
|
15 | let chain_name = chain.name || "eosio";
|
16 | let hookEvents = {};
|
17 | let sys_bn, nore_bn;
|
18 | let app = new App(Config.DBconnString);
|
19 |
|
20 | app.db.use(require('./defs'));
|
21 |
|
22 | let checkBlockNum = (block_num, type) => {
|
23 |
|
24 | block_num = Number(block_num);
|
25 | let check_num = sys_bn;
|
26 | if (type && type == "irreversible") check_num = nore_bn;
|
27 | if (check_num >= block_num) {
|
28 | console.warn("sys block_num(%s) >= node block_num(%s)", check_num, block_num);
|
29 | return false;
|
30 | }
|
31 |
|
32 | return true;
|
33 | }
|
34 |
|
35 | this.app = app;
|
36 |
|
37 | this.use = (model) => {
|
38 | if (!model) throw new Error("use:function(model)");
|
39 |
|
40 | if (!model.defines || !model.hooks) throw new Error("model define error: Array(defines) JSON(hooks)");
|
41 |
|
42 | let defines = model.defines;
|
43 | let hooks = model.hooks;
|
44 |
|
45 | app.db.use(util.isArray(defines) ? defines : [defines]);
|
46 |
|
47 | for (let f in hooks) {
|
48 | hookEvents[f] = hookEvents[f] || [];
|
49 | hookEvents[f].push(hooks[f]);
|
50 | }
|
51 | };
|
52 |
|
53 | function dealData(db, msg, event) {
|
54 | let messages = {};
|
55 | event = event ? event + ":" : "";
|
56 | let collectMessage = (_at) => {
|
57 | function _c(f) {
|
58 | if (hookEvents[f]) {
|
59 | messages[f] = messages[f] || [];
|
60 | messages[f].push(_at);
|
61 | }
|
62 | }
|
63 |
|
64 | if (_at.receipt.receiver !== _at.act.account) return;
|
65 | _c(event + _at.act.account);
|
66 |
|
67 | _c(event + _at.act.account + "/" + _at.act.name);
|
68 | }
|
69 |
|
70 | function execActions(at, parent) {
|
71 | if (parent) {
|
72 | let _parent = parent;
|
73 | delete _parent.inline_traces;
|
74 | at.parent = _parent;
|
75 | }
|
76 |
|
77 | collectMessage(at);
|
78 |
|
79 | if (at.inline_traces)
|
80 | at.inline_traces.forEach((_at) => {
|
81 | execActions(_at, at);
|
82 | });
|
83 | }
|
84 |
|
85 | execActions(msg);
|
86 |
|
87 | for (let f in messages) {
|
88 | let ats = messages[f];
|
89 | let hooks = hookEvents[f];
|
90 | if (hooks) hooks.forEach((hook) => {
|
91 | try {
|
92 | hook(db, ats)
|
93 | } catch (e) {
|
94 | console.error("[%s]", f, ats, e.stack);
|
95 | }
|
96 | });
|
97 | }
|
98 | }
|
99 |
|
100 | function cleanTrans(trx) {
|
101 | trx.action_traces.forEach(at => {
|
102 | delete at.receipt.act_digest;
|
103 | delete at.receipt.auth_sequence;
|
104 | delete at.act.data;
|
105 | delete at.act.hex_data;
|
106 | delete at.act.authorization;
|
107 | delete at.account_ram_deltas;
|
108 | delete at.account_disk_deltas;
|
109 | delete at.return_value_hex_data;
|
110 | });
|
111 | }
|
112 |
|
113 | function cleanBlock(blk) {
|
114 | blk.transactions.forEach(t => {
|
115 | cleanTrans(t.rawData);
|
116 | });
|
117 | }
|
118 |
|
119 | this.emitter = () => {
|
120 | sys_bn = app.db(db => {
|
121 | return db.models.blocks.get_final_block();
|
122 | });
|
123 | nore_bn = app.db(db => {
|
124 | return db.models.blocks.get_final_irreversible_block();
|
125 | })
|
126 | if (Config.replay) {
|
127 | let replayStatrBn = Config.replayStatrBn || 0;
|
128 | while (replayStatrBn < sys_bn) {
|
129 | app.db(db => {
|
130 | console.time(`[replay block on:${replayStatrBn} ] use`);
|
131 | let blocks = db.driver.execQuerySync(`select block_num,status,producer_block_id from blocks where block_num>? order by block_num limit 1000`, [replayStatrBn]);
|
132 | db.trans(() => {
|
133 | blocks.forEach(bk => {
|
134 | let trxs = db.driver.execQuerySync(`select * from transactions where producer_block_id = ?`, [bk.producer_block_id]);
|
135 | if (!trxs.length) return;
|
136 |
|
137 | trxs.forEach((trx) => {
|
138 | JSON.parse(trx.rawData.toString()).action_traces.forEach((msg) => { dealData(db, msg, "pending"); });
|
139 | });
|
140 |
|
141 | if (["lightconfirm", "irreversible"].includes(bk.status)) {
|
142 | trxs.forEach((trx) => {
|
143 | JSON.parse(trx.rawData.toString()).action_traces.forEach((msg) => { dealData(db, msg); });
|
144 | });
|
145 | }
|
146 |
|
147 | if (bk.status == "irreversible") {
|
148 | trxs.forEach((trx) => {
|
149 | JSON.parse(trx.rawData.toString()).action_traces.forEach((msg) => { dealData(db, msg, "irreversible"); });
|
150 | });
|
151 | }
|
152 | })
|
153 | })
|
154 | console.timeEnd(`[replay block on:${replayStatrBn} ] use`);
|
155 | replayStatrBn = blocks[blocks.length - 1].block_num;
|
156 | })
|
157 | }
|
158 | }
|
159 |
|
160 | chain.load("emitter");
|
161 |
|
162 | chain.on({
|
163 | transaction: (trx) => {
|
164 | let block_num = trx.block_num.toString();
|
165 | let producer_block_id = trx.producer_block_id;
|
166 |
|
167 | if (!producer_block_id) return;
|
168 |
|
169 | if (!checkBlockNum(block_num)) return;
|
170 |
|
171 | if (!trx.action_traces) {
|
172 | console.warn("Invalid Transaction:", trx);
|
173 | return;
|
174 | }
|
175 |
|
176 | if (!trx.action_traces.length) return;
|
177 | let contract_action = trx.action_traces[0].act.account + "/" + trx.action_traces[0].act.name;
|
178 | if (contract_action == `${chain_name}/onblock`) return;
|
179 |
|
180 | app.db(db => {
|
181 | let Transactions = db.models.transactions;
|
182 |
|
183 | let t = Transactions.oneSync({
|
184 | trx_id: trx.id,
|
185 | producer_block_id: trx.producer_block_id,
|
186 | })
|
187 |
|
188 | if (t) return;
|
189 | db.trans(() => {
|
190 | let transaction = Transactions.createSync({
|
191 | trx_id: trx.id,
|
192 | producer_block_id: trx.producer_block_id,
|
193 | rawData: trx,
|
194 | contract_action: contract_action
|
195 | });
|
196 |
|
197 | trx.action_traces.forEach(m => { saveActions(m); })
|
198 |
|
199 | function saveActions(m, p_id) {
|
200 | let _m = m;
|
201 | delete _m.inline_traces;
|
202 | let _p_id;
|
203 |
|
204 | if (_m.receipt.receiver == _m.act.account) {
|
205 | _p_id = db.driver.execQuerySync(`insert into actions(trx_id,global_sequence,contract_action,rawData,parent_id,transaction_id) values(?,?,?,?,?,?)`, [_m.trx_id, _m.receipt.global_sequence, _m.act.account + "/" + _m.act.name, JSON.stringify(_m), p_id, transaction.id]).insertId;
|
206 | }
|
207 |
|
208 | if (m.inline_traces)
|
209 | m.inline_traces.forEach(_m => { saveActions(_m, _p_id); })
|
210 | }
|
211 | })
|
212 | });
|
213 |
|
214 | cleanTrans(trx);
|
215 |
|
216 | block_caches.get(producer_block_id, (id) => { return { transactions: [] } }).transactions.push({ rawData: trx });
|
217 | },
|
218 | block: (bk) => {
|
219 | let block_num = bk.block_num.toString();
|
220 |
|
221 | if (!checkBlockNum(block_num)) return;
|
222 |
|
223 | if (!bk.block) {
|
224 | console.warn("Invalid Block!");
|
225 | return;
|
226 | }
|
227 |
|
228 | let _trxs = block_caches.get(bk.id);
|
229 |
|
230 | let now_block = {
|
231 | producer_block_id: bk.id,
|
232 | previous: bk.block.previous,
|
233 | block_num: bk.block_num,
|
234 | producer: bk.block.producer,
|
235 | block_time: bk.block.timestamp,
|
236 | transactions: !!_trxs ? _trxs.transactions : [],
|
237 | status: "pending"
|
238 | };
|
239 | let c_block = now_block;
|
240 |
|
241 | cleanBlock(now_block);
|
242 | block_caches.set(now_block.producer_block_id, now_block);
|
243 |
|
244 | app.db(db => {
|
245 | let Blocks = db.models.blocks;
|
246 |
|
247 | let arr = [];
|
248 | while (arr.length < 14 && now_block) {
|
249 | arr.push(now_block);
|
250 | let previous = now_block.previous;
|
251 | now_block = block_caches.get(previous, (previous) => {
|
252 | if (previous == "0000000000000000000000000000000000000000000000000000000000000000") return null;
|
253 |
|
254 | let block = Blocks.oneSync({
|
255 | producer_block_id: previous
|
256 | });
|
257 |
|
258 | if (!block) {
|
259 | console.warn("Invalid previous block:", previous);
|
260 | return;
|
261 | }
|
262 |
|
263 | if (!block.block_num) return null;
|
264 | let _transactions = db.models.transactions.find({ producer_block_id: block.producer_block_id }).order("id").runSync();
|
265 | let blk = {
|
266 | producer_block_id: block.producer_block_id,
|
267 | previous: block.previous,
|
268 | block_num: block.block_num,
|
269 | producer: block.producer,
|
270 | block_time: block.block_time,
|
271 | transactions: _transactions,
|
272 | status: arr.length == '13' ? 'lightconfirm' : block.status
|
273 | }
|
274 |
|
275 | cleanBlock(blk);
|
276 |
|
277 | return blk;
|
278 | });
|
279 | }
|
280 |
|
281 | let deal_block = [];
|
282 | if (arr.length > 12) {
|
283 | let producer = arr[12].producer;
|
284 |
|
285 | let confirm = () => {
|
286 | for (let i = 12; i > 0; i--) {
|
287 | if (arr[i].producer == producer) {
|
288 | if (arr[i].status == 'pending') arr[i].status = "lightconfirm";
|
289 | let _block = block_caches.get(arr[i].producer_block_id);
|
290 | if (_block && _block.transactions && _block.transactions.length) deal_block.push(_block);
|
291 | } else {
|
292 | break;
|
293 | }
|
294 | }
|
295 | }
|
296 |
|
297 | if (arr.length == 14) {
|
298 | if (!["lightconfirm", "irreversible"].includes(arr[13].status)) throw new Error("13 status != lightconfirm&irreversible" + arr[13].status);
|
299 | if (arr[12].status == "pending") confirm();
|
300 | } else {
|
301 | confirm();
|
302 | }
|
303 | }
|
304 |
|
305 | db.trans(() => {
|
306 | if (Blocks.get(bk.id)) {
|
307 | console.warn("Reentrant block id:", bk.id);
|
308 | return;
|
309 | }
|
310 |
|
311 | let f_block = Blocks.createSync({
|
312 | block_num: c_block.block_num,
|
313 | block_time: c_block.block_time,
|
314 | producer: c_block.producer,
|
315 | producer_block_id: c_block.producer_block_id,
|
316 | previous: c_block.previous,
|
317 | status: "pending"
|
318 | });
|
319 |
|
320 | c_block.transactions.forEach((trx) => {
|
321 | db.driver.execQuerySync(`update transactions set block_id = ? where producer_block_id =?`, [f_block.id, c_block.producer_block_id]);
|
322 | trx.rawData.action_traces.forEach((msg) => { dealData(db, msg, 'pending'); })
|
323 | })
|
324 |
|
325 | if (deal_block.length) {
|
326 | deal_block.forEach(bk => {
|
327 | if (bk.status != 'lightconfirm') return;
|
328 | db.driver.execQuerySync(`update blocks set status = 'lightconfirm' where producer_block_id = ?`, [bk.id]);
|
329 | bk.transactions.forEach((trx) => { trx.rawData.action_traces.forEach((msg) => { dealData(db, msg); }); });
|
330 | });
|
331 | }
|
332 | });
|
333 | });
|
334 | },
|
335 | irreversible_block: (blk) => {
|
336 | let block_num = blk.block_num.toString();
|
337 | if (!checkBlockNum(block_num, 'irreversible')) return;
|
338 |
|
339 | let producer_block_id = blk.id;
|
340 | app.db(db => {
|
341 | var block;
|
342 |
|
343 | let _block = db.models.blocks.oneSync({
|
344 | producer_block_id: producer_block_id
|
345 | });
|
346 |
|
347 | if (!_block)
|
348 | return;
|
349 |
|
350 | let _transactions = db.models.transactions.find({ producer_block_id: producer_block_id }).order("id").runSync();
|
351 |
|
352 | if (!_transactions || !_transactions.length)
|
353 | return;
|
354 |
|
355 | block = {
|
356 | producer_block_id: _block.producer_block_id,
|
357 | previous: _block.previous,
|
358 | block_num: _block.block_num,
|
359 | producer: _block.producer,
|
360 | block_time: _block.block_time,
|
361 | transactions: _transactions,
|
362 | status: _block.status
|
363 | }
|
364 |
|
365 | db.trans(() => {
|
366 | if (block.status === 'pending') {
|
367 | block.transactions.forEach(trx => { trx.rawData.action_traces.forEach(msg => { dealData(db, msg) }); })
|
368 | }
|
369 | block.status = "irreversible";
|
370 | block.transactions.forEach(trx => { trx.rawData.action_traces.forEach(msg => { dealData(db, msg, 'irreversible') }); })
|
371 | db.driver.execQuerySync(`update blocks set status = 'irreversible' where producer_block_id = ?`, [producer_block_id]);
|
372 | })
|
373 | })
|
374 | },
|
375 | close: () => app.db.clear()
|
376 | });
|
377 | }
|
378 |
|
379 | this.diagram = () => fs.writeTextFile(process.cwd() + '/diagram.svg', app.diagram());
|
380 |
|
381 | this.stop = () => {
|
382 | if (chain) chain.stop();
|
383 | process.exit();
|
384 | }
|
385 | }
|
386 |
|
387 | Tracker.Config = Config;
|
388 |
|
389 | module.exports = Tracker;
|