'use strict'
const {
isUnsignedInteger,
isEthAddressHeximal,
getUnkownAttribute
} = require('./validator')
const {numberToHeximal, heximalToNumber} = require('./formatter')
/*eslint-disable no-unused-vars*/
const {Gateway} = require('./gateway')
const {Node} = require('./node')
const {
UnsignedInteger,
LogFilter,
Log,
Heximal,
EthAddressHeximal
} = require('./type')
/*eslint-enable no-unused-vars*/
/**
* @typedef {object} DiaryConfig
* @property {UnsignedInteger} [maxRetry=10] - Number of times for retring
* getting log before give up.
* @property {UnsignedInteger} [reorganisationBlocks=6] - There is a block
* `n = latest - reorganisationBlocks`, the diary does not process things
* that has block number greater than `n`. Where `latest` is latest mined
* block number.
*/
/**
* @typedef {object} RpcLogFilter
* @property {Heximal} fromBlock - Searching where block number
* is greater than or equal this one.
* @property {Heximal} toBlock - Searching where block number
* is less than or equal this one.
* @property {Array<EthAddressHeximal>} [addresses=[]] - List of addresses
* that emits log records.
* @property {Array<Topic>} [topics=[]] - Searching for matched topics.
*/
class DiaryError extends Error {
constructor(message) {
super(message)
this.name = 'DiaryError'
}
}
/**
* @private
*/
class Diary {
/**
*
* @param {Gateway} gateway
* @param {DiaryConfig} config
*/
constructor(gateway, config) {
let validConfig = Diary._standardizeConfig(config)
this._gateway = gateway
this._maxRetry = validConfig.maxRetry
this._reorganisationBlocks = validConfig.reorganisationBlocks
}
/**
*
* @param {LogFilter} filter
* @returns {Promise<Array<Log>>}
*/
async getLogs(filter) {
let validFilter = Diary._standardizeFilter(filter)
if (this._gateway.safeBlockNumber === undefined) {
throw new DiaryError('no safe block for calling')
}
if (validFilter.toBlock > this._gateway.safeBlockNumber) {
throw new DiaryError('unsafe block calling')
}
let node = this._pickNode()
return await this._getLogsFromNode(node, validFilter)
}
/**
* @private
* @returns {Node}
*/
_pickNode() {
let node = this._gateway.pickNode()
if (!node) {
throw new DiaryError('no avaiable node')
}
return node
}
/**
* @private
* @param {Node} node
* @param {LogFilter} filter
* @returns {Promise<Array<Log>>}
*/
async _getLogsFromNode(node, filter) {
let rpcFilter = Diary._toRpcLogfilter(filter)
let logs = await node.call('eth_getLogs', [rpcFilter])
logs.forEach(Diary._formatLogInplace)
return logs
}
/**
* @private
* @param {any} filter
* @returns {RpcLogFilter}
*/
static _toRpcLogfilter(filter) {
return {
address: filter.addresses,
fromBlock: numberToHeximal(filter.fromBlock),
toBlock: numberToHeximal(filter.toBlock),
topics: filter.topics
}
}
/**
* @private
* @param {any} log
* @returns {Log}
*/
static _formatLogInplace(log) {
log.blockNumber = heximalToNumber(log.blockNumber)
log.logIndex = heximalToNumber(log.logIndex)
log.transactionIndex = heximalToNumber(log.transactionIndex)
}
/**
* @private
* @param {object} config
* @returns {DiaryConfig}
*/
static _standardizeConfig(config) {
let defaultConfig = {
maxRetry: 10,
reorganisationBlocks: 6
}
return Object.assign(defaultConfig, config)
}
/**
* @private
* @param {object} filter
* @returns {LogFilter}
*/
static _standardizeFilter(filter) {
let invalidAttribute = getUnkownAttribute(filter, [
'fromBlock',
'toBlock',
'addresses',
'topics'
])
if (invalidAttribute !== undefined) {
throw new TypeError('unkown attribute: ' + invalidAttribute)
}
if (!isUnsignedInteger(filter.fromBlock)) {
throw new DiaryError('invalid filter.fromBlock')
}
if (!isUnsignedInteger(filter.toBlock)) {
throw new DiaryError('invalid filter.toBlock')
}
if (filter.fromBlock > filter.toBlock) {
throw new DiaryError(
'filter.fromBlock is greater than filter.toBlock'
)
}
if (filter.addresses !== undefined) {
if (!Array.isArray(filter.addresses)) {
throw new DiaryError(
'invalid filter.addresses'
)
}
for (let i = 0; i < filter.addresses.length; ++i) {
if (!isEthAddressHeximal(filter.addresses[i])) {
throw new DiaryError(
`invalid filter.addresses[${i}]`
)
}
}
}
return Object.assign({}, filter)
}
}
module.exports = {
Diary,
DiaryError
}