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

Implementation of 'eth_gasPrice' #2396

Merged
merged 8 commits into from
Nov 11, 2022
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
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 53 additions & 0 deletions packages/client/lib/rpc/modules/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,8 @@ export class Eth {
1,
[[validators.blockOption]]
)

this.gasPrice = middleware(this.gasPrice.bind(this), 0, [])
}

/**
Expand Down Expand Up @@ -973,4 +975,55 @@ export class Eth {
const block = await getBlockByOption(blockOpt, this._chain)
return intToHex(block.transactions.length)
}

/**
* Gas price oracle.
*
* Returns a suggested gas price.
* @returns a hex code of an integer representing the suggested gas price in wei.
*/
async gasPrice() {
const minGasPrice: bigint = this._chain.config.chainCommon.param('gasConfig', 'minPrice')
let gasPrice = BigInt(0)
const latest = await this._chain.getCanonicalHeadHeader()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I generally wonder if we want to make this dependent from the synchronized status of the client this.client.config.synchronized? 🤔 it likely doesn't make sense to return anything (respectively: is not useful) if the client is still syncing and we are not calculating upon the latest blocks?

Not sure, maybe to test this in Geth what is responded in such a case?

Also, then: any reason to not also take the chain common object here for the 1559 check (below)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Also, then: any reason to not also take the chain common object here for the 1559 check (below)?

->Made a change where the min gas price from the chain common object is now checked on both (1559 and non 1559) chains.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, maybe to test this in Geth what is responded in such a case?

I checked this in geth and it does return a gas price even when syncing. Not sure where that gas price comes from but it's producing something. The spec doesn't give any specifics about how to calculate current gas price so I guess I would just we go with compute best gas price available based on the current view of the chain that we have (i.e. what is implemented here) and leave it to the consumer to validate the sync status of the client via the eth_syncing when evaluating gas price.

if (this._vm !== undefined && this._vm._common.isActivatedEIP(1559)) {
const baseFee = latest.calcNextBaseFee()
let priorityFee = BigInt(0)
const block = await this._chain.getBlock(latest.number)
for (const tx of block.transactions) {
const maxPriorityFeePerGas = (tx as FeeMarketEIP1559Transaction).maxPriorityFeePerGas
priorityFee += maxPriorityFeePerGas
}

priorityFee =
priorityFee !== BigInt(0) ? priorityFee / BigInt(block.transactions.length) : BigInt(1)
gasPrice = baseFee + priorityFee > minGasPrice ? baseFee + priorityFee : minGasPrice
} else {
// For chains that don't support EIP-1559 we iterate over the last 20
// blocks to get an average gas price.
const blockIterations = 20 < latest.number ? 20 : latest.number
let txCount = BigInt(0)
for (let i = 0; i < blockIterations; i++) {
const block = await this._chain.getBlock(latest.number - BigInt(i))
if (block.transactions.length === 0) {
continue
}

for (const tx of block.transactions) {
const txGasPrice = (tx as Transaction).gasPrice
gasPrice += txGasPrice
txCount++
}
}

if (txCount > 0) {
const avgGasPrice = gasPrice / txCount
gasPrice = avgGasPrice > minGasPrice ? avgGasPrice : minGasPrice
} else {
gasPrice = minGasPrice
}
}

return bigIntToHex(gasPrice)
}
}
201 changes: 201 additions & 0 deletions packages/client/test/rpc/eth/gasPrice.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import { FeeMarketEIP1559Transaction, Transaction } from '@ethereumjs/tx'
import { bigIntToHex, intToHex } from '@ethereumjs/util'
import * as tape from 'tape'

import {
baseRequest,
dummy,
gethGenesisStartLondon,
params,
runBlockWithTxs,
setupChain,
} from '../helpers'

import pow = require('./../../testdata/geth-genesis/pow.json')

const method = 'eth_gasPrice'

tape(`${method}: call with legacy transaction data`, async (t) => {
const { chain, common, execution, server } = await setupChain(pow, 'pow')

const GAS_PRICE = 100
// construct tx
const tx = Transaction.fromTxData(
{ gasLimit: 21000, gasPrice: GAS_PRICE, to: '0x0000000000000000000000000000000000000000' },
{ common }
).sign(dummy.privKey)

await runBlockWithTxs(chain, execution, [tx])

const req = params(method, [])
const expectRes = (res: any) => {
const msg = 'should return the correct suggested gas price with 1 legacy transaction'
t.equal(res.body.result, intToHex(GAS_PRICE), msg)
}
await baseRequest(t, server, req, 200, expectRes)
})

tape(`${method}: call with multiple legacy transactions`, async (t) => {
const { chain, common, execution, server } = await setupChain(pow, 'pow')
const iterations = BigInt(20)
let averageGasPrice = BigInt(0)
for (let i = 0; i < iterations; i++) {
const gasPrice = i * 100
averageGasPrice += BigInt(gasPrice)
const tx = Transaction.fromTxData(
{ nonce: i, gasLimit: 21000, gasPrice, to: '0x0000000000000000000000000000000000000000' },
{ common }
).sign(dummy.privKey)
await runBlockWithTxs(chain, execution, [tx])
}

averageGasPrice = averageGasPrice / iterations
const req = params(method, [])
const expectRes = (res: any) => {
const msg = 'should return the correct gas price with multiple legacy transactions'
t.equal(res.body.result, bigIntToHex(averageGasPrice), msg)
}
await baseRequest(t, server, req, 200, expectRes)
})

