178 lines
6.3 KiB
Nim
178 lines
6.3 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.
|
|
|
|
{.push raises: [].}
|
|
|
|
import
|
|
chronos/timer,
|
|
eth/trie/ordered_trie,
|
|
../../network_metadata,
|
|
./history_type_conversions,
|
|
./validation/historical_hashes_accumulator
|
|
|
|
from eth/common/eth_types_rlp import rlpHash
|
|
|
|
export historical_hashes_accumulator
|
|
|
|
func validateHeader(header: Header, blockHash: Hash32): Result[void, string] =
|
|
if not (header.rlpHash() == blockHash):
|
|
err("Header hash does not match")
|
|
else:
|
|
ok()
|
|
|
|
func validateHeader(header: Header, number: uint64): Result[void, string] =
|
|
if not (header.number == number):
|
|
err("Header number does not match")
|
|
else:
|
|
ok()
|
|
|
|
func validateHeaderBytes*(
|
|
bytes: openArray[byte], id: uint64 | Hash32
|
|
): Result[Header, string] =
|
|
# Note:
|
|
# No additional quick-checks are addedhere such as timestamp vs the optional
|
|
# (later forks) added fields. E.g. Shanghai field, Cancun fields,
|
|
# zero ommersHash, etc.
|
|
# This is because the block hash comparison + canonical verification will
|
|
# catch these. For comparison by number this is will also be caught by the
|
|
# canonical verification.
|
|
let header = ?decodeRlp(bytes, Header)
|
|
|
|
?header.validateHeader(id)
|
|
|
|
ok(header)
|
|
|
|
func verifyBlockHeaderProof*(
|
|
a: FinishedHistoricalHashesAccumulator, header: Header, proof: BlockHeaderProof
|
|
): Result[void, string] =
|
|
case proof.proofType
|
|
of BlockHeaderProofType.historicalHashesAccumulatorProof:
|
|
a.verifyAccumulatorProof(header, proof.historicalHashesAccumulatorProof)
|
|
of BlockHeaderProofType.none:
|
|
if header.isPreMerge():
|
|
err("Pre merge header requires HistoricalHashesAccumulatorProof")
|
|
else:
|
|
# TODO:
|
|
# Add verification post merge based on historical_roots & historical_summaries
|
|
ok()
|
|
|
|
func validateCanonicalHeaderBytes*(
|
|
bytes: openArray[byte], id: uint64 | Hash32, a: FinishedHistoricalHashesAccumulator
|
|
): Result[Header, string] =
|
|
let headerWithProof = decodeSsz(bytes, BlockHeaderWithProof).valueOr:
|
|
return err("Failed decoding header with proof: " & error)
|
|
let header = ?validateHeaderBytes(headerWithProof.header.asSeq(), id)
|
|
|
|
?a.verifyBlockHeaderProof(header, headerWithProof.proof)
|
|
|
|
ok(header)
|
|
|
|
func validateBlockBody*(
|
|
body: PortalBlockBodyLegacy, header: Header
|
|
): Result[void, string] =
|
|
## Validate the block body against the txRoot and ommersHash from the header.
|
|
let calculatedOmmersHash = keccak256(body.uncles.asSeq())
|
|
if calculatedOmmersHash != header.ommersHash:
|
|
return err("Invalid ommers hash")
|
|
|
|
let calculatedTxsRoot = orderedTrieRoot(body.transactions.asSeq)
|
|
if calculatedTxsRoot != header.txRoot:
|
|
return err(
|
|
"Invalid transactions root: expected " & $header.txRoot & " - got " &
|
|
$calculatedTxsRoot
|
|
)
|
|
|
|
ok()
|
|
|
|
func validateBlockBody*(
|
|
body: PortalBlockBodyShanghai, header: Header
|
|
): Result[void, string] =
|
|
## Validate the block body against the txRoot, ommersHash and withdrawalsRoot
|
|
## from the header.
|
|
# Shortcut the ommersHash calculation as uncles must be an RLP encoded
|
|
# empty list
|
|
if body.uncles.asSeq() != @[byte 0xc0]:
|
|
return err("Invalid ommers hash, uncles list is not empty")
|
|
|
|
let calculatedTxsRoot = orderedTrieRoot(body.transactions.asSeq)
|
|
if calculatedTxsRoot != header.txRoot:
|
|
return err(
|
|
"Invalid transactions root: expected " & $header.txRoot & " - got " &
|
|
$calculatedTxsRoot
|
|
)
|
|
|
|
# TODO: This check is done higher up but perhaps this can become cleaner with
|
|
# some refactor.
|
|
doAssert(header.withdrawalsRoot.isSome())
|
|
|
|
let
|
|
calculatedWithdrawalsRoot = orderedTrieRoot(body.withdrawals.asSeq)
|
|
headerWithdrawalsRoot = header.withdrawalsRoot.get()
|
|
if calculatedWithdrawalsRoot != headerWithdrawalsRoot:
|
|
return err(
|
|
"Invalid withdrawals root: expected " & $headerWithdrawalsRoot & " - got " &
|
|
$calculatedWithdrawalsRoot
|
|
)
|
|
|
|
ok()
|
|
|
|
func validateBlockBodyBytes*(
|
|
bytes: openArray[byte], header: Header
|
|
): Result[BlockBody, string] =
|
|
## Fully decode the SSZ encoded Portal Block Body and validate it against the
|
|
## header.
|
|
## TODO: improve this decoding in combination with the block body validation
|
|
## calls.
|
|
let timestamp = Moment.init(header.timestamp.int64, Second)
|
|
# TODO: The additional header checks are not needed as header is implicitly
|
|
# verified by means of the accumulator? Except that we don't use this yet
|
|
# post merge, so the checks are still useful, for now.
|
|
if isShanghai(chainConfig, timestamp):
|
|
if header.withdrawalsRoot.isNone():
|
|
err("Expected withdrawalsRoot for Shanghai block")
|
|
elif header.ommersHash != EMPTY_UNCLE_HASH:
|
|
err("Expected empty uncles for a Shanghai block")
|
|
else:
|
|
let body = ?decodeSsz(bytes, PortalBlockBodyShanghai)
|
|
?validateBlockBody(body, header)
|
|
BlockBody.fromPortalBlockBody(body)
|
|
elif isPoSBlock(chainConfig, header.number):
|
|
if header.withdrawalsRoot.isSome():
|
|
err("Expected no withdrawalsRoot for pre Shanghai block")
|
|
elif header.ommersHash != EMPTY_UNCLE_HASH:
|
|
err("Expected empty uncles for a PoS block")
|
|
else:
|
|
let body = ?decodeSsz(bytes, PortalBlockBodyLegacy)
|
|
?validateBlockBody(body, header)
|
|
BlockBody.fromPortalBlockBody(body)
|
|
else:
|
|
if header.withdrawalsRoot.isSome():
|
|
err("Expected no withdrawalsRoot for pre Shanghai block")
|
|
else:
|
|
let body = ?decodeSsz(bytes, PortalBlockBodyLegacy)
|
|
?validateBlockBody(body, header)
|
|
BlockBody.fromPortalBlockBody(body)
|
|
|
|
func validateReceipts*(
|
|
receipts: PortalReceipts, receiptsRoot: Hash32
|
|
): Result[void, string] =
|
|
if orderedTrieRoot(receipts.asSeq) != receiptsRoot:
|
|
err("Unexpected receipt root")
|
|
else:
|
|
ok()
|
|
|
|
func validateReceiptsBytes*(
|
|
bytes: openArray[byte], receiptsRoot: Hash32
|
|
): Result[seq[Receipt], string] =
|
|
## Fully decode the SSZ encoded receipts and validate it against the header's
|
|
## receipts root.
|
|
let receipts = ?decodeSsz(bytes, PortalReceipts)
|
|
|
|
?validateReceipts(receipts, receiptsRoot)
|
|
|
|
seq[Receipt].fromPortalReceipts(receipts)
|