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

enforce polygon maxPriorityFee minimum value as 30 #1329

Merged
merged 6 commits into from
Jan 12, 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
123 changes: 123 additions & 0 deletions main/chains/gas/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { intToHex } from '@ethereumjs/util'

interface GasCalculator {
calculateGas: (blocks: Block[]) => GasFees
}

type RawGasFees = {
nextBaseFee: number
maxBaseFeePerGas: number
maxPriorityFeePerGas: number
maxFeePerGas: number
}

export type Block = {
baseFee: number
rewards: number[]
gasUsedRatio: number
}

function feesToHex(fees: RawGasFees) {
return {
nextBaseFee: intToHex(fees.nextBaseFee),
maxBaseFeePerGas: intToHex(fees.maxBaseFeePerGas),
maxPriorityFeePerGas: intToHex(fees.maxPriorityFeePerGas),
maxFeePerGas: intToHex(fees.maxFeePerGas)
}
}

function calculateReward(blocks: Block[]) {
const recentBlocks = 10
const allBlocks = blocks.length

// these strategies will be tried in descending order until one finds
// at least 1 eligible block from which to calculate the reward
const rewardCalculationStrategies = [
// use recent blocks that weren't almost empty or almost full
{ minRatio: 0.1, maxRatio: 0.9, blockSampleSize: recentBlocks },
// include recent blocks that were full
{ minRatio: 0.1, maxRatio: 1.05, blockSampleSize: recentBlocks },
// use the entire block sample but still limit to blocks that were not almost empty
{ minRatio: 0.1, maxRatio: 1.05, blockSampleSize: allBlocks },
// use any recent block with transactions
{ minRatio: 0, maxRatio: Number.MAX_SAFE_INTEGER, blockSampleSize: recentBlocks },
// use any block with transactions
{ minRatio: 0, maxRatio: Number.MAX_SAFE_INTEGER, blockSampleSize: allBlocks }
]

const eligibleRewardsBlocks = rewardCalculationStrategies.reduce((foundBlocks, strategy) => {
if (foundBlocks.length === 0) {
const blockSample = blocks.slice(blocks.length - Math.min(strategy.blockSampleSize, blocks.length))
const eligibleBlocks = blockSample.filter(
(block) => block.gasUsedRatio > strategy.minRatio && block.gasUsedRatio <= strategy.maxRatio
)

if (eligibleBlocks.length > 0) return eligibleBlocks
}

return foundBlocks
}, [] as Block[])

// use the median reward from the block sample or use the fee from the last block as a last resort
const lastBlockFee = blocks[blocks.length - 1].rewards[0]
return (
eligibleRewardsBlocks.map((block) => block.rewards[0]).sort()[
Math.floor(eligibleRewardsBlocks.length / 2)
] || lastBlockFee
)
}

function estimateGasFees(blocks: Block[]) {
// plan for max fee of 2 full blocks, each one increasing the fee by 12.5%
const nextBlockFee = blocks[blocks.length - 1].baseFee // base fee for next block
const calculatedFee = Math.ceil(nextBlockFee * 1.125 * 1.125)

// the last block contains only the base fee for the next block but no fee history, so
// don't use it in the block reward calculation
const medianBlockReward = calculateReward(blocks.slice(0, blocks.length - 1))

const estimatedGasFees = {
nextBaseFee: nextBlockFee,
maxBaseFeePerGas: calculatedFee,
maxPriorityFeePerGas: medianBlockReward,
maxFeePerGas: calculatedFee + medianBlockReward
}

return estimatedGasFees
}

function DefaultGasCalculator() {
return {
calculateGas: (blocks: Block[]) => {
const estimatedGasFees = estimateGasFees(blocks)

return feesToHex(estimatedGasFees)
}
}
}

function PolygonGasCalculator() {
return {
calculateGas: (blocks: Block[]) => {
const fees = estimateGasFees(blocks)

const maxPriorityFeePerGas = Math.max(fees.maxPriorityFeePerGas, 30e9)

return feesToHex({
...fees,
maxPriorityFeePerGas,
maxFeePerGas: fees.maxBaseFeePerGas + maxPriorityFeePerGas
})
}
}
}

