nimbus-eth1/fluffy/rpc/portal_rpc_client.nim
Kim De Mey 833719a866
Remove usage of aliases for Hash32 such as BlockHash (#2707)
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.
2024-10-07 22:39:07 +02:00

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)