reth icon indicating copy to clipboard operation
reth copied to clipboard

refactor: re-implement `eth_feeHistory`

Open onbjerg opened this issue 2 years ago • 4 comments

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.

onbjerg avatar Jun 20 '23 22:06 onbjerg

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

onbjerg avatar Jun 20 '23 22:06 onbjerg

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%) :arrow_down:
unit-tests 64.34% <61.38%> (-0.14%) :arrow_down:

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%) :arrow_down:
crates/rpc/rpc-api/src/eth.rs 100.00% <ø> (ø)
crates/rpc/rpc/src/eth/api/mod.rs 87.09% <ø> (-0.31%) :arrow_down:
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%) :arrow_down:
crates/rpc/rpc/src/eth/api/server.rs 95.41% <87.12%> (-2.87%) :arrow_down:
crates/rpc/rpc-types/src/eth/fee.rs 7.14% <100.00%> (-50.76%) :arrow_down:
crates/rpc/rpc/src/eth/gas_oracle.rs 39.59% <100.00%> (+1.24%) :arrow_up:

... and 14 files with indirect coverage changes

codecov[bot] avatar Jun 20 '23 22:06 codecov[bot]

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 avatar Jun 21 '23 22:06 onbjerg

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?

mattsse avatar Jun 22 '23 02:06 mattsse

@mattsse Now uses the cache and sealed headers

onbjerg avatar Jun 26 '23 12:06 onbjerg

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

onbjerg avatar Jun 26 '23 12:06 onbjerg

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

onbjerg avatar Jun 26 '23 13:06 onbjerg

@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

mattsse avatar Jun 26 '23 15:06 mattsse

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 avatar Jun 26 '23 15:06 onbjerg

it's always capped, otherwise you can DOS the endpoint

https://github.com/ethereum/go-ethereum/blob/2754b197c935ee63101cbbca2752338246384fec/eth/gasprice/feehistory.go#L222-L225

it's configurable in geth via config though

https://github.com/ethereum/go-ethereum/blob/2754b197c935ee63101cbbca2752338246384fec/eth/gasprice/gasprice.go#L42-L50

mattsse avatar Jun 26 '23 15:06 mattsse

@mattsse used GPO config to cap the length

onbjerg avatar Jun 26 '23 15:06 onbjerg

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:

https://github.com/paradigmxyz/reth/blob/49922bfd897a16b3750db4c20aa0f40b073d6f05/crates/rpc/rpc/src/eth/gas_oracle.rs#L22-L33

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

https://github.com/paradigmxyz/reth/blob/49922bfd897a16b3750db4c20aa0f40b073d6f05/crates/rpc/rpc/src/eth/api/mod.rs#L287-L288

mattsse avatar Jun 26 '23 15:06 mattsse