mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-03-01 12:20:49 +00:00
These create only confusion as if they are actual different types and it is within their usage already clear what they are about because of the name of the variable or the function. They are also nowhere aliased like this in any of the Portal specification.
159 lines
5.9 KiB
Nim
159 lines
5.9 KiB
Nim
# fluffy
|
|
# Copyright (c) 2021-2024 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.
|
|
|
|
import
|
|
json_serialization,
|
|
chronos,
|
|
stew/byteutils,
|
|
results,
|
|
eth/common/[headers_rlp, blocks_rlp, receipts_rlp],
|
|
json_rpc/rpcclient,
|
|
../common/common_types,
|
|
../network/history/[history_content, history_network],
|
|
./rpc_calls/[rpc_discovery_calls, rpc_portal_calls, rpc_portal_debug_calls]
|
|
|
|
export rpcclient, rpc_discovery_calls, rpc_portal_calls, rpc_portal_debug_calls, results
|
|
|
|
type
|
|
PortalRpcClient* = distinct RpcClient
|
|
|
|
PortalRpcError* = enum
|
|
ContentNotFound
|
|
InvalidContentKey
|
|
InvalidContentValue
|
|
ContentValidationFailed
|
|
|
|
ErrorResponse = object
|
|
code*: int
|
|
message*: string
|
|
|
|
proc init*(T: type PortalRpcClient, rpcClient: RpcClient): T =
|
|
T(rpcClient)
|
|
|
|
func toPortalRpcError(e: ref CatchableError): PortalRpcError =
|
|
let error =
|
|
try:
|
|
Json.decode(e.msg, ErrorResponse)
|
|
except SerializationError as e:
|
|
raiseAssert(e.msg)
|
|
|
|
if error.code == -39001:
|
|
ContentNotFound
|
|
elif error.code == -32602:
|
|
InvalidContentKey
|
|
else:
|
|
raiseAssert(e.msg)
|
|
|
|
proc historyLocalContent(
|
|
client: PortalRpcClient, contentKey: string
|
|
): Future[Result[string, PortalRpcError]] {.async: (raises: []).} =
|
|
try:
|
|
let content = await RpcClient(client).portal_historyLocalContent(contentKey)
|
|
ok(content)
|
|
except CatchableError as e:
|
|
err(e.toPortalRpcError())
|
|
|
|
proc historyRecursiveFindContent(
|
|
client: PortalRpcClient, contentKey: string
|
|
): Future[Result[string, PortalRpcError]] {.async: (raises: []).} =
|
|
try:
|
|
let contentInfo =
|
|
await RpcClient(client).portal_historyRecursiveFindContent(contentKey)
|
|
ok(contentInfo.content)
|
|
except CatchableError as e:
|
|
err(e.toPortalRpcError())
|
|
|
|
template toBytes(content: string): seq[byte] =
|
|
try:
|
|
hexToSeqByte(content)
|
|
except ValueError as e:
|
|
raiseAssert(e.msg)
|
|
|
|
template valueOrErr[T](res: Result[T, string], error: PortalRpcError): auto =
|
|
if res.isOk():
|
|
ok(res.value)
|
|
else:
|
|
err(error)
|
|
|
|
proc historyGetContent(
|
|
client: PortalRpcClient, contentKey: string
|
|
): Future[Result[string, PortalRpcError]] {.async: (raises: []).} =
|
|
# Look up the content from the local db before trying to get it from the network
|
|
let content = (await client.historyLocalContent(contentKey)).valueOr:
|
|
if error == ContentNotFound:
|
|
?await client.historyRecursiveFindContent(contentKey)
|
|
else:
|
|
return err(error)
|
|
ok(content)
|
|
|
|
proc historyGetBlockHeader*(
|
|
client: PortalRpcClient, blockHash: Hash32, validateContent = true
|
|
): Future[Result[Header, PortalRpcError]] {.async: (raises: []).} =
|
|
## Fetches the block header for the given hash from the Portal History Network.
|
|
## The data is first looked up in the node's local database before trying to
|
|
## fetch it from the network.
|
|
##
|
|
## Note: This does not validate that the returned header is part of the canonical
|
|
## chain, it only validates that the header matches the block hash. For example,
|
|
## a malicious portal node could return a valid but non-canonical header such
|
|
## as an uncle block that matches the block hash. For this reason the caller
|
|
## needs to use another method to verify the header is part of the canonical chain.
|
|
|
|
let
|
|
contentKey = blockHeaderContentKey(blockHash).encode().asSeq().to0xHex()
|
|
content = ?await client.historyGetContent(contentKey)
|
|
headerWithProof = decodeSsz(content.toBytes(), BlockHeaderWithProof).valueOr:
|
|
return err(InvalidContentValue)
|
|
headerBytes = headerWithProof.header.asSeq()
|
|
|
|
if validateContent:
|
|
validateBlockHeaderBytes(headerBytes, blockHash).valueOrErr(ContentValidationFailed)
|
|
else:
|
|
decodeRlp(headerBytes, Header).valueOrErr(InvalidContentValue)
|
|
|
|
proc historyGetBlockBody*(
|
|
client: PortalRpcClient, blockHash: Hash32, validateContent = true
|
|
): Future[Result[BlockBody, PortalRpcError]] {.async: (raises: []).} =
|
|
## Fetches the block body for the given block hash from the Portal History
|
|
## Network. The data is first looked up in the node's local database before
|
|
## trying to fetch it from the network. If validateContent is true, the
|
|
## block header is fetched first in order to run the content validation.
|
|
|
|
let
|
|
contentKey = blockBodyContentKey(blockHash).encode().asSeq().to0xHex()
|
|
content = ?await client.historyGetContent(contentKey)
|
|
|
|
if validateContent:
|
|
let blockHeader = ?await client.historyGetBlockHeader(blockHash)
|
|
validateBlockBodyBytes(content.toBytes(), blockHeader).valueOrErr(
|
|
ContentValidationFailed
|
|
)
|
|
else:
|
|
decodeBlockBodyBytes(content.toBytes()).valueOrErr(InvalidContentValue)
|
|
|
|
proc historyGetReceipts*(
|
|
client: PortalRpcClient, blockHash: Hash32, validateContent = true
|
|
): Future[Result[seq[Receipt], PortalRpcError]] {.async: (raises: []).} =
|
|
## Fetches the receipts for the given block hash from the Portal History
|
|
## Network. The data is first looked up in the node's local database before
|
|
## trying to fetch it from the network. If validateContent is true, the
|
|
## block header is fetched first in order to run the content validation.
|
|
|
|
let
|
|
contentKey = receiptsContentKey(blockHash).encode().asSeq().to0xHex()
|
|
content = ?await client.historyGetContent(contentKey)
|
|
|
|
if validateContent:
|
|
let blockHeader = ?await client.historyGetBlockHeader(blockHash)
|
|
validateReceiptsBytes(content.toBytes(), blockHeader.receiptsRoot).valueOrErr(
|
|
ContentValidationFailed
|
|
)
|
|
else:
|
|
let receipts = decodeSsz(content.toBytes(), PortalReceipts).valueOr:
|
|
return err(InvalidContentValue)
|
|
seq[Receipt].fromPortalReceipts(receipts).valueOrErr(InvalidContentValue)
|