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

Add support for eth_maxPriorityFeeperGas #607

Merged
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
5 changes: 5 additions & 0 deletions client/rpc-core/src/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,11 @@ pub trait EthApi {
newest_block: BlockNumber,
reward_percentiles: Option<Vec<f64>>,
) -> Result<FeeHistory>;

/// Introduced in EIP-1159, a Geth-specific and simplified priority fee oracle.
/// Leverages the already existing fee history cache.
#[rpc(name = "eth_maxPriorityFeePerGas")]
fn max_priority_fee_per_gas(&self) -> Result<U256>;
}

/// Eth filters rpc api (polling).
Expand Down
29 changes: 29 additions & 0 deletions client/rpc/src/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2427,6 +2427,35 @@ where
newest_block
)))
}

fn max_priority_fee_per_gas(&self) -> Result<U256> {
// https://github.com/ethereum/go-ethereum/blob/master/eth/ethconfig/config.go#L44-L51
let at_percentile = 60;
let block_count = 20;
let index = (at_percentile * 2) as usize;

let highest =
UniqueSaturatedInto::<u64>::unique_saturated_into(self.client.info().best_number);
let lowest = highest.saturating_sub(block_count - 1);

// https://github.com/ethereum/go-ethereum/blob/master/eth/gasprice/gasprice.go#L149
let mut rewards = Vec::new();
if let Ok(fee_history_cache) = &self.fee_history_cache.lock() {
for n in lowest..highest + 1 {
if let Some(block) = fee_history_cache.get(&n) {
let reward = if let Some(r) = block.rewards.get(index as usize) {
U256::from(*r)
} else {
U256::zero()
};
rewards.push(reward);
}
}
} else {
return Err(internal_err(format!("Failed to read fee oracle cache.")));
}
Ok(*rewards.iter().min().unwrap_or(&U256::zero()))
}
}

pub struct EthFilterApi<B: BlockT, C, BE> {
Expand Down
6 changes: 3 additions & 3 deletions ts-tests/tests/test-fee-history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describeWithFrontier("Frontier RPC (Fee History)", (context) => {
}
}

async function createBlocks(block_count, reward_percentiles, priority_fees) {
async function createBlocks(block_count, priority_fees) {
for(var b = 0; b < block_count; b++) {
for(var p = 0; p < priority_fees.length; p++) {
await sendTransaction(context, {
Expand Down Expand Up @@ -70,7 +70,7 @@ describeWithFrontier("Frontier RPC (Fee History)", (context) => {
let block_count = 2;
let reward_percentiles = [20,50,70];
let priority_fees = [1, 2, 3];
await createBlocks(block_count, reward_percentiles, priority_fees);
await createBlocks(block_count, priority_fees);
let result = (await customRequest(context.web3, "eth_feeHistory", ["0x2", "latest", reward_percentiles])).result;

// baseFeePerGas is always the requested block range + 1 (the next derived base fee).
Expand All @@ -90,7 +90,7 @@ describeWithFrontier("Frontier RPC (Fee History)", (context) => {
let block_count = 11;
let reward_percentiles = [20,50,70,85,100];
let priority_fees = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
await createBlocks(block_count, reward_percentiles, priority_fees);
await createBlocks(block_count, priority_fees);
let result = (await customRequest(context.web3, "eth_feeHistory", ["0xA", "latest", reward_percentiles])).result;

// Calculate the percentiles in javascript.
Expand Down
109 changes: 109 additions & 0 deletions ts-tests/tests/test-max-priority-fee-per-gas-rpc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { ethers } from "ethers";
import { expect } from "chai";
import { step } from "mocha-steps";

import { createAndFinalizeBlock, describeWithFrontier, customRequest } from "./util";

// We use ethers library in this test as apparently web3js's types are not fully EIP-1559 compliant yet.
describeWithFrontier("Frontier RPC (Max Priority Fee Per Gas)", (context) => {

const GENESIS_ACCOUNT = "0x6be02d1d3665660d22ff9624b7be0551ee1ac91b";
const GENESIS_ACCOUNT_PRIVATE_KEY = "0x99B3C12287537E38C90A9219D4CB074A89A16E9CDB20BF85728EBD97C343E342";

async function sendTransaction(context, payload: any) {
let signer = new ethers.Wallet(GENESIS_ACCOUNT_PRIVATE_KEY, context.ethersjs);
// Ethers internally matches the locally calculated transaction hash against the one returned as a response.
// Test would fail in case of mismatch.
const tx = await signer.sendTransaction(payload);
return tx;
}

let nonce = 0;

function get_percentile(percentile, array) {
array.sort(function (a, b) { return a - b; });
let index = ((percentile/100) * (array.length))-1;
if (Math.floor(index) == index) {
return array[index];
}
else {
return Math.ceil((array[Math.floor(index)] + array[Math.ceil(index)])/2);
}
}

async function createBlocks(block_count, priority_fees) {
for(var b = 0; b < block_count; b++) {
for(var p = 0; p < priority_fees.length; p++) {
await sendTransaction(context, {
from: GENESIS_ACCOUNT,
to: "0x0000000000000000000000000000000000000000",
data: "0x",
value: "0x00",
maxFeePerGas: "0x3B9ACA00",
maxPriorityFeePerGas: context.web3.utils.numberToHex(priority_fees[p]),
accessList: [],
nonce: nonce,
gasLimit: "0x5208",
chainId: 42
});
nonce++;
}
await createAndFinalizeBlock(context.web3);
}
}

step("should default to zero on genesis", async function () {
let result = await customRequest(context.web3, "eth_maxPriorityFeePerGas", []);
expect(result.result).to.be.eq("0x0");
});

step("should default to zero on empty blocks", async function () {
await createAndFinalizeBlock(context.web3);
let result = await customRequest(context.web3, "eth_maxPriorityFeePerGas", []);
expect(result.result).to.be.eq("0x0");
});

// - Create 20 blocks, each with 10 txns.
// - Every txn includes a monotonically increasing tip.
// - The oracle returns the minimum fee in the percentile 60 for the last 20 blocks.
// - In this case, and being the first tip 0, that minimum fee is 5.
step("maxPriorityFeePerGas should suggest the percentile 60 tip", async function () {
this.timeout(100000);

let block_count = 20;
let txns_per_block = 10;

let priority_fee = 0;

for(let i = 0; i < block_count; i++) {
let priority_fees = [];
for(let j = 0; j < txns_per_block; j++) {
priority_fees.push(priority_fee)
priority_fee++;
}
await createBlocks(1, priority_fees);
}

let result = (await customRequest(context.web3, "eth_maxPriorityFeePerGas", [])).result;
expect(result).to.be.eq("0x5");
});

// If in the last 20 blocks at least one is empty (or only contains zero-tip txns), the
// suggested tip will be zero.
// That's the expected behaviour in this simplified oracle version: there is a decent chance of
// being able to include a zero-tip txn in a low congested network.
step("maxPriorityFeePerGas should suggest zero if there are recent empty blocks", async function () {
this.timeout(100000);

for(let i = 0; i < 10; i++) {
await createBlocks(1, [0, 1, 2, 3, 4, 5]);
}
await createAndFinalizeBlock(context.web3);
for(let i = 0; i < 9; i++) {
await createBlocks(1, [0, 1, 2, 3, 4, 5]);
}

let result = (await customRequest(context.web3, "eth_maxPriorityFeePerGas", [])).result;
expect(result).to.be.eq("0x0");
});
});