validate EL block hash in EL simulation (#4420)

When simulating EL with `--optimistic` flag, perform block hash check.
This commit is contained in:
Etan Kissling 2022-12-20 09:24:33 +01:00 committed by GitHub
parent 07d4160e00
commit c91d9d61e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 49 additions and 20 deletions

View File

@ -374,7 +374,8 @@ proc storeBlock*(
self.consensusManager.quarantine[].addUnviable(signedBlock.root)
return err((VerifierError.UnviableFork, ProcessingStatus.completed))
if NewPayloadStatus.noResponse == payloadStatus and not self[].optimistic:
if NewPayloadStatus.noResponse == payloadStatus:
if not self[].optimistic:
# Disallow the `MissingParent` from leaking to the sync/request managers
# as it will be descored. However sync and request managers interact via
# `processBlock` (indirectly). `validator_duties` does call `storeBlock`
@ -383,6 +384,19 @@ proc storeBlock*(
return err((
VerifierError.MissingParent, ProcessingStatus.notCompleted))
# Client software MUST validate blockHash value as being equivalent to
# Keccak256(RLP(ExecutionBlockHeader))
# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.1/src/engine/specification.md#specification
when typeof(signedBlock).toFork() >= BeaconBlockFork.Bellatrix:
template payload(): auto = signedBlock.message.body.execution_payload
if payload.block_hash != payload.compute_execution_block_hash():
debug "EL block hash validation failed", execution_payload = payload
doAssert strictVerification notin dag.updateFlags
self.consensusManager.quarantine[].addUnviable(signedBlock.root)
return err((VerifierError.UnviableFork, ProcessingStatus.completed))
else:
discard
# We'll also remove the block as an orphan: it's unlikely the parent is
# missing if we get this far - should that be the case, the block will
# be re-added later

View File

@ -372,6 +372,19 @@ func compute_timestamp_at_slot*(state: ForkyBeaconState, slot: Slot): uint64 =
let slots_since_genesis = slot - GENESIS_SLOT
state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT
proc computeTransactionsTrieRoot*(
payload: bellatrix.ExecutionPayload | capella.ExecutionPayload): Hash256 =
if payload.transactions.len == 0:
return EMPTY_ROOT_HASH
var tr = initHexaryTrie(newMemoryDB())
for i, transaction in payload.transactions:
try:
tr.put(rlp.encode(i), distinctBase(transaction)) # Already RLP encoded
except RlpError as exc:
doAssert false, "HexaryTrie.put failed: " & $exc.msg
tr.rootHash()
func gweiToWei*(gwei: Gwei): UInt256 =
gwei.u256 * 1_000_000_000.u256
@ -397,18 +410,15 @@ proc computeWithdrawalsTrieRoot*(
doAssert false, "HexaryTrie.put failed: " & $exc.msg
tr.rootHash()
proc emptyPayloadToBlockHeader*(
proc payloadToBlockHeader*(
payload: bellatrix.ExecutionPayload | capella.ExecutionPayload
): ExecutionBlockHeader =
static: # `GasInt` is signed. We only use it for hashing.
doAssert sizeof(GasInt) == sizeof(payload.gas_limit)
doAssert sizeof(GasInt) == sizeof(payload.gas_used)
## This function assumes that the payload is empty!
doAssert payload.transactions.len == 0
let
txRoot = EMPTY_ROOT_HASH
txRoot = payload.computeTransactionsTrieRoot()
withdrawalsRoot =
when payload is bellatrix.ExecutionPayload:
none(Hash256)
@ -434,7 +444,12 @@ proc emptyPayloadToBlockHeader*(
fee : some payload.base_fee_per_gas,
withdrawalsRoot: withdrawalsRoot)
func build_empty_execution_payload*(
proc compute_execution_block_hash*(
payload: bellatrix.ExecutionPayload | capella.ExecutionPayload
): Eth2Digest =
rlpHash payloadToBlockHeader(payload)
proc build_empty_execution_payload*(
state: bellatrix.BeaconState,
feeRecipient: Eth1Address): bellatrix.ExecutionPayload =
## Assuming a pre-state of the same slot, build a valid ExecutionPayload
@ -459,7 +474,7 @@ func build_empty_execution_payload*(
timestamp: timestamp,
base_fee_per_gas: base_fee)
payload.block_hash = rlpHash emptyPayloadToBlockHeader(payload)
payload.block_hash = payload.compute_execution_block_hash()
payload
@ -491,6 +506,6 @@ proc build_empty_execution_payload*(
for withdrawal in expectedWithdrawals:
doAssert payload.withdrawals.add withdrawal
payload.block_hash = rlpHash emptyPayloadToBlockHeader(payload)
payload.block_hash = payload.compute_execution_block_hash()
payload

View File

@ -96,7 +96,7 @@ suite "Spec helpers":
for i, withdrawal in withdrawals:
check payload.withdrawals[i] == withdrawal
let elHeader = emptyPayloadToBlockHeader(payload)
let elHeader = payloadToBlockHeader(payload)
check elHeader.withdrawalsRoot.isSome
if withdrawals.len == 0:
check elHeader.withdrawalsRoot.get == EMPTY_ROOT_HASH

View File

@ -81,7 +81,7 @@ func signBlock(
from ../beacon_chain/spec/datatypes/capella import
BeaconState, ExecutionPayload, SignedBLSToExecutionChangeList
func build_empty_merge_execution_payload(state: bellatrix.BeaconState):
proc build_empty_merge_execution_payload(state: bellatrix.BeaconState):
bellatrix.ExecutionPayload =
## Assuming a pre-state of the same slot, build a valid ExecutionPayload
## without any transactions from a non-merged block.
@ -104,7 +104,7 @@ func build_empty_merge_execution_payload(state: bellatrix.BeaconState):
timestamp: timestamp,
base_fee_per_gas: EIP1559_INITIAL_BASE_FEE)
payload.block_hash = rlpHash emptyPayloadToBlockHeader(payload)
payload.block_hash = rlpHash payloadToBlockHeader(payload)
payload
@ -131,7 +131,7 @@ proc build_empty_merge_execution_payload(state: capella.BeaconState):
timestamp: timestamp,
base_fee_per_gas: EIP1559_INITIAL_BASE_FEE)
payload.block_hash = rlpHash emptyPayloadToBlockHeader(payload)
payload.block_hash = rlpHash payloadToBlockHeader(payload)
payload