mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-13 13:55:45 +00:00
d8a1adacaa
* Part of EIP-4895: add withdrawals processing to block processing.
* Refactoring: extracted the engine API handler bodies into procs.
Intending to implement the V2 versions next. (I need the bodies to be
in separate procs so that multiple versions can use them.)
* Working on Engine API changes for Shanghai.
* Updated nim-web3, resolved ambiguity in Hash256 type.
* Updated nim-eth3 to point to master, now that I've merged that.
* I'm confused about what's going on with engine_client.
But let's try resolving this Hash256 ambiguity.
* Still trying to fix this conflict with the Hash256 types.
* Does this work now that nimbus-eth2 has been updated?
* Corrected blockValue in getPayload responses back to UInt256.
c834f67a37
* Working on getting the withdrawals-related tests to pass.
* Fixing more of those Hash256 ambiguities.
(I'm not sure why the nim-web3 library introduced a conflicting type
named Hash256, but right now I just want to get this code to compile again.)
* Bumped a couple of libraries to fix some error messages.
* Needed to get "make fluffy-tools" to pass, too.
* Getting "make nimbus_verified_proxy" to build.
344 lines
10 KiB
Nim
344 lines
10 KiB
Nim
# nimbus_verified_proxy
|
|
# Copyright (c) 2022-2023 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/strutils,
|
|
stint,
|
|
stew/[byteutils, results],
|
|
chronicles,
|
|
json_rpc/[rpcproxy, rpcserver, rpcclient],
|
|
eth/common/eth_types as etypes,
|
|
web3,
|
|
web3/[ethhexstrings, ethtypes],
|
|
beacon_chain/eth1/eth1_monitor,
|
|
beacon_chain/networking/network_metadata,
|
|
beacon_chain/spec/forks,
|
|
./rpc_utils,
|
|
../validate_proof,
|
|
../block_cache
|
|
|
|
export forks
|
|
|
|
logScope:
|
|
topics = "verified_proxy"
|
|
|
|
proc `==`(x, y: Quantity): bool {.borrow, noSideEffect.}
|
|
|
|
template encodeQuantity(value: UInt256): HexQuantityStr =
|
|
hexQuantityStr("0x" & value.toHex())
|
|
|
|
template encodeHexData(value: UInt256): HexDataStr =
|
|
hexDataStr("0x" & toBytesBE(value).toHex)
|
|
|
|
template bytesToHex(bytes: seq[byte]): HexDataStr =
|
|
hexDataStr("0x" & toHex(bytes))
|
|
|
|
template encodeQuantity(value: Quantity): HexQuantityStr =
|
|
hexQuantityStr(encodeQuantity(value.uint64))
|
|
|
|
type
|
|
VerifiedRpcProxy* = ref object
|
|
proxy: RpcProxy
|
|
blockCache: BlockCache
|
|
chainId: Quantity
|
|
|
|
QuantityTagKind = enum
|
|
LatestBlock, BlockNumber
|
|
|
|
QuantityTag = object
|
|
case kind: QuantityTagKind
|
|
of LatestBlock:
|
|
discard
|
|
of BlockNumber:
|
|
blockNumber: Quantity
|
|
|
|
func parseHexIntResult(tag: string): Result[uint64, string] =
|
|
try:
|
|
ok(parseHexInt(tag).uint64)
|
|
except ValueError as e:
|
|
err(e.msg)
|
|
|
|
func parseHexQuantity(tag: string): Result[Quantity, string] =
|
|
let hexQuantity = hexQuantityStr(tag)
|
|
if validate(hexQuantity):
|
|
let parsed = ? parseHexIntResult(tag)
|
|
return ok(Quantity(parsed))
|
|
else:
|
|
return err("Invalid hex quantity.")
|
|
|
|
func parseQuantityTag(blockTag: string): Result[QuantityTag, string] =
|
|
let tag = blockTag.toLowerAscii
|
|
case tag
|
|
of "latest":
|
|
return ok(QuantityTag(kind: LatestBlock))
|
|
else:
|
|
let quantity = ? parseHexQuantity(tag)
|
|
return ok(QuantityTag(kind: BlockNumber, blockNumber: quantity))
|
|
|
|
template checkPreconditions(proxy: VerifiedRpcProxy) =
|
|
if proxy.blockCache.isEmpty():
|
|
raise newException(ValueError, "Syncing")
|
|
|
|
template rpcClient(lcProxy: VerifiedRpcProxy): RpcClient =
|
|
lcProxy.proxy.getClient()
|
|
|
|
proc getPayloadByTag(
|
|
proxy: VerifiedRpcProxy,
|
|
quantityTag: string):
|
|
results.Opt[ExecutionData] {.raises: [ValueError].} =
|
|
checkPreconditions(proxy)
|
|
|
|
let tagResult = parseQuantityTag(quantityTag)
|
|
|
|
if tagResult.isErr:
|
|
raise newException(ValueError, tagResult.error)
|
|
|
|
let tag = tagResult.get()
|
|
|
|
case tag.kind
|
|
of LatestBlock:
|
|
# this will always return some block, as we always checkPreconditions
|
|
return proxy.blockCache.latest
|
|
of BlockNumber:
|
|
return proxy.blockCache.getByNumber(tag.blockNumber)
|
|
|
|
proc getPayloadByTagOrThrow(
|
|
proxy: VerifiedRpcProxy,
|
|
quantityTag: string): ExecutionData {.raises: [ValueError].} =
|
|
|
|
let tagResult = getPayloadByTag(proxy, quantityTag)
|
|
|
|
if tagResult.isErr:
|
|
raise newException(ValueError, "No block stored for given tag " & quantityTag)
|
|
|
|
return tagResult.get()
|
|
|
|
proc installEthApiHandlers*(lcProxy: VerifiedRpcProxy) =
|
|
lcProxy.proxy.rpc("eth_chainId") do() -> HexQuantityStr:
|
|
return encodeQuantity(lcProxy.chainId)
|
|
|
|
lcProxy.proxy.rpc("eth_blockNumber") do() -> HexQuantityStr:
|
|
## Returns the number of the most recent block.
|
|
checkPreconditions(lcProxy)
|
|
|
|
return encodeQuantity(lcProxy.blockCache.latest.get.blockNumber)
|
|
|
|
lcProxy.proxy.rpc("eth_getBalance") do(
|
|
address: Address, quantityTag: string) -> HexQuantityStr:
|
|
# When requesting state for `latest` block number, we need to translate
|
|
# `latest` to actual block number as `latest` on proxy and on data provider
|
|
# can mean different blocks and ultimatly piece received piece of state
|
|
# must by validated against correct state root
|
|
let
|
|
executionPayload = lcProxy.getPayloadByTagOrThrow(quantityTag)
|
|
blockNumber = executionPayload.blockNumber.uint64
|
|
|
|
info "Forwarding eth_getBalance call", blockNumber
|
|
|
|
let proof = await lcProxy.rpcClient.eth_getProof(
|
|
address, @[], blockId(blockNumber))
|
|
|
|
let accountResult = getAccountFromProof(
|
|
executionPayload.stateRoot,
|
|
proof.address,
|
|
proof.balance,
|
|
proof.nonce,
|
|
proof.codeHash,
|
|
proof.storageHash,
|
|
proof.accountProof
|
|
)
|
|
|
|
if accountResult.isOk():
|
|
return encodeQuantity(accountResult.get.balance)
|
|
else:
|
|
raise newException(ValueError, accountResult.error)
|
|
|
|
lcProxy.proxy.rpc("eth_getStorageAt") do(
|
|
address: Address, slot: HexDataStr, quantityTag: string) -> HexDataStr:
|
|
let
|
|
executionPayload = lcProxy.getPayloadByTagOrThrow(quantityTag)
|
|
uslot = UInt256.fromHex(slot.string)
|
|
blockNumber = executionPayload.blockNumber.uint64
|
|
|
|
info "Forwarding eth_getStorageAt", blockNumber
|
|
|
|
let proof = await lcProxy.rpcClient.eth_getProof(
|
|
address, @[uslot], blockId(blockNumber))
|
|
|
|
let dataResult = getStorageData(executionPayload.stateRoot, uslot, proof)
|
|
|
|
if dataResult.isOk():
|
|
let slotValue = dataResult.get()
|
|
return encodeHexData(slotValue)
|
|
else:
|
|
raise newException(ValueError, dataResult.error)
|
|
|
|
lcProxy.proxy.rpc("eth_getTransactionCount") do(
|
|
address: Address, quantityTag: string) -> HexQuantityStr:
|
|
let
|
|
executionPayload = lcProxy.getPayloadByTagOrThrow(quantityTag)
|
|
blockNumber = executionPayload.blockNumber.uint64
|
|
|
|
info "Forwarding eth_getTransactionCount", blockNumber
|
|
|
|
let proof = await lcProxy.rpcClient.eth_getProof(
|
|
address, @[], blockId(blockNumber))
|
|
|
|
let accountResult = getAccountFromProof(
|
|
executionPayload.stateRoot,
|
|
proof.address,
|
|
proof.balance,
|
|
proof.nonce,
|
|
proof.codeHash,
|
|
proof.storageHash,
|
|
proof.accountProof
|
|
)
|
|
|
|
if accountResult.isOk():
|
|
return hexQuantityStr(encodeQuantity(accountResult.get.nonce))
|
|
else:
|
|
raise newException(ValueError, accountResult.error)
|
|
|
|
lcProxy.proxy.rpc("eth_getCode") do(
|
|
address: Address, quantityTag: string) -> HexDataStr:
|
|
let
|
|
executionPayload = lcProxy.getPayloadByTagOrThrow(quantityTag)
|
|
blockNumber = executionPayload.blockNumber.uint64
|
|
|
|
let
|
|
proof = await lcProxy.rpcClient.eth_getProof(
|
|
address, @[], blockId(blockNumber))
|
|
accountResult = getAccountFromProof(
|
|
executionPayload.stateRoot,
|
|
proof.address,
|
|
proof.balance,
|
|
proof.nonce,
|
|
proof.codeHash,
|
|
proof.storageHash,
|
|
proof.accountProof
|
|
)
|
|
|
|
if accountResult.isErr():
|
|
raise newException(ValueError, accountResult.error)
|
|
|
|
let account = accountResult.get()
|
|
|
|
if account.codeHash == etypes.EMPTY_CODE_HASH:
|
|
# account does not have any code, return empty hex data
|
|
return hexDataStr("0x")
|
|
|
|
info "Forwarding eth_getCode", blockNumber
|
|
|
|
let code = await lcProxy.rpcClient.eth_getCode(
|
|
address,
|
|
blockId(blockNumber)
|
|
)
|
|
|
|
if isValidCode(account, code):
|
|
return bytesToHex(code)
|
|
else:
|
|
raise newException(ValueError,
|
|
"Received code which does not match the account code hash")
|
|
|
|
# TODO:
|
|
# Following methods are forwarded directly to the web3 provider and therefore
|
|
# are not validated in any way.
|
|
lcProxy.proxy.registerProxyMethod("net_version")
|
|
lcProxy.proxy.registerProxyMethod("eth_call")
|
|
lcProxy.proxy.registerProxyMethod("eth_sendRawTransaction")
|
|
lcProxy.proxy.registerProxyMethod("eth_getTransactionReceipt")
|
|
|
|
# TODO currently we do not handle fullTransactions flag. It require updates on
|
|
# nim-web3 side
|
|
lcProxy.proxy.rpc("eth_getBlockByNumber") do(
|
|
quantityTag: string, fullTransactions: bool) -> Option[BlockObject]:
|
|
let executionPayload = lcProxy.getPayloadByTag(quantityTag)
|
|
|
|
if executionPayload.isErr:
|
|
return none(BlockObject)
|
|
|
|
return some(asBlockObject(executionPayload.get()))
|
|
|
|
lcProxy.proxy.rpc("eth_getBlockByHash") do(
|
|
blockHash: BlockHash, fullTransactions: bool) -> Option[BlockObject]:
|
|
let executionPayload = lcProxy.blockCache.getPayloadByHash(blockHash)
|
|
|
|
if executionPayload.isErr:
|
|
return none(BlockObject)
|
|
|
|
return some(asBlockObject(executionPayload.get()))
|
|
|
|
proc new*(
|
|
T: type VerifiedRpcProxy,
|
|
proxy: RpcProxy,
|
|
blockCache: BlockCache,
|
|
chainId: Quantity): T =
|
|
VerifiedRpcProxy(
|
|
proxy: proxy,
|
|
blockCache: blockCache,
|
|
chainId: chainId)
|
|
|
|
# Used to be in eth1_monitor.nim; not sure why it was deleted,
|
|
# so I copied it here. --Adam
|
|
template awaitWithRetries*[T](lazyFutExpr: Future[T],
|
|
retries = 3,
|
|
timeout = 60.seconds): untyped =
|
|
const
|
|
reqType = astToStr(lazyFutExpr)
|
|
var
|
|
retryDelayMs = 16000
|
|
f: Future[T]
|
|
attempts = 0
|
|
|
|
while true:
|
|
f = lazyFutExpr
|
|
yield f or sleepAsync(timeout)
|
|
if not f.finished:
|
|
await cancelAndWait(f)
|
|
elif f.failed:
|
|
when not (f.error of CatchableError):
|
|
static: doAssert false, "f.error not CatchableError"
|
|
debug "Web3 request failed", req = reqType, err = f.error.msg
|
|
else:
|
|
break
|
|
|
|
inc attempts
|
|
if attempts >= retries:
|
|
var errorMsg = reqType & " failed " & $retries & " times"
|
|
if f.failed: errorMsg &= ". Last error: " & f.error.msg
|
|
raise newException(DataProviderFailure, errorMsg)
|
|
|
|
await sleepAsync(chronos.milliseconds(retryDelayMs))
|
|
retryDelayMs *= 2
|
|
|
|
read(f)
|
|
|
|
proc verifyChaindId*(p: VerifiedRpcProxy): Future[void] {.async.} =
|
|
let localId = p.chainId
|
|
|
|
# retry 2 times, if the data provider fails despite the re-tries, propagate
|
|
# exception to the caller.
|
|
let providerId = awaitWithRetries(
|
|
p.rpcClient.eth_chainId(),
|
|
retries = 2,
|
|
timeout = seconds(30)
|
|
)
|
|
|
|
# This is a chain/network mismatch error between the Nimbus verified proxy and
|
|
# the application using it. Fail fast to avoid misusage. The user must fix
|
|
# the configuration.
|
|
if localId != providerId:
|
|
fatal "The specified data provider serves data for a different chain",
|
|
expectedChain = distinctBase(localId),
|
|
providerChain = distinctBase(providerId)
|
|
quit 1
|
|
|
|
return
|
|
|