Skip to content

Commit

Permalink
Add internal account validation (#135)
Browse files Browse the repository at this point in the history
* Add internal account validation 

* Fix response value

* Add supportId header for validation request
  • Loading branch information
Leonid Tyurin authored Jan 9, 2023
1 parent 8badee9 commit df25eea
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 33 deletions.
4 changes: 3 additions & 1 deletion CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,6 @@
| PERMIT_DEADLINE_THRESHOLD_INITIAL | Minimum time threshold in seconds for permit signature deadline to be valid (before initial transaction submission) | integer |
| PERMIT_DEADLINE_THRESHOLD_RESEND | Minimum time threshold in seconds for permit signature deadline to be valid (for re-send attempts) | integer |
| RELAYER_REQUIRE_TRACE_ID | If set to `true`, then requests to relayer (except `/info`, `/version`, `/params/hash/tree`, `/params/hash/tx`) without `zkbob-support-id` header will be rejected. | boolean |
| RELAYER_REQUIRE_HTTPS | If set to `true`, then RPC URL(s) must be in HTTPS format. HTTP RPC URL(s) should be used in test environment only. | boolean |
| RELAYER_REQUIRE_HTTPS | If set to `true`, then RPC URL(s) must be in HTTPS format. HTTP RPC URL(s) should be used in test environment only. | boolean |
| RELAYER_SCREENER_URL | Screener service URL | URL |
| RELAYER_SCREENER_TOKEN | Authorization token for screener service | string |
2 changes: 2 additions & 0 deletions zp-relayer/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ const config = {
.map(s => parseInt(s, 10)),
requireTraceId: process.env.RELAYER_REQUIRE_TRACE_ID === 'true',
requireHTTPS: process.env.RELAYER_REQUIRE_HTTPS === 'true',
screenerUrl: process.env.RELAYER_SCREENER_URL || null,
screenerToken: process.env.RELAYER_SCREENER_TOKEN || null,
}

export default config
2 changes: 1 addition & 1 deletion zp-relayer/test/worker-tests/poolWorker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ describe('poolWorker', () => {
gasPriceService.stop()
})

async function expectJobFinished(job: Job<TxPayload[], PoolTxResult[]>) {
async function expectJobFinished(job: Job<BatchTx, PoolTxResult[]>) {
const [[initialHash, sentId]] = await job.waitUntilFinished(poolQueueEvents)
expect(initialHash.length).eq(66)

Expand Down
2 changes: 1 addition & 1 deletion zp-relayer/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export function waitForFunds(
address: string,
cb: (balance: BN) => void,
minimumBalance: BN,
timeout: number,
timeout: number
) {
return promiseRetry(
async retry => {
Expand Down
73 changes: 51 additions & 22 deletions zp-relayer/validateTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import TokenAbi from './abi/token-abi.json'
import { web3 } from './services/web3'
import { numToHex, unpackSignature } from './utils/helpers'
import { recoverSaltedPermit } from './utils/EIP712SaltedPermit'
import { ZERO_ADDRESS } from './utils/constants'
import { ZERO_ADDRESS, TRACE_ID } from './utils/constants'
import { TxPayload } from './queue/poolTxQueue'
import { getTxProofField, parseDelta } from './utils/proofInputs'
import type { PoolState } from './state/PoolState'
Expand Down Expand Up @@ -207,7 +207,39 @@ async function checkRoot(proofIndex: BN, proofRoot: string, state: PoolState) {
return null
}

export async function validateTx({ txType, rawMemo, txProof, depositSignature }: TxPayload, pool: Pool) {
async function checkScreener(address: string, traceId?: string) {
if (config.screenerUrl === null || config.screenerToken === null) {
return null
}

const ACC_VALIDATION_FAILED = 'Internal account validation failed'

const headers: Record<string, string> = {
'Content-type': 'application/json',
'Authorization': `Bearer ${config.screenerToken}`,
}

if (traceId) headers[TRACE_ID] = traceId

try {
const rawResponse = await fetch(config.screenerUrl, {
method: 'POST',
headers,
body: JSON.stringify({ address }),
})
const response = await rawResponse.json()
if (response.result === true) {
return new TxValidationError(ACC_VALIDATION_FAILED)
}
} catch (e) {
logger.error('Request to screener failed', { error: (e as Error).message })
return new TxValidationError(ACC_VALIDATION_FAILED)
}

return null
}

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 All @@ -223,44 +255,41 @@ export async function validateTx({ txType, rawMemo, txProof, depositSignature }:
fee.toString(10)
)

// prettier-ignore
await checkAssertion(() => checkRoot(
delta.transferIndex,
root,
pool.optimisticState,
))
await checkAssertion(() => checkRoot(delta.transferIndex, root, pool.optimisticState))
await checkAssertion(() => checkNullifier(nullifier, pool.state.nullifiers))
await checkAssertion(() => checkNullifier(nullifier, pool.optimisticState.nullifiers))
await checkAssertion(() => checkTransferIndex(toBN(pool.optimisticState.getNextIndex()), delta.transferIndex))

await checkAssertion(() => checkFee(fee))

if (txType === TxType.WITHDRAWAL) {
const { nativeAmount, receiver } = txData as WithdrawTxData
const receiverAddress = web3.utils.bytesToHex(Array.from(receiver))
logger.info('Withdraw address: %s', receiverAddress)
await checkAssertion(() => checkNonZeroWithdrawAddress(receiverAddress))
await checkAssertion(() => checkNativeAmount(toBN(nativeAmount)))
}

await checkAssertion(() => checkProof(txProof, (p, i) => pool.verifyProof(p, i)))

const tokenAmountWithFee = delta.tokenAmount.add(fee)
await checkAssertion(() => checkTxSpecificFields(txType, tokenAmountWithFee, delta.energyAmount))

const requiredTokenAmount = tokenAmountWithFee.mul(pool.denominator)
let userAddress = ZERO_ADDRESS
if (txType === TxType.DEPOSIT || txType === TxType.PERMITTABLE_DEPOSIT) {

if (txType === TxType.WITHDRAWAL) {
const { nativeAmount, receiver } = txData as WithdrawTxData
userAddress = web3.utils.bytesToHex(Array.from(receiver))
logger.info('Withdraw address: %s', userAddress)
await checkAssertion(() => checkNonZeroWithdrawAddress(userAddress))
await checkAssertion(() => checkNativeAmount(toBN(nativeAmount)))
} else if (txType === TxType.DEPOSIT || txType === TxType.PERMITTABLE_DEPOSIT) {
const requiredTokenAmount = tokenAmountWithFee.mul(pool.denominator)
userAddress = await getRecoveredAddress(txType, nullifier, txData, requiredTokenAmount, depositSignature)
logger.info('Deposit address: %s', userAddress)
await checkAssertion(() => checkDepositEnoughBalance(userAddress, requiredTokenAmount))
}

const limits = await pool.getLimitsFor(userAddress)
await checkAssertion(() => checkLimits(limits, delta.tokenAmount))

if (txType === TxType.PERMITTABLE_DEPOSIT) {
const { deadline } = txData as PermittableDepositTxData
logger.info('Deadline: %s', deadline)
await checkAssertion(() => checkDeadline(toBN(deadline), config.permitDeadlineThresholdInitial))
}

const limits = await pool.getLimitsFor(userAddress)
await checkAssertion(() => checkLimits(limits, delta.tokenAmount))
if (txType === TxType.DEPOSIT || txType === TxType.PERMITTABLE_DEPOSIT || txType === TxType.WITHDRAWAL) {
await checkAssertion(() => checkScreener(userAddress, traceId))
}
}
13 changes: 7 additions & 6 deletions zp-relayer/workers/poolTxWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { TxValidationError } from '@/validateTx'

export async function createPoolTxWorker<T extends EstimationType>(
gasPrice: GasPrice<T>,
validateTx: (tx: TxPayload, pool: Pool) => Promise<void>,
validateTx: (tx: TxPayload, pool: Pool, traceId?: string) => Promise<void>,
mutex: Mutex,
redis: Redis
) {
Expand Down Expand Up @@ -53,7 +53,7 @@ export async function createPoolTxWorker<T extends EstimationType>(
for (const tx of txs) {
const { gas, amount, rawMemo, txType, txProof } = tx

await validateTx(tx, pool)
await validateTx(tx, pool, traceId)

const { data, commitIndex, rootAfter } = await processTx(tx)

Expand Down Expand Up @@ -141,10 +141,11 @@ export async function createPoolTxWorker<T extends EstimationType>(

const poolTxWorker = new Worker<BatchTx, PoolTxResult[]>(
TX_QUEUE_NAME,
job => withErrorLog(
withMutex(mutex, () => poolTxWorkerProcessor(job)),
[TxValidationError]
),
job =>
withErrorLog(
withMutex(mutex, () => poolTxWorkerProcessor(job)),
[TxValidationError]
),
WORKER_OPTIONS
)

Expand Down
10 changes: 8 additions & 2 deletions zp-relayer/workers/sentTxWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@ import config from '@/config'
import { pool } from '@/pool'
import { web3, web3Redundant } from '@/services/web3'
import { logger } from '@/services/appLogger'
import { GasPrice, EstimationType, chooseGasPriceOptions, addExtraGasPrice, getMaxRequiredGasPrice } from '@/services/gas-price'
import { buildPrefixedMemo, waitForFunds, withErrorLog, withLoop, withMutex } from '@/utils/helpers'
import {
GasPrice,
EstimationType,
chooseGasPriceOptions,
addExtraGasPrice,
getMaxRequiredGasPrice,
} from '@/services/gas-price'
import { buildPrefixedMemo, withErrorLog, withLoop, withMutex } from '@/utils/helpers'
import { OUTPLUSONE, SENT_TX_QUEUE_NAME } from '@/utils/constants'
import { isGasPriceError, isInsufficientBalanceError, isSameTransactionError } from '@/utils/web3Errors'
import { SendAttempt, SentTxPayload, sentTxQueue, SentTxResult, SentTxState } from '@/queue/sentTxQueue'
Expand Down

0 comments on commit df25eea

Please sign in to comment.