-
-
Notifications
You must be signed in to change notification settings - Fork 38
/
log-filter.js
102 lines (90 loc) · 3.52 KB
/
log-filter.js
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
const EthQuery = require('@metamask/eth-query')
const pify = require('pify')
const BaseFilterWithHistory = require('./base-filter-history')
const { bnToHex, hexToInt, incrementHexInt, minBlockRef, blockRefIsNumber } = require('./hexUtils')
class LogFilter extends BaseFilterWithHistory {
constructor ({ provider, params }) {
super()
this.type = 'log'
this.ethQuery = new EthQuery(provider)
this.params = Object.assign({
fromBlock: 'latest',
toBlock: 'latest',
address: undefined,
topics: [],
}, params)
// normalize address parameter
if (this.params.address) {
// ensure array
if (!Array.isArray(this.params.address)) {
this.params.address = [this.params.address]
}
// ensure lowercase
this.params.address = this.params.address.map(address => address.toLowerCase())
}
}
async initialize({ currentBlock }) {
// resolve params.fromBlock
let fromBlock = this.params.fromBlock
if (['latest', 'pending'].includes(fromBlock)) fromBlock = currentBlock
if ('earliest' === fromBlock) fromBlock = '0x0'
this.params.fromBlock = fromBlock
// set toBlock for initial lookup
const toBlock = minBlockRef(this.params.toBlock, currentBlock)
const params = Object.assign({}, this.params, { toBlock })
// fetch logs and add to results
const newLogs = await this._fetchLogs(params)
this.addInitialResults(newLogs)
}
async update ({ oldBlock, newBlock }) {
// configure params for this update
const toBlock = newBlock
let fromBlock
// oldBlock is empty on first sync
if (oldBlock) {
fromBlock = incrementHexInt(oldBlock)
} else {
fromBlock = newBlock
}
// fetch logs
const params = Object.assign({}, this.params, { fromBlock, toBlock })
const newLogs = await this._fetchLogs(params)
const matchingLogs = newLogs.filter(log => this.matchLog(log))
// add to results
this.addResults(matchingLogs)
}
async _fetchLogs (params) {
const newLogs = await pify(cb => this.ethQuery.getLogs(params, cb))()
// add to results
return newLogs
}
matchLog(log) {
// check if block number in bounds:
if (hexToInt(this.params.fromBlock) >= hexToInt(log.blockNumber)) return false
if (blockRefIsNumber(this.params.toBlock) && hexToInt(this.params.toBlock) <= hexToInt(log.blockNumber)) return false
// address is correct:
const normalizedLogAddress = log.address && log.address.toLowerCase()
if (this.params.address && normalizedLogAddress && !this.params.address.includes(normalizedLogAddress)) return false
// topics match:
// topics are position-dependant
// topics can be nested to represent `or` [[a || b], c]
// topics can be null, representing a wild card for that position
const topicsMatch = this.params.topics.every((topicPattern, index) => {
// pattern is longer than actual topics
let logTopic = log.topics[index]
if (!logTopic) return false
logTopic = logTopic.toLowerCase()
// normalize subTopics
let subtopicsToMatch = Array.isArray(topicPattern) ? topicPattern : [topicPattern]
// check for wild card
const subtopicsIncludeWildcard = subtopicsToMatch.includes(null)
if (subtopicsIncludeWildcard) return true
subtopicsToMatch = subtopicsToMatch.map(topic => topic.toLowerCase())
// check each possible matching topic
const topicDoesMatch = subtopicsToMatch.includes(logTopic)
return topicDoesMatch
})
return topicsMatch
}
}
module.exports = LogFilter