Skip to content

Commit

Permalink
Add root storage (#76)
Browse files Browse the repository at this point in the history
  • Loading branch information
Leonid Tyurin authored Oct 29, 2022
1 parent 764d28f commit 8ee76d0
Show file tree
Hide file tree
Showing 27 changed files with 495 additions and 229 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ prover.js
# Log file
zp.log
yarn-error.log
zp-relayer/state/
zp-relayer/POOL_STATE
7 changes: 5 additions & 2 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,18 @@
| TREE_UPDATE_PARAMS_PATH | Local path to tree update circuit parameters | string |
| TRANSFER_PARAMS_PATH | Local path to transfer circuit parameters | string |
| TX_VK_PATH | Local path to transaction curcuit verification key | string |
| STATE_DIR_PATH | Path to persistent state files related to tree and transactions storage. Default: `./state` | string |
| STATE_DIR_PATH | Path to persistent state files related to tree and transactions storage. Default: `./POOL_STATE` | string |
| GAS_PRICE_FALLBACK | Default fallback gas price | integer |
| GAS_PRICE_ESTIMATION_TYPE | Gas price estimation type | `web3` / `gas-price-oracle` / `eip1559-gas-estimation` / `polygon-gasstation-v2` |
| GAS_PRICE_SPEED_TYPE | This parameter specifies the desirable transaction speed | `instant` / `fast` / `standard` / `low` |
| GAS_PRICE_FACTOR | A value that will multiply the gas price of the oracle to convert it to gwei. If the oracle API returns gas prices in gwei then this can be set to `1`. Also, it could be used to intentionally pay more gas than suggested by the oracle to guarantee the transaction verification. E.g. `1.25` or `1.5`. | integer |
| GAS_PRICE_UPDATE_INTERVAL | Interval in milliseconds used to get the updated gas price value using specified estimation type | integer |
| MAX_FEE_PER_GAS_LIMIT | Max limit on `maxFeePerGas` parameter for each transaction in wei | integer |
| START_BLOCK | The block number used to start searching for events when the relayer instance is run for the first time | integer
| EVENTS_PROCESSING_BATCH_SIZE | Batch size for one `eth_getLogs` request when reprocessing old logs. Defaults to `10000` | integer
| RELAYER_LOG_LEVEL | Log level | Winston log level |
| RELAYER_REDIS_URL | Url to redis instance | URL |
| RPC_URL | Url to RPC node | URL |
| SENT_TX_DELAY | Delay in milliseconds for sentTxWorker to verify submitted transactions | integer
| SENT_TX_DELAY | Delay in milliseconds for sentTxWorker to verify submitted transactions | integer |
| PERMIT_DEADLINE_THRESHOLD_INITIAL | Minimum time threshold in seconds for permit signature deadline to be valid (before initial transaction submition) | integer |
| PERMIT_DEADLINE_THRESHOLD_RESEND | Minimum time threshold in seconds for permit signature deadline to be valid (for re-send attempts) | integer |
3 changes: 0 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6765,6 +6765,3 @@ yocto-queue@^0.1.0:
dependencies:
borsh "^0.5.0"
buffer "^6.0.3"
stream-http "^3.2.0"
web3 "1.7.4"
webpack "^5.46.0"
10 changes: 4 additions & 6 deletions zp-memo-parser/memo.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import BN from 'bn.js'
import { Buffer } from 'buffer'
import { deserialize, BinaryReader } from 'borsh'
import { toBN } from 'web3-utils'

type Option<T> = T | null

Expand All @@ -13,16 +11,16 @@ export enum TxType {
}

interface DefaultTxData {
fee: BN
fee: string
}

export interface WithdrawTxData extends DefaultTxData {
nativeAmount: BN
nativeAmount: string
reciever: Uint8Array
}

export interface PermittableDepositTxData extends DefaultTxData {
deadline: BN
deadline: string
holder: Uint8Array
}

Expand Down Expand Up @@ -97,7 +95,7 @@ function getNoteHashes(rawHashes: Buffer, num: number, maxNotes: number): Uint8A
export function getTxData(data: Buffer, txType: Option<TxType>): TxData {
function readU64(offset: number) {
let uint = data.readBigUInt64BE(offset)
return toBN(uint.toString())
return uint.toString(10)
}
let offset = 0
const fee = readU64(offset)
Expand Down
5 changes: 1 addition & 4 deletions zp-memo-parser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
},
"dependencies": {
"borsh": "^0.5.0",
"buffer": "^6.0.3",
"stream-http": "^3.2.0",
"webpack": "^5.46.0",
"web3": "1.7.4"
"buffer": "^6.0.3"
}
}
2 changes: 1 addition & 1 deletion zp-relayer/clear.sh
Original file line number Diff line number Diff line change
@@ -1 +1 @@
rm -rf ./state
rm -rf ./POOL_STATE
5 changes: 4 additions & 1 deletion zp-relayer/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,21 @@ const config = {
treeUpdateParamsPath: process.env.TREE_UPDATE_PARAMS_PATH || './params/tree_params.bin',
transferParamsPath: process.env.TRANSFER_PARAMS_PATH || './params/transfer_params.bin',
txVKPath: process.env.TX_VK_PATH || './params/transfer_verification_key.json',
stateDirPath: process.env.STATE_DIR_PATH || './state',
stateDirPath: process.env.STATE_DIR_PATH || './POOL_STATE',
gasPriceFallback: process.env.GAS_PRICE_FALLBACK as string,
gasPriceEstimationType: (process.env.GAS_PRICE_ESTIMATION_TYPE as EstimationType) || 'web3',
gasPriceSpeedType: (process.env.GAS_PRICE_SPEED_TYPE as GasPriceKey) || 'fast',
gasPriceFactor: parseInt(process.env.GAS_PRICE_FACTOR || '1'),
gasPriceUpdateInterval: parseInt(process.env.GAS_PRICE_UPDATE_INTERVAL || '5000'),
maxFeeLimit: process.env.MAX_FEE_PER_GAS_LIMIT ? toBN(process.env.MAX_FEE_PER_GAS_LIMIT) : null,
startBlock: parseInt(process.env.START_BLOCK || '0'),
eventsProcessingBatchSize: parseInt(process.env.EVENTS_PROCESSING_BATCH_SIZE || '10000'),
logLevel: process.env.RELAYER_LOG_LEVEL || 'debug',
redisUrl: process.env.RELAYER_REDIS_URL,
rpcUrl: process.env.RPC_URL as string,
sentTxDelay: parseInt(process.env.SENT_TX_DELAY || '30000'),
permitDeadlineThresholdInitial: parseInt(process.env.PERMIT_DEADLINE_THRESHOLD_INITIAL || '300'),
permitDeadlineThresholdResend: parseInt(process.env.PERMIT_DEADLINE_THRESHOLD_RESEND || '10'),
}

