diff --git a/.prettierignore b/.prettierignore index e4fb6197..a37ce996 100644 --- a/.prettierignore +++ b/.prettierignore @@ -24,4 +24,5 @@ zp-relayer/poolTxs.db *.md *.yml **/lib/ -**/build/ \ No newline at end of file +**/build/ +**/*.json \ No newline at end of file diff --git a/README.md b/README.md index f5d9f3f0..aacaa9c2 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,13 @@ For a detailed description of each method's payload you can refer to [`zp-relaye - `/merkle/root/:index?` - get Merkle Tree root at specified index. +- `/siblings?index=${index}` - get left siblings starting from a leaf node at `index` up to the tree root. + + **Response** + ``` + "${height}${sibling_index}${sibling_value}"[] + ``` + - `/job/:id` - information about user's job state. **Response** diff --git a/yarn.lock b/yarn.lock index 2b5acd1b..ab00c681 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3497,6 +3497,13 @@ libzkbob-rs-node@0.1.27: dependencies: cargo-cp-artifact "^0.1" +libzkbob-rs-node@0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/libzkbob-rs-node/-/libzkbob-rs-node-0.2.1.tgz#58340fce40c5ba06641f3b3afb131ccfe5fef634" + integrity sha512-Qh2EXpXTVOi1CZ6bvbKjXRw9dkoXgq7ss/YX+fxpJGYRAa6UIyt+Lrv4jZrSddNOGMrlItkDFtNOsXZVfdFSRw== + dependencies: + cargo-cp-artifact "^0.1" + libzkbob-rs-wasm-web@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/libzkbob-rs-wasm-web/-/libzkbob-rs-wasm-web-0.7.0.tgz#2bd54bb0687a0194ce7c3e43ceecb55a378dba06" diff --git a/zp-relayer/endpoints.ts b/zp-relayer/endpoints.ts index b0b855b9..368748bf 100644 --- a/zp-relayer/endpoints.ts +++ b/zp-relayer/endpoints.ts @@ -5,6 +5,7 @@ import { poolTxQueue } from './queue/poolTxQueue' import config from './config' import { checkGetLimits, + checkGetSiblings, checkGetTransactions, checkGetTransactionsV2, checkMerkleRootErrors, @@ -251,6 +252,25 @@ async function getLimits(req: Request, res: Response) { res.json(limitsFetch) } +function getSiblings(req: Request, res: Response) { + const errors = checkGetSiblings(req.query) + if (errors) { + logger.info('Request errors: %o', errors) + res.status(400).json({ errors }) + return + } + + const index = req.query.index as unknown as number + + if (index >= pool.state.getNextIndex()) { + res.status(400).json({ errors: ['Index out of range'] }) + return + } + + const siblings = pool.state.getSiblings(index) + res.json(siblings) +} + function getParamsHash(type: 'tree' | 'transfer') { const hash = type === 'tree' ? pool.treeParamsHash : pool.transferParamsHash return (req: Request, res: Response) => { @@ -272,6 +292,7 @@ export default { relayerInfo, getFee, getLimits, + getSiblings, getParamsHash, root, } diff --git a/zp-relayer/package.json b/zp-relayer/package.json index e6b77302..b99d1e37 100644 --- a/zp-relayer/package.json +++ b/zp-relayer/package.json @@ -26,7 +26,7 @@ "express-winston": "4.2.0", "gas-price-oracle": "0.5.1", "ioredis": "5.2.4", - "libzkbob-rs-node": "0.1.27", + "libzkbob-rs-node": "0.2.1", "node-fetch": "^2.6.1", "promise-retry": "^2.0.1", "web3": "1.7.4", diff --git a/zp-relayer/router.ts b/zp-relayer/router.ts index f0f480d8..20177c0f 100644 --- a/zp-relayer/router.ts +++ b/zp-relayer/router.ts @@ -1,6 +1,7 @@ import express, { NextFunction, Request, Response } from 'express' import cors from 'cors' import endpoints from './endpoints' +import { logger } from './services/appLogger' function wrapErr(f: (_req: Request, _res: Response, _next: NextFunction) => Promise | void) { return async (req: Request, res: Response, next: NextFunction) => { @@ -21,7 +22,7 @@ router.use(express.text()) router.use((err: any, req: Request, res: Response, next: NextFunction) => { if (err) { - console.error('Request error:', err) + logger.error('Request error:', err) return res.sendStatus(500) } next() @@ -37,6 +38,7 @@ router.get('/job/:id', wrapErr(endpoints.getJob)) router.get('/info', wrapErr(endpoints.relayerInfo)) router.get('/fee', wrapErr(endpoints.getFee)) router.get('/limits', wrapErr(endpoints.getLimits)) +router.get('/siblings', wrapErr(endpoints.getSiblings)) router.get('/params/hash/tree', wrapErr(endpoints.getParamsHash('tree'))) router.get('/params/hash/tx', wrapErr(endpoints.getParamsHash('transfer'))) diff --git a/zp-relayer/state/PoolState.ts b/zp-relayer/state/PoolState.ts index 9e98d766..90542312 100644 --- a/zp-relayer/state/PoolState.ts +++ b/zp-relayer/state/PoolState.ts @@ -100,6 +100,10 @@ export class PoolState { return this.tree.getNextIndex() } + getSiblings(index: number) { + return this.tree.getLeftSiblings(index) + } + addTx(i: number, tx: Buffer) { this.txs.add(i, tx) } diff --git a/zp-relayer/test/worker-tests/poolWorker.test.ts b/zp-relayer/test/worker-tests/poolWorker.test.ts index 29d97824..0d67da6c 100644 --- a/zp-relayer/test/worker-tests/poolWorker.test.ts +++ b/zp-relayer/test/worker-tests/poolWorker.test.ts @@ -16,14 +16,7 @@ import { GasPrice } from '../../services/gas-price' import { redis } from '../../services/redisClient' import { initializeDomain } from '../../utils/EIP712SaltedPermit' import { FlowOutputItem } from '../../../test-flow-generator/src/types' -import { - disableMining, - enableMining, - evmRevert, - evmSnapshot, - mintTokens, - newConnection, -} from './utils' +import { disableMining, enableMining, evmRevert, evmSnapshot, mintTokens, newConnection } from './utils' import { validateTx } from '../../validateTx' import flow from '../flows/flow_independent_deposits_5.json' import flowDependentDeposits from '../flows/flow_dependent_deposits_2.json' @@ -151,7 +144,7 @@ describe('poolWorker', () => { await mintTokens(deposit.txTypeData.from as string, parseInt(deposit.txTypeData.amount)) await sentWorker.pause() - const mockPoolWorker = await createPoolTxWorker(gasPriceService, async () => { }, workerMutex, newConnection()) + const mockPoolWorker = await createPoolTxWorker(gasPriceService, async () => {}, workerMutex, newConnection()) mockPoolWorker.run() await mockPoolWorker.waitUntilReady() @@ -242,7 +235,7 @@ describe('poolWorker', () => { }) const sentJob = (await sentTxQueue.getJob(sentId)) as Job - const [status, sentHash,] = await sentJob.waitUntilFinished(sentQueueEvents) + const [status, sentHash] = await sentJob.waitUntilFinished(sentQueueEvents) expect(status).eq(SentTxState.MINED) expect(txHash).not.eq(sentHash) diff --git a/zp-relayer/validation/validation.ts b/zp-relayer/validation/validation.ts index bbae2aa9..de46f670 100644 --- a/zp-relayer/validation/validation.ts +++ b/zp-relayer/validation/validation.ts @@ -15,6 +15,14 @@ ajv.addKeyword({ errors: true, }) +ajv.addKeyword({ + keyword: 'isDivBy128', + validate: (schema: any, n: number) => { + return n % 128 === 0 + }, + errors: true, +}) + const AjvString: JSONSchemaType = { type: 'string' } const AjvNullableAddress: JSONSchemaType = { @@ -146,6 +154,20 @@ const AjvMerkleRootSchema: JSONSchemaType<{ required: ['index'], } +const AjvGetSiblingsSchema: JSONSchemaType<{ + index: string | number +}> = { + type: 'object', + properties: { + index: { + type: 'integer', + minimum: 1, + isDivBy128: true, + }, + }, + required: ['index'], +} + function checkErrors(schema: JSONSchemaType) { const validate = ajv.compile(schema) return (data: any) => { @@ -165,3 +187,4 @@ export const checkSendTransactionsErrors = checkErrors(AjvSendTransactionsSchema export const checkGetTransactions = checkErrors(AjvGetTransactionsSchema) export const checkGetTransactionsV2 = checkErrors(AjvGetTransactionsV2Schema) export const checkGetLimits = checkErrors(AjvGetLimitsSchema) +export const checkGetSiblings = checkErrors(AjvGetSiblingsSchema)