aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts/lib/eth-store.js
blob: 7e2caf884af7cf152a7f217634748bfce379247e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/* Ethereum Store
 *
 * This module is responsible for tracking any number of accounts
 * and caching their current balances & transaction counts.
 *
 * It also tracks transaction hashes, and checks their inclusion status
 * on each new block.
 */

const EventEmitter = require('events').EventEmitter
const inherits = require('util').inherits
const async = require('async')
const clone = require('clone')
const EthQuery = require('eth-query')

module.exports = EthereumStore


inherits(EthereumStore, EventEmitter)
function EthereumStore(engine) {
  const self = this
  EventEmitter.call(self)
  self._currentState = {
    accounts: {},
    transactions: {},
  }
  self._query = new EthQuery(engine)

  engine.on('block', self._updateForBlock.bind(self))
}

//
// public
//

EthereumStore.prototype.getState = function () {
  const self = this
  return clone(self._currentState)
}

EthereumStore.prototype.addAccount = function (address) {
  const self = this
  self._currentState.accounts[address] = {}
  self._didUpdate()
  if (!self.currentBlockNumber) return
  self._updateAccount(address, () => {
    self._didUpdate()
  })
}

EthereumStore.prototype.removeAccount = function (address) {
  const self = this
  delete self._currentState.accounts[address]
  self._didUpdate()
}

EthereumStore.prototype.addTransaction = function (txHash) {
  const self = this
  self._currentState.transactions[txHash] = {}
  self._didUpdate()
  if (!self.currentBlockNumber) return
  self._updateTransaction(self.currentBlockNumber, txHash, noop)
}

EthereumStore.prototype.removeTransaction = function (address) {
  const self = this
  delete self._currentState.transactions[address]
  self._didUpdate()
}


//
// private
//

EthereumStore.prototype._didUpdate = function () {
  const self = this
  var state = self.getState()
  self.emit('update', state)
}

EthereumStore.prototype._updateForBlock = function (block) {
  const self = this
  var blockNumber = '0x' + block.number.toString('hex')
  self.currentBlockNumber = blockNumber
  async.parallel([
    self._updateAccounts.bind(self),
    self._updateTransactions.bind(self, blockNumber),
  ], function (err) {
    if (err) return console.error(err)
    self.emit('block', self.getState())
    self._didUpdate()
  })
}

EthereumStore.prototype._updateAccounts = function (cb) {
  var accountsState = this._currentState.accounts
  var addresses = Object.keys(accountsState)
  async.each(addresses, this._updateAccount.bind(this), cb)
}

EthereumStore.prototype._updateAccount = function (address, cb) {
  var accountsState = this._currentState.accounts
  this.getAccount(address, function (err, result) {
    if (err) return cb(err)
    result.address = address
    // only populate if the entry is still present
    if (accountsState[address]) {
      accountsState[address] = result
    }
    cb(null, result)
  })
}

EthereumStore.prototype.getAccount = function (address, cb) {
  const query = this._query
  async.parallel({
    balance: query.getBalance.bind(query, address),
    nonce: query.getTransactionCount.bind(query, address),
    code: query.getCode.bind(query, address),
  }, cb)
}

EthereumStore.prototype._updateTransactions = function (block, cb) {
  const self = this
  var transactionsState = self._currentState.transactions
  var txHashes = Object.keys(transactionsState)
  async.each(txHashes, self._updateTransaction.bind(self, block), cb)
}

EthereumStore.prototype._updateTransaction = function (block, txHash, cb) {
  const self = this
  // would use the block here to determine how many confirmations the tx has
  var transactionsState = self._currentState.transactions
  self._query.getTransaction(txHash, function (err, result) {
    if (err) return cb(err)
    // only populate if the entry is still present
    if (transactionsState[txHash]) {
      transactionsState[txHash] = result
      self._didUpdate()
    }
    cb(null, result)
  })
}

function noop() {}