Skip to content

Commit bd8f082

Browse files
authored
Implement skip_randao_verification for blinded blocks (#4435)
* Implement skip_randao_verification for blinded blocks * fix redundant randao verification on block replay (5% faster) * check randao in REST instead of internally * avoid redundant copies when making blocks * cleanup leftover randao skipping code * fix test summary
1 parent 7501f10 commit bd8f082

File tree

7 files changed

+56
-111
lines changed

7 files changed

+56
-111
lines changed

AllTests-mainnet.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,12 @@ OK: 1/1 Fail: 0/1 Skip: 0/1
7575
## Block pool processing [Preset: mainnet]
7676
```diff
7777
+ Adding the same block twice returns a Duplicate error [Preset: mainnet] OK
78-
+ Randao skip and non-skip OK
7978
+ Simple block add&get [Preset: mainnet] OK
8079
+ basic ops OK
8180
+ updateHead updates head and headState [Preset: mainnet] OK
8281
+ updateState sanity [Preset: mainnet] OK
8382
```
84-
OK: 6/6 Fail: 0/6 Skip: 0/6
83+
OK: 5/5 Fail: 0/5 Skip: 0/5
8584
## Block processor [Preset: mainnet]
8685
```diff
8786
+ Reverse order block add & get [Preset: mainnet] OK
@@ -615,4 +614,4 @@ OK: 2/2 Fail: 0/2 Skip: 0/2
615614
OK: 9/9 Fail: 0/9 Skip: 0/9
616615

617616
---TOTAL---
618-
OK: 344/349 Fail: 0/349 Skip: 5/349
617+
OK: 343/348 Fail: 0/348 Skip: 5/348

beacon_chain/extras.nim

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,6 @@ type
2727
## Skip verification of BLS signatures in block processing.
2828
## Predominantly intended for use in testing, e.g. to allow extra coverage.
2929
## Also useful to avoid unnecessary work when replaying known, good blocks.
30-
skipRandaoVerification ##\
31-
## Skip verification of the proposer's randao reveal in block processing, but do ensure
32-
## that they set the randao reveal to the point at infinity.
3330
skipStateRootValidation ##\
3431
## Skip verification of block state root.
3532
strictVerification ##\

beacon_chain/rpc/rest_utils.nim

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,3 +308,22 @@ const
308308
jsonMediaType* = MediaType.init("application/json")
309309
sszMediaType* = MediaType.init("application/octet-stream")
310310
textEventStreamMediaType* = MediaType.init("text/event-stream")
311+
312+
proc verifyRandao*(
313+
node: BeaconNode, slot: Slot, proposer: ValidatorIndex,
314+
randao: ValidatorSig, skip_randao_verification: bool): bool =
315+
let
316+
proposer_pubkey = node.dag.validatorKey(proposer)
317+
if proposer_pubkey.isNone():
318+
return false
319+
320+
if skip_randao_verification:
321+
randao == ValidatorSig.infinity()
322+
else:
323+
let
324+
fork = node.dag.forkAtEpoch(slot.epoch)
325+
genesis_validators_root = node.dag.genesis_validators_root
326+
327+
verify_epoch_signature(
328+
fork, genesis_validators_root, slot.epoch, proposer_pubkey.get(),
329+
randao)

beacon_chain/rpc/rest_validator_api.nim

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -308,8 +308,9 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
308308

309309
# https://ethereum.github.io/beacon-APIs/#/Validator/produceBlockV2
310310
router.api(MethodGet, "/eth/v2/validator/blocks/{slot}") do (
311-
slot: Slot, randao_reveal: Option[ValidatorSig],
312-
graffiti: Option[GraffitiBytes], skip_randao_verification: Option[string]) -> RestApiResponse:
311+
slot: Slot, randao_reveal: Option[ValidatorSig],
312+
graffiti: Option[GraffitiBytes],
313+
skip_randao_verification: Option[string]) -> RestApiResponse:
313314
let message =
314315
block:
315316
let qslot = block:
@@ -363,13 +364,18 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
363364
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError,
364365
$res.error())
365366
res.get()
366-
let proposer = node.dag.getProposer(qhead, qslot)
367+
let
368+
proposer = node.dag.getProposer(qhead, qslot)
367369
if proposer.isNone():
368370
return RestApiResponse.jsonError(Http400, ProposerNotFoundError)
371+
372+
if not node.verifyRandao(
373+
qslot, proposer.get(), qrandao, qskip_randao_verification):
374+
return RestApiResponse.jsonError(Http400, InvalidRandaoRevealValue)
375+
369376
let res =
370377
await makeBeaconBlockForHeadAndSlot[bellatrix.ExecutionPayload](
371-
node, qrandao, proposer.get(), qgraffiti, qhead, qslot,
372-
qskip_randao_verification)
378+
node, qrandao, proposer.get(), qgraffiti, qhead, qslot)
373379
if res.isErr():
374380
return RestApiResponse.jsonError(Http400, res.error())
375381
res.get()
@@ -378,8 +384,9 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
378384
# https://ethereum.github.io/beacon-APIs/#/Validator/produceBlindedBlock
379385
# https://github.com/ethereum/beacon-APIs/blob/v2.3.0/apis/validator/blinded_block.yaml
380386
router.api(MethodGet, "/eth/v1/validator/blinded_blocks/{slot}") do (
381-
slot: Slot, randao_reveal: Option[ValidatorSig],
382-
graffiti: Option[GraffitiBytes]) -> RestApiResponse:
387+
slot: Slot, randao_reveal: Option[ValidatorSig],
388+
graffiti: Option[GraffitiBytes],
389+
skip_randao_verification: Option[string]) -> RestApiResponse:
383390
## Requests a beacon node to produce a valid blinded block, which can then
384391
## be signed by a validator. A blinded block is a block with only a
385392
## transactions root, rather than a full transactions list.
@@ -408,6 +415,15 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
408415
return RestApiResponse.jsonError(Http400, InvalidSlotValueError,
409416
"Slot cannot be in the future")
410417
res
418+
let qskip_randao_verification =
419+
if skip_randao_verification.isNone():
420+
false
421+
else:
422+
let res = skip_randao_verification.get()
423+
if res.isErr() or res.get() != "":
424+
return RestApiResponse.jsonError(Http400,
425+
InvalidSkipRandaoVerificationValue)
426+
true
411427
let qrandao =
412428
if randao_reveal.isNone():
413429
return RestApiResponse.jsonError(Http400, MissingRandaoRevealValue)
@@ -439,6 +455,10 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
439455
if proposer.isNone():
440456
return RestApiResponse.jsonError(Http400, ProposerNotFoundError)
441457

458+
if not node.verifyRandao(
459+
qslot, proposer.get(), qrandao, qskip_randao_verification):
460+
return RestApiResponse.jsonError(Http400, InvalidRandaoRevealValue)
461+
442462
template responsePlain(response: untyped): untyped =
443463
if contentType == sszMediaType:
444464
RestApiResponse.sszResponse(response)

beacon_chain/spec/state_transition_block.nim

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,21 +92,15 @@ proc process_randao(
9292
# Verify RANDAO reveal
9393
let epoch = state.get_current_epoch()
9494

95-
if skipRandaoVerification in flags:
96-
if body.randao_reveal.toRaw != ValidatorSig.infinity.toRaw:
97-
return err("process_randao: expected point-at-infinity for skipRandaoVerification")
98-
elif skipBlsValidation notin flags:
95+
if skipBlsValidation notin flags and body.randao_reveal isnot TrustedSig:
9996
let proposer_pubkey = state.validators.item(proposer_index.get).pubkey
10097

10198
# `state_transition.makeBeaconBlock` ensures this is run with a trusted
10299
# signature, but unless the full skipBlsValidation is specified, RANDAO
103100
# epoch signatures still have to be verified.
104101
if not verify_epoch_signature(
105102
state.fork, state.genesis_validators_root, epoch, proposer_pubkey,
106-
when body.randao_reveal is ValidatorSig:
107-
body.randao_reveal
108-
else:
109-
isomorphicCast[ValidatorSig](body.randao_reveal)):
103+
body.randao_reveal):
110104

111105
return err("process_randao: invalid epoch signature")
112106

beacon_chain/validators/validator_duties.nim

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,6 @@ proc makeBeaconBlockForHeadAndSlot*[EP](
432432
node: BeaconNode, randao_reveal: ValidatorSig,
433433
validator_index: ValidatorIndex, graffiti: GraffitiBytes, head: BlockRef,
434434
slot: Slot,
435-
skip_randao_verification_bool: bool,
436435
execution_payload: Opt[EP],
437436
transactions_root: Opt[Eth2Digest],
438437
execution_payload_root: Opt[Eth2Digest]):
@@ -455,9 +454,9 @@ proc makeBeaconBlockForHeadAndSlot*[EP](
455454
let
456455
state = maybeState.get
457456
payloadFut =
458-
if executionPayload.isSome:
457+
if execution_payload.isSome:
459458
let fut = newFuture[Opt[EP]]("given-payload")
460-
fut.complete(executionPayload)
459+
fut.complete(execution_payload)
461460
fut
462461
elif slot.epoch < node.dag.cfg.BELLATRIX_FORK_EPOCH or
463462
not (
@@ -510,10 +509,7 @@ proc makeBeaconBlockForHeadAndSlot*[EP](
510509
(static(default(SignedBLSToExecutionChangeList))),
511510
noRollback, # Temporary state - no need for rollback
512511
cache,
513-
# makeBeaconBlock doesn't verify BLS at all, but does have special case
514-
# for skipRandaoVerification separately
515-
verificationFlags =
516-
if skip_randao_verification_bool: {skipRandaoVerification} else: {},
512+
verificationFlags = {},
517513
transactions_root = transactions_root,
518514
execution_payload_root = execution_payload_root).mapErr do (error: cstring) -> string:
519515
# This is almost certainly a bug, but it's complex enough that there's a
@@ -530,21 +526,10 @@ proc makeBeaconBlockForHeadAndSlot*[EP](
530526
node: BeaconNode, randao_reveal: ValidatorSig,
531527
validator_index: ValidatorIndex, graffiti: GraffitiBytes, head: BlockRef,
532528
slot: Slot):
533-
Future[ForkedBlockResult] {.async.} =
534-
return await makeBeaconBlockForHeadAndSlot[EP](
535-
node, randao_reveal, validator_index, graffiti, head, slot,
536-
false, execution_payload = Opt.none(EP),
537-
transactions_root = Opt.none(Eth2Digest),
538-
execution_payload_root = Opt.none(Eth2Digest))
539-
540-
proc makeBeaconBlockForHeadAndSlot*[EP](
541-
node: BeaconNode, randao_reveal: ValidatorSig,
542-
validator_index: ValidatorIndex, graffiti: GraffitiBytes, head: BlockRef,
543-
slot: Slot, skip_randao_verification: bool):
544-
Future[ForkedBlockResult] {.async.} =
545-
return await makeBeaconBlockForHeadAndSlot[EP](
529+
Future[ForkedBlockResult] =
530+
return makeBeaconBlockForHeadAndSlot[EP](
546531
node, randao_reveal, validator_index, graffiti, head, slot,
547-
skip_randao_verification, execution_payload = Opt.none(EP),
532+
execution_payload = Opt.none(EP),
548533
transactions_root = Opt.none(Eth2Digest),
549534
execution_payload_root = Opt.none(Eth2Digest))
550535

@@ -695,7 +680,6 @@ proc getBlindedBlockParts(
695680

696681
let newBlock = await makeBeaconBlockForHeadAndSlot[bellatrix.ExecutionPayload](
697682
node, randao, validator_index, graffiti, head, slot,
698-
skip_randao_verification_bool = false,
699683
execution_payload = Opt.some shimExecutionPayload,
700684
transactions_root = Opt.some executionPayloadHeader.get.transactions_root,
701685
execution_payload_root =

tests/test_blockchain_dag.nim

Lines changed: 0 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -206,74 +206,6 @@ suite "Block pool processing" & preset():
206206
# Getting an EpochRef should not result in states being stored
207207
db.getStateRoot(stateCheckpoint.bid.root, stateCheckpoint.slot).isOk()
208208

209-
test "Randao skip and non-skip":
210-
process_slots(
211-
dag.cfg, state[], getStateField(
212-
state[], slot) + 1, cache, info, {}).expect("can advance 1")
213-
214-
let
215-
proposer_index = get_beacon_proposer_index(
216-
state[], cache, getStateField(state[], slot))
217-
privKey = MockPrivKeys[proposer_index.get]
218-
randao_reveal = get_epoch_signature(
219-
getStateField(state[], fork),
220-
getStateField(state[], genesis_validators_root),
221-
getStateField(state[], slot).epoch, privKey).toValidatorSig()
222-
var bad_randao_reveal = randao_reveal
223-
bad_randao_reveal.blob[5] += 1
224-
225-
block: # bad randao + no skip = bad
226-
let
227-
tmpState = assignClone(state[])
228-
message = makeBeaconBlock(
229-
dag.cfg, tmpState[], proposer_index.get(),
230-
bad_randao_reveal,
231-
getStateField(tmpState[], eth1_data),
232-
default(GraffitiBytes), @[], @[], BeaconBlockExits(),
233-
default(SyncAggregate), default(ExecutionPayload),
234-
default(SignedBLSToExecutionChangeList),
235-
noRollback, cache)
236-
check: message.isErr
237-
238-
block: # bad randao + skip = bad
239-
let
240-
tmpState = assignClone(state[])
241-
message = makeBeaconBlock(
242-
dag.cfg, tmpState[], proposer_index.get(),
243-
bad_randao_reveal,
244-
getStateField(tmpState[], eth1_data),
245-
default(GraffitiBytes), @[], @[], BeaconBlockExits(),
246-
default(SyncAggregate), default(ExecutionPayload),
247-
default(SignedBLSToExecutionChangeList),
248-
noRollback, cache, {skipRandaoVerification})
249-
check: message.isErr
250-
251-
block: # infinity + no skip = bad
252-
let
253-
tmpState = assignClone(state[])
254-
message = makeBeaconBlock(
255-
dag.cfg, tmpState[], proposer_index.get(),
256-
ValidatorSig.infinity(),
257-
getStateField(tmpState[], eth1_data),
258-
default(GraffitiBytes), @[], @[], BeaconBlockExits(),
259-
default(SyncAggregate), default(ExecutionPayload),
260-
default(SignedBLSToExecutionChangeList),
261-
noRollback, cache, {})
262-
check: message.isErr
263-
264-
block: # Infinity + skip = ok!
265-
let
266-
tmpState = assignClone(state[])
267-
message = makeBeaconBlock(
268-
dag.cfg, tmpState[], proposer_index.get(),
269-
ValidatorSig.infinity(),
270-
getStateField(tmpState[], eth1_data),
271-
default(GraffitiBytes), @[], @[], BeaconBlockExits(),
272-
default(SyncAggregate), default(ExecutionPayload),
273-
default(SignedBLSToExecutionChangeList),
274-
noRollback, cache, {skipRandaoVerification})
275-
check: message.isOk
276-
277209
test "Adding the same block twice returns a Duplicate error" & preset():
278210
let
279211
b10 = dag.addHeadBlock(verifier, b1, nilPhase0Callback)

0 commit comments

Comments
 (0)