Skip to content

Commit c63862c

Browse files
etan-statustersec
andauthored
validate EL block hash when running consensus block tests (#6406)
* validate EL block hash when running consensus block tests We currently don't have an easy way to test EL block hash computation. As the EL block hash in consensus-spec-tests is computed correctly, update the test runners that load block from test files to also verify the EL block hash. This increases missing test coverage. Requires ethereum/consensus-specs#3829 * fix * resolve merge conflicts * fix genesis case, and deal with `incorrect_block_hash` test * add missing export marker * fix import * htr mutates underlying data, messing with differ, create copy in test * Handle payloads with empty tx (unsupported in ordered trie tool) * Update copyright years --------- Co-authored-by: tersec <[email protected]>
1 parent adda973 commit c63862c

12 files changed

+124
-58
lines changed

beacon_chain/spec/beaconstate.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2408,7 +2408,7 @@ func upgrade_to_fulu*(
24082408

24092409
post
24102410

2411-
func latest_block_root(state: ForkyBeaconState, state_root: Eth2Digest):
2411+
func latest_block_root*(state: ForkyBeaconState, state_root: Eth2Digest):
24122412
Eth2Digest =
24132413
# The root of the last block that was successfully applied to this state -
24142414
# normally, when a block is applied, the data from the header is stored in

beacon_chain/spec/helpers.nim

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -389,14 +389,17 @@ func is_merge_transition_complete*(
389389
state.latest_execution_payload_header != defaultExecutionPayloadHeader
390390

391391
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.9/sync/optimistic.md#helpers
392-
func is_execution_block*(blck: SomeForkyBeaconBlock): bool =
393-
when typeof(blck).kind >= ConsensusFork.Bellatrix:
392+
func is_execution_block*(body: SomeForkyBeaconBlockBody): bool =
393+
when typeof(body).kind >= ConsensusFork.Bellatrix:
394394
const defaultExecutionPayload =
395-
default(typeof(blck.body.execution_payload))
396-
blck.body.execution_payload != defaultExecutionPayload
395+
default(typeof(body.execution_payload))
396+
body.execution_payload != defaultExecutionPayload
397397
else:
398398
false
399399

400+
func is_execution_block*(blck: SomeForkyBeaconBlock): bool =
401+
blck.body.is_execution_block
402+
400403
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/bellatrix/beacon-chain.md#is_merge_transition_block
401404
func is_merge_transition_block(
402405
state: bellatrix.BeaconState | capella.BeaconState | deneb.BeaconState |
@@ -480,9 +483,10 @@ func computeRequestsHash(
480483

481484
requestsHash.to(EthHash32)
482485

483-
proc blockToBlockHeader*(blck: ForkyBeaconBlock): EthHeader =
484-
template payload: auto = blck.body.execution_payload
485-
486+
proc toExecutionBlockHeader(
487+
payload: ForkyExecutionPayload,
488+
parentRoot: Eth2Digest,
489+
requestsHash = Opt.none(EthHash32)): EthHeader =
486490
static: # `GasInt` is signed. We only use it for hashing.
487491
doAssert sizeof(GasInt) == sizeof(payload.gas_limit)
488492
doAssert sizeof(GasInt) == sizeof(payload.gas_used)
@@ -506,12 +510,7 @@ proc blockToBlockHeader*(blck: ForkyBeaconBlock): EthHeader =
506510
Opt.none(uint64)
507511
parentBeaconBlockRoot =
508512
when typeof(payload).kind >= ConsensusFork.Deneb:
509-
Opt.some EthHash32(blck.parent_root.data)
510-
else:
511-
Opt.none(EthHash32)
512-
requestsHash =
513-
when typeof(payload).kind >= ConsensusFork.Electra:
514-
Opt.some blck.body.execution_requests.computeRequestsHash()
513+
Opt.some EthHash32(parentRoot.data)
515514
else:
516515
Opt.none(EthHash32)
517516

@@ -538,8 +537,19 @@ proc blockToBlockHeader*(blck: ForkyBeaconBlock): EthHeader =
538537
parentBeaconBlockRoot : parentBeaconBlockRoot, # EIP-4788
539538
requestsHash : requestsHash) # EIP-7685
540539

540+
proc compute_execution_block_hash*(
541+
body: ForkyBeaconBlockBody,
542+
parentRoot: Eth2Digest): Eth2Digest =
543+
when typeof(body).kind >= ConsensusFork.Electra:
544+
body.execution_payload.toExecutionBlockHeader(
545+
parentRoot, Opt.some body.execution_requests.computeRequestsHash())
546+
.rlpHash().to(Eth2Digest)
547+
else:
548+
body.execution_payload.toExecutionBlockHeader(parentRoot)
549+
.rlpHash().to(Eth2Digest)
550+
541551
proc compute_execution_block_hash*(blck: ForkyBeaconBlock): Eth2Digest =
542-
rlpHash(blockToBlockHeader(blck)).to(Eth2Digest)
552+
blck.body.compute_execution_block_hash(blck.parent_root)
543553

544554
from std/math import exp, ln
545555
from std/sequtils import foldl

tests/consensus_spec/bellatrix/test_fixture_operations.nim

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ import
2020
../fixtures_utils, ../os_ops,
2121
../../helpers/debug_state
2222

23-
from std/sequtils import mapIt, toSeq
23+
from std/sequtils import anyIt, mapIt, toSeq
2424
from std/strutils import contains
2525
from ../../../beacon_chain/spec/beaconstate import
2626
get_base_reward_per_increment, get_state_exit_queue_info,
27-
get_total_active_balance, process_attestation
27+
get_total_active_balance, latest_block_root, process_attestation
2828

2929
const
3030
OpDir = SszTestsDir/const_preset/"bellatrix"/"operations"
@@ -110,9 +110,12 @@ suite baseDescription & "Attester Slashing " & preset():
110110
applyAttesterSlashing, path)
111111

112112
suite baseDescription & "Block Header " & preset():
113-
func applyBlockHeader(
113+
proc applyBlockHeader(
114114
preState: var bellatrix.BeaconState, blck: bellatrix.BeaconBlock):
115115
Result[void, cstring] =
116+
if blck.is_execution_block:
117+
check blck.body.execution_payload.block_hash ==
118+
blck.compute_execution_block_hash()
116119
var cache: StateCache
117120
process_block_header(preState, blck, {}, cache)
118121

@@ -144,6 +147,13 @@ suite baseDescription & "Execution Payload " & preset():
144147
let payloadValid = os_ops.readFile(
145148
OpExecutionPayloadDir/"pyspec_tests"/path/"execution.yaml"
146149
).contains("execution_valid: true")
150+
if payloadValid and body.is_execution_block and
151+
not body.execution_payload.transactions.anyIt(it.len == 0):
152+
let expectedOk = (path != "incorrect_block_hash")
153+
check expectedOk == (body.execution_payload.block_hash ==
154+
body.compute_execution_block_hash(
155+
preState.latest_block_root(
156+
assignClone(preState)[].hash_tree_root())))
147157
func executePayload(_: bellatrix.ExecutionPayload): bool = payloadValid
148158
process_execution_payload(
149159
preState, body.execution_payload, executePayload)
@@ -199,4 +209,4 @@ suite baseDescription & "Voluntary Exit " & preset():
199209
for path in walkTests(OpVoluntaryExitDir):
200210
runTest[SignedVoluntaryExit, typeof applyVoluntaryExit](
201211
OpVoluntaryExitDir, suiteName, "Voluntary Exit", "voluntary_exit",
202-
applyVoluntaryExit, path)
212+
applyVoluntaryExit, path)

tests/consensus_spec/capella/test_fixture_operations.nim

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ import
2020
../fixtures_utils, ../os_ops,
2121
../../helpers/debug_state
2222

23-
from std/sequtils import mapIt, toSeq
23+
from std/sequtils import anyIt, mapIt, toSeq
2424
from std/strutils import contains
2525
from ../../../beacon_chain/spec/beaconstate import
2626
get_base_reward_per_increment, get_state_exit_queue_info,
27-
get_total_active_balance, process_attestation
27+
get_total_active_balance, latest_block_root, process_attestation
2828

2929
const
3030
OpDir = SszTestsDir/const_preset/"capella"/"operations"
@@ -114,9 +114,12 @@ suite baseDescription & "Attester Slashing " & preset():
114114
applyAttesterSlashing, path)
115115

116116
suite baseDescription & "Block Header " & preset():
117-
func applyBlockHeader(
117+
proc applyBlockHeader(
118118
preState: var capella.BeaconState, blck: capella.BeaconBlock):
119119
Result[void, cstring] =
120+
if blck.is_execution_block:
121+
check blck.body.execution_payload.block_hash ==
122+
blck.compute_execution_block_hash()
120123
var cache: StateCache
121124
process_block_header(preState, blck, {}, cache)
122125

@@ -161,6 +164,13 @@ suite baseDescription & "Execution Payload " & preset():
161164
let payloadValid = os_ops.readFile(
162165
OpExecutionPayloadDir/"pyspec_tests"/path/"execution.yaml"
163166
).contains("execution_valid: true")
167+
if payloadValid and body.is_execution_block and
168+
not body.execution_payload.transactions.anyIt(it.len == 0):
169+
let expectedOk = (path != "incorrect_block_hash")
170+
check expectedOk == (body.execution_payload.block_hash ==
171+
body.compute_execution_block_hash(
172+
preState.latest_block_root(
173+
assignClone(preState)[].hash_tree_root())))
164174
func executePayload(_: capella.ExecutionPayload): bool = payloadValid
165175
process_execution_payload(
166176
preState, body.execution_payload, executePayload)
@@ -227,4 +237,4 @@ suite baseDescription & "Withdrawals " & preset():
227237
for path in walkTests(OpWithdrawalsDir):
228238
runTest[capella.ExecutionPayload, typeof applyWithdrawals](
229239
OpWithdrawalsDir, suiteName, "Withdrawals", "execution_payload",
230-
applyWithdrawals, path)
240+
applyWithdrawals, path)

tests/consensus_spec/deneb/test_fixture_operations.nim

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ import
2020
../fixtures_utils, ../os_ops,
2121
../../helpers/debug_state
2222

23-
from std/sequtils import mapIt, toSeq
23+
from std/sequtils import anyIt, mapIt, toSeq
2424
from std/strutils import contains
2525
from ../../../beacon_chain/spec/beaconstate import
2626
get_base_reward_per_increment, get_state_exit_queue_info,
27-
get_total_active_balance, process_attestation
27+
get_total_active_balance, latest_block_root, process_attestation
2828

2929
const
3030
OpDir = SszTestsDir/const_preset/"deneb"/"operations"
@@ -114,9 +114,12 @@ suite baseDescription & "Attester Slashing " & preset():
114114
applyAttesterSlashing, path)
115115

116116
suite baseDescription & "Block Header " & preset():
117-
func applyBlockHeader(
117+
proc applyBlockHeader(
118118
preState: var deneb.BeaconState, blck: deneb.BeaconBlock):
119119
Result[void, cstring] =
120+
if blck.is_execution_block:
121+
check blck.body.execution_payload.block_hash ==
122+
blck.compute_execution_block_hash()
120123
var cache: StateCache
121124
process_block_header(preState, blck, {}, cache)
122125

@@ -164,6 +167,13 @@ suite baseDescription & "Execution Payload " & preset():
164167
let payloadValid = os_ops.readFile(
165168
OpExecutionPayloadDir/"pyspec_tests"/path/"execution.yaml"
166169
).contains("execution_valid: true")
170+
if payloadValid and body.is_execution_block and
171+
not body.execution_payload.transactions.anyIt(it.len == 0):
172+
let expectedOk = (path != "incorrect_block_hash")
173+
check expectedOk == (body.execution_payload.block_hash ==
174+
body.compute_execution_block_hash(
175+
preState.latest_block_root(
176+
assignClone(preState)[].hash_tree_root())))
167177
func executePayload(_: deneb.ExecutionPayload): bool = payloadValid
168178
process_execution_payload(preState, body, executePayload)
169179

@@ -229,4 +239,4 @@ suite baseDescription & "Withdrawals " & preset():
229239
for path in walkTests(OpWithdrawalsDir):
230240
runTest[deneb.ExecutionPayload, typeof applyWithdrawals](
231241
OpWithdrawalsDir, suiteName, "Withdrawals", "execution_payload",
232-
applyWithdrawals, path)
242+
applyWithdrawals, path)

tests/consensus_spec/electra/test_fixture_operations.nim

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ import
2020
../fixtures_utils, ../os_ops,
2121
../../helpers/debug_state
2222

23-
from std/sequtils import mapIt, toSeq
23+
from std/sequtils import anyIt, mapIt, toSeq
2424
from std/strutils import contains
2525
from ../../../beacon_chain/spec/beaconstate import
2626
get_base_reward_per_increment, get_state_exit_queue_info,
27-
get_total_active_balance, process_attestation
27+
get_total_active_balance, latest_block_root, process_attestation
2828

2929
const
3030
OpDir = SszTestsDir/const_preset/"electra"/"operations"
@@ -121,9 +121,12 @@ suite baseDescription & "Attester Slashing " & preset():
121121
applyAttesterSlashing, path)
122122

