Add simple block cache. Handle numeric params (#1241)
* Add simple block cache. Handle numeric params
This commit is contained in:
parent
1bd4911e78
commit
f489845a19
|
@ -0,0 +1,62 @@
|
||||||
|
# light client proxy
|
||||||
|
# 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
|
||||||
|
std/tables,
|
||||||
|
web3/ethtypes,
|
||||||
|
stew/[results, keyed_queue]
|
||||||
|
|
||||||
|
|
||||||
|
## payloads received through block gossip (and validated by light client).
|
||||||
|
## Payloads are stored in order of arrival. When cache is full the oldest
|
||||||
|
## payload is deleted first.
|
||||||
|
type BlockCache* = ref object
|
||||||
|
max: int
|
||||||
|
blocks: KeyedQueue[BlockHash, ExecutionPayloadV1]
|
||||||
|
|
||||||
|
proc `==`(x, y: Quantity): bool {.borrow, noSideEffect.}
|
||||||
|
|
||||||
|
proc new*(T: type BlockCache, max: uint32): T =
|
||||||
|
let maxAsInt = int(max)
|
||||||
|
return BlockCache(
|
||||||
|
max: maxAsInt,
|
||||||
|
blocks: KeyedQueue[BlockHash, ExecutionPayloadV1].init(maxAsInt)
|
||||||
|
)
|
||||||
|
|
||||||
|
func len*(self: BlockCache): int =
|
||||||
|
return len(self.blocks)
|
||||||
|
|
||||||
|
func isEmpty*(self: BlockCache): bool =
|
||||||
|
return len(self.blocks) == 0
|
||||||
|
|
||||||
|
proc add*(self: BlockCache, payload: ExecutionPayloadV1) =
|
||||||
|
if self.blocks.hasKey(payload.blockHash):
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(self.blocks) >= self.max:
|
||||||
|
discard self.blocks.shift()
|
||||||
|
|
||||||
|
discard self.blocks.append(payload.blockHash, payload)
|
||||||
|
|
||||||
|
proc latest*(self: BlockCache): results.Opt[ExecutionPayloadV1] =
|
||||||
|
let latestPair = ? self.blocks.last()
|
||||||
|
return Opt.some(latestPair.data)
|
||||||
|
|
||||||
|
proc getByNumber*(
|
||||||
|
self: BlockCache,
|
||||||
|
number: Quantity): Opt[ExecutionPayloadV1] =
|
||||||
|
|
||||||
|
var payloadResult: Opt[ExecutionPayloadV1]
|
||||||
|
|
||||||
|
for payload in self.blocks.prevValues:
|
||||||
|
if payload.blockNumber == number:
|
||||||
|
payloadResult = Opt.some(payload)
|
||||||
|
break
|
||||||
|
|
||||||
|
return payloadResult
|
|
@ -23,7 +23,8 @@ import
|
||||||
beacon_chain/[light_client, nimbus_binary_common, version],
|
beacon_chain/[light_client, nimbus_binary_common, version],
|
||||||
../nimbus/rpc/cors,
|
../nimbus/rpc/cors,
|
||||||
./rpc/rpc_eth_lc_api,
|
./rpc/rpc_eth_lc_api,
|
||||||
./lc_proxy_conf
|
./lc_proxy_conf,
|
||||||
|
./block_cache
|
||||||
|
|
||||||
from beacon_chain/gossip_processing/block_processor import newExecutionPayload
|
from beacon_chain/gossip_processing/block_processor import newExecutionPayload
|
||||||
from beacon_chain/gossip_processing/eth2_processor import toValidationResult
|
from beacon_chain/gossip_processing/eth2_processor import toValidationResult
|
||||||
|
@ -94,6 +95,8 @@ proc run() {.raises: [Exception, Defect].} =
|
||||||
forkDigests, getBeaconTime, genesis_validators_root
|
forkDigests, getBeaconTime, genesis_validators_root
|
||||||
)
|
)
|
||||||
|
|
||||||
|
blockCache = BlockCache.new(uint32(64))
|
||||||
|
|
||||||
# TODO: for now we serve all cross origin requests
|
# TODO: for now we serve all cross origin requests
|
||||||
authHooks = @[httpCors(@[])]
|
authHooks = @[httpCors(@[])]
|
||||||
|
|
||||||
|
@ -105,7 +108,7 @@ proc run() {.raises: [Exception, Defect].} =
|
||||||
authHooks
|
authHooks
|
||||||
)
|
)
|
||||||
|
|
||||||
lcProxy = LightClientRpcProxy.new(rpcProxy, chainId)
|
lcProxy = LightClientRpcProxy.new(rpcProxy, blockCache, chainId)
|
||||||
|
|
||||||
optimisticHandler = proc(signedBlock: ForkedMsgTrustedSignedBeaconBlock):
|
optimisticHandler = proc(signedBlock: ForkedMsgTrustedSignedBeaconBlock):
|
||||||
Future[void] {.async.} =
|
Future[void] {.async.} =
|
||||||
|
@ -116,7 +119,7 @@ proc run() {.raises: [Exception, Defect].} =
|
||||||
when stateFork >= BeaconStateFork.Bellatrix:
|
when stateFork >= BeaconStateFork.Bellatrix:
|
||||||
if blck.message.is_execution_block:
|
if blck.message.is_execution_block:
|
||||||
template payload(): auto = blck.message.body.execution_payload
|
template payload(): auto = blck.message.body.execution_payload
|
||||||
lcProxy.executionPayload.ok payload.asEngineExecutionPayload()
|
blockCache.add(payload.asEngineExecutionPayload())
|
||||||
else: discard
|
else: discard
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,9 @@
|
||||||
{.push raises: [Defect].}
|
{.push raises: [Defect].}
|
||||||
|
|
||||||
import
|
import
|
||||||
|
std/strutils,
|
||||||
stint,
|
stint,
|
||||||
stew/byteutils,
|
stew/[byteutils, results],
|
||||||
chronicles,
|
chronicles,
|
||||||
json_rpc/[rpcproxy, rpcserver, rpcclient],
|
json_rpc/[rpcproxy, rpcserver, rpcclient],
|
||||||
web3,
|
web3,
|
||||||
|
@ -17,7 +18,8 @@ import
|
||||||
beacon_chain/eth1/eth1_monitor,
|
beacon_chain/eth1/eth1_monitor,
|
||||||
beacon_chain/networking/network_metadata,
|
beacon_chain/networking/network_metadata,
|
||||||
beacon_chain/spec/forks,
|
beacon_chain/spec/forks,
|
||||||
../validate_proof
|
../validate_proof,
|
||||||
|
../block_cache
|
||||||
|
|
||||||
export forks
|
export forks
|
||||||
|
|
||||||
|
@ -35,26 +37,79 @@ template encodeHexData(value: UInt256): HexDataStr =
|
||||||
template encodeQuantity(value: Quantity): HexQuantityStr =
|
template encodeQuantity(value: Quantity): HexQuantityStr =
|
||||||
hexQuantityStr(encodeQuantity(value.uint64))
|
hexQuantityStr(encodeQuantity(value.uint64))
|
||||||
|
|
||||||
type LightClientRpcProxy* = ref object
|
type
|
||||||
|
LightClientRpcProxy* = ref object
|
||||||
proxy: RpcProxy
|
proxy: RpcProxy
|
||||||
executionPayload*: Opt[ExecutionPayloadV1]
|
blockCache: BlockCache
|
||||||
chainId: Quantity
|
chainId: Quantity
|
||||||
|
|
||||||
template checkPreconditions(payload: Opt[ExecutionPayloadV1], quantityTag: string) =
|
QuantityTagKind = enum
|
||||||
if payload.isNone():
|
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 Etheruem 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: LightClientRpcProxy) =
|
||||||
|
if proxy.blockCache.isEmpty():
|
||||||
raise newException(ValueError, "Syncing")
|
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")
|
|
||||||
|
|
||||||
template rpcClient(lcProxy: LightClientRpcProxy): RpcClient = lcProxy.proxy.getClient()
|
template rpcClient(lcProxy: LightClientRpcProxy): RpcClient = lcProxy.proxy.getClient()
|
||||||
|
|
||||||
|
proc getPayloadByTag(
|
||||||
|
proxy: LightClientRpcProxy,
|
||||||
|
quantityTag: string): ExecutionPayloadV1 {.raises: [ValueError, Defect].} =
|
||||||
|
checkPreconditions(proxy)
|
||||||
|
|
||||||
|
let tagResult = parseQuantityTag(quantityTag)
|
||||||
|
|
||||||
|
if tagResult.isErr:
|
||||||
|
raise newException(ValueError, tagResult.error)
|
||||||
|
|
||||||
|
let tag = tagResult.get()
|
||||||
|
|
||||||
|
var payload: ExecutionPayloadV1
|
||||||
|
|
||||||
|
case tag.kind
|
||||||
|
of LatestBlock:
|
||||||
|
# this will always be ok as we always validate that cache is not empty
|
||||||
|
payload = proxy.blockCache.latest.get
|
||||||
|
of BlockNumber:
|
||||||
|
let payLoadResult = proxy.blockCache.getByNumber(tag.blockNumber)
|
||||||
|
if payLoadResult.isErr():
|
||||||
|
raise newException(
|
||||||
|
ValueError, "Block not stored in cache " & $tag.blockNumber
|
||||||
|
)
|
||||||
|
payload = payLoadResult.get
|
||||||
|
|
||||||
|
return payload
|
||||||
|
|
||||||
proc installEthApiHandlers*(lcProxy: LightClientRpcProxy) =
|
proc installEthApiHandlers*(lcProxy: LightClientRpcProxy) =
|
||||||
template payload(): Opt[ExecutionPayloadV1] = lcProxy.executionPayload
|
template payload(): Opt[ExecutionPayloadV1] = lcProxy.executionPayload
|
||||||
|
|
||||||
|
@ -63,21 +118,17 @@ proc installEthApiHandlers*(lcProxy: LightClientRpcProxy) =
|
||||||
|
|
||||||
lcProxy.proxy.rpc("eth_blockNumber") do() -> HexQuantityStr:
|
lcProxy.proxy.rpc("eth_blockNumber") do() -> HexQuantityStr:
|
||||||
## Returns the number of most recent block.
|
## Returns the number of most recent block.
|
||||||
if payload.isNone:
|
checkPreconditions(lcProxy)
|
||||||
raise newException(ValueError, "Syncing")
|
|
||||||
|
|
||||||
return encodeQuantity(payload.get.blockNumber)
|
return encodeQuantity(lcProxy.blockCache.latest.get.blockNumber)
|
||||||
|
|
||||||
# TODO quantity tag should be better typed
|
|
||||||
lcProxy.proxy.rpc("eth_getBalance") do(address: Address, quantityTag: string) -> HexQuantityStr:
|
lcProxy.proxy.rpc("eth_getBalance") do(address: Address, quantityTag: string) -> HexQuantityStr:
|
||||||
checkPreconditions(payload, quantityTag)
|
|
||||||
|
|
||||||
# When requesting state for `latest` block number, we need to translate
|
# When requesting state for `latest` block number, we need to translate
|
||||||
# `latest` to actual block number as `latest` on proxy and on data provider
|
# `latest` to actual block number as `latest` on proxy and on data provider
|
||||||
# can mean different blocks and ultimatly piece received piece of state
|
# can mean different blocks and ultimatly piece received piece of state
|
||||||
# must by validated against correct state root
|
# must by validated against correct state root
|
||||||
let
|
let
|
||||||
executionPayload = payload.get
|
executionPayload = lcProxy.getPayloadByTag(quantityTag)
|
||||||
blockNumber = executionPayload.blockNumber.uint64
|
blockNumber = executionPayload.blockNumber.uint64
|
||||||
|
|
||||||
info "Forwarding get_Balance", executionBn = blockNumber
|
info "Forwarding get_Balance", executionBn = blockNumber
|
||||||
|
@ -100,10 +151,8 @@ proc installEthApiHandlers*(lcProxy: LightClientRpcProxy) =
|
||||||
raise newException(ValueError, accountResult.error)
|
raise newException(ValueError, accountResult.error)
|
||||||
|
|
||||||
lcProxy.proxy.rpc("eth_getStorageAt") do(address: Address, slot: HexDataStr, quantityTag: string) -> HexDataStr:
|
lcProxy.proxy.rpc("eth_getStorageAt") do(address: Address, slot: HexDataStr, quantityTag: string) -> HexDataStr:
|
||||||
checkPreconditions(payload, quantityTag)
|
|
||||||
|
|
||||||
let
|
let
|
||||||
executionPayload = payload.get
|
executionPayload = lcProxy.getPayloadByTag(quantityTag)
|
||||||
uslot = UInt256.fromHex(slot.string)
|
uslot = UInt256.fromHex(slot.string)
|
||||||
blockNumber = executionPayload.blockNumber.uint64
|
blockNumber = executionPayload.blockNumber.uint64
|
||||||
|
|
||||||
|
@ -132,10 +181,12 @@ proc installEthApiHandlers*(lcProxy: LightClientRpcProxy) =
|
||||||
proc new*(
|
proc new*(
|
||||||
T: type LightClientRpcProxy,
|
T: type LightClientRpcProxy,
|
||||||
proxy: RpcProxy,
|
proxy: RpcProxy,
|
||||||
|
blockCache: BlockCache,
|
||||||
chainId: Quantity): T =
|
chainId: Quantity): T =
|
||||||
|
|
||||||
return LightClientRpcProxy(
|
return LightClientRpcProxy(
|
||||||
proxy: proxy,
|
proxy: proxy,
|
||||||
|
blockCache: blockCache,
|
||||||
chainId: chainId
|
chainId: chainId
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue