Skip to content

Commit

Permalink
Add support for eth_maxPriorityFeeperGas (polkadot-evm#607)
Browse files Browse the repository at this point in the history
* Add `max_priority_fee_per_gas` rpc method

* Remove unused arg

* Add ts tests

* Simple transfer instead contract create

* editorconfig
  • Loading branch information
tgmichel committed Mar 17, 2022
1 parent b9f2f33 commit 807830e
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 3 deletions.
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");
});
});

0 comments on commit 807830e

Please sign in to comment.