nimbus-eth1/fluffy/rpc/rpc_eth_api.nim
Kim De Mey dbe3393f5c
Fix eth/common & web3 related deprecation warnings for fluffy (#2698)
* Fix eth/common & web3 related deprecation warnings for fluffy

This commit uses the new types in the new eth/common/ structure
to remove deprecation warnings.

It is however more than just a mass replace as also all places
where eth/common or eth/common/eth_types or eth/common/eth_types_rlp
got imported have been revised and adjusted to a better per submodule
based import.

There are still a bunch of toMDigest deprecation warnings but that
convertor is not needed for fluffy code anymore so in theory it
should not be used (bug?). It seems to still get imported via export
leaks ffrom imported nimbus code I think.

* Address review comments

* Remove two more unused eth/common imports
2024-10-04 23:21:26 +02:00

415 lines
16 KiB
Nim

# Fluffy
# Copyright (c) 2021-2024 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
{.push raises: [].}
import
std/sequtils,
json_rpc/rpcserver,
chronicles,
web3/[eth_api_types, conversions],
eth/common/transaction_utils,
beacon_chain/spec/forks,
../network/history/[history_network, history_content],
../network/state/[state_network, state_content, state_endpoints],
../network/beacon/beacon_light_client,
../version
from ../../nimbus/errors import ValidationError
from ../../nimbus/rpc/filters import headerBloomFilter, deriveLogs, filterLogs
from ../../nimbus/beacon/web3_eth_conv import w3Addr, w3Hash, ethHash
from eth/common/eth_types_rlp import rlpHash
export rpcserver
# See the list of Ethereum execution JSON-RPC APIs which will be supported by
# Portal Network clients such as Fluffy:
# https://github.com/ethereum/portal-network-specs?tab=readme-ov-file#the-json-rpc-api
func init*(
T: type TransactionObject,
tx: transactions.Transaction,
header: Header,
txIndex: int,
): T {.raises: [ValidationError].} =
let sender = tx.recoverSender().valueOr:
raise (ref ValidationError)(msg: "Invalid tx signature")
TransactionObject(
blockHash: Opt.some(w3Hash header.rlpHash),
blockNumber: Opt.some(Quantity(header.number)),
`from`: sender,
gas: Quantity(tx.gasLimit),
gasPrice: Quantity(tx.gasPrice),
hash: w3Hash tx.rlpHash,
input: tx.payload,
nonce: Quantity(tx.nonce),
to: Opt.some(w3Addr tx.destination),
transactionIndex: Opt.some(Quantity(txIndex)),
value: tx.value,
v: Quantity(tx.V),
r: tx.R,
s: tx.S,
`type`: Opt.some(Quantity(tx.txType)),
maxFeePerGas: Opt.some(Quantity(tx.maxFeePerGas)),
maxPriorityFeePerGas: Opt.some(Quantity(tx.maxPriorityFeePerGas)),
)
# Note: Similar as `populateBlockObject` from rpc_utils, but lacking the
# total difficulty
func init*(
T: type BlockObject, header: Header, body: BlockBody, fullTx = true, isUncle = false
): T {.raises: [ValidationError].} =
let blockHash = header.rlpHash
var blockObject = BlockObject(
number: Quantity(header.number),
hash: w3Hash blockHash,
parentHash: w3Hash header.parentHash,
nonce: Opt.some(FixedBytes[8](header.nonce)),
sha3Uncles: w3Hash header.ommersHash,
logsBloom: FixedBytes[256] header.logsBloom,
transactionsRoot: w3Hash header.txRoot,
stateRoot: w3Hash header.stateRoot,
receiptsRoot: w3Hash header.receiptsRoot,
miner: w3Addr header.coinbase,
difficulty: header.difficulty,
extraData: HistoricExtraData header.extraData,
# TODO: This is optional according to
# https://playground.open-rpc.org/?schemaUrl=https://raw.githubusercontent.com/ethereum/eth1.0-apis/assembled-spec/openrpc.json
# So we should probably change `BlockObject`.
totalDifficulty: UInt256.low(),
gasLimit: Quantity(header.gasLimit),
gasUsed: Quantity(header.gasUsed),
timestamp: Quantity(header.timestamp),
)
# TODO: This was copied from `rpc_utils`, but the block size calculation does
# not make sense. TO FIX.
let size = sizeof(Header) - sizeof(seq[byte]) + header.extraData.len
blockObject.size = Quantity(size.uint)
if not isUncle:
blockObject.uncles = body.uncles.map(
proc(h: Header): Hash32 =
w3Hash h.rlpHash
)
if fullTx:
var i = 0
for tx in body.transactions:
blockObject.transactions.add txOrHash(TransactionObject.init(tx, header, i))
inc i
else:
for tx in body.transactions:
blockObject.transactions.add txOrHash(w3Hash rlpHash(tx))
blockObject
template getOrRaise(historyNetwork: Opt[HistoryNetwork]): HistoryNetwork =
let hn = historyNetwork.valueOr:
raise newException(ValueError, "history sub-network not enabled")
hn
template getOrRaise(beaconLightClient: Opt[LightClient]): LightClient =
let sn = beaconLightClient.valueOr:
raise newException(ValueError, "beacon sub-network not enabled")
sn
template getOrRaise(stateNetwork: Opt[StateNetwork]): StateNetwork =
let sn = stateNetwork.valueOr:
raise newException(ValueError, "state sub-network not enabled")
sn
proc installEthApiHandlers*(
rpcServer: RpcServer,
historyNetwork: Opt[HistoryNetwork],
beaconLightClient: Opt[LightClient],
stateNetwork: Opt[StateNetwork],
) =
rpcServer.rpc("web3_clientVersion") do() -> string:
return clientVersion
rpcServer.rpc("eth_chainId") do() -> Quantity:
# The Portal Network can only support MainNet at the moment, so always return
# 1
return Quantity(uint64(1))
rpcServer.rpc("eth_getBlockByHash") do(
blockHash: Hash32, fullTransactions: bool
) -> Opt[BlockObject]:
## Returns information about a block by hash.
##
## data: Hash of a block.
## fullTransactions: If true it returns the full transaction objects, if
## false only the hashes of the transactions.
##
## Returns BlockObject or nil when no block was found.
let
hn = historyNetwork.getOrRaise()
(header, body) = (await hn.getBlock(blockHash)).valueOr:
return Opt.none(BlockObject)
return Opt.some(BlockObject.init(header, body, fullTransactions))
rpcServer.rpc("eth_getBlockByNumber") do(
quantityTag: RtBlockIdentifier, fullTransactions: bool
) -> Opt[BlockObject]:
let hn = historyNetwork.getOrRaise()
if quantityTag.kind == bidAlias:
let tag = quantityTag.alias.toLowerAscii
case tag
of "latest":
# TODO:
# I assume this would refer to the content in the latest optimistic update
# in case the majority treshold is not met. And if it is met it is the
# same as the safe version?
raise newException(ValueError, "Latest tag not yet implemented")
of "earliest":
raise newException(ValueError, "Earliest tag not yet implemented")
of "safe":
let blc = beaconLightClient.getOrRaise()
withForkyStore(blc.store[]):
when lcDataFork > LightClientDataFork.Altair:
let
blockHash = forkyStore.optimistic_header.execution.block_hash.to(Hash32)
(header, body) = (await hn.getBlock(blockHash)).valueOr:
return Opt.none(BlockObject)
return Opt.some(BlockObject.init(header, body, fullTransactions))
else:
raise newException(ValueError, "Not available before Capella - not synced?")
of "finalized":
let blc = beaconLightClient.getOrRaise()
withForkyStore(blc.store[]):
when lcDataFork > LightClientDataFork.Altair:
let
blockHash = forkyStore.finalized_header.execution.block_hash.to(Hash32)
(header, body) = (await hn.getBlock(blockHash)).valueOr:
return Opt.none(BlockObject)
return Opt.some(BlockObject.init(header, body, fullTransactions))
else:
raise newException(ValueError, "Not available before Capella - not synced?")
of "pending":
raise newException(ValueError, "Pending tag not yet implemented")
else:
raise newException(ValueError, "Unsupported block tag " & tag)
else:
let
blockNumber = quantityTag.number.uint64
(header, body) = (await hn.getBlock(blockNumber)).valueOr:
return Opt.none(BlockObject)
return Opt.some(BlockObject.init(header, body, fullTransactions))
rpcServer.rpc("eth_getBlockTransactionCountByHash") do(blockHash: Hash32) -> Quantity:
## Returns the number of transactions in a block from a block matching the
## given block hash.
##
## data: hash of a block
## Returns integer of the number of transactions in this block.
let
hn = historyNetwork.getOrRaise()
(_, body) = (await hn.getBlock(blockHash)).valueOr:
raise newException(ValueError, "Could not find block with requested hash")
var txCount: uint = 0
for tx in body.transactions:
txCount.inc()
return Quantity(txCount)
# Note: can't implement this yet as the fluffy node doesn't know the relation
# of tx hash -> block number -> block hash, in order to get the receipt
# from from the block with that block hash. The Canonical Indices Network
# would need to be implemented to get this information.
# rpcServer.rpc("eth_getTransactionReceipt") do(
# data: EthHashStr) -> Opt[ReceiptObject]:
rpcServer.rpc("eth_getLogs") do(filterOptions: FilterOptions) -> seq[LogObject]:
if filterOptions.blockHash.isNone():
# Currently only queries by blockhash are supported.
# TODO: Can impolement range queries by block number now.
raise newException(
ValueError,
"Unsupported query: Only `blockHash` queries are currently supported",
)
let
hn = historyNetwork.getOrRaise()
hash = ethHash filterOptions.blockHash.unsafeGet()
header = (await hn.getVerifiedBlockHeader(hash)).valueOr:
raise newException(ValueError, "Could not find header with requested hash")
if headerBloomFilter(header, filterOptions.address, filterOptions.topics):
# TODO: These queries could be done concurrently, investigate if there
# are no assumptions about usage of concurrent queries on portal
# wire protocol level
let
body = (await hn.getBlockBody(hash, header)).valueOr:
raise newException(ValueError, "Could not find block body for requested hash")
receipts = (await hn.getReceipts(hash, header)).valueOr:
raise newException(ValueError, "Could not find receipts for requested hash")
logs = deriveLogs(header, body.transactions, receipts)
filteredLogs = filterLogs(logs, filterOptions.address, filterOptions.topics)
return filteredLogs
else:
# bloomfilter returned false, there are no logs matching the criteria
return @[]
rpcServer.rpc("eth_getBalance") do(
data: Address, quantityTag: RtBlockIdentifier
) -> UInt256:
## Returns the balance of the account of given address.
##
## data: address to check for balance.
## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter.
## Returns integer of the current balance in wei.
if quantityTag.kind == bidAlias:
# TODO: Implement
raise newException(ValueError, "tag not yet implemented")
# This endpoint requires history network to be enabled in order to look up
# the state root by block number in the call to getBalance
discard historyNetwork.getOrRaise()
let
sn = stateNetwork.getOrRaise()
blockNumber = quantityTag.number.uint64
balance = (await sn.getBalance(blockNumber, data.EthAddress)).valueOr:
raise newException(ValueError, "Unable to get balance")
return balance
rpcServer.rpc("eth_getTransactionCount") do(
data: Address, quantityTag: RtBlockIdentifier
) -> Quantity:
## Returns the number of transactions sent from an address.
##
## data: address.
## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter.
## Returns integer of the number of transactions send from this address.
if quantityTag.kind == bidAlias:
# TODO: Implement
raise newException(ValueError, "tag not yet implemented")
# This endpoint requires history network to be enabled in order to look up
# the state root by block number in the call to getTransactionCount
discard historyNetwork.getOrRaise()
let
sn = stateNetwork.getOrRaise()
blockNumber = quantityTag.number.uint64
nonce = (await sn.getTransactionCount(blockNumber, data.EthAddress)).valueOr:
raise newException(ValueError, "Unable to get transaction count")
return nonce.Quantity
rpcServer.rpc("eth_getStorageAt") do(
data: Address, slot: UInt256, quantityTag: RtBlockIdentifier
) -> FixedBytes[32]:
## Returns the value from a storage position at a given address.
##
## data: address of the storage.
## slot: integer of the position in the storage.
## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter.
## Returns: the value at this storage position.
if quantityTag.kind == bidAlias:
# TODO: Implement
raise newException(ValueError, "tag not yet implemented")
# This endpoint requires history network to be enabled in order to look up
# the state root by block number in the call to getStorageAt
discard historyNetwork.getOrRaise()
let
sn = stateNetwork.getOrRaise()
blockNumber = quantityTag.number.uint64
slotValue = (await sn.getStorageAt(blockNumber, data.EthAddress, slot)).valueOr:
raise newException(ValueError, "Unable to get storage slot")
return FixedBytes[32](slotValue.toBytesBE())
rpcServer.rpc("eth_getCode") do(
data: Address, quantityTag: RtBlockIdentifier
) -> seq[byte]:
## Returns code at a given address.
##
## data: address
## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter.
## Returns the code from the given address.
if quantityTag.kind == bidAlias:
# TODO: Implement
raise newException(ValueError, "tag not yet implemented")
# This endpoint requires history network to be enabled in order to look up
# the state root by block number in the call to getCode
discard historyNetwork.getOrRaise()
let
sn = stateNetwork.getOrRaise()
blockNumber = quantityTag.number.uint64
bytecode = (await sn.getCode(blockNumber, data.EthAddress)).valueOr:
raise newException(ValueError, "Unable to get code")
return bytecode.asSeq()
rpcServer.rpc("eth_getProof") do(
data: Address, slots: seq[UInt256], quantityTag: RtBlockIdentifier
) -> ProofResponse:
## Returns information about an account and storage slots along with account
## and storage proofs which prove the existence of the values in the state.
## See spec here: https://eips.ethereum.org/EIPS/eip-1186
##
## data: address of the account.
## slots: integers of the positions in the storage to return.
## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter.
## Returns: the proof response containing the account, account proof and storage proof
if quantityTag.kind == bidAlias:
# TODO: Implement
raise newException(ValueError, "tag not yet implemented")
# This endpoint requires history network to be enabled in order to look up
# the state root by block number in the call to getProof
discard historyNetwork.getOrRaise()
let
sn = stateNetwork.getOrRaise()
blockNumber = quantityTag.number.uint64
proofs = (await sn.getProofs(blockNumber, data.EthAddress, slots)).valueOr:
raise newException(ValueError, "Unable to get proofs")
var storageProof = newSeqOfCap[StorageProof](slots.len)
for i, slot in slots:
let (slotKey, slotValue) = proofs.slots[i]
storageProof.add(
StorageProof(
key: slotKey,
value: slotValue,
proof: seq[RlpEncodedBytes](proofs.slotProofs[i]),
)
)
return ProofResponse(
address: data,
accountProof: seq[RlpEncodedBytes](proofs.accountProof),
balance: proofs.account.balance,
nonce: Quantity(proofs.account.nonce),
codeHash: proofs.account.codeHash,
storageHash: proofs.account.storageRoot,
storageProof: storageProof,
)