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

refactor: re-implement eth_feeHistory #3288

Merged
merged 21 commits into from
Jun 26, 2023
Merged

refactor: re-implement eth_feeHistory #3288

merged 21 commits into from
Jun 26, 2023

Conversation

onbjerg
Copy link
Member

@onbjerg onbjerg commented Jun 20, 2023

The existing implementation of eth_feeHistory had some issues:

  1. It did not limit the request size
  2. It did not correctly calculate percentiles, because it was comparing header.gas_used to the percentile threshold, but it should be comparing a cumulative gas usage of transactions to the percentile threshold
  3. It did not include the base fee of the next block (even if it doesn't exist) as per the spec
  4. It always responded with at least reward: [], but if the request does not include percentiles, then it should not respond with this at all
  5. It always loaded and sorted transactions, even if there were no percentiles (so it would be unnecessary)
  6. It supported block hashes when it should only support numbers or tags

I also opted to remove the fee history cache, since it adds additional overhead, and it is hard to cache this endpoint, as the response can change significantly based on the reward percentiles.

Perhaps we can use some sort of basic cache for the headers only, or the headers and a pre-sorted list of transactions, but I don't think it would really give us much benefit.

Also closes #3328

@onbjerg onbjerg added C-bug An unexpected or incorrect behavior A-rpc Related to the RPC implementation labels Jun 20, 2023
@onbjerg
Copy link
Member Author

onbjerg commented Jun 20, 2023

I am going to test this on a node and compare the results to Erigon to get a sense of if the implementation is flawed or not

@codecov
Copy link

codecov bot commented Jun 20, 2023

Codecov Report

Merging #3288 (9f1db8a) into main (49922bf) will decrease coverage by 0.15%.
The diff coverage is 61.38%.

@@            Coverage Diff             @@
##             main    #3288      +/-   ##
==========================================
- Coverage   69.41%   69.27%   -0.15%     
==========================================
  Files         537      537              
  Lines       71991    72041      +50     
==========================================
- Hits        49973    49906      -67     
- Misses      22018    22135     +117     
Flag Coverage Δ
integration-tests 16.33% <2.47%> (-0.01%) ⬇️
unit-tests 64.34% <61.38%> (-0.14%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
crates/primitives/src/transaction/mod.rs 85.23% <0.00%> (-1.57%) ⬇️
crates/rpc/rpc-api/src/eth.rs 100.00% <ø> (ø)
crates/rpc/rpc/src/eth/api/mod.rs 87.09% <ø> (-0.31%) ⬇️
crates/rpc/rpc/src/eth/error.rs 7.50% <0.00%> (ø)
crates/rpc/rpc/src/eth/api/fees.rs 48.41% <33.68%> (-38.55%) ⬇️
crates/rpc/rpc/src/eth/api/server.rs 95.41% <87.12%> (-2.87%) ⬇️
crates/rpc/rpc-types/src/eth/fee.rs 7.14% <100.00%> (-50.76%) ⬇️
crates/rpc/rpc/src/eth/gas_oracle.rs 39.59% <100.00%> (+1.24%) ⬆️

... and 14 files with indirect coverage changes

@onbjerg onbjerg marked this pull request as draft June 20, 2023 22:57
@onbjerg onbjerg force-pushed the onbjerg/eth-fee-history branch 2 times, most recently from 714c97b to f661913 Compare June 21, 2023 01:05
@onbjerg onbjerg added the M-changelog This change should be included in the changelog label Jun 21, 2023
Comment on lines 121 to 128
self.inner.provider.receipts_by_block(header.number.into())? else {
// If there are no receipts, then we do not have all info on the block
return Err(EthApiError::InvalidBlockRange)
};
let Some(mut transactions): Option<Vec<_>> = self
.inner
.provider
.transactions_by_block(header.number.into())?
Copy link
Collaborator

Choose a reason for hiding this comment

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

these should make use of the async caching layer I think

Copy link
Member Author

Choose a reason for hiding this comment

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

how do I do that?

@onbjerg
Copy link
Member Author

onbjerg commented Jun 21, 2023

TL;DR: It matches except in the case of block count of 0, but this case is not defined in the spec..

Comparison between Erigon's impl and Reth's impl. Each example uses the same percentiles (0, 10, 25, 50, 100).

Block 17m, block count of 1

Erigon:

{
   "jsonrpc":"2.0",
   "id":1,
   "result":{
      "oldestBlock":"0x1036640",
      "reward":[
         [
            "0x0",
            "0x5f5e100",
            "0x5f5e100",
            "0xb376620",
            "0x26df9ef958"
         ]
      ],
      "baseFeePerGas":[
         "0x4cad3abe1",
         "0x48f2114b8"
      ],
      "gasUsedRatio":[
         0.3053592666666667
      ]
   }
}

Reth:

{
   "jsonrpc":"2.0",
   "result":{
      "baseFeePerGas":[
         "0x4cad3abe1",
         "0x48f2114b8"
      ],
      "gasUsedRatio":[
         0.3053592666666667
      ],
      "oldestBlock":"0x1036640",
      "reward":[
         [
            "0x0",
            "0x5f5e100",
            "0x5f5e100",
            "0xb376620",
            "0x26df9ef958"
         ]
      ]
   },
   "id":1
}

Block 17m, block count of 0

Erigon:

{
   "jsonrpc":"2.0",
   "id":1,
   "result":{
      "oldestBlock":"0x0",
      "gasUsedRatio":null
   }
}

Reth:

{
   "jsonrpc":"2.0",
   "result":{
      "baseFeePerGas":[
         
      ],
      "gasUsedRatio":[
         
      ],
      "oldestBlock":"0x0",
      "reward":null
   },
   "id":1
}

Doesn't match, but are logically similar I think

Block 17m, block count of 10

Erigon:

{
   "jsonrpc":"2.0",
   "id":1,
   "result":{
      "oldestBlock":"0x1036637",
      "reward":[
         [
            "0x0",
            "0x59a5380",
            "0x5f5e100",
            "0x11e1a300",
            "0xcb7c3cc58"
         ],
         [
            "0x0",
            "0x1f8dead",
            "0x1f8dead",
            "0x4a75410",
            "0x2cd3a56ad"
         ],
         [
            "0x0",
            "0x5f5e100",
            "0x121738e4",
            "0x77359400",
            "0x527935f3e"
         ],
         [
            "0x0",
            "0x5f5e100",
            "0x5f5e100",
            "0x5f5e100",
            "0x8680c0212"
         ],
         [
            "0x0",
            "0x5f5e100",
            "0x11e1a300",
            "0x3b9aca00",
            "0x2b6e7d7f4"
         ],
         [
            "0x5f5e100",
            "0xa1fa7e6",
            "0x2d8dfc0c",
            "0x2d8dfc0c",
            "0x4cc25aef1"
         ],
         [
            "0x0",
            "0x5f5e100",
            "0x5f5e100",
            "0x5f5e100",
            "0x42a860c240"
         ],
         [
            "0x0",
            "0x5f5e100",
            "0x5f5e100",
            "0x5f5e100",
            "0xdb597ddd3"
         ],
         [
            "0x0",
            "0x485ce38",
            "0x59a5380",
            "0xb376620",
            "0x419b5992a"
         ],
         [
            "0x0",
            "0x5f5e100",
            "0x5f5e100",
            "0xb376620",
            "0x26df9ef958"
         ]
      ],
      "baseFeePerGas":[
         "0x4475fcda8",
         "0x42ee95553",
         "0x4b4ae5842",
         "0x44d989922",
         "0x45200f7f8",
         "0x4035437f4",
         "0x4839a89b2",
         "0x4705ccab0",
         "0x4f68b16aa",
         "0x4cad3abe1",
         "0x48f2114b8"
      ],
      "gasUsedRatio":[
         0.4106688,
         0.9996475666666667,
         0.1577187,
         0.5160047666666666,
         0.2154628,
         0.9994493,
         0.43339863333333334,
         0.9723170666666666,
         0.3623692,
         0.3053592666666667
      ]
   }
}

Reth:

{
   "jsonrpc":"2.0",
   "result":{
      "baseFeePerGas":[
         "0x4475fcda8",
         "0x42ee95553",
         "0x4b4ae5842",
         "0x44d989922",
         "0x45200f7f8",
         "0x4035437f4",
         "0x4839a89b2",
         "0x4705ccab0",
         "0x4f68b16aa",
         "0x4cad3abe1",
         "0x48f2114b8"
      ],
      "gasUsedRatio":[
         0.4106688,
         0.9996475666666667,
         0.1577187,
         0.5160047666666666,
         0.2154628,
         0.9994493,
         0.43339863333333334,
         0.9723170666666666,
         0.3623692,
         0.3053592666666667
      ],
      "oldestBlock":"0x1036637",
      "reward":[
         [
            "0x0",
            "0x59a5380",
            "0x5f5e100",
            "0x11e1a300",
            "0xcb7c3cc58"
         ],
         [
            "0x0",
            "0x1f8dead",
            "0x1f8dead",
            "0x4a75410",
            "0x2cd3a56ad"
         ],
         [
            "0x0",
            "0x5f5e100",
            "0x121738e4",
            "0x77359400",
            "0x527935f3e"
         ],
         [
            "0x0",
            "0x5f5e100",
            "0x5f5e100",
            "0x5f5e100",
            "0x8680c0212"
         ],
         [
            "0x0",
            "0x5f5e100",
            "0x11e1a300",
            "0x3b9aca00",
            "0x2b6e7d7f4"
         ],
         [
            "0x5f5e100",
            "0xa1fa7e6",
            "0x2d8dfc0c",
            "0x2d8dfc0c",
            "0x4cc25aef1"
         ],
         [
            "0x0",
            "0x5f5e100",
            "0x5f5e100",
            "0x5f5e100",
            "0x42a860c240"
         ],
         [
            "0x0",
            "0x5f5e100",
            "0x5f5e100",
            "0x5f5e100",
            "0xdb597ddd3"
         ],
         [
            "0x0",
            "0x485ce38",
            "0x59a5380",
            "0xb376620",
            "0x419b5992a"
         ],
         [
            "0x0",
            "0x5f5e100",
            "0x5f5e100",
            "0xb376620",
            "0x26df9ef958"
         ]
      ]
   },
   "id":1
}

Pre-EIP 1559 (block 4 with a block count of 4, i.e. genesis..=4)

Erigon:

{
   "jsonrpc":"2.0",
   "id":1,
   "result":{
      "oldestBlock":"0x1",
      "reward":[
         [
            "0x0",
            "0x0",
            "0x0",
            "0x0",
            "0x0"
         ],
         [
            "0x0",
            "0x0",
            "0x0",
            "0x0",
            "0x0"
         ],
         [
            "0x0",
            "0x0",
            "0x0",
            "0x0",
            "0x0"
         ],
         [
            "0x0",
            "0x0",
            "0x0",
            "0x0",
            "0x0"
         ]
      ],
      "baseFeePerGas":[
         "0x0",
         "0x0",
         "0x0",
         "0x0",
         "0x0"
      ],
      "gasUsedRatio":[
         0,
         0,
         0,
         0
      ]
   }
}

Reth:

{
   "jsonrpc":"2.0",
   "result":{
      "baseFeePerGas":[
         "0x0",
         "0x0",
         "0x0",
         "0x0",
         "0x0"
      ],
      "gasUsedRatio":[
         0.0,
         0.0,
         0.0,
         0.0
      ],
      "oldestBlock":"0x1",
      "reward":[
         [
            "0x0",
            "0x0",
            "0x0",
            "0x0",
            "0x0"
         ],
         [
            "0x0",
            "0x0",
            "0x0",
            "0x0",
            "0x0"
         ],
         [
            "0x0",
            "0x0",
            "0x0",
            "0x0",
            "0x0"
         ],
         [
            "0x0",
            "0x0",
            "0x0",
            "0x0",
            "0x0"
         ]
      ]
   },
   "id":1
}

@onbjerg onbjerg marked this pull request as ready for review June 21, 2023 23:05
@mattsse
Copy link
Collaborator

mattsse commented Jun 22, 2023

reth:

"reward":null

looks like erigon/geth don't return this field if null?

but return empty

"gasUsedRatio":[
         
      ],

as null

should we mimic this as well?

Copy link
Collaborator

@mattsse mattsse left a comment

Choose a reason for hiding this comment

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

nice!

left a few suggestions to asyncify this

crates/rpc/rpc/src/eth/api/fees.rs Outdated Show resolved Hide resolved
crates/rpc/rpc/src/eth/api/fees.rs Outdated Show resolved Hide resolved
crates/rpc/rpc/src/eth/api/fees.rs Outdated Show resolved Hide resolved
crates/rpc/rpc/src/eth/api/fees.rs Outdated Show resolved Hide resolved
crates/rpc/rpc/src/eth/api/fees.rs Outdated Show resolved Hide resolved
crates/rpc/rpc/src/eth/api/fees.rs Outdated Show resolved Hide resolved
crates/rpc/rpc/src/eth/api/fees.rs Show resolved Hide resolved
@onbjerg
Copy link
Member Author

onbjerg commented Jun 26, 2023

@mattsse Now uses the cache and sealed headers

@onbjerg
Copy link
Member Author

onbjerg commented Jun 26, 2023

reth:

"reward":null

looks like erigon/geth don't return this field if null?

but return empty

"gasUsedRatio":[
         
      ],

as null

should we mimic this as well?

It's annoying that it responds like that, but I implemented 1:1 compat in af48ee1

Copy link
Collaborator

@mattsse mattsse left a comment

Choose a reason for hiding this comment

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

lgtm,
pending clippy

suggestions re Option<Vec

crates/rpc/rpc-types/src/eth/fee.rs Outdated Show resolved Hide resolved
crates/rpc/rpc-types/src/eth/fee.rs Outdated Show resolved Hide resolved
Comment on lines 123 to 124
self.cache().get_receipts(header.hash.into()).await? else {
// If there are no receipts, then we do not have all info on the block
return Err(EthApiError::InvalidBlockRange)
};
let Some(mut transactions): Option<Vec<_>> = self
.cache()
.get_block_transactions(header.hash).await?
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is fine,
I'll add a new helper function that fetches both concurrently.

@onbjerg
Copy link
Member Author

onbjerg commented Jun 26, 2023

@mattsse I removed the 1024 limit, it seems I was mistaken and this is not actually part of the spec.

@mattsse
Copy link
Collaborator

mattsse commented Jun 26, 2023

@mattsse I removed the 1024 limit, it seems I was mistaken and this is not actually part of the spec.

we definitely need to cap it,

geth caps it to 1024:

https://github.com/ethereum/go-ethereum/blob/2754b197c935ee63101cbbca2752338246384fec/eth/ethconfig/config.go#L38-L46

@onbjerg
Copy link
Member Author

onbjerg commented Jun 26, 2023

This is the full node config, i.e. it is capped because it will not have data for more than 1024 blocks at head afaik. Unsure what the best approach is to cap it

Edit: Ah, nvm, I see, this is separate from the regular full node config, instead it is a GPO config and these are just the defaults for full node. Do we have some sort of config mechanism for the RPC where we could add this (with a default of 1024)?

@onbjerg
Copy link
Member Author

onbjerg commented Jun 26, 2023

@mattsse used GPO config to cap the length

@mattsse
Copy link
Collaborator

mattsse commented Jun 26, 2023

Do we have some sort of config mechanism for the RPC where we could add this (with a default of 1024)?

we have in in this:

/// Settings for the [GasPriceOracle]
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GasPriceOracleConfig {
/// The number of populated blocks to produce the gas price estimate
pub blocks: u32,
/// The percentile of gas prices to use for the estimate
pub percentile: u32,
/// The maximum number of headers to keep in the cache
pub max_header_history: u64,

which we should be able to query in eth_feeHistory via .gas_oracle()

/// The async gas oracle frontend for gas price suggestions
gas_oracle: GasPriceOracle<Provider>,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-rpc Related to the RPC implementation C-bug An unexpected or incorrect behavior M-changelog This change should be included in the changelog
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support hexOrDecimal on eth_feeHistory
2 participants