123123
suite baseDescription & "Block Header " & preset():
124-
func applyBlockHeader(
124+
proc applyBlockHeader(
125125
preState: var electra.BeaconState, blck: electra.BeaconBlock):
126126
Result[void, cstring] =
127+
if blck.is_execution_block:
128+
check blck.body.execution_payload.block_hash ==
129+
blck.compute_execution_block_hash()
127130
var cache: StateCache
128131
process_block_header(preState, blck, {}, cache)
129132

@@ -199,6 +202,13 @@ suite baseDescription & "Execution Payload " & preset():
199202
let payloadValid = os_ops.readFile(
200203
OpExecutionPayloadDir/"pyspec_tests"/path/"execution.yaml"
201204
).contains("execution_valid: true")
205+
if payloadValid and body.is_execution_block and
206+
not body.execution_payload.transactions.anyIt(it.len == 0):
207+
let expectedOk = (path != "incorrect_block_hash")
208+
check expectedOk == (body.execution_payload.block_hash ==
209+
body.compute_execution_block_hash(
210+
preState.latest_block_root(
211+
assignClone(preState)[].hash_tree_root())))
202212
func executePayload(_: electra.ExecutionPayload): bool = payloadValid
203213
process_execution_payload(
204214
defaultRuntimeConfig, preState, body, executePayload)
@@ -281,4 +291,4 @@ suite baseDescription & "Withdrawals " & preset():
281291
for path in walkTests(OpWithdrawalsDir):
282292
runTest[electra.ExecutionPayload, typeof applyWithdrawals](
283293
OpWithdrawalsDir, suiteName, "Withdrawals", "execution_payload",
284-
applyWithdrawals, path)
294+
applyWithdrawals, path)

