Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix failed contract calls #145

Merged
merged 1 commit into from
Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions zp-relayer/pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import crypto from 'crypto'
import BN from 'bn.js'
import PoolAbi from './abi/pool-abi.json'
import { AbiItem, toBN } from 'web3-utils'
import { Contract } from 'web3-eth-contract'
import type { Contract } from 'web3-eth-contract'
import config from './config'
import { web3 } from './services/web3'
import { logger } from './services/appLogger'
Expand All @@ -14,8 +14,8 @@ import { getBlockNumber, getEvents, getTransaction } from './utils/web3'
import { Helpers, Params, Proof, SnarkProof, VK } from 'libzkbob-rs-node'
import { PoolState } from './state/PoolState'

import { TxType } from 'zp-memo-parser'
import { numToHex, toTxType, truncateHexPrefix, truncateMemoTxPrefix } from './utils/helpers'
import type { TxType } from 'zp-memo-parser'
import { contractCallRetry, numToHex, toTxType, truncateHexPrefix, truncateMemoTxPrefix } from './utils/helpers'
import { PoolCalldataParser } from './utils/PoolCalldataParser'
import { OUTPLUSONE } from './utils/constants'

Expand Down Expand Up @@ -216,7 +216,7 @@ class Pool {
}

async getContractIndex() {
const poolIndex = await this.PoolInstance.methods.pool_index().call()
const poolIndex = await contractCallRetry(this.PoolInstance, 'pool_index')
return Number(poolIndex)
}

Expand All @@ -225,12 +225,12 @@ class Pool {
index = await this.getContractIndex()
logger.info('CONTRACT INDEX %d', index)
}
const root = await this.PoolInstance.methods.roots(index).call()
const root = await contractCallRetry(this.PoolInstance, 'roots', [index])
return root.toString()
}

async getLimitsFor(address: string): Promise<Limits> {
const limits = await this.PoolInstance.methods.getLimitsFor(address).call()
const limits = await contractCallRetry(this.PoolInstance, 'getLimitsFor', [address])
return {
tvlCap: toBN(limits.tvlCap),
tvl: toBN(limits.tvl),
Expand Down
12 changes: 6 additions & 6 deletions zp-relayer/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@ router.get('/params/hash/tree', wrapErr(endpoints.getParamsHash('tree')))
router.get('/params/hash/tx', wrapErr(endpoints.getParamsHash('transfer')))

// Error handler middleware
router.use((err: any, req: Request, res: Response, next: NextFunction) => {
if (err instanceof ValidationError) {
const validationErrors = err.validationErrors
logger.info('Request errors: %o', validationErrors, { path: req.path })
router.use((error: any, req: Request, res: Response) => {
if (error instanceof ValidationError) {
const validationErrors = error.validationErrors
logger.warn('Validation errors', { errors: validationErrors, path: req.path })
res.status(400).json(validationErrors)
next()
} else {
next(err)
logger.error('Internal error', { error, path: req.path })
res.status(500).send('Internal server error')
}
})

Expand Down
24 changes: 24 additions & 0 deletions zp-relayer/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import type Web3 from 'web3'
import type { Contract } from 'web3-eth-contract'
import type BN from 'bn.js'
import { padLeft, toBN } from 'web3-utils'
import { logger } from '@/services/appLogger'
import type { SnarkProof } from 'libzkbob-rs-node'
import { TxType } from 'zp-memo-parser'
import type { Mutex } from 'async-mutex'
import promiseRetry from 'promise-retry'
import { isContractCallError } from './web3Errors'

const S_MASK = toBN('0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')
const S_MAX = toBN('0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0')
Expand Down Expand Up @@ -211,3 +213,25 @@ export function checkHTTPS(isRequired: boolean) {
}
}
}

export function contractCallRetry(contract: Contract, method: string, args: any[] = []) {
return promiseRetry(
async retry => {
try {
return await contract.methods[method](...args).call()
} catch (e) {
if (isContractCallError(e as Error)) {
logger.warn('Retrying failed contract call', { method, args })
retry(e)
} else {
throw e
}
}
},
{
retries: 2,
minTimeout: 500,
maxTimeout: 500,
}
)
}
5 changes: 5 additions & 0 deletions zp-relayer/utils/web3Errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,8 @@ export function isInsufficientBalanceError(e: Error) {
const message = e.message.toLowerCase()
return message.includes('insufficient funds')
}

export function isContractCallError(e: Error) {
const message = e.message.toLowerCase()
return message.includes('did it run out of gas')
}
12 changes: 8 additions & 4 deletions zp-relayer/validateTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { Limits, Pool } from './pool'
import type { NullifierSet } from './state/nullifierSet'
import TokenAbi from './abi/token-abi.json'
import { web3 } from './services/web3'
import { numToHex, unpackSignature } from './utils/helpers'
import { contractCallRetry, numToHex, unpackSignature } from './utils/helpers'
import { recoverSaltedPermit } from './utils/EIP712SaltedPermit'
import { ZERO_ADDRESS, TRACE_ID } from './utils/constants'
import { TxPayload } from './queue/poolTxQueue'
Expand Down Expand Up @@ -39,7 +39,7 @@ export function checkSize(data: string, size: number) {
}

export async function checkBalance(address: string, minBalance: string) {
const balance = await tokenContract.methods.balanceOf(address).call()
const balance = await contractCallRetry(tokenContract, 'balanceOf', [address])
const res = toBN(balance).gte(toBN(minBalance))
if (!res) {
return new TxValidationError('Not enough balance for deposit')
Expand Down Expand Up @@ -175,7 +175,7 @@ async function getRecoveredAddress(
const { deadline, holder } = txData as PermittableDepositTxData
const owner = web3.utils.toChecksumAddress(web3.utils.bytesToHex(Array.from(holder)))
const spender = web3.utils.toChecksumAddress(config.poolAddress as string)
const nonce = await tokenContract.methods.nonces(owner).call()
const nonce = await contractCallRetry(tokenContract, 'nonces', [owner])

const message = {
owner,
Expand Down Expand Up @@ -239,7 +239,11 @@ async function checkScreener(address: string, traceId?: string) {
return null
}

export async function validateTx({ txType, rawMemo, txProof, depositSignature }: TxPayload, pool: Pool, traceId?: string) {
export async function validateTx(
{ txType, rawMemo, txProof, depositSignature }: TxPayload,
pool: Pool,
traceId?: string
) {
const buf = Buffer.from(rawMemo, 'hex')
const txData = getTxData(buf, txType)

Expand Down