|
|
|
import { eachOf } from 'async'
|
|
|
|
import { randomBytes } from 'crypto'
|
|
|
|
|
|
|
|
export class LogsManager {
|
|
|
|
notificationCallbacks
|
|
|
|
subscriptions
|
|
|
|
filters
|
|
|
|
filterTracking
|
|
|
|
oldLogs
|
|
|
|
|
|
|
|
constructor () {
|
|
|
|
this.notificationCallbacks = []
|
|
|
|
this.subscriptions = {}
|
|
|
|
this.filters = {}
|
|
|
|
this.filterTracking = {}
|
|
|
|
this.oldLogs = []
|
|
|
|
}
|
|
|
|
|
|
|
|
checkBlock (blockNumber, block, web3) {
|
|
|
|
eachOf(block.transactions, (tx, i, next) => {
|
|
|
|
const txHash = '0x' + tx.hash().toString('hex')
|
|
|
|
|
|
|
|
web3.eth.getTransactionReceipt(txHash, (_error, receipt) => {
|
|
|
|
for (const log of receipt.logs) {
|
|
|
|
this.oldLogs.push({ type: 'block', blockNumber, block, tx, log, txNumber: i })
|
|
|
|
const subscriptions = this.getSubscriptionsFor({ type: 'block', blockNumber, block, tx, log })
|
|
|
|
|
|
|
|
for (const subscriptionId of subscriptions) {
|
|
|
|
const result = {
|
|
|
|
logIndex: '0x1', // 1
|
|
|
|
blockNumber: blockNumber,
|
|
|
|
blockHash: ('0x' + block.hash().toString('hex')),
|
|
|
|
transactionHash: ('0x' + tx.hash().toString('hex')),
|
|
|
|
transactionIndex: '0x' + i.toString(16),
|
|
|
|
// TODO: if it's a contract deploy, it should be that address instead
|
|
|
|
address: log.address,
|
|
|
|
data: log.data,
|
|
|
|
topics: log.topics
|
|
|
|
}
|
|
|
|
|
|
|
|
if (result.address === '0x') {
|
|
|
|
delete result.address
|
|
|
|
}
|
|
|
|
|
|
|
|
const response = { jsonrpc: '2.0', method: 'eth_subscription', params: { result: result, subscription: subscriptionId } }
|
|
|
|
this.transmit(response)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}, (_err) => {
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
eventMatchesFilter (changeEvent, queryType, queryFilter) {
|
|
|
|
if (queryFilter.topics.filter((logTopic) => changeEvent.log.topics.indexOf(logTopic) >= 0).length === 0) return false
|
|
|
|
|
|
|
|
if (queryType === 'logs') {
|
|
|
|
const fromBlock = queryFilter.fromBlock || '0x0'
|
|
|
|
const toBlock = queryFilter.toBlock || this.oldLogs.length ? this.oldLogs[this.oldLogs.length - 1].blockNumber : '0x0'
|
|
|
|
if ((queryFilter.address === (changeEvent.tx.to || '').toString()) || queryFilter.address === (changeEvent.tx.getSenderAddress().toString())) {
|
|
|
|
if ((parseInt(toBlock) >= parseInt(changeEvent.blockNumber)) && (parseInt(fromBlock) <= parseInt(changeEvent.blockNumber))) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
getSubscriptionsFor (changeEvent) {
|
|
|
|
const matchedSubscriptions = []
|
|
|
|
for (const subscriptionId of Object.keys(this.subscriptions)) {
|
|
|
|
const subscriptionParams = this.subscriptions[subscriptionId]
|
|
|
|
const [queryType, queryFilter] = subscriptionParams
|
|
|
|
|
|
|
|
if (this.eventMatchesFilter(changeEvent, queryType, queryFilter || { topics: [] })) {
|
|
|
|
matchedSubscriptions.push(subscriptionId)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return matchedSubscriptions
|
|
|
|
}
|
|
|
|
|
|
|
|
getLogsForSubscription (subscriptionId) {
|
|
|
|
const subscriptionParams = this.subscriptions[subscriptionId]
|
|
|
|
const [_queryType, queryFilter] = subscriptionParams // eslint-disable-line
|
|
|
|
|
|
|
|
return this.getLogsFor(queryFilter)
|
|
|
|
}
|
|
|
|
|
|
|
|
transmit (result) {
|
|
|
|
this.notificationCallbacks.forEach((callback) => {
|
|
|
|
if (result.params.result.raw) {
|
|
|
|
result.params.result.data = result.params.result.raw.data
|
|
|
|
result.params.result.topics = result.params.result.raw.topics
|
|
|
|
}
|
|
|
|
callback(result)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
addListener (_type, cb) {
|
|
|
|
this.notificationCallbacks.push(cb)
|
|
|
|
}
|
|
|
|
|
|
|
|
subscribe (params) {
|
|
|
|
const subscriptionId = '0x' + randomBytes(16).toString('hex')
|
|
|
|
this.subscriptions[subscriptionId] = params
|
|
|
|
return subscriptionId
|
|
|
|
}
|
|
|
|
|
|
|
|
unsubscribe (subscriptionId) {
|
|
|
|
delete this.subscriptions[subscriptionId]
|
|
|
|
}
|
|
|
|
|
|
|
|
newFilter (filterType, params) {
|
|
|
|
const filterId = '0x' + randomBytes(16).toString('hex')
|
|
|
|
if (filterType === 'block' || filterType === 'pendingTransactions') {
|
|
|
|
this.filters[filterId] = { filterType }
|
|
|
|
}
|
|
|
|
if (filterType === 'filter') {
|
|
|
|
this.filters[filterId] = { filterType, params }
|
|
|
|
}
|
|
|
|
this.filterTracking[filterId] = {}
|
|
|
|
return filterId
|
|
|
|
}
|
|
|
|
|
|
|
|
uninstallFilter (filterId) {
|
|
|
|
delete this.filters[filterId]
|
|
|
|
}
|
|
|
|
|
|
|
|
getLogsForFilter (filterId, logsOnly) {
|
|
|
|
const { filterType, params } = this.filters[filterId]
|
|
|
|
const tracking = this.filterTracking[filterId]
|
|
|
|
|
|
|
|
if (logsOnly || filterType === 'filter') {
|
|
|
|
return this.getLogsFor(params || { topics: [] })
|
|
|
|
}
|
|
|
|
if (filterType === 'block') {
|
|
|
|
const blocks = this.oldLogs.filter(x => x.type === 'block').filter(x => tracking.block === undefined || x.blockNumber >= tracking.block)
|
|
|
|
tracking.block = blocks[blocks.length - 1]
|
|
|
|
return blocks.map(block => ('0x' + block.hash().toString('hex')))
|
|
|
|
}
|
|
|
|
if (filterType === 'pendingTransactions') {
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getLogsByTxHash (hash) {
|
|
|
|
return this.oldLogs.filter((log) => '0x' + log.tx.hash().toString('hex') === hash)
|
|
|
|
.map((log) => {
|
|
|
|
return {
|
|
|
|
logIndex: '0x1', // 1
|
|
|
|
blockNumber: log.blockNumber,
|
|
|
|
blockHash: ('0x' + log.block.hash().toString('hex')),
|
|
|
|
transactionHash: ('0x' + log.tx.hash().toString('hex')),
|
|
|
|
transactionIndex: '0x' + log.txNumber.toString(16),
|
|
|
|
// TODO: if it's a contract deploy, it should be that address instead
|
|
|
|
address: log.log.address,
|
|
|
|
data: log.log.data,
|
|
|
|
topics: log.log.topics
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
getLogsFor (params) {
|
|
|
|
const results = []
|
|
|
|
for (const log of this.oldLogs) {
|
|
|
|
if (this.eventMatchesFilter(log, 'logs', params)) {
|
|
|
|
results.push({
|
|
|
|
logIndex: '0x1', // 1
|
|
|
|
blockNumber: log.blockNumber,
|
|
|
|
blockHash: ('0x' + log.block.hash().toString('hex')),
|
|
|
|
transactionHash: ('0x' + log.tx.hash().toString('hex')),
|
|
|
|
transactionIndex: '0x' + log.txNumber.toString(16),
|
|
|
|
// TODO: if it's a contract deploy, it should be that address instead
|
|
|
|
address: log.log.address,
|
|
|
|
data: log.log.data,
|
|
|
|
topics: log.log.topics
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return results
|
|
|
|
}
|
|
|
|
}
|