tape(`${method}: call with multiple legacy transactions in a single block`, async (t) => {
const { chain, common, execution, server } = await setupChain(pow, 'pow')

const G1 = 100
const G2 = 1231231

const tx1 = Transaction.fromTxData(
{ gasLimit: 21000, gasPrice: G1, to: '0x0000000000000000000000000000000000000000' },
{ common }
).sign(dummy.privKey)
const tx2 = Transaction.fromTxData(
{ nonce: 1, gasLimit: 21000, gasPrice: G2, to: '0x0000000000000000000000000000000000000000' },
{ common }
).sign(dummy.privKey)

await runBlockWithTxs(chain, execution, [tx1, tx2])

const averageGasPrice = (G1 + G2) / 2
const req = params(method, [])
const expectRes = (res: any) => {
const msg = 'should return the correct gas price with multiple legacy transactions in a block'
t.equal(res.body.result, intToHex(Math.trunc(averageGasPrice)), msg)
}
await baseRequest(t, server, req, 200, () => expectRes)
})

tape(`${method}: call with 1559 transaction data`, async (t) => {
const { chain, common, execution, server } = await setupChain(
gethGenesisStartLondon(pow),
'powLondon'
)

const tx = FeeMarketEIP1559Transaction.fromTxData(
{
gasLimit: 21000,
maxPriorityFeePerGas: 10,
maxFeePerGas: 975000000,
to: '0x0000000000000000000000000000000000000000',
},
{ common }
).sign(dummy.privKey)

await runBlockWithTxs(chain, execution, [tx])
const req = params(method, [])
const latest = await chain.getCanonicalHeadHeader()
const baseFee = latest.calcNextBaseFee()
const gasPrice = BigInt(baseFee + tx.maxPriorityFeePerGas)

const expectRes = (res: any) => {
const msg = 'should return the correct gas price with 1 1559 transaction'
t.equal(res.body.result, bigIntToHex(gasPrice), msg)
}
await baseRequest(t, server, req, 200, expectRes)
})

tape(`${method}: call with multiple 1559 transactions`, async (t) => {
const { chain, common, execution, server } = await setupChain(
gethGenesisStartLondon(pow),
'powLondon'
)

const maxPriority1 = 10
const maxPriority2 = 1231231
const tx1 = FeeMarketEIP1559Transaction.fromTxData(
{
gasLimit: 21000,
maxPriorityFeePerGas: maxPriority1,
maxFeePerGas: 975000000,
to: '0x0000000000000000000000000000000000000000',
},
{ common }
).sign(dummy.privKey)
const tx2 = FeeMarketEIP1559Transaction.fromTxData(
{
nonce: 1,
gasLimit: 21000,
maxPriorityFeePerGas: maxPriority2,
maxFeePerGas: 975000000,
to: '0x0000000000000000000000000000000000000000',
},
{ common }
).sign(dummy.privKey)

await runBlockWithTxs(chain, execution, [tx1, tx2])
const req = params(method, [])
const averagePriorityFee = BigInt(Math.trunc((maxPriority1 + maxPriority2) / 2))
const latest = await chain.getCanonicalHeadHeader()
const baseFee = latest.calcNextBaseFee()
const gasPrice = BigInt(baseFee + averagePriorityFee)
const expectRes = (res: any) => {
const msg = 'should return the correct gas price with 1 1559 transaction'
t.equal(res.body.result, bigIntToHex(gasPrice), msg)
}
await baseRequest(t, server, req, 200, expectRes)
})

tape(`${method}: compute average gas price for 21 blocks`, async (t) => {
const { chain, common, execution, server } = await setupChain(pow, 'pow')
const iterations = BigInt(21)
const gasPrice = BigInt(20)
const firstBlockGasPrice = BigInt(11111111111111)
let tx: Transaction
for (let i = 0; i < iterations; i++) {
if (i === 0) {
tx = Transaction.fromTxData(
{
nonce: i,
gasLimit: 21000,
gasPrice: firstBlockGasPrice,
to: '0x0000000000000000000000000000000000000000',
},
{ common }
).sign(dummy.privKey)
} else {
tx = Transaction.fromTxData(
{
nonce: i,
gasLimit: 21000,
gasPrice,
to: '0x0000000000000000000000000000000000000000',
},
{ common }
).sign(dummy.privKey)
}
await runBlockWithTxs(chain, execution, [tx!])
}

const latest = await chain.getCanonicalHeadHeader()
const blockNumber = latest.number

// Should be block number 21
t.equal(blockNumber, 21n)

const req = params(method, [])
const expectRes = (res: any) => {
const msg = 'should return the correct gas price for 21 blocks'
t.equal(res.body.result, bigIntToHex(gasPrice), msg)
}

await baseRequest(t, server, req, 200, expectRes)
})