2022-09-06 16:14:50 +00:00
|
|
|
# ligh client proxy
|
2022-08-26 11:54:10 +00:00
|
|
|
# Copyright (c) 2022 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: [Defect].}
|
|
|
|
|
|
|
|
import
|
2022-08-31 05:48:48 +00:00
|
|
|
stint,
|
2022-09-09 13:59:36 +00:00
|
|
|
stew/byteutils,
|
2022-08-31 05:48:48 +00:00
|
|
|
chronicles,
|
|
|
|
json_rpc/[rpcserver, rpcclient],
|
|
|
|
web3,
|
2022-09-21 16:44:46 +00:00
|
|
|
web3/[ethhexstrings, ethtypes],
|
2022-08-26 11:54:10 +00:00
|
|
|
beacon_chain/eth1/eth1_monitor,
|
2022-09-21 16:44:46 +00:00
|
|
|
beacon_chain/networking/network_metadata,
|
2022-09-06 16:14:50 +00:00
|
|
|
beacon_chain/spec/forks,
|
|
|
|
../validate_proof
|
2022-08-26 11:54:10 +00:00
|
|
|
|
2022-08-31 05:48:48 +00:00
|
|
|
export forks
|
|
|
|
|
|
|
|
logScope:
|
|
|
|
topics = "light_proxy"
|
2022-08-26 11:54:10 +00:00
|
|
|
|
2022-09-21 16:44:46 +00:00
|
|
|
proc `==`(x, y: Quantity): bool {.borrow, noSideEffect.}
|
|
|
|
|
2022-08-26 11:54:10 +00:00
|
|
|
template encodeQuantity(value: UInt256): HexQuantityStr =
|
2022-08-31 05:48:48 +00:00
|
|
|
hexQuantityStr("0x" & value.toHex())
|
2022-08-26 11:54:10 +00:00
|
|
|
|
2022-09-09 13:59:36 +00:00
|
|
|
template encodeHexData(value: UInt256): HexDataStr =
|
2022-09-10 19:00:27 +00:00
|
|
|
hexDataStr("0x" & toBytesBE(value).toHex)
|
2022-09-09 13:59:36 +00:00
|
|
|
|
2022-08-31 05:48:48 +00:00
|
|
|
template encodeQuantity(value: Quantity): HexQuantityStr =
|
|
|
|
hexQuantityStr(encodeQuantity(value.uint64))
|
2022-08-26 11:54:10 +00:00
|
|
|
|
|
|
|
type LightClientRpcProxy* = ref object
|
2022-08-31 05:48:48 +00:00
|
|
|
client*: RpcClient
|
2022-09-21 16:44:46 +00:00
|
|
|
server*: RpcServer
|
2022-08-26 11:54:10 +00:00
|
|
|
executionPayload*: Opt[ExecutionPayloadV1]
|
2022-09-21 16:44:46 +00:00
|
|
|
chainId: Quantity
|
2022-09-09 13:59:36 +00:00
|
|
|
|
|
|
|
template checkPreconditions(payload: Opt[ExecutionPayloadV1], quantityTag: string) =
|
|
|
|
if payload.isNone():
|
|
|
|
raise newException(ValueError, "Syncing")
|
|
|
|
|
|
|
|
if quantityTag != "latest":
|
|
|
|
# TODO: for now we support only latest block, as its semantically most straight
|
|
|
|
# forward, i.e it is last received and a valid ExecutionPayloadV1.
|
|
|
|
# Ultimately we could keep track of n last valid payloads and support number
|
|
|
|
# queries for this set of blocks.
|
|
|
|
# `Pending` could be mapped to some optimistic header with the block
|
|
|
|
# fetched on demand.
|
|
|
|
raise newException(ValueError, "Only latest block is supported")
|
|
|
|
|
2022-08-26 11:54:10 +00:00
|
|
|
proc installEthApiHandlers*(lcProxy: LightClientRpcProxy) =
|
|
|
|
template payload(): Opt[ExecutionPayloadV1] = lcProxy.executionPayload
|
|
|
|
|
2022-09-21 16:44:46 +00:00
|
|
|
lcProxy.server.rpc("eth_chainId") do() -> HexQuantityStr:
|
|
|
|
return encodeQuantity(lcProxy.chainId)
|
|
|
|
|
2022-08-31 05:48:48 +00:00
|
|
|
lcProxy.server.rpc("eth_blockNumber") do() -> HexQuantityStr:
|
2022-08-26 11:54:10 +00:00
|
|
|
## Returns the number of most recent block.
|
|
|
|
if payload.isNone:
|
|
|
|
raise newException(ValueError, "Syncing")
|
|
|
|
|
|
|
|
return encodeQuantity(payload.get.blockNumber)
|
|
|
|
|
2022-08-31 05:48:48 +00:00
|
|
|
# TODO quantity tag should be better typed
|
|
|
|
lcProxy.server.rpc("eth_getBalance") do(address: Address, quantityTag: string) -> HexQuantityStr:
|
2022-09-09 13:59:36 +00:00
|
|
|
checkPreconditions(payload, quantityTag)
|
2022-08-26 11:54:10 +00:00
|
|
|
|
2022-08-31 05:48:48 +00:00
|
|
|
# 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
|
2022-09-06 16:14:50 +00:00
|
|
|
let
|
|
|
|
executionPayload = payload.get
|
|
|
|
blockNumber = executionPayload.blockNumber.uint64
|
2022-08-31 05:48:48 +00:00
|
|
|
|
|
|
|
info "Forwarding get_Balance", executionBn = blockNumber
|
|
|
|
|
2022-09-06 16:14:50 +00:00
|
|
|
let proof = await lcProxy.client.eth_getProof(address, @[], blockId(blockNumber))
|
|
|
|
|
2022-09-15 11:04:41 +00:00
|
|
|
let accountResult = getAccountFromProof(
|
2022-09-06 16:14:50 +00:00
|
|
|
executionPayload.stateRoot,
|
|
|
|
proof.address,
|
|
|
|
proof.balance,
|
|
|
|
proof.nonce,
|
|
|
|
proof.codeHash,
|
|
|
|
proof.storageHash,
|
|
|
|
proof.accountProof
|
|
|
|
)
|
|
|
|
|
2022-09-15 11:04:41 +00:00
|
|
|
if accountResult.isOk():
|
|
|
|
return encodeQuantity(accountResult.get.balance)
|
2022-09-06 16:14:50 +00:00
|
|
|
else:
|
2022-09-15 11:04:41 +00:00
|
|
|
raise newException(ValueError, accountResult.error)
|
2022-09-09 13:59:36 +00:00
|
|
|
|
|
|
|
lcProxy.server.rpc("eth_getStorageAt") do(address: Address, slot: HexDataStr, quantityTag: string) -> HexDataStr:
|
|
|
|
checkPreconditions(payload, quantityTag)
|
|
|
|
|
|
|
|
let
|
|
|
|
executionPayload = payload.get
|
|
|
|
uslot = UInt256.fromHex(slot.string)
|
|
|
|
blockNumber = executionPayload.blockNumber.uint64
|
|
|
|
|
|
|
|
info "Forwarding eth_getStorageAt", executionBn = blockNumber
|
|
|
|
|
|
|
|
let proof = await lcProxy.client.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)
|
2022-09-21 16:44:46 +00:00
|
|
|
|
|
|
|
proc new*(
|
|
|
|
T: type LightClientRpcProxy,
|
|
|
|
server: RpcServer,
|
|
|
|
client: RpcClient,
|
|
|
|
chainId: Quantity): T =
|
|
|
|
|
|
|
|
return LightClientRpcProxy(
|
|
|
|
client: client,
|
|
|
|
server: server,
|
|
|
|
chainId: chainId
|
|
|
|
)
|
|
|
|
|
|
|
|
proc verifyChaindId*(p: LightClientRpcProxy): Future[void] {.async.} =
|
|
|
|
let localId = p.chainId
|
|
|
|
|
|
|
|
# retry 2 times, if the data provider will fail despite re-tries, propagate
|
|
|
|
# exception to the caller.
|
|
|
|
let providerId = awaitWithRetries(
|
|
|
|
p.client.eth_chainId(),
|
|
|
|
retries = 2,
|
|
|
|
timeout = seconds(30)
|
|
|
|
)
|
|
|
|
|
|
|
|
# this configuration error, in theory we could allow proxy to chung on, but
|
|
|
|
# it would only mislead the user. It is better to fail fast here.
|
|
|
|
if localId != providerId:
|
|
|
|
fatal "The specified data provider serves data for a different chain",
|
|
|
|
expectedChain = distinctBase(localId),
|
|
|
|
providerChain = distinctBase(providerId)
|
|
|
|
quit 1
|
|
|
|
|
|
|
|
return
|
|
|
|
|