tests/consensus_spec/fixtures_utils.nim

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,18 @@
99

1010
import
1111
# Standard library
12-
std/[strutils, typetraits],
12+
std/[sequtils, strutils, typetraits],
1313
# Internals
1414
./os_ops,
1515
../../beacon_chain/spec/datatypes/[phase0, altair, bellatrix],
1616
../../beacon_chain/spec/[
17-
eth2_merkleization, eth2_ssz_serialization, forks],
17+
eth2_merkleization, eth2_ssz_serialization, forks, helpers],
1818
# Status libs,
1919
snappy,
2020
stew/byteutils
2121

2222
export
23-
eth2_merkleization, eth2_ssz_serialization
23+
eth2_merkleization, eth2_ssz_serialization, helpers
2424

2525
# Process current EF test format
2626
# ---------------------------------------------
@@ -173,4 +173,22 @@ proc loadForkedState*(
173173
withState(state[]):
174174
forkyState.data = parseTest(path, SSZ, consensusFork.BeaconState)
175175
forkyState.root = hash_tree_root(forkyState.data)
176-
state
176+
state
177+
178+
proc loadBlock*(
179+
path: string,
180+
consensusFork: static ConsensusFork,
181+
validateBlockHash = true): auto =
182+
var blck = parseTest(path, SSZ, consensusFork.SignedBeaconBlock)
183+
blck.root = hash_tree_root(blck.message)
184+
when consensusFork >= ConsensusFork.Bellatrix:
185+
if blck.message.is_execution_block and
186+
not blck.message.body.execution_payload.transactions.anyIt(it.len == 0):
187+
if blck.message.body.execution_payload.block_hash !=
188+
blck.message.compute_execution_block_hash():
189+
try:
190+
stderr.write "Invalid `block_hash`: ", path, "\n"
191+
except IOError:
192+
discard
193+
quit 1
194+
blck

tests/consensus_spec/test_fixture_fork_choice.nim

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# beacon_chain
2-
# Copyright (c) 2018-2024 Status Research & Development GmbH
2+
# Copyright (c) 2018-2025 Status Research & Development GmbH
33
# Licensed and distributed under either of
44
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
55
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@@ -119,10 +119,7 @@ proc loadOps(
119119
doAssert step.hasKey"blobs" == step.hasKey"proofs"
120120
withConsensusFork(fork):
121121
let
122-
blck = parseTest(
123-
path/filename & ".ssz_snappy",
124-
SSZ, consensusFork.SignedBeaconBlock)
125-
122+
blck = loadBlock(path/filename & ".ssz_snappy", consensusFork)
126123
blobData =
127124
when consensusFork >= ConsensusFork.Deneb:
128125
if step.hasKey"blobs":

tests/consensus_spec/test_fixture_kzg.nim

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ proc runComputeCellsAndKzgProofsTest(suiteName, suitePath, path: string) =
221221
check output.kind == JNull
222222
else:
223223
let p_val = p[].get
224-
for i in 0..<CELLS_PER_EXT_BLOB:
224+
for i in 0..<kzg_abi.CELLS_PER_EXT_BLOB:
225225
check p_val.cells[i].bytes == fromHex[2048](output[0][i].getStr).get
226226
check p_val.proofs[i].bytes == fromHex[48](output[1][i].getStr).get
227227

@@ -281,7 +281,7 @@ proc runRecoverCellsAndKzgProofsTest(suiteName, suitePath, path: string) =
281281
check output.kind == JNull
282282
else:
283283
let val = v[].get
284-
for i in 0..<CELLS_PER_EXT_BLOB:
284+
for i in 0..<kzg_abi.CELLS_PER_EXT_BLOB:
285285
check val.cells[i].bytes == fromHex[2048](output[0][i].getStr).get
286286
check val.proofs[i].bytes == fromHex[48](output[1][i].getStr).get
287287

0 commit comments

Comments
 (0)