Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion execution_chain/utils/debug.nim
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ proc `$`(acl: transactions.AccessList): string =
result.add " * " & $ap.address & "\n"
for i, k in ap.storageKeys:
result.add " - " & k.toHex
if i < ap.storageKeys.len-1:
if i < ap.storageKeys.len - 1:
result.add "\n"

proc debug*(tx: Transaction): string =
Expand Down
197 changes: 197 additions & 0 deletions nimbus_verified_proxy/c_frontend.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# nimbus_verified_proxy
# Copyright (c) 2025 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: [], gcsafe.}

import
stint,
std/strutils,
json_rpc/[rpcserver, rpcproxy],
web3/[eth_api, eth_api_types],
../execution_chain/rpc/cors,
./engine/types,
./nimbus_verified_proxy_conf

type JsonRpcServer* = ref object
case kind*: ClientKind #we reuse clientKind for servers also
of Http:
httpServer: RpcHttpServer
of WebSocket:
wsServer: RpcWebSocketServer

proc init*(
T: type JsonRpcServer, url: Web3Url
): JsonRpcServer {.raises: [JsonRpcError, ValueError, TransportAddressError].} =
let
auth = @[httpCors(@[])] # TODO: for now we serve all cross origin requests
parsedUrl = parseUri(url.web3Url)
hostname = if parsedUrl.hostname == "": "127.0.0.1" else: parsedUrl.hostname
port =
if parsedUrl.port == "":
8545
else:
parseInt(parsedUrl.port)
listenAddress = initTAddress(hostname, port)

case url.kind
of HttpUrl:
JsonRpcServer(
kind: Http, httpServer: newRpcHttpServer([listenAddress], RpcRouter.init(), auth)
)
of WsUrl:
let server =
JsonRpcServer(kind: WebSocket, wsServer: newRpcWebSocketServer(listenAddress))

server.wsServer.router = RpcRouter.init()
server

func getServer(server: JsonRpcServer): RpcServer =
case server.kind
of Http: server.httpServer
of WebSocket: server.wsServer

proc start*(server: JsonRpcServer): Result[void, string] =
try:
case server.kind
of Http:
server.httpServer.start()
of WebSocket:
server.wsServer.start()
except CatchableError as e:
return err(e.msg)

ok()

proc injectEngineFrontend*(server: JsonRpcServer, frontend: EthApiFrontend) =
server.getServer().rpc("eth_blockNumber") do() -> uint64:
await frontend.eth_blockNumber()

server.getServer().rpc("eth_getBalance") do(
address: Address, quantityTag: BlockTag
) -> UInt256:
await frontend.eth_getBalance(address, quantityTag)

server.getServer().rpc("eth_getStorageAt") do(
address: Address, slot: UInt256, quantityTag: BlockTag
) -> FixedBytes[32]:
await frontend.eth_getStorageAt(address, slot, quantityTag)

server.getServer().rpc("eth_getTransactionCount") do(
address: Address, quantityTag: BlockTag
) -> Quantity:
await frontend.eth_getTransactionCount(address, quantityTag)

server.getServer().rpc("eth_getCode") do(
address: Address, quantityTag: BlockTag
) -> seq[byte]:
await frontend.eth_getCode(address, quantityTag)

server.getServer().rpc("eth_getBlockByHash") do(
blockHash: Hash32, fullTransactions: bool
) -> BlockObject:
await frontend.eth_getBlockByHash(blockHash, fullTransactions)

server.getServer().rpc("eth_getBlockByNumber") do(
blockTag: BlockTag, fullTransactions: bool
) -> BlockObject:
await frontend.eth_getBlockByNumber(blockTag, fullTransactions)

server.getServer().rpc("eth_getUncleCountByBlockNumber") do(
blockTag: BlockTag
) -> Quantity:
await frontend.eth_getUncleCountByBlockNumber(blockTag)

server.getServer().rpc("eth_getUncleCountByBlockHash") do(
blockHash: Hash32
) -> Quantity:
await frontend.eth_getUncleCountByBlockHash(blockHash)

server.getServer().rpc("eth_getBlockTransactionCountByNumber") do(
blockTag: BlockTag
) -> Quantity:
await frontend.eth_getBlockTransactionCountByNumber(blockTag)

server.getServer().rpc("eth_getBlockTransactionCountByHash") do(
blockHash: Hash32
) -> Quantity:
await frontend.eth_getBlockTransactionCountByHash(blockHash)

server.getServer().rpc("eth_getTransactionByBlockNumberAndIndex") do(
blockTag: BlockTag, index: Quantity
) -> TransactionObject:
await frontend.eth_getTransactionByBlockNumberAndIndex(blockTag, index)

server.getServer().rpc("eth_getTransactionByBlockHashAndIndex") do(
blockHash: Hash32, index: Quantity
) -> TransactionObject:
await frontend.eth_getTransactionByBlockHashAndIndex(blockHash, index)

