Skip to content

Commit

Permalink
Reporter exceptions (#237)
Browse files Browse the repository at this point in the history
* feat: Add data type for reporter functions

* feat: Limited reporter tests

* fix: Resolve issues

* fix: Remove commented code

* feat: Rethrow unhandled exception

* feat: Rethrow uncatched exception in `buildWallet`
  • Loading branch information
martinkersner authored Feb 7, 2023
1 parent f246a1d commit 7429567
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 41 deletions.
7 changes: 6 additions & 1 deletion core/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export class IcnError extends Error {
constructor(public readonly code: IcnErrorCode, message?: string, public readonly value?) {
super(message)
this.name = IcnErrorCode[code]
this.value = value
Object.setPrototypeOf(this, new.target.prototype)
}
}
Expand All @@ -22,5 +23,9 @@ export enum IcnErrorCode {
InvalidPriceFeed,
InvalidPriceFeedFormat,
MissingKeyValuePair,
UnexpectedQueryOutput
UnexpectedQueryOutput,
TxInvalidAddress,
TxProcessingResponseError,
TxCannotEstimateGasError,
ProviderNetworkError
}
17 changes: 7 additions & 10 deletions core/src/reporter/aggregator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,21 @@ import { Worker } from 'bullmq'
import { ethers } from 'ethers'
import { Logger } from 'pino'
import { Aggregator__factory } from '@bisonai-cic/icn-contracts'
import { sendTransaction, buildWallet } from './utils'
import { loadWalletParameters, sendTransaction, buildWallet } from './utils'
import { REPORTER_AGGREGATOR_QUEUE_NAME, BULLMQ_CONNECTION } from '../settings'
import { IAggregatorWorkerReporter } from '../types'

const FILE_NAME = import.meta.url

export async function aggregatorReporter(_logger: Logger) {
_logger.debug({ name: 'aggregatorReporter', file: FILE_NAME })
export async function reporter(_logger: Logger) {
_logger.debug({ name: 'reporter', file: FILE_NAME })

const wallet = buildWallet(_logger)
new Worker(
REPORTER_AGGREGATOR_QUEUE_NAME,
await aggregatorJob(wallet, _logger),
BULLMQ_CONNECTION
)
const { privateKey, providerUrl } = loadWalletParameters()
const wallet = buildWallet({ privateKey, providerUrl })
new Worker(REPORTER_AGGREGATOR_QUEUE_NAME, await job(wallet, _logger), BULLMQ_CONNECTION)
}

function aggregatorJob(wallet, _logger: Logger) {
function job(wallet, _logger: Logger) {
const logger = _logger.child({ name: 'aggregatorJob', file: FILE_NAME })
const iface = new ethers.utils.Interface(Aggregator__factory.abi)

Expand Down
9 changes: 5 additions & 4 deletions core/src/reporter/main.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { parseArgs } from 'node:util'
import { buildLogger } from '../logger'
import { aggregatorReporter } from './aggregator'
import { vrfReporter } from './vrf'
import { reporter as aggregatorReporter } from './aggregator'
import { reporter as vrfReporter } from './vrf'
import { reporter as requestResponseReporter } from './request-response'
import { launchHealthCheck } from '../health-check'
import { hookConsoleError } from '../utils'
import { IReporters } from './types'

const REPORTERS = {
const REPORTERS: IReporters = {
AGGREGATOR: aggregatorReporter,
VRF: vrfReporter,
REQUEST_RESPONSE: requestResponseReporter
Expand All @@ -21,7 +22,7 @@ async function main() {
launchHealthCheck()
}

function loadArgs() {
function loadArgs(): string {
const {
values: { reporter }
} = parseArgs({
Expand Down
5 changes: 3 additions & 2 deletions core/src/reporter/request-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import { Worker } from 'bullmq'
import { ethers } from 'ethers'
import { Logger } from 'pino'
import { RequestResponseCoordinator__factory } from '@bisonai-cic/icn-contracts'
import { sendTransaction, buildWallet } from './utils'
import { sendTransaction, loadWalletParameters, buildWallet } from './utils'
import { REPORTER_REQUEST_RESPONSE_QUEUE_NAME, BULLMQ_CONNECTION } from '../settings'
import { IRequestResponseWorkerReporter, RequestCommitmentRequestResponse } from '../types'

const FILE_NAME = import.meta.url

export async function reporter(_logger: Logger) {
_logger.debug({ name: 'reporter', file: FILE_NAME })
const wallet = buildWallet(_logger)
const { privateKey, providerUrl } = loadWalletParameters()
const wallet = buildWallet({ privateKey, providerUrl })
new Worker(REPORTER_REQUEST_RESPONSE_QUEUE_NAME, await job(wallet, _logger), BULLMQ_CONNECTION)
}

Expand Down
5 changes: 5 additions & 0 deletions core/src/reporter/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Logger } from 'pino'

export interface IReporters {
[index: string]: (_logger: Logger) => Promise<void>
}
74 changes: 56 additions & 18 deletions core/src/reporter/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,34 @@ import { add0x } from '../utils'

const FILE_NAME = import.meta.url

export function buildWallet(logger: Logger) {
try {
const { PRIVATE_KEY, PROVIDER } = checkParameters()
const provider = new ethers.providers.JsonRpcProvider(PROVIDER)
const wallet = new ethers.Wallet(PRIVATE_KEY, provider)
return wallet
} catch (e) {
logger.error(e)
export async function buildWallet({
privateKey,
providerUrl,
testConnection
}: {
privateKey: string
providerUrl: string
testConnection?: boolean
}) {
const provider = new ethers.providers.JsonRpcProvider(providerUrl)
const wallet = new ethers.Wallet(privateKey, provider)

if (testConnection) {
try {
await wallet.getTransactionCount()
} catch (e) {
if (e.code == 'NETWORK_ERROR') {
throw new IcnError(IcnErrorCode.ProviderNetworkError, 'ProviderNetworkError', e.reason)
} else {
throw e
}
}
}

return wallet
}

function checkParameters() {
export function loadWalletParameters() {
if (!PRIVATE_KEY_ENV) {
throw new IcnError(IcnErrorCode.MissingMnemonic)
}
Expand All @@ -26,7 +42,7 @@ function checkParameters() {
throw new IcnError(IcnErrorCode.MissingJsonRpcProvider)
}

return { PRIVATE_KEY: PRIVATE_KEY_ENV, PROVIDER: PROVIDER_ENV }
return { privateKey: PRIVATE_KEY_ENV, providerUrl: PROVIDER_ENV }
}

export async function sendTransaction({
Expand All @@ -39,25 +55,47 @@ export async function sendTransaction({
}: {
wallet
to: string
payload: string
payload?: string
gasLimit?: number | string
value?: number | string
_logger: Logger
value?: number | string | ethers.BigNumber
_logger?: Logger
}) {
const logger = _logger.child({ name: 'sendTransaction', file: FILE_NAME })
const logger = _logger?.child({ name: 'sendTransaction', file: FILE_NAME })

if (payload) {
payload = add0x(payload)
}

const tx = {
from: wallet.address,
to: to,
data: add0x(payload),
data: payload || '0x00',
value: value || '0x00'
}

if (gasLimit) {
tx['gasLimit'] = gasLimit
}
logger.debug(tx, 'tx')
logger?.debug(tx, 'tx')

try {
const txReceipt = await (await wallet.sendTransaction(tx)).wait()
logger?.debug(txReceipt, 'txReceipt')
} catch (e) {
logger?.debug(e, 'e')

const txReceipt = await wallet.sendTransaction(tx)
logger.debug(txReceipt, 'txReceipt')
if (e.reason == 'invalid address') {
throw new IcnError(IcnErrorCode.TxInvalidAddress, 'TxInvalidAddress', e.value)
} else if (e.reason == 'processing response error') {
throw new IcnError(
IcnErrorCode.TxProcessingResponseError,
'TxProcessingResponseError',
e.value
)
} else if (e.code == 'UNPREDICTABLE_GAS_LIMIT') {
throw new IcnError(IcnErrorCode.TxCannotEstimateGasError, 'TxCannotEstimateGasError', e.value)
} else {
throw e
}
}
}
13 changes: 7 additions & 6 deletions core/src/reporter/vrf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@ import { Worker } from 'bullmq'
import { ethers } from 'ethers'
import { Logger } from 'pino'
import { VRFCoordinator__factory } from '@bisonai-cic/icn-contracts'
import { sendTransaction, buildWallet } from './utils'
import { loadWalletParameters, sendTransaction, buildWallet } from './utils'
import { REPORTER_VRF_QUEUE_NAME, BULLMQ_CONNECTION } from '../settings'
import { IVrfWorkerReporter, RequestCommitmentVRF, Proof } from '../types'

const FILE_NAME = import.meta.url

export async function vrfReporter(_logger: Logger) {
export async function reporter(_logger: Logger) {
_logger.debug({ name: 'vrfrReporter', file: FILE_NAME })
const wallet = buildWallet(_logger)
new Worker(REPORTER_VRF_QUEUE_NAME, await vrfJob(wallet, _logger), BULLMQ_CONNECTION)
const { privateKey, providerUrl } = loadWalletParameters()
const wallet = buildWallet({ privateKey, providerUrl })
new Worker(REPORTER_VRF_QUEUE_NAME, await job(wallet, _logger), BULLMQ_CONNECTION)
}

function vrfJob(wallet, _logger: Logger) {
const logger = _logger.child({ name: 'vrfJob', file: FILE_NAME })
function job(wallet, _logger: Logger) {
const logger = _logger.child({ name: 'job', file: FILE_NAME })
const iface = new ethers.utils.Interface(VRFCoordinator__factory.abi)
const gasLimit = 3_000_000 // FIXME

Expand Down
61 changes: 61 additions & 0 deletions core/test/reporter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { describe, test, expect } from '@jest/globals'
import { buildWallet, sendTransaction } from '../src/reporter/utils'
import { ethers } from 'ethers'
import { IcnErrorCode } from '../src/errors'

// The following tests have to be run with hardhat network launched.
// If the hardhat cannot be detected tests are skipped.
// TODO Include hardhat network launch to Github Actions pipeline.
describe('Reporter', function () {
const PROVIDER_URL = 'http://127.0.0.1:8545'
const PRIVATE_KEY = '0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a' // hardhat account 2

test('Send payload to invalid address', async function () {
try {
const wallet = await buildWallet({
privateKey: PRIVATE_KEY,
providerUrl: PROVIDER_URL,
testConnection: true
})

wallet.getBalance()
const to = '0x000000000000000000000000000000000000000' // wrong address
const payload = '0x'

expect(async () => {
await sendTransaction({ wallet, to, payload })
}).rejects.toThrow('TxInvalidAddress')
} catch (e) {
if (e.code == IcnErrorCode.ProviderNetworkError) {
return 0
} else {
throw e
}
}
})

test('Send value without insufficient balance', async function () {
try {
const privateKey = '0xa5061ebc3567c2d3422807986c1c27425455fa62f4d9286c66d07a9afc6d9869' // account with 0 balance

const wallet = await buildWallet({
privateKey,
providerUrl: PROVIDER_URL,
testConnection: true
})

const to = '0x976EA74026E726554dB657fA54763abd0C3a0aa9' // hardhat account 6
const value = ethers.utils.parseUnits('1')

expect(async () => {
await sendTransaction({ wallet, to, value })
}).rejects.toThrow('TxProcessingResponseError')
} catch (e) {
if (e.code == IcnErrorCode.ProviderNetworkError) {
return 0
} else {
throw e
}
}
})
})

0 comments on commit 7429567

Please sign in to comment.