Implement eth_feeHistory (#2130)
* Implement eth_feeHistory * Fix copyright year
This commit is contained in:
parent
dac7a2cbe1
commit
e713f3c287
|
@ -123,8 +123,9 @@ proc newEngineEnv*(conf: var NimbusConf, chainFile: string, enableAuth: bool): E
|
||||||
else:
|
else:
|
||||||
BeaconSyncRef(nil)
|
BeaconSyncRef(nil)
|
||||||
beaconEngine = BeaconEngineRef.new(txPool, chain)
|
beaconEngine = BeaconEngineRef.new(txPool, chain)
|
||||||
|
oracle = Oracle.new(com)
|
||||||
|
|
||||||
setupEthRpc(node, ctx, com, txPool, server)
|
setupEthRpc(node, ctx, com, txPool, oracle, server)
|
||||||
setupEngineAPI(beaconEngine, server)
|
setupEngineAPI(beaconEngine, server)
|
||||||
setupDebugRpc(com, txPool, server)
|
setupDebugRpc(com, txPool, server)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# Nimbus
|
# Nimbus
|
||||||
# Copyright (c) 2023 Status Research & Development GmbH
|
# Copyright (c) 2023-2024 Status Research & Development GmbH
|
||||||
# Licensed under either of
|
# Licensed under either of
|
||||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0)
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
@ -73,7 +73,8 @@ proc setupELClient*(t: TestEnv, conf: ChainConfig, node: JsonNode) =
|
||||||
t.rpcServer = newRpcHttpServer(["127.0.0.1:8545"])
|
t.rpcServer = newRpcHttpServer(["127.0.0.1:8545"])
|
||||||
|
|
||||||
let beaconEngine = BeaconEngineRef.new(txPool, t.chainRef)
|
let beaconEngine = BeaconEngineRef.new(txPool, t.chainRef)
|
||||||
setupEthRpc(t.ethNode, t.ctx, t.com, txPool, t.rpcServer)
|
let oracle = Oracle.new(t.com)
|
||||||
|
setupEthRpc(t.ethNode, t.ctx, t.com, txPool, oracle, t.rpcServer)
|
||||||
setupEngineAPI(beaconEngine, t.rpcServer)
|
setupEngineAPI(beaconEngine, t.rpcServer)
|
||||||
|
|
||||||
t.rpcServer.start()
|
t.rpcServer.start()
|
||||||
|
|
|
@ -47,8 +47,9 @@ proc setupRpcServer(ctx: EthContext, com: CommonRef,
|
||||||
ethNode: EthereumNode, txPool: TxPoolRef,
|
ethNode: EthereumNode, txPool: TxPoolRef,
|
||||||
conf: NimbusConf): RpcServer =
|
conf: NimbusConf): RpcServer =
|
||||||
let rpcServer = newRpcHttpServer([initTAddress(conf.httpAddress, conf.httpPort)])
|
let rpcServer = newRpcHttpServer([initTAddress(conf.httpAddress, conf.httpPort)])
|
||||||
|
let oracle = Oracle.new(com)
|
||||||
setupCommonRpc(ethNode, conf, rpcServer)
|
setupCommonRpc(ethNode, conf, rpcServer)
|
||||||
setupEthRpc(ethNode, ctx, com, txPool, rpcServer)
|
setupEthRpc(ethNode, ctx, com, txPool, oracle, rpcServer)
|
||||||
|
|
||||||
rpcServer.start()
|
rpcServer.start()
|
||||||
rpcServer
|
rpcServer
|
||||||
|
|
|
@ -20,6 +20,7 @@ import
|
||||||
./rpc/cors,
|
./rpc/cors,
|
||||||
./rpc/rpc_server,
|
./rpc/rpc_server,
|
||||||
./rpc/experimental,
|
./rpc/experimental,
|
||||||
|
./rpc/oracle,
|
||||||
./nimbus_desc,
|
./nimbus_desc,
|
||||||
./graphql/ethapi
|
./graphql/ethapi
|
||||||
|
|
||||||
|
@ -31,7 +32,8 @@ export
|
||||||
jwt_auth,
|
jwt_auth,
|
||||||
cors,
|
cors,
|
||||||
rpc_server,
|
rpc_server,
|
||||||
experimental
|
experimental,
|
||||||
|
oracle
|
||||||
|
|
||||||
{.push gcsafe, raises: [].}
|
{.push gcsafe, raises: [].}
|
||||||
|
|
||||||
|
@ -49,12 +51,13 @@ proc installRPC(server: RpcServer,
|
||||||
nimbus: NimbusNode,
|
nimbus: NimbusNode,
|
||||||
conf: NimbusConf,
|
conf: NimbusConf,
|
||||||
com: CommonRef,
|
com: CommonRef,
|
||||||
|
oracle: Oracle,
|
||||||
flags: set[RpcFlag]) =
|
flags: set[RpcFlag]) =
|
||||||
|
|
||||||
setupCommonRpc(nimbus.ethNode, conf, server)
|
setupCommonRpc(nimbus.ethNode, conf, server)
|
||||||
|
|
||||||
if RpcFlag.Eth in flags:
|
if RpcFlag.Eth in flags:
|
||||||
setupEthRpc(nimbus.ethNode, nimbus.ctx, com, nimbus.txPool, server)
|
setupEthRpc(nimbus.ethNode, nimbus.ctx, com, nimbus.txPool, oracle, server)
|
||||||
|
|
||||||
if RpcFlag.Debug in flags:
|
if RpcFlag.Debug in flags:
|
||||||
setupDebugRpc(com, nimbus.txPool, server)
|
setupDebugRpc(com, nimbus.txPool, server)
|
||||||
|
@ -142,7 +145,8 @@ proc addHandler(handlers: var seq[RpcHandlerProc],
|
||||||
|
|
||||||
proc addHttpServices(handlers: var seq[RpcHandlerProc],
|
proc addHttpServices(handlers: var seq[RpcHandlerProc],
|
||||||
nimbus: NimbusNode, conf: NimbusConf,
|
nimbus: NimbusNode, conf: NimbusConf,
|
||||||
com: CommonRef, protocols: set[ProtocolFlag]) =
|
com: CommonRef, oracle: Oracle,
|
||||||
|
protocols: set[ProtocolFlag]) =
|
||||||
|
|
||||||
# The order is important: graphql, ws, rpc
|
# The order is important: graphql, ws, rpc
|
||||||
# graphql depends on /graphl path
|
# graphql depends on /graphl path
|
||||||
|
@ -158,37 +162,37 @@ proc addHttpServices(handlers: var seq[RpcHandlerProc],
|
||||||
let server = newRpcWebsocketHandler()
|
let server = newRpcWebsocketHandler()
|
||||||
var rpcFlags = conf.getWsFlags()
|
var rpcFlags = conf.getWsFlags()
|
||||||
if ProtocolFlag.Eth in protocols: rpcFlags.incl RpcFlag.Eth
|
if ProtocolFlag.Eth in protocols: rpcFlags.incl RpcFlag.Eth
|
||||||
installRPC(server, nimbus, conf, com, rpcFlags)
|
installRPC(server, nimbus, conf, com, oracle, rpcFlags)
|
||||||
handlers.addHandler(server)
|
handlers.addHandler(server)
|
||||||
|
|
||||||
if conf.rpcEnabled:
|
if conf.rpcEnabled:
|
||||||
let server = newRpcHttpHandler()
|
let server = newRpcHttpHandler()
|
||||||
var rpcFlags = conf.getRpcFlags()
|
var rpcFlags = conf.getRpcFlags()
|
||||||
if ProtocolFlag.Eth in protocols: rpcFlags.incl RpcFlag.Eth
|
if ProtocolFlag.Eth in protocols: rpcFlags.incl RpcFlag.Eth
|
||||||
installRPC(server, nimbus, conf, com, rpcFlags)
|
installRPC(server, nimbus, conf, com, oracle, rpcFlags)
|
||||||
handlers.addHandler(server)
|
handlers.addHandler(server)
|
||||||
|
|
||||||
proc addEngineApiServices(handlers: var seq[RpcHandlerProc],
|
proc addEngineApiServices(handlers: var seq[RpcHandlerProc],
|
||||||
nimbus: NimbusNode, conf: NimbusConf,
|
nimbus: NimbusNode, conf: NimbusConf,
|
||||||
com: CommonRef) =
|
com: CommonRef, oracle: Oracle,) =
|
||||||
|
|
||||||
# The order is important: ws, rpc
|
# The order is important: ws, rpc
|
||||||
|
|
||||||
if conf.engineApiWsEnabled:
|
if conf.engineApiWsEnabled:
|
||||||
let server = newRpcWebsocketHandler()
|
let server = newRpcWebsocketHandler()
|
||||||
setupEngineAPI(nimbus.beaconEngine, server)
|
setupEngineAPI(nimbus.beaconEngine, server)
|
||||||
installRPC(server, nimbus, conf, com, {RpcFlag.Eth})
|
installRPC(server, nimbus, conf, com, oracle, {RpcFlag.Eth})
|
||||||
handlers.addHandler(server)
|
handlers.addHandler(server)
|
||||||
|
|
||||||
if conf.engineApiEnabled:
|
if conf.engineApiEnabled:
|
||||||
let server = newRpcHttpHandler()
|
let server = newRpcHttpHandler()
|
||||||
setupEngineAPI(nimbus.beaconEngine, server)
|
setupEngineAPI(nimbus.beaconEngine, server)
|
||||||
installRPC(server, nimbus, conf, com, {RpcFlag.Eth})
|
installRPC(server, nimbus, conf, com, oracle, {RpcFlag.Eth})
|
||||||
handlers.addHandler(server)
|
handlers.addHandler(server)
|
||||||
|
|
||||||
proc addServices(handlers: var seq[RpcHandlerProc],
|
proc addServices(handlers: var seq[RpcHandlerProc],
|
||||||
nimbus: NimbusNode, conf: NimbusConf,
|
nimbus: NimbusNode, conf: NimbusConf,
|
||||||
com: CommonRef, protocols: set[ProtocolFlag]) =
|
com: CommonRef, oracle: Oracle, protocols: set[ProtocolFlag]) =
|
||||||
|
|
||||||
# The order is important: graphql, ws, rpc
|
# The order is important: graphql, ws, rpc
|
||||||
|
|
||||||
|
@ -202,12 +206,12 @@ proc addServices(handlers: var seq[RpcHandlerProc],
|
||||||
if conf.engineApiWsEnabled:
|
if conf.engineApiWsEnabled:
|
||||||
setupEngineAPI(nimbus.beaconEngine, server)
|
setupEngineAPI(nimbus.beaconEngine, server)
|
||||||
if not conf.wsEnabled:
|
if not conf.wsEnabled:
|
||||||
installRPC(server, nimbus, conf, com, {RpcFlag.Eth})
|
installRPC(server, nimbus, conf, com, oracle, {RpcFlag.Eth})
|
||||||
|
|
||||||
if conf.wsEnabled:
|
if conf.wsEnabled:
|
||||||
var rpcFlags = conf.getWsFlags()
|
var rpcFlags = conf.getWsFlags()
|
||||||
if ProtocolFlag.Eth in protocols: rpcFlags.incl RpcFlag.Eth
|
if ProtocolFlag.Eth in protocols: rpcFlags.incl RpcFlag.Eth
|
||||||
installRPC(server, nimbus, conf, com, rpcFlags)
|
installRPC(server, nimbus, conf, com, oracle, rpcFlags)
|
||||||
handlers.addHandler(server)
|
handlers.addHandler(server)
|
||||||
|
|
||||||
if conf.rpcEnabled or conf.engineApiEnabled:
|
if conf.rpcEnabled or conf.engineApiEnabled:
|
||||||
|
@ -215,12 +219,12 @@ proc addServices(handlers: var seq[RpcHandlerProc],
|
||||||
if conf.engineApiEnabled:
|
if conf.engineApiEnabled:
|
||||||
setupEngineAPI(nimbus.beaconEngine, server)
|
setupEngineAPI(nimbus.beaconEngine, server)
|
||||||
if not conf.rpcEnabled:
|
if not conf.rpcEnabled:
|
||||||
installRPC(server, nimbus, conf, com, {RpcFlag.Eth})
|
installRPC(server, nimbus, conf, com, oracle, {RpcFlag.Eth})
|
||||||
|
|
||||||
if conf.rpcEnabled:
|
if conf.rpcEnabled:
|
||||||
var rpcFlags = conf.getRpcFlags()
|
var rpcFlags = conf.getRpcFlags()
|
||||||
if ProtocolFlag.Eth in protocols: rpcFlags.incl RpcFlag.Eth
|
if ProtocolFlag.Eth in protocols: rpcFlags.incl RpcFlag.Eth
|
||||||
installRPC(server, nimbus, conf, com, rpcFlags)
|
installRPC(server, nimbus, conf, com, oracle, rpcFlags)
|
||||||
handlers.addHandler(server)
|
handlers.addHandler(server)
|
||||||
|
|
||||||
proc setupRpc*(nimbus: NimbusNode, conf: NimbusConf,
|
proc setupRpc*(nimbus: NimbusNode, conf: NimbusConf,
|
||||||
|
@ -242,11 +246,12 @@ proc setupRpc*(nimbus: NimbusNode, conf: NimbusConf,
|
||||||
allowedOrigins = conf.getAllowedOrigins()
|
allowedOrigins = conf.getAllowedOrigins()
|
||||||
jwtAuthHook = httpJwtAuth(jwtKey)
|
jwtAuthHook = httpJwtAuth(jwtKey)
|
||||||
corsHook = httpCors(allowedOrigins)
|
corsHook = httpCors(allowedOrigins)
|
||||||
|
oracle = Oracle.new(com)
|
||||||
|
|
||||||
if conf.combinedServer:
|
if conf.combinedServer:
|
||||||
let hooks = @[jwtAuthHook, corsHook]
|
let hooks = @[jwtAuthHook, corsHook]
|
||||||
var handlers: seq[RpcHandlerProc]
|
var handlers: seq[RpcHandlerProc]
|
||||||
handlers.addServices(nimbus, conf, com, protocols)
|
handlers.addServices(nimbus, conf, com, oracle, protocols)
|
||||||
let address = initTAddress(conf.httpAddress, conf.httpPort)
|
let address = initTAddress(conf.httpAddress, conf.httpPort)
|
||||||
let res = newHttpServerWithParams(address, hooks, handlers)
|
let res = newHttpServerWithParams(address, hooks, handlers)
|
||||||
if res.isErr:
|
if res.isErr:
|
||||||
|
@ -259,7 +264,7 @@ proc setupRpc*(nimbus: NimbusNode, conf: NimbusConf,
|
||||||
if conf.httpServerEnabled:
|
if conf.httpServerEnabled:
|
||||||
let hooks = @[corsHook]
|
let hooks = @[corsHook]
|
||||||
var handlers: seq[RpcHandlerProc]
|
var handlers: seq[RpcHandlerProc]
|
||||||
handlers.addHttpServices(nimbus, conf, com, protocols)
|
handlers.addHttpServices(nimbus, conf, com, oracle, protocols)
|
||||||
let address = initTAddress(conf.httpAddress, conf.httpPort)
|
let address = initTAddress(conf.httpAddress, conf.httpPort)
|
||||||
let res = newHttpServerWithParams(address, hooks, handlers)
|
let res = newHttpServerWithParams(address, hooks, handlers)
|
||||||
if res.isErr:
|
if res.isErr:
|
||||||
|
@ -271,7 +276,7 @@ proc setupRpc*(nimbus: NimbusNode, conf: NimbusConf,
|
||||||
if conf.engineApiServerEnabled:
|
if conf.engineApiServerEnabled:
|
||||||
let hooks = @[jwtAuthHook, corsHook]
|
let hooks = @[jwtAuthHook, corsHook]
|
||||||
var handlers: seq[RpcHandlerProc]
|
var handlers: seq[RpcHandlerProc]
|
||||||
handlers.addEngineApiServices(nimbus, conf, com)
|
handlers.addEngineApiServices(nimbus, conf, com, oracle)
|
||||||
let address = initTAddress(conf.engineApiAddress, conf.engineApiPort)
|
let address = initTAddress(conf.engineApiAddress, conf.engineApiPort)
|
||||||
let res = newHttpServerWithParams(address, hooks, handlers)
|
let res = newHttpServerWithParams(address, hooks, handlers)
|
||||||
if res.isErr:
|
if res.isErr:
|
||||||
|
|
|
@ -0,0 +1,377 @@
|
||||||
|
# Nimbus
|
||||||
|
# Copyright (c) 2024 Status Research & Development GmbH
|
||||||
|
# Licensed under either of
|
||||||
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||||
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||||
|
# at your option.
|
||||||
|
# This file may not be copied, modified, or distributed except according to
|
||||||
|
# those terms.
|
||||||
|
|
||||||
|
import
|
||||||
|
std/[hashes, algorithm, strutils],
|
||||||
|
eth/eip1559,
|
||||||
|
stew/keyed_queue,
|
||||||
|
stew/endians2,
|
||||||
|
results,
|
||||||
|
../transaction,
|
||||||
|
../common/common,
|
||||||
|
../core/eip4844
|
||||||
|
|
||||||
|
from ./rpc_types import
|
||||||
|
Quantity,
|
||||||
|
BlockTag,
|
||||||
|
BlockIdentifierKind,
|
||||||
|
FeeHistoryResult,
|
||||||
|
FeeHistoryReward
|
||||||
|
|
||||||
|
from ./rpc_utils import headerFromTag
|
||||||
|
|
||||||
|
type
|
||||||
|
# ProcessedFees contains the results of a processed block.
|
||||||
|
ProcessedFees = ref object
|
||||||
|
reward : seq[UInt256]
|
||||||
|
baseFee : UInt256
|
||||||
|
blobBaseFee : UInt256
|
||||||
|
nextBaseFee : UInt256
|
||||||
|
nextBlobBaseFee : UInt256
|
||||||
|
gasUsedRatio : float64
|
||||||
|
blobGasUsedRatio: float64
|
||||||
|
|
||||||
|
# BlockContent represents a single block for processing
|
||||||
|
BlockContent = object
|
||||||
|
blockNumber: uint64
|
||||||
|
header : BlockHeader
|
||||||
|
txs : seq[Transaction]
|
||||||
|
receipts : seq[Receipt]
|
||||||
|
|
||||||
|
CacheKey = object
|
||||||
|
number: uint64
|
||||||
|
percentiles: seq[byte]
|
||||||
|
|
||||||
|
# txGasAndReward is sorted in ascending order based on reward
|
||||||
|
TxGasAndReward = object
|
||||||
|
gasUsed: uint64
|
||||||
|
reward : UInt256
|
||||||
|
|
||||||
|
BlockRange = object
|
||||||
|
pendingBlock: Opt[uint64]
|
||||||
|
lastBlock: uint64
|
||||||
|
blocks: uint64
|
||||||
|
|
||||||
|
Oracle* = ref object
|
||||||
|
com: CommonRef
|
||||||
|
maxHeaderHistory: uint64
|
||||||
|
maxBlockHistory : uint64
|
||||||
|
historyCache : KeyedQueue[CacheKey, ProcessedFees]
|
||||||
|
|
||||||
|
{.push gcsafe, raises: [].}
|
||||||
|
|
||||||
|
func new*(_: type Oracle, com: CommonRef): Oracle =
|
||||||
|
Oracle(
|
||||||
|
com: com,
|
||||||
|
maxHeaderHistory: 1024,
|
||||||
|
maxBlockHistory: 1024,
|
||||||
|
historyCache: KeyedQueue[CacheKey, ProcessedFees].init(),
|
||||||
|
)
|
||||||
|
|
||||||
|
func hash*(x: CacheKey): Hash =
|
||||||
|
var h: Hash = 0
|
||||||
|
h = h !& hash(x.number)
|
||||||
|
h = h !& hash(x.percentiles)
|
||||||
|
result = !$h
|
||||||
|
|
||||||
|
func toBytes(list: openArray[float64]): seq[byte] =
|
||||||
|
for x in list:
|
||||||
|
result.add(cast[uint64](x).toBytesLE)
|
||||||
|
|
||||||
|
func calcBaseFee(com: CommonRef, bc: BlockContent): UInt256 =
|
||||||
|
if com.isLondon((bc.blockNumber + 1).toBlockNumber):
|
||||||
|
calcEip1599BaseFee(
|
||||||
|
bc.header.gasLimit,
|
||||||
|
bc.header.gasUsed,
|
||||||
|
bc.header.baseFee)
|
||||||
|
else:
|
||||||
|
0.u256
|
||||||
|
|
||||||
|
# processBlock takes a blockFees structure with the blockNumber, the header and optionally
|
||||||
|
# the block field filled in, retrieves the block from the backend if not present yet and
|
||||||
|
# fills in the rest of the fields.
|
||||||
|
proc processBlock(oracle: Oracle, bc: BlockContent, percentiles: openArray[float64]): ProcessedFees =
|
||||||
|
result = ProcessedFees(
|
||||||
|
baseFee: bc.header.baseFee,
|
||||||
|
blobBaseFee: getBlobBaseFee(bc.header.excessBlobGas.get(0'u64)),
|
||||||
|
nextBaseFee: calcBaseFee(oracle.com, bc),
|
||||||
|
nextBlobBaseFee: getBlobBaseFee(calcExcessBlobGas(bc.header)),
|
||||||
|
gasUsedRatio: float64(bc.header.gasUsed) / float64(bc.header.gasLimit),
|
||||||
|
blobGasUsedRatio: float64(bc.header.blobGasUsed.get(0'u64)) / float64(MAX_BLOB_GAS_PER_BLOCK)
|
||||||
|
)
|
||||||
|
|
||||||
|
if percentiles.len == 0:
|
||||||
|
# rewards were not requested, return
|
||||||
|
return
|
||||||
|
|
||||||
|
if bc.receipts.len == 0 and bc.txs.len != 0:
|
||||||
|
# log.Error("receipts are missing while reward percentiles are requested")
|
||||||
|
return
|
||||||
|
|
||||||
|
result.reward = newSeq[UInt256](percentiles.len)
|
||||||
|
if bc.txs.len == 0:
|
||||||
|
# return an all zero row if there are no transactions to gather data from
|
||||||
|
return
|
||||||
|
|
||||||
|
var
|
||||||
|
sorter = newSeq[TxGasAndReward](bc.txs.len)
|
||||||
|
prevUsed = 0.GasInt
|
||||||
|
|
||||||
|
for i, tx in bc.txs:
|
||||||
|
let
|
||||||
|
reward = tx.effectiveGasTip(bc.header.fee)
|
||||||
|
gasUsed = bc.receipts[i].cumulativeGasUsed - prevUsed
|
||||||
|
sorter[i] = TxGasAndReward(
|
||||||
|
gasUsed: gasUsed.uint64,
|
||||||
|
reward: reward.u256
|
||||||
|
)
|
||||||
|
prevUsed = bc.receipts[i].cumulativeGasUsed
|
||||||
|
|
||||||
|
|
||||||
|
sorter.sort(proc(a, b: TxGasAndReward): int =
|
||||||
|
if a.reward >= b.reward: 1
|
||||||
|
else: -1
|
||||||
|
)
|
||||||
|
|
||||||
|
var
|
||||||
|
txIndex: int
|
||||||
|
sumGasUsed = sorter[0].gasUsed
|
||||||
|
|
||||||
|
for i, p in percentiles:
|
||||||
|
let thresholdGasUsed = uint64(float64(bc.header.gasUsed) * p / 100.0'f64)
|
||||||
|
while sumGasUsed < thresholdGasUsed and txIndex < bc.txs.len-1:
|
||||||
|
inc txIndex
|
||||||
|
sumGasUsed += sorter[txIndex].gasUsed
|
||||||
|
|
||||||
|
result.reward[i] = sorter[txIndex].reward
|
||||||
|
|
||||||
|
# resolveBlockRange resolves the specified block range to absolute block numbers while also
|
||||||
|
# enforcing backend specific limitations. The pending block and corresponding receipts are
|
||||||
|
# also returned if requested and available.
|
||||||
|
# Note: an error is only returned if retrieving the head header has failed. If there are no
|
||||||
|
# retrievable blocks in the specified range then zero block count is returned with no error.
|
||||||
|
proc resolveBlockRange(oracle: Oracle, blockId: BlockTag, numBlocks: uint64): Result[BlockRange, string] =
|
||||||
|
# Get the chain's current head.
|
||||||
|
let
|
||||||
|
headBlock = try:
|
||||||
|
oracle.com.db.getCanonicalHead()
|
||||||
|
except CatchableError as exc:
|
||||||
|
return err(exc.msg)
|
||||||
|
head = headBlock.blockNumber.truncate(uint64)
|
||||||
|
|
||||||
|
var
|
||||||
|
reqEnd: uint64
|
||||||
|
blocks = numBlocks
|
||||||
|
pendingBlock: Opt[uint64]
|
||||||
|
|
||||||
|
if blockId.kind == bidNumber:
|
||||||
|
reqEnd = blockId.number.uint64
|
||||||
|
# Fail if request block is beyond the chain's current head.
|
||||||
|
if head < reqEnd:
|
||||||
|
return err("RequestBeyondHead: requested " & $reqEnd & ", head " & $head)
|
||||||
|
else:
|
||||||
|
# Resolve block tag.
|
||||||
|
let tag = blockId.alias.toLowerAscii
|
||||||
|
var resolved: BlockHeader
|
||||||
|
if tag == "pending":
|
||||||
|
try:
|
||||||
|
resolved = headerFromTag(oracle.com.db, blockId)
|
||||||
|
pendingBlock = Opt.some(resolved.blockNumber.truncate(uint64))
|
||||||
|
except CatchableError:
|
||||||
|
# Pending block not supported by backend, process only until latest block.
|
||||||
|
resolved = headBlock
|
||||||
|
# Update total blocks to return to account for this.
|
||||||
|
dec blocks
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
resolved = headerFromTag(oracle.com.db, blockId)
|
||||||
|
except CatchableError as exc:
|
||||||
|
return err(exc.msg)
|
||||||
|
|
||||||
|
# Absolute number resolved.
|
||||||
|
reqEnd = resolved.blockNumber.truncate(uint64)
|
||||||
|
|
||||||
|
# If there are no blocks to return, short circuit.
|
||||||
|
if blocks == 0:
|
||||||
|
return ok(BlockRange())
|
||||||
|
|
||||||
|
# Ensure not trying to retrieve before genesis.
|
||||||
|
if reqEnd+1 < blocks:
|
||||||
|
blocks = reqEnd + 1
|
||||||
|
|
||||||
|
ok(BlockRange(
|
||||||
|
pendingBlock:pendingBlock,
|
||||||
|
lastBlock: reqEnd,
|
||||||
|
blocks: blocks,
|
||||||
|
))
|
||||||
|
|
||||||
|
proc getBlockContent(oracle: Oracle,
|
||||||
|
blockNumber: uint64,
|
||||||
|
blockTag: uint64,
|
||||||
|
fullBlock: bool): Result[BlockContent, string] =
|
||||||
|
var bc = BlockContent(
|
||||||
|
blockNumber: blockNumber
|
||||||
|
)
|
||||||
|
|
||||||
|
let db = oracle.com.db
|
||||||
|
try:
|
||||||
|
bc.header = db.getBlockHeader(blockNumber.toblockNumber)
|
||||||
|
for tx in db.getBlockTransactions(bc.header):
|
||||||
|
bc.txs.add tx
|
||||||
|
|
||||||
|
for rc in db.getReceipts(bc.header.receiptRoot):
|
||||||
|
bc.receipts.add rc
|
||||||
|
|
||||||
|
return ok(bc)
|
||||||
|
except RlpError as exc:
|
||||||
|
return err(exc.msg)
|
||||||
|
except BlockNotFound as exc:
|
||||||
|
return err(exc.msg)
|
||||||
|
|
||||||
|
type
|
||||||
|
OracleResult = object
|
||||||
|
reward : seq[seq[UInt256]]
|
||||||
|
baseFee : seq[UInt256]
|
||||||
|
blobBaseFee : seq[UInt256]
|
||||||
|
gasUsedRatio : seq[float64]
|
||||||
|
blobGasUsedRatio: seq[float64]
|
||||||
|
firstMissing : int
|
||||||
|
|
||||||
|
func init(_: type OracleResult, blocks: int): OracleResult =
|
||||||
|
OracleResult(
|
||||||
|
reward : newSeq[seq[UInt256]](blocks),
|
||||||
|
baseFee : newSeq[UInt256](blocks+1),
|
||||||
|
blobBaseFee : newSeq[UInt256](blocks+1),
|
||||||
|
gasUsedRatio : newSeq[float64](blocks),
|
||||||
|
blobGasUsedRatio: newSeq[float64](blocks),
|
||||||
|
firstMissing : blocks,
|
||||||
|
)
|
||||||
|
|
||||||
|
proc addToResult(res: var OracleResult, i: int, fees: ProcessedFees) =
|
||||||
|
if fees.isNil:
|
||||||
|
# getting no block and no error means we are requesting into the future
|
||||||
|
# (might happen because of a reorg)
|
||||||
|
if i < res.firstMissing:
|
||||||
|
res.firstMissing = i
|
||||||
|
else:
|
||||||
|
res.reward[i] = fees.reward
|
||||||
|
res.baseFee[i] = fees.baseFee
|
||||||
|
res.baseFee[i+1] = fees.nextBaseFee
|
||||||
|
res.gasUsedRatio[i] = fees.gasUsedRatio
|
||||||
|
res.blobBaseFee[i] = fees.blobBaseFee
|
||||||
|
res.blobBaseFee[i+1] = fees.nextBlobBaseFee
|
||||||
|
res.blobGasUsedRatio[i] = fees.blobGasUsedRatio
|
||||||
|
|
||||||
|
|
||||||
|
# FeeHistory returns data relevant for fee estimation based on the specified range of blocks.
|
||||||
|
# The range can be specified either with absolute block numbers or ending with the latest
|
||||||
|
# or pending block. Backends may or may not support gathering data from the pending block
|
||||||
|
# or blocks older than a certain age (specified in maxHistory). The first block of the
|
||||||
|
# actually processed range is returned to avoid ambiguity when parts of the requested range
|
||||||
|
# are not available or when the head has changed during processing this request.
|
||||||
|
# Three arrays are returned based on the processed blocks:
|
||||||
|
# - reward: the requested percentiles of effective priority fees per gas of transactions in each
|
||||||
|
# block, sorted in ascending order and weighted by gas used.
|
||||||
|
# - baseFee: base fee per gas in the given block
|
||||||
|
# - gasUsedRatio: gasUsed/gasLimit in the given block
|
||||||
|
#
|
||||||
|
# Note: baseFee includes the next block after the newest of the returned range, because this
|
||||||
|
# value can be derived from the newest block.
|
||||||
|
proc feeHistory*(oracle: Oracle,
|
||||||
|
blocks: uint64,
|
||||||
|
unresolvedLastBlock: BlockTag,
|
||||||
|
rewardPercentiles: openArray[float64]): Result[FeeHistoryResult, string] =
|
||||||
|
|
||||||
|
var blocks = blocks
|
||||||
|
if blocks < 1:
|
||||||
|
# returning with no data and no error means there are no retrievable blocks
|
||||||
|
return
|
||||||
|
|
||||||
|
let maxFeeHistory = if rewardPercentiles.len == 0:
|
||||||
|
oracle.maxHeaderHistory
|
||||||
|
else:
|
||||||
|
oracle.maxBlockHistory
|
||||||
|
|
||||||
|
if blocks > maxFeeHistory:
|
||||||
|
# log.Warn("Sanitizing fee history length", "requested", blocks, "truncated", maxFeeHistory)
|
||||||
|
blocks = maxFeeHistory
|
||||||
|
|
||||||
|
for i, p in rewardPercentiles:
|
||||||
|
if p < 0.0 or p > 100.0:
|
||||||
|
return err("Invalid percentile: " & $p)
|
||||||
|
|
||||||
|
if i > 0 and p <= rewardPercentiles[i-1]:
|
||||||
|
return err("Invalid percentile: #" & $(i-1) &
|
||||||
|
":" & $rewardPercentiles[i-1] & " >= #" & $i & ":" & $p)
|
||||||
|
|
||||||
|
let br = oracle.resolveBlockRange(unresolvedLastBlock, blocks).valueOr:
|
||||||
|
return err(error)
|
||||||
|
|
||||||
|
let
|
||||||
|
oldestBlock = br.lastBlock + 1 - br.blocks
|
||||||
|
percentileKey = rewardPercentiles.toBytes
|
||||||
|
fullBlock = rewardPercentiles.len != 0
|
||||||
|
|
||||||
|
var
|
||||||
|
next = oldestBlock
|
||||||
|
res = OracleResult.init(br.blocks.int)
|
||||||
|
|
||||||
|
for i in 0..<blocks:
|
||||||
|
# Retrieve the next block number to fetch
|
||||||
|
let blockNumber = next
|
||||||
|
inc next
|
||||||
|
if blockNumber > br.lastBlock:
|
||||||
|
break
|
||||||
|
|
||||||
|
if br.pendingBlock.isSome and blockNumber >= br.pendingBlock.get:
|
||||||
|
let
|
||||||
|
bc = oracle.getBlockContent(blockNumber, br.pendingBlock.get, fullBlock).valueOr:
|
||||||
|
return err(error)
|
||||||
|
fees = oracle.processBlock(bc, rewardPercentiles)
|
||||||
|
res.addToResult((blockNumber - oldestBlock).int, fees)
|
||||||
|
else:
|
||||||
|
let
|
||||||
|
cacheKey = CacheKey(number: blockNumber, percentiles: percentileKey)
|
||||||
|
fr = oracle.historyCache.lruFetch(cacheKey)
|
||||||
|
|
||||||
|
if fr.isOk:
|
||||||
|
res.addToResult((blockNumber - oldestBlock).int, fr.get)
|
||||||
|
else:
|
||||||
|
let bc = oracle.getBlockContent(blockNumber, blockNumber, fullBlock).valueOr:
|
||||||
|
return err(error)
|
||||||
|
let fees = oracle.processBlock(bc, rewardPercentiles)
|
||||||
|
discard oracle.historyCache.lruAppend(cacheKey, fees, 2048)
|
||||||
|
# send to results even if empty to guarantee that blocks items are sent in total
|
||||||
|
res.addToResult((blockNumber - oldestBlock).int, fees)
|
||||||
|
|
||||||
|
if res.firstMissing == 0:
|
||||||
|
return ok(FeeHistoryResult())
|
||||||
|
|
||||||
|
var historyResult: FeeHistoryResult
|
||||||
|
|
||||||
|
if rewardPercentiles.len != 0:
|
||||||
|
res.reward.setLen(res.firstMissing)
|
||||||
|
historyResult.reward = some(system.move res.reward)
|
||||||
|
else:
|
||||||
|
historyResult.reward = none(seq[FeeHistoryReward])
|
||||||
|
|
||||||
|
res.baseFee.setLen(res.firstMissing+1)
|
||||||
|
res.gasUsedRatio.setLen(res.firstMissing)
|
||||||
|
res.blobBaseFee.setLen(res.firstMissing+1)
|
||||||
|
res.blobGasUsedRatio.setLen(res.firstMissing)
|
||||||
|
|
||||||
|
historyResult.oldestBlock = Quantity oldestBlock
|
||||||
|
historyResult.baseFeePerGas = system.move(res.baseFee)
|
||||||
|
historyResult.baseFeePerBlobGas = system.move(res.blobBaseFee)
|
||||||
|
historyResult.gasUsedRatio = system.move(res.gasUsedRatio)
|
||||||
|
historyResult.blobGasUsedRatio = system.move(res.blobGasUsedRatio)
|
||||||
|
|
||||||
|
ok(historyResult)
|
||||||
|
|
||||||
|
{.pop.}
|
|
@ -17,7 +17,7 @@ import
|
||||||
eth/[keys, rlp, p2p],
|
eth/[keys, rlp, p2p],
|
||||||
".."/[transaction, vm_state, constants],
|
".."/[transaction, vm_state, constants],
|
||||||
../db/state_db,
|
../db/state_db,
|
||||||
rpc_types, rpc_utils,
|
./rpc_types, ./rpc_utils, ./oracle,
|
||||||
../transaction/call_evm,
|
../transaction/call_evm,
|
||||||
../core/tx_pool,
|
../core/tx_pool,
|
||||||
../core/eip4844,
|
../core/eip4844,
|
||||||
|
@ -66,7 +66,7 @@ proc getProof*(
|
||||||
|
|
||||||
proc setupEthRpc*(
|
proc setupEthRpc*(
|
||||||
node: EthereumNode, ctx: EthContext, com: CommonRef,
|
node: EthereumNode, ctx: EthContext, com: CommonRef,
|
||||||
txPool: TxPoolRef, server: RpcServer) =
|
txPool: TxPoolRef, oracle: Oracle, server: RpcServer) =
|
||||||
|
|
||||||
let chainDB = com.db
|
let chainDB = com.db
|
||||||
proc getStateDB(header: BlockHeader): ReadOnlyStateDB =
|
proc getStateDB(header: BlockHeader): ReadOnlyStateDB =
|
||||||
|
@ -603,6 +603,17 @@ proc setupEthRpc*(
|
||||||
raise newException(ValueError, "blobBaseFee is bigger than uint64.max")
|
raise newException(ValueError, "blobBaseFee is bigger than uint64.max")
|
||||||
return w3Qty blobBaseFee.truncate(uint64)
|
return w3Qty blobBaseFee.truncate(uint64)
|
||||||
|
|
||||||
|
server.rpc("eth_feeHistory") do(blockCount: Quantity,
|
||||||
|
newestBlock: BlockTag,
|
||||||
|
rewardPercentiles: Option[seq[float64]]) -> FeeHistoryResult:
|
||||||
|
let
|
||||||
|
blocks = blockCount.uint64
|
||||||
|
percentiles = rewardPercentiles.get(newSeq[float64]())
|
||||||
|
res = feeHistory(oracle, blocks, newestBlock, percentiles)
|
||||||
|
if res.isErr:
|
||||||
|
raise newException(ValueError, res.error)
|
||||||
|
return res.get
|
||||||
|
|
||||||
#[
|
#[
|
||||||
server.rpc("eth_newFilter") do(filterOptions: FilterOptions) -> int:
|
server.rpc("eth_newFilter") do(filterOptions: FilterOptions) -> int:
|
||||||
## Creates a filter object, based on filter options, to notify when the state changes (logs).
|
## Creates a filter object, based on filter options, to notify when the state changes (logs).
|
||||||
|
|
|
@ -83,8 +83,9 @@ proc runTest(steps: Steps) =
|
||||||
txPool, EnginePostMerge
|
txPool, EnginePostMerge
|
||||||
)
|
)
|
||||||
beaconEngine = BeaconEngineRef.new(txPool, chainRef)
|
beaconEngine = BeaconEngineRef.new(txPool, chainRef)
|
||||||
|
oracle = Oracle.new(com)
|
||||||
|
|
||||||
setupEthRpc(ethNode, ctx, com, txPool, rpcServer)
|
setupEthRpc(ethNode, ctx, com, txPool, oracle, rpcServer)
|
||||||
setupEngineAPI(beaconEngine, rpcServer)
|
setupEngineAPI(beaconEngine, rpcServer)
|
||||||
|
|
||||||
sealingEngine.start()
|
sealingEngine.start()
|
||||||
|
|
|
@ -241,9 +241,10 @@ proc rpcMain*() =
|
||||||
rpcServer = newRpcSocketServer(["127.0.0.1:" & $RPC_PORT])
|
rpcServer = newRpcSocketServer(["127.0.0.1:" & $RPC_PORT])
|
||||||
client = newRpcSocketClient()
|
client = newRpcSocketClient()
|
||||||
txPool = TxPoolRef.new(com, conf.engineSigner)
|
txPool = TxPoolRef.new(com, conf.engineSigner)
|
||||||
|
oracle = Oracle.new(com)
|
||||||
|
|
||||||
setupCommonRpc(ethNode, conf, rpcServer)
|
setupCommonRpc(ethNode, conf, rpcServer)
|
||||||
setupEthRpc(ethNode, ctx, com, txPool, rpcServer)
|
setupEthRpc(ethNode, ctx, com, txPool, oracle, rpcServer)
|
||||||
|
|
||||||
# Begin tests
|
# Begin tests
|
||||||
rpcServer.start()
|
rpcServer.start()
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit a31bc63448d8259255a00c130af68d6e558c60f5
|
Subproject commit 9620fee53f630efcc4b6a2c0cd9f22bfcb376928
|
Loading…
Reference in New Issue