index.js
 1  class TransactionTracker {
 2    constructor(embark, _options) {
 3      this.logger = embark.logger;
 4      this.events = embark.events;
 5      this.transactions = {};
 6      this.embark = embark;
 7      this.startTimestamp = Date.now() / 1000;
 8  
 9      embark.events.on("block:pending:transaction", this.onPendingTransaction.bind(this));
10      embark.events.on("block:header", this.onBlockHeader.bind(this));
11      this.registerAPICalls();
12    }
13  
14    onPendingTransaction(pendingTransactionHash) {
15      this.transactions[pendingTransactionHash] = {
16        startTimestamp: Date.now() / 1000
17      };
18    }
19  
20    onBlockHeader(blockHeader) {
21      this.events.request("blockchain:block:byNumber", blockHeader.hash, (err, block) => {
22        if (err) {
23          return this.logger.error('Error getting block header', err);
24        }
25        // Don't know why, but sometimes we receive nothing
26        if (!block || !block.transactions) {
27          return;
28        }
29        block.transactions.forEach(transaction => {
30          if (this.transactions[transaction.hash]) {
31            let wait = block.timestamp - this.transactions[transaction.hash].startTimestamp;
32            if (wait < 0.1) {
33              wait = 0.1;
34            }
35            Object.assign(this.transactions[transaction.hash],
36              {endTimestamp: block.timestamp, wait, gasPrice: transaction.gasPrice});
37          }
38        });
39        this.events.emit('blockchain:gas:oracle:new');
40        this.cullOldTransactions();
41      });
42    }
43  
44    cullOldTransactions() {
45      const timeLimit = (Date.now() / 1000) - 600; // Transactions old of 10 minutes are not to be counted anymore
46      if (this.startTimestamp > timeLimit) {
47        return;
48      }
49      Object.keys(this.transactions).forEach(transactionHash => {
50        if (this.transactions[transactionHash].startTimestamp < timeLimit) {
51          delete this.transactions[transactionHash];
52        }
53      });
54    }
55  
56    calculateGasPriceSpeeds() {
57      return Object.keys(this.transactions).reduce((acc, transactionHash) => {
58        const transaction = this.transactions[transactionHash];
59        if (!transaction.gasPrice) {
60          return acc;
61        }
62        if (!acc[transaction.gasPrice]) {
63          acc[transaction.gasPrice] = {
64            nbTxs: 0,
65            totalWait: 0
66          };
67        }
68        acc[transaction.gasPrice].nbTxs++;
69        acc[transaction.gasPrice].totalWait += transaction.wait;
70        acc[transaction.gasPrice].averageWait = acc[transaction.gasPrice].totalWait / acc[transaction.gasPrice].nbTxs;
71  
72        return acc;
73      }, {});
74    }
75  
76    registerAPICalls() {
77      const self = this;
78      self.embark.registerAPICall(
79        'get',
80        '/embark-api/blockchain/gas/oracle',
81        (req, res) => {
82          res.send(self.calculateGasPriceSpeeds());
83        }
84      );
85      self.embark.registerAPICall(
86        'ws',
87        '/embark-api/blockchain/gas/oracle',
88        (ws) => {
89          self.events.on('blockchain:gas:oracle:new', () => {
90            ws.send(JSON.stringify(self.calculateGasPriceSpeeds()), () => {});
91          });
92        }
93      );
94    }
95  }
96  
97  module.exports = TransactionTracker;