server.getServer().rpc("eth_call") do(
tx: TransactionArgs, blockTag: BlockTag, optimisticStateFetch: Opt[bool]
) -> seq[byte]:
await frontend.eth_call(tx, blockTag, optimisticStateFetch.get(true))

server.getServer().rpc("eth_createAccessList") do(
tx: TransactionArgs, blockTag: BlockTag, optimisticStateFetch: Opt[bool]
) -> AccessListResult:
await frontend.eth_createAccessList(tx, blockTag, optimisticStateFetch.get(true))

server.getServer().rpc("eth_estimateGas") do(
tx: TransactionArgs, blockTag: BlockTag, optimisticStateFetch: Opt[bool]
) -> Quantity:
await frontend.eth_estimateGas(tx, blockTag, optimisticStateFetch.get(true))

server.getServer().rpc("eth_getTransactionByHash") do(
txHash: Hash32
) -> TransactionObject:
await frontend.eth_getTransactionByHash(txHash)

server.getServer().rpc("eth_getBlockReceipts") do(
blockTag: BlockTag
) -> Opt[seq[ReceiptObject]]:
await frontend.eth_getBlockReceipts(blockTag)

server.getServer().rpc("eth_getTransactionReceipt") do(
txHash: Hash32
) -> ReceiptObject:
await frontend.eth_getTransactionReceipt(txHash)

server.getServer().rpc("eth_getLogs") do(
filterOptions: FilterOptions
) -> seq[LogObject]:
await frontend.eth_getLogs(filterOptions)

server.getServer().rpc("eth_newFilter") do(filterOptions: FilterOptions) -> string:
await frontend.eth_newFilter(filterOptions)

server.getServer().rpc("eth_uninstallFilter") do(filterId: string) -> bool:
await frontend.eth_uninstallFilter(filterId)

server.getServer().rpc("eth_getFilterLogs") do(filterId: string) -> seq[LogObject]:
await frontend.eth_getFilterLogs(filterId)

server.getServer().rpc("eth_getFilterChanges") do(filterId: string) -> seq[LogObject]:
await frontend.eth_getFilterChanges(filterId)

server.getServer().rpc("eth_blobBaseFee") do() -> UInt256:
await frontend.eth_blobBaseFee()

server.getServer().rpc("eth_gasPrice") do() -> Quantity:
await frontend.eth_gasPrice()

server.getServer().rpc("eth_maxPriorityFeePerGas") do() -> Quantity:
await frontend.eth_maxPriorityFeePerGas()

proc stop*(server: JsonRpcServer) {.async: (raises: [CancelledError]).} =
try:
case server.kind
of Http:
await server.httpServer.closeWait()
of WebSocket:
await server.wsServer.closeWait()
except CatchableError as e:
raise newException(CancelledError, e.msg)
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import
json_rpc/[rpcserver, rpcclient],
web3/[primitives, eth_api_types, eth_api],
../../execution_chain/beacon/web3_eth_conv,
../types
./types