export default config
1 change: 1 addition & 0 deletions zp-relayer/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export async function init() {
const gasPriceService = new GasPrice(web3, config.gasPriceUpdateInterval, config.gasPriceEstimationType, {
speedType: config.gasPriceSpeedType,
factor: config.gasPriceFactor,
maxFeeLimit: config.maxFeeLimit,
})
await gasPriceService.start()
const workerMutex = new Mutex()
Expand Down
23 changes: 0 additions & 23 deletions zp-relayer/nullifierSet.ts

This file was deleted.

2 changes: 1 addition & 1 deletion zp-relayer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"dev:worker": "ts-node poolTxWorker.ts",
"start:dev": "ts-node index.ts",
"start:prod": "node index.js",
"test": "ts-mocha --timeout 1000000 test/**/*.test.ts"
"test": "ts-mocha --paths --timeout 1000000 test/**/*.test.ts"
},
"dependencies": {
"@metamask/eth-sig-util": "^4.0.1",
Expand Down
43 changes: 31 additions & 12 deletions zp-relayer/pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ import { Contract } from 'web3-eth-contract'
import config from './config'
import { web3 } from './services/web3'
import { logger } from './services/appLogger'
import { redis } from './services/redisClient'
import { poolTxQueue } from './queue/poolTxQueue'
import { getBlockNumber, getEvents, getTransaction } from './utils/web3'
import { Helpers, Params, Proof, SnarkProof, VK } from 'libzkbob-rs-node'
import { PoolState } from './state'
import { PoolState } from './state/PoolState'

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

export interface PoolTx {
proof: Proof
Expand Down Expand Up @@ -84,8 +85,8 @@ class Pool {
const txVK = require(config.txVKPath)
this.txVK = txVK

this.state = new PoolState('pool', config.stateDirPath)
this.optimisticState = new PoolState('optimistic', config.stateDirPath)
this.state = new PoolState('pool', redis, config.stateDirPath)
this.optimisticState = new PoolState('optimistic', redis, config.stateDirPath)
}

private static getHash(path: string) {
Expand Down Expand Up @@ -136,11 +137,19 @@ class Pool {
logger.debug(`LOCAL ROOT: ${localRoot}; LOCAL INDEX: ${localIndex}`)
logger.debug(`CONTRACT ROOT: ${contractRoot}; CONTRACT INDEX: ${contractIndex}`)

if (contractRoot === localRoot && contractIndex === localIndex) {
const rootSetRoot = await this.state.roots.get(localIndex.toString(10))
logger.debug(`ROOT FROM ROOTSET: ${rootSetRoot}`)

if (contractRoot === localRoot && rootSetRoot === localRoot && contractIndex === localIndex) {
logger.info('State is ok, no need to resync')
return
}

// Set initial root
await this.state.roots.add({
0: INIT_ROOT,
})

const numTxs = Math.floor((contractIndex - localIndex) / OUTPLUSONE)
const missedIndices = Array(numTxs)
for (let i = 0; i < numTxs; i++) {
Expand Down Expand Up @@ -171,9 +180,6 @@ class Pool {

const parser = new PoolCalldataParser(calldata)

const nullifier = parser.getField('nullifier')
await this.state.nullifiers.add([web3.utils.hexToNumberString(nullifier)])

const outCommitRaw = parser.getField('outCommit')
const outCommit = web3.utils.hexToNumberString(outCommitRaw)

Expand All @@ -186,16 +192,29 @@ class Pool {
const truncatedMemo = truncateMemoTxPrefix(memoRaw, txType)
const commitAndMemo = numToHex(toBN(outCommit)).concat(transactionHash.slice(2)).concat(truncatedMemo)

const index = Number(returnValues.index) - OUTPLUSONE
const newPoolIndex = Number(returnValues.index)
const prevPoolIndex = newPoolIndex - OUTPLUSONE
const prevCommitIndex = Math.floor(Number(prevPoolIndex) / OUTPLUSONE)

for (let state of [this.state, this.optimisticState]) {
state.addCommitment(Math.floor(index / OUTPLUSONE), Helpers.strToNum(outCommit))
state.addTx(index, Buffer.from(commitAndMemo, 'hex'))
state.addCommitment(prevCommitIndex, Helpers.strToNum(outCommit))
state.addTx(prevPoolIndex, Buffer.from(commitAndMemo, 'hex'))
}

// Save nullifier in confirmed state
const nullifier = parser.getField('nullifier')
await this.state.nullifiers.add([web3.utils.hexToNumberString(nullifier)])

// Save root in confirmed state
const root = this.state.getMerkleRoot()
await this.state.roots.add({
[newPoolIndex]: root,
})
}
}

const newLocalRoot = this.state.getMerkleRoot()
logger.debug(`LOCAL ROOT AFTER UPDATE ${localRoot}`)
logger.debug(`LOCAL ROOT AFTER UPDATE ${newLocalRoot}`)
if (newLocalRoot !== contractRoot) {
logger.error('State is corrupted, roots mismatch')
}
Expand Down
21 changes: 17 additions & 4 deletions zp-relayer/queue/sentTxQueue.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
import { Queue, QueueScheduler } from 'bullmq'
import { redis } from '@/services/redisClient'
import { SENT_TX_QUEUE_NAME } from '@/utils/constants'
import { TxPayload } from './poolTxQueue'
import type { TransactionConfig } from 'web3-core'
import { GasPriceValue } from '@/services/gas-price'
import { TxData, TxType } from 'zp-memo-parser'

export interface SentTxPayload {
payload: TxPayload
txType: TxType
root: string
outCommit: string
commitIndex: number
txHash: string
txData: string
prefixedMemo: string
txConfig: TransactionConfig
nullifier: string
gasPriceOptions: GasPriceValue
txData: TxData
}

export enum SentTxState {
MINED = 'MINED',
REVERT = 'REVERT',
RESEND = 'RESEND',
FAILED = 'FAILED',
}

export type SentTxResult = [SentTxState, string]

// Required for delayed jobs processing
const sentTxQueueScheduler = new QueueScheduler(SENT_TX_QUEUE_NAME, {
connection: redis,
})

export const sentTxQueue = new Queue<SentTxPayload, string>(SENT_TX_QUEUE_NAME, {
export const sentTxQueue = new Queue<SentTxPayload, SentTxResult>(SENT_TX_QUEUE_NAME, {
connection: redis,
})
56 changes: 51 additions & 5 deletions zp-relayer/services/gas-price/GasPrice.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import BN from 'bn.js'
import type Web3 from 'web3'
import { toWei } from 'web3-utils'
import { toWei, toBN } from 'web3-utils'
import config from '@/config'
import { setIntervalAndRun } from '@/utils/helpers'
import { estimateFees } from '@mycrypto/gas-estimation'
Expand All @@ -17,6 +18,8 @@ import {
PolygonGSV2Response,
PolygonGSV2GasPriceKey,
GasPriceKey,
LegacyGasPrice,
EIP1559GasPrice,
} from './types'

const polygonGasPriceKeyMapping: Record<GasPriceKey, PolygonGSV2GasPriceKey> = {
Expand All @@ -26,6 +29,43 @@ const polygonGasPriceKeyMapping: Record<GasPriceKey, PolygonGSV2GasPriceKey> = {
instant: 'fast',
}

function isLegacyGasPrice(gp: GasPriceValue): gp is LegacyGasPrice {
return 'gasPrice' in gp
}

function isEIP1559GasPrice(gp: GasPriceValue): gp is EIP1559GasPrice {
return 'maxFeePerGas' in gp && 'maxPriorityFeePerGas' in gp
}

export function chooseGasPriceOptions(a: GasPriceValue, b: GasPriceValue): GasPriceValue {
if (isLegacyGasPrice(a) && isLegacyGasPrice(b)) {
return { gasPrice: BN.max(toBN(a.gasPrice), toBN(b.gasPrice)).toString(10) }
}
if (isEIP1559GasPrice(a) && isEIP1559GasPrice(b)) {
return {
maxFeePerGas: BN.max(toBN(a.maxFeePerGas), toBN(b.maxFeePerGas)).toString(10),
maxPriorityFeePerGas: BN.max(toBN(a.maxPriorityFeePerGas), toBN(b.maxPriorityFeePerGas)).toString(10),
}
}
return b
}

export function EIP1559GasPriceWithinLimit(fees: EIP1559GasPrice, maxFeeLimit: BN | null): EIP1559GasPrice {
if (!maxFeeLimit) return fees

const diff = toBN(fees.maxFeePerGas).sub(maxFeeLimit)
if (diff.isNeg()) {
return fees
} else {
const maxFeePerGas = maxFeeLimit.toString(10)
const maxPriorityFeePerGas = BN.min(toBN(fees.maxPriorityFeePerGas), maxFeeLimit).toString(10)
return {
maxFeePerGas,
maxPriorityFeePerGas,
}
}
}

export class GasPrice<ET extends EstimationType> {
private fetchGasPriceInterval: NodeJS.Timeout | null = null
private cachedGasPrice: GasPriceValue
Expand Down Expand Up @@ -99,10 +139,16 @@ export class GasPrice<ET extends EstimationType> {
const json: PolygonGSV2Response = await response.json()
const speedType = polygonGasPriceKeyMapping[options.speedType]
const { maxFee, maxPriorityFee } = json[speedType]
return {
maxFeePerGas: GasPrice.normalizeGasPrice(maxFee),
maxPriorityFeePerGas: GasPrice.normalizeGasPrice(maxPriorityFee),
}

const gasPriceOptions = EIP1559GasPriceWithinLimit(
{
maxFeePerGas: GasPrice.normalizeGasPrice(maxFee),
maxPriorityFeePerGas: GasPrice.normalizeGasPrice(maxPriorityFee),
},
options.maxFeeLimit
)

return gasPriceOptions
}

static normalizeGasPrice(rawGasPrice: number, factor = 1) {
Expand Down
8 changes: 5 additions & 3 deletions zp-relayer/services/gas-price/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type BN from 'bn.js'

// GasPrice fields
interface LegacyGasPrice {
export interface LegacyGasPrice {
gasPrice: string
}
interface EIP1559GasPrice {
export interface EIP1559GasPrice {
maxFeePerGas: string
maxPriorityFeePerGas: string
}
Expand Down Expand Up @@ -38,7 +40,7 @@ export type EstimationPolygonGSV2 = 'polygon-gasstation-v2'
export type EstimationType = EstimationEIP1559 | EstimationOracle | EstimationWeb3 | EstimationPolygonGSV2

export type EstimationOracleOptions = { speedType: GasPriceKey; factor: number }
export type EstimationPolygonGSV2Options = { speedType: GasPriceKey }
export type EstimationPolygonGSV2Options = { speedType: GasPriceKey; maxFeeLimit: BN | null }
export type EstimationOptions<ET extends EstimationType> = ET extends EstimationOracle
? EstimationOracleOptions
: ET extends EstimationPolygonGSV2
Expand Down
Loading

0 comments on commit 8ee76d0

Please sign in to comment.