export function createGasCalculator(chainId: number): GasCalculator {
// TODO: maybe this can be tied into chain config somehow
if (chainId === 137 || chainId === 80001) {
// Polygon and Mumbai testnet
return PolygonGasCalculator()
}

return DefaultGasCalculator()
}
13 changes: 9 additions & 4 deletions main/chains/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const log = require('electron-log')
const store = require('../store').default
const { default: BlockMonitor } = require('./blocks')
const { default: chainConfig } = require('./config')
const { default: GasCalculator } = require('../transaction/gasCalculator')
const { default: GasMonitor } = require('../transaction/gasMonitor')
const { createGasCalculator } = require('./gas')

// These chain IDs are known to not support EIP-1559 and will be forced
// not to use that mechanism
Expand All @@ -36,6 +37,9 @@ class ChainConnection extends EventEmitter {
// to update it to london
this.chainConfig = chainConfig(parseInt(this.chainId), 'istanbul')

// TODO: maybe this can be tied into chain config somehow
this.gasCalculator = createGasCalculator(this.chainId)

this.primary = {
status: 'off',
network: '',
Expand Down Expand Up @@ -81,12 +85,13 @@ class ChainConnection extends EventEmitter {
monitor.on('data', async (block) => {
let feeMarket = null

const gasCalculator = new GasCalculator(provider)
const gasMonitor = new GasMonitor(provider)

if (allowEip1559 && 'baseFeePerGas' in block) {
try {
// only consider this an EIP-1559 block if fee market can be loaded
feeMarket = await gasCalculator.getFeePerGas()
const feeHistory = await gasMonitor.getFeeHistory(10, [10])
feeMarket = this.gasCalculator.calculateGas(feeHistory)

this.chainConfig.setHardforkByBlockNumber(block.number)

Expand All @@ -108,7 +113,7 @@ class ChainConnection extends EventEmitter {
store.setGasPrices(this.type, this.chainId, { fast: addHexPrefix(gasPrice.toString(16)) })
store.setGasDefault(this.type, this.chainId, 'fast')
} else {
const gas = await gasCalculator.getGasPrices()
const gas = await gasMonitor.getGasPrices()
const customLevel = store('main.networksMeta', this.type, this.chainId, 'gas.price.levels.custom')

store.setGasPrices(this.type, this.chainId, {
Expand Down
124 changes: 0 additions & 124 deletions main/transaction/gasCalculator.ts

This file was deleted.

59 changes: 59 additions & 0 deletions main/transaction/gasMonitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { intToHex } from '@ethereumjs/util'

import type { Block } from '../chains/gas'

interface FeeHistoryResponse {
baseFeePerGas: string[]
gasUsedRatio: number[]
reward: Array<string[]>
oldestBlock: string
}

interface GasPrices {
slow: string
standard: string
fast: string
asap: string
}

export default class GasMonitor {
private connection

constructor(connection: any /* Chains */) {
this.connection = connection
}

async getFeeHistory(
numBlocks: number,
rewardPercentiles: number[],
newestBlock = 'latest'
): Promise<Block[]> {
const blockCount = intToHex(numBlocks)
const payload = { method: 'eth_feeHistory', params: [blockCount, newestBlock, rewardPercentiles] }

const feeHistory: FeeHistoryResponse = await this.connection.send(payload)

const feeHistoryBlocks = feeHistory.baseFeePerGas.map((baseFee, i) => {
return {
baseFee: parseInt(baseFee, 16),
gasUsedRatio: feeHistory.gasUsedRatio[i],
rewards: (feeHistory.reward[i] || []).map((reward) => parseInt(reward, 16))
}
})

return feeHistoryBlocks
}

async getGasPrices(): Promise<GasPrices> {
const gasPrice = await this.connection.send({ method: 'eth_gasPrice' })

// in the future we may want to have specific calculators to calculate variations
// in the gas price or eliminate this structure altogether
return {
slow: gasPrice,
standard: gasPrice,
fast: gasPrice,
asap: gasPrice
}
}
}
Loading