proc getAccountFromProof*(
stateRoot: Hash32,
Expand Down Expand Up @@ -99,14 +99,14 @@ proc getStorageFromProof*(
getStorageFromProof(account, storageProof)

proc getAccount*(
lcProxy: VerifiedRpcProxy,
engine: RpcVerificationEngine,
address: Address,
blockNumber: base.BlockNumber,
stateRoot: Root,
): Future[Result[Account, string]] {.async: (raises: []).} =
let
cacheKey = (stateRoot, address)
cachedAcc = lcProxy.accountsCache.get(cacheKey)
cachedAcc = engine.accountsCache.get(cacheKey)
if cachedAcc.isSome():
return ok(cachedAcc.get())

Expand All @@ -115,7 +115,7 @@ proc getAccount*(
let
proof =
try:
await lcProxy.rpcClient.eth_getProof(address, @[], blockId(blockNumber))
await engine.backend.eth_getProof(address, @[], blockId(blockNumber))
except CatchableError as e:
return err(e.msg)

Expand All @@ -125,18 +125,18 @@ proc getAccount*(
)

if account.isOk():
lcProxy.accountsCache.put(cacheKey, account.get())
engine.accountsCache.put(cacheKey, account.get())

return account

proc getCode*(
lcProxy: VerifiedRpcProxy,
engine: RpcVerificationEngine,
address: Address,
blockNumber: base.BlockNumber,
stateRoot: Root,
): Future[Result[seq[byte], string]] {.async.} =
): Future[Result[seq[byte], string]] {.async: (raises: []).} =
# get verified account details for the address at blockNumber
let account = (await lcProxy.getAccount(address, blockNumber, stateRoot)).valueOr:
let account = (await engine.getAccount(address, blockNumber, stateRoot)).valueOr:
return err(error)

# if the account does not have any code, return empty hex data
Expand All @@ -145,36 +145,36 @@ proc getCode*(

let
cacheKey = (stateRoot, address)
cachedCode = lcProxy.codeCache.get(cacheKey)
cachedCode = engine.codeCache.get(cacheKey)
if cachedCode.isSome():
return ok(cachedCode.get())

info "Forwarding eth_getCode", blockNumber

let code =
try:
await lcProxy.rpcClient.eth_getCode(address, blockId(blockNumber))
await engine.backend.eth_getCode(address, blockId(blockNumber))
except CatchableError as e:
return err(e.msg)

# verify the byte code. since we verified the account against
# the state root we just need to verify the code hash
if account.codeHash == keccak256(code):
lcProxy.codeCache.put(cacheKey, code)
engine.codeCache.put(cacheKey, code)
return ok(code)
else:
return err("received code doesn't match the account code hash")

proc getStorageAt*(
lcProxy: VerifiedRpcProxy,
engine: RpcVerificationEngine,
address: Address,
slot: UInt256,
blockNumber: base.BlockNumber,
stateRoot: Root,
): Future[Result[UInt256, string]] {.async: (raises: []).} =
let
cacheKey = (stateRoot, address, slot)
cachedSlotValue = lcProxy.storageCache.get(cacheKey)
cachedSlotValue = engine.storageCache.get(cacheKey)
if cachedSlotValue.isSome():
return ok(cachedSlotValue.get())

Expand All @@ -183,19 +183,19 @@ proc getStorageAt*(
let
proof =
try:
await lcProxy.rpcClient.eth_getProof(address, @[slot], blockId(blockNumber))
await engine.backend.eth_getProof(address, @[slot], blockId(blockNumber))
except CatchableError as e:
return err(e.msg)

slotValue = getStorageFromProof(stateRoot, slot, proof)

if slotValue.isOk():
lcProxy.storageCache.put(cacheKey, slotValue.get())
engine.storageCache.put(cacheKey, slotValue.get())

return slotValue

proc populateCachesForAccountAndSlots(
lcProxy: VerifiedRpcProxy,
engine: RpcVerificationEngine,
address: Address,
slots: seq[UInt256],
blockNumber: base.BlockNumber,
Expand All @@ -204,18 +204,16 @@ proc populateCachesForAccountAndSlots(
var slotsToFetch: seq[UInt256]
for s in slots:
let storageCacheKey = (stateRoot, address, s)
if lcProxy.storageCache.get(storageCacheKey).isNone():
if engine.storageCache.get(storageCacheKey).isNone():
slotsToFetch.add(s)

let accountCacheKey = (stateRoot, address)

if lcProxy.accountsCache.get(accountCacheKey).isNone() or slotsToFetch.len() > 0:
if engine.accountsCache.get(accountCacheKey).isNone() or slotsToFetch.len() > 0:
let
proof =
try:
await lcProxy.rpcClient.eth_getProof(
address, slotsToFetch, blockId(blockNumber)
)
await engine.backend.eth_getProof(address, slotsToFetch, blockId(blockNumber))
except CatchableError as e:
return err(e.msg)
account = getAccountFromProof(
Expand All @@ -224,33 +222,33 @@ proc populateCachesForAccountAndSlots(
)

if account.isOk():
lcProxy.accountsCache.put(accountCacheKey, account.get())
engine.accountsCache.put(accountCacheKey, account.get())

for i, s in slotsToFetch:
let slotValue = getStorageFromProof(stateRoot, s, proof, i)

if slotValue.isOk():
let storageCacheKey = (stateRoot, address, s)
lcProxy.storageCache.put(storageCacheKey, slotValue.get())
engine.storageCache.put(storageCacheKey, slotValue.get())

ok()

proc populateCachesUsingAccessList*(
lcProxy: VerifiedRpcProxy,
engine: RpcVerificationEngine,
blockNumber: base.BlockNumber,
stateRoot: Root,
tx: TransactionArgs,
): Future[Result[void, string]] {.async: (raises: []).} =
let accessListRes: AccessListResult =
try:
await lcProxy.rpcClient.eth_createAccessList(tx, blockId(blockNumber))
await engine.backend.eth_createAccessList(tx, blockId(blockNumber))
except CatchableError as e:
return err(e.msg)

var futs = newSeqOfCap[Future[Result[void, string]]](accessListRes.accessList.len())
for accessPair in accessListRes.accessList:
let slots = accessPair.storageKeys.mapIt(UInt256.fromBytesBE(it.data))
futs.add lcProxy.populateCachesForAccountAndSlots(
futs.add engine.populateCachesForAccountAndSlots(
accessPair.address, slots, blockNumber, stateRoot
)

Expand Down
Loading