Add simple block cache. Handle numeric params (#1241)

* Add simple block cache. Handle numeric params
This commit is contained in:
KonradStaniec 2022-09-28 10:01:32 +02:00 committed by GitHub
parent 1bd4911e78
commit f489845a19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 146 additions and 30 deletions

62
lc_proxy/block_cache.nim Normal file
View File

@ -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

View File

@ -23,7 +23,8 @@ import
beacon_chain/[light_client, nimbus_binary_common, version],
../nimbus/rpc/cors,
./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/eth2_processor import toValidationResult
@ -94,6 +95,8 @@ proc run() {.raises: [Exception, Defect].} =
forkDigests, getBeaconTime, genesis_validators_root
)
blockCache = BlockCache.new(uint32(64))
# TODO: for now we serve all cross origin requests
authHooks = @[httpCors(@[])]
@ -105,7 +108,7 @@ proc run() {.raises: [Exception, Defect].} =
authHooks
)
lcProxy = LightClientRpcProxy.new(rpcProxy, chainId)
lcProxy = LightClientRpcProxy.new(rpcProxy, blockCache, chainId)
optimisticHandler = proc(signedBlock: ForkedMsgTrustedSignedBeaconBlock):
Future[void] {.async.} =
@ -116,7 +119,7 @@ proc run() {.raises: [Exception, Defect].} =
when stateFork >= BeaconStateFork.Bellatrix:
if blck.message.is_execution_block:
template payload(): auto = blck.message.body.execution_payload
lcProxy.executionPayload.ok payload.asEngineExecutionPayload()
blockCache.add(payload.asEngineExecutionPayload())
else: discard
return

View File

@ -8,8 +8,9 @@
{.push raises: [Defect].}
import
std/strutils,
stint,
stew/byteutils,
stew/[byteutils, results],
chronicles,
json_rpc/[rpcproxy, rpcserver, rpcclient],
web3,
@ -17,7 +18,8 @@ import
beacon_chain/eth1/eth1_monitor,
beacon_chain/networking/network_metadata,
beacon_chain/spec/forks,
../validate_proof
../validate_proof,
../block_cache
export forks
@ -35,26 +37,79 @@ template encodeHexData(value: UInt256): HexDataStr =
template encodeQuantity(value: Quantity): HexQuantityStr =
hexQuantityStr(encodeQuantity(value.uint64))
type LightClientRpcProxy* = ref object
proxy: RpcProxy
executionPayload*: Opt[ExecutionPayloadV1]
chainId: Quantity
type
LightClientRpcProxy* = ref object
proxy: RpcProxy
blockCache: BlockCache
chainId: Quantity
template checkPreconditions(payload: Opt[ExecutionPayloadV1], quantityTag: string) =
if payload.isNone():
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 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")
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()
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) =
template payload(): Opt[ExecutionPayloadV1] = lcProxy.executionPayload
@ -63,21 +118,17 @@ proc installEthApiHandlers*(lcProxy: LightClientRpcProxy) =
lcProxy.proxy.rpc("eth_blockNumber") do() -> HexQuantityStr:
## Returns the number of most recent block.
if payload.isNone:
raise newException(ValueError, "Syncing")
checkPreconditions(lcProxy)
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:
checkPreconditions(payload, quantityTag)
# 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 = payload.get
executionPayload = lcProxy.getPayloadByTag(quantityTag)
blockNumber = executionPayload.blockNumber.uint64
info "Forwarding get_Balance", executionBn = blockNumber
@ -100,10 +151,8 @@ proc installEthApiHandlers*(lcProxy: LightClientRpcProxy) =
raise newException(ValueError, accountResult.error)
lcProxy.proxy.rpc("eth_getStorageAt") do(address: Address, slot: HexDataStr, quantityTag: string) -> HexDataStr:
checkPreconditions(payload, quantityTag)
let
executionPayload = payload.get
executionPayload = lcProxy.getPayloadByTag(quantityTag)
uslot = UInt256.fromHex(slot.string)
blockNumber = executionPayload.blockNumber.uint64
@ -132,10 +181,12 @@ proc installEthApiHandlers*(lcProxy: LightClientRpcProxy) =
proc new*(
T: type LightClientRpcProxy,
proxy: RpcProxy,
blockCache: BlockCache,
chainId: Quantity): T =
return LightClientRpcProxy(
proxy: proxy,
blockCache: blockCache,
chainId: chainId
)