UNPKG

11.1 kBJavaScriptView Raw
1"use strict";
2
3const App = require('fib-app');
4const util = require("util");
5const fs = require("fs");
6const Config = require("./conf/conf.json");
7const chain = require("chain");
8
9BigInt.prototype.toJSON = function() { return this.toString(); }
10
11let block_caches = new util.LruCache(20);
12
13function 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
387Tracker.Config = Config;
388
389module.exports = Tracker;