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

Cache fee options #180

Merged
merged 4 commits into from
Jun 6, 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
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)
lok52 marked this conversation as resolved.
Show resolved Hide resolved
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