Skip to content

Commit

Permalink
Cache fee options (#180)
Browse files Browse the repository at this point in the history
  • Loading branch information
lok52 authored Jun 6, 2023
1 parent e1a6bd1 commit 208217d
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 18 deletions.
1 change: 1 addition & 0 deletions zp-relayer/configs/relayerConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const config = {
treeProverType: (process.env.RELAYER_TREE_PROVER_TYPE || ProverType.Local) as ProverType,
directDepositProverType: (process.env.RELAYER_DD_PROVER_TYPE || ProverType.Local) as ProverType,
feeManagerType: (process.env.RELAYER_FEE_MANAGER_TYPE || FeeManagerType.Dynamic) as FeeManagerType,
feeManagerUpdateInterval: parseInt(process.env.RELAYER_FEE_MANAGER_UPDATE_INTERVAL || '10000'),
feeMarginFactor: toBN(process.env.RELAYER_FEE_MARGIN_FACTOR || '100'),
feeScalingFactor: toBN(process.env.RELAYER_FEE_SCALING_FACTOR || '100'),
priceFeedType: (process.env.RELAYER_PRICE_FEED_TYPE || PriceFeedType.Native) as PriceFeedType,
Expand Down
2 changes: 1 addition & 1 deletion zp-relayer/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ function getFeeBuilder(feeManager: FeeManager) {
return async (req: Request, res: Response) => {
validateBatch([[checkTraceId, req.headers]])

const feeOptions = await feeManager.getFees({ gasLimit: config.relayerGasLimit })
const feeOptions = await feeManager.getFeeOptions({ gasLimit: config.relayerGasLimit })
const fees = feeOptions.denominate(pool.denominator).getObject()

res.json(fees)
Expand Down
4 changes: 3 additions & 1 deletion zp-relayer/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ function buildFeeManager(
priceFeed,
scaleFactor: config.feeScalingFactor,
marginFactor: config.feeMarginFactor,
updateInterval: config.feeManagerUpdateInterval,
defaultFeeOptionsParams: { gasLimit: config.relayerGasLimit },
}
if (type === FeeManagerType.Static) {
if (config.relayerFee === null) throw new Error('Static relayer fee is not set')
Expand Down Expand Up @@ -105,7 +107,7 @@ export async function init() {

const priceFeed = buildPriceFeed(config.priceFeedType, web3)
const feeManager = buildFeeManager(config.feeManagerType, priceFeed, gasPriceService, web3)
await feeManager.init()
await feeManager.start()

const workerPromises = [
createPoolTxWorker({
Expand Down
2 changes: 1 addition & 1 deletion zp-relayer/services/fee/DynamicFeeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class DynamicFeeManager extends FeeManager {
return new FeeEstimate(toBN(fee))
}

async _getFees({ gasLimit }: IGetFeesParams) {
async _fetchFeeOptions({ gasLimit }: IGetFeesParams) {
const baseFee = await FeeManager.estimateExecutionFee(this.gasPrice, gasLimit)
return new DefaultUserFeeOptions(baseFee)
}
Expand Down
48 changes: 43 additions & 5 deletions zp-relayer/services/fee/FeeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type BN from 'bn.js'
import { toBN } from 'web3-utils'
import type { IPriceFeed } from '../price-feed/IPriceFeed'
import { GasPrice, EstimationType, getMaxRequiredGasPrice } from '../gas-price'
import { setIntervalAndRun } from '@/utils/helpers'
import { logger } from '../appLogger'

export interface IGetFeesParams {
gasLimit: BN
Expand Down Expand Up @@ -53,12 +55,32 @@ export interface IFeeManagerConfig {
priceFeed: IPriceFeed
scaleFactor: BN
marginFactor: BN
updateInterval: number
defaultFeeOptionsParams: IGetFeesParams
}

export abstract class FeeManager {
private cachedFeeOptions: IUserFeeOptions | null = null
private updateFeeOptionsInterval: NodeJS.Timeout | null = null

constructor(protected config: IFeeManagerConfig) {}

abstract init(): Promise<void>
protected abstract init(): Promise<void>

async start() {
await this.init()

if (this.updateFeeOptionsInterval) clearInterval(this.updateFeeOptionsInterval)

this.updateFeeOptionsInterval = await setIntervalAndRun(async () => {
const feeOptions = await this.fetchFeeOptions(this.config.defaultFeeOptionsParams)
logger.debug('Updating cached fee options', {
old: this.cachedFeeOptions?.getObject(),
new: feeOptions.getObject(),
})
this.cachedFeeOptions = feeOptions
}, this.config.updateInterval)
}

static async estimateExecutionFee(gasPrice: GasPrice<EstimationType>, gasLimit: BN): Promise<BN> {
const price = await gasPrice.fetchOnce()
Expand All @@ -72,21 +94,37 @@ export abstract class FeeManager {
}

async estimateFee(params: IFeeEstimateParams): Promise<FeeEstimate> {
const fees = await this.getFees(params)
const fees = await this.getFeeOptions(params, false)
const estimatedFee = await this._estimateFee(params, fees)
const marginedFee = estimatedFee.applyFactor(this.config.marginFactor)
return marginedFee
}

async getFees(params: IGetFeesParams): Promise<IUserFeeOptions> {
const feeOptions = await this._getFees(params)
async fetchFeeOptions(params: IGetFeesParams): Promise<IUserFeeOptions> {
const feeOptions = await this._fetchFeeOptions(params)
const convertedFees = await this.convertAndScale(feeOptions)

return convertedFees
}

async getFeeOptions(params: IGetFeesParams, useCached = true): Promise<IUserFeeOptions> {
if (useCached && this.cachedFeeOptions) return this.cachedFeeOptions
let feeOptions: IUserFeeOptions
try {
feeOptions = await this.fetchFeeOptions(params)
logger.debug('Fetched fee options', feeOptions.getObject())
} catch (e) {
logger.error('Failed to fetch fee options', e)
if (!this.cachedFeeOptions) throw e
logger.debug('Fallback to cache fee options')
feeOptions = this.cachedFeeOptions
}
return feeOptions
}

// Should be used for tx fee validation
protected abstract _estimateFee(params: IFeeEstimateParams, fees: IUserFeeOptions): Promise<FeeEstimate>

// Should provide fee estimations for users
protected abstract _getFees(params: IGetFeesParams): Promise<IUserFeeOptions>
protected abstract _fetchFeeOptions(params: IGetFeesParams): Promise<IUserFeeOptions>
}
11 changes: 5 additions & 6 deletions zp-relayer/services/fee/OptimismFeeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class OptimismUserFeeOptions implements IUserFeeOptions {

getObject() {
return {
baseFee: this.baseFee.toString(10),
fee: this.baseFee.toString(10),
oneByteFee: this.oneByteFee.toString(10),
}
}
Expand Down Expand Up @@ -84,23 +84,22 @@ export class OptimismFeeManager extends FeeManager {
}

async _estimateFee({ extraData }: IFeeEstimateParams, feeOptions: OptimismUserFeeOptions) {
const { baseFee, oneByteFee } = feeOptions.getObject()
const { fee: baseFee, oneByteFee } = feeOptions.getObject()

const unscaledL1Fee = this.getL1Fee(MOCK_CALLDATA + extraData, toBN(oneByteFee))

// Because oneByteFee = l1BaseFee * NZERO_BYTE_GAS, we need to divide the estimation
// We do it here to get a more accurate result
const l1Fee = unscaledL1Fee.divn(NZERO_BYTE_GAS)

const fee = toBN(baseFee).add(l1Fee)
const feeEstimate = toBN(baseFee).add(l1Fee)

return new FeeEstimate(fee)
return new FeeEstimate(feeEstimate)
}

async _getFees({ gasLimit }: IGetFeesParams): Promise<OptimismUserFeeOptions> {
async _fetchFeeOptions({ gasLimit }: IGetFeesParams): Promise<OptimismUserFeeOptions> {
const baseFee = await FeeManager.estimateExecutionFee(this.gasPrice, gasLimit)

// TODO: cache
const l1BaseFee = await contractCallRetry(this.oracle, 'l1BaseFee').then(toBN)
// Use an upper bound for the oneByteFee
const oneByteFee = l1BaseFee.muln(NZERO_BYTE_GAS)
Expand Down
2 changes: 1 addition & 1 deletion zp-relayer/services/fee/StaticFeeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export class StaticFeeManager extends FeeManager {
return new FeeEstimate(this.staticFee)
}

async _getFees() {
async _fetchFeeOptions() {
return new DefaultUserFeeOptions(this.staticFee)
}
}
8 changes: 5 additions & 3 deletions zp-relayer/test/worker-tests/poolWorker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import flowZeroAddressWithdraw from '../flows/flow_zero-address_withdraw_2.json'
import { Params } from 'libzkbob-rs-node'
import { directDepositQueue } from '../../queue/directDepositQueue'
import { createDirectDepositWorker } from '../../workers/directDepositWorker'
import { FeeManager, DefaultFeeManager } from '../../services/fee'
import { DynamicFeeManager, FeeManager } from '../../services/fee'

chai.use(chaiAsPromised)
const expect = chai.expect
Expand Down Expand Up @@ -106,9 +106,11 @@ describe('poolWorker', () => {
priceFeed: mockPriceFeed,
scaleFactor: toBN(1),
marginFactor: toBN(1),
updateInterval: config.feeManagerUpdateInterval,
defaultFeeOptionsParams: { gasLimit: config.relayerGasLimit },
}
feeManager = new DefaultFeeManager(managerConfig)
await feeManager.init()
feeManager = new DynamicFeeManager(managerConfig, gasPriceService)
await feeManager.start()

txManager = new TxManager(web3, config.relayerPrivateKey, gasPriceService)
await txManager.init()
Expand Down

0 comments on commit 208217d

Please sign in to comment.