mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-01-11 14:54:12 +00:00
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
This commit is contained in:
parent
7501f10587
commit
bd8f08204e
@ -75,13 +75,12 @@ OK: 1/1 Fail: 0/1 Skip: 0/1
|
|||||||
## Block pool processing [Preset: mainnet]
|
## Block pool processing [Preset: mainnet]
|
||||||
```diff
|
```diff
|
||||||
+ Adding the same block twice returns a Duplicate error [Preset: mainnet] OK
|
+ Adding the same block twice returns a Duplicate error [Preset: mainnet] OK
|
||||||
+ Randao skip and non-skip OK
|
|
||||||
+ Simple block add&get [Preset: mainnet] OK
|
+ Simple block add&get [Preset: mainnet] OK
|
||||||
+ basic ops OK
|
+ basic ops OK
|
||||||
+ updateHead updates head and headState [Preset: mainnet] OK
|
+ updateHead updates head and headState [Preset: mainnet] OK
|
||||||
+ updateState sanity [Preset: mainnet] OK
|
+ updateState sanity [Preset: mainnet] OK
|
||||||
```
|
```
|
||||||
OK: 6/6 Fail: 0/6 Skip: 0/6
|
OK: 5/5 Fail: 0/5 Skip: 0/5
|
||||||
## Block processor [Preset: mainnet]
|
## Block processor [Preset: mainnet]
|
||||||
```diff
|
```diff
|
||||||
+ Reverse order block add & get [Preset: mainnet] OK
|
+ Reverse order block add & get [Preset: mainnet] OK
|
||||||
@ -615,4 +614,4 @@ OK: 2/2 Fail: 0/2 Skip: 0/2
|
|||||||
OK: 9/9 Fail: 0/9 Skip: 0/9
|
OK: 9/9 Fail: 0/9 Skip: 0/9
|
||||||
|
|
||||||
---TOTAL---
|
---TOTAL---
|
||||||
OK: 344/349 Fail: 0/349 Skip: 5/349
|
OK: 343/348 Fail: 0/348 Skip: 5/348
|
||||||
|
@ -27,9 +27,6 @@ type
|
|||||||
## Skip verification of BLS signatures in block processing.
|
## Skip verification of BLS signatures in block processing.
|
||||||
## Predominantly intended for use in testing, e.g. to allow extra coverage.
|
## Predominantly intended for use in testing, e.g. to allow extra coverage.
|
||||||
## Also useful to avoid unnecessary work when replaying known, good blocks.
|
## Also useful to avoid unnecessary work when replaying known, good blocks.
|
||||||
skipRandaoVerification ##\
|
|
||||||
## Skip verification of the proposer's randao reveal in block processing, but do ensure
|
|
||||||
## that they set the randao reveal to the point at infinity.
|
|
||||||
skipStateRootValidation ##\
|
skipStateRootValidation ##\
|
||||||
## Skip verification of block state root.
|
## Skip verification of block state root.
|
||||||
strictVerification ##\
|
strictVerification ##\
|
||||||
|
@ -308,3 +308,22 @@ const
|
|||||||
jsonMediaType* = MediaType.init("application/json")
|
jsonMediaType* = MediaType.init("application/json")
|
||||||
sszMediaType* = MediaType.init("application/octet-stream")
|
sszMediaType* = MediaType.init("application/octet-stream")
|
||||||
textEventStreamMediaType* = MediaType.init("text/event-stream")
|
textEventStreamMediaType* = MediaType.init("text/event-stream")
|
||||||
|
|
||||||
|
proc verifyRandao*(
|
||||||
|
node: BeaconNode, slot: Slot, proposer: ValidatorIndex,
|
||||||
|
randao: ValidatorSig, skip_randao_verification: bool): bool =
|
||||||
|
let
|
||||||
|
proposer_pubkey = node.dag.validatorKey(proposer)
|
||||||
|
if proposer_pubkey.isNone():
|
||||||
|
return false
|
||||||
|
|
||||||
|
if skip_randao_verification:
|
||||||
|
randao == ValidatorSig.infinity()
|
||||||
|
else:
|
||||||
|
let
|
||||||
|
fork = node.dag.forkAtEpoch(slot.epoch)
|
||||||
|
genesis_validators_root = node.dag.genesis_validators_root
|
||||||
|
|
||||||
|
verify_epoch_signature(
|
||||||
|
fork, genesis_validators_root, slot.epoch, proposer_pubkey.get(),
|
||||||
|
randao)
|
||||||
|
@ -308,8 +308,9 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||||||
|
|
||||||
# https://ethereum.github.io/beacon-APIs/#/Validator/produceBlockV2
|
# https://ethereum.github.io/beacon-APIs/#/Validator/produceBlockV2
|
||||||
router.api(MethodGet, "/eth/v2/validator/blocks/{slot}") do (
|
router.api(MethodGet, "/eth/v2/validator/blocks/{slot}") do (
|
||||||
slot: Slot, randao_reveal: Option[ValidatorSig],
|
slot: Slot, randao_reveal: Option[ValidatorSig],
|
||||||
graffiti: Option[GraffitiBytes], skip_randao_verification: Option[string]) -> RestApiResponse:
|
graffiti: Option[GraffitiBytes],
|
||||||
|
skip_randao_verification: Option[string]) -> RestApiResponse:
|
||||||
let message =
|
let message =
|
||||||
block:
|
block:
|
||||||
let qslot = block:
|
let qslot = block:
|
||||||
@ -363,13 +364,18 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||||||
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError,
|
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError,
|
||||||
$res.error())
|
$res.error())
|
||||||
res.get()
|
res.get()
|
||||||
let proposer = node.dag.getProposer(qhead, qslot)
|
let
|
||||||
|
proposer = node.dag.getProposer(qhead, qslot)
|
||||||
if proposer.isNone():
|
if proposer.isNone():
|
||||||
return RestApiResponse.jsonError(Http400, ProposerNotFoundError)
|
return RestApiResponse.jsonError(Http400, ProposerNotFoundError)
|
||||||
|
|
||||||
|
if not node.verifyRandao(
|
||||||
|
qslot, proposer.get(), qrandao, qskip_randao_verification):
|
||||||
|
return RestApiResponse.jsonError(Http400, InvalidRandaoRevealValue)
|
||||||
|
|
||||||
let res =
|
let res =
|
||||||
await makeBeaconBlockForHeadAndSlot[bellatrix.ExecutionPayload](
|
await makeBeaconBlockForHeadAndSlot[bellatrix.ExecutionPayload](
|
||||||
node, qrandao, proposer.get(), qgraffiti, qhead, qslot,
|
node, qrandao, proposer.get(), qgraffiti, qhead, qslot)
|
||||||
qskip_randao_verification)
|
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
return RestApiResponse.jsonError(Http400, res.error())
|
return RestApiResponse.jsonError(Http400, res.error())
|
||||||
res.get()
|
res.get()
|
||||||
@ -378,8 +384,9 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||||||
# https://ethereum.github.io/beacon-APIs/#/Validator/produceBlindedBlock
|
# https://ethereum.github.io/beacon-APIs/#/Validator/produceBlindedBlock
|
||||||
# https://github.com/ethereum/beacon-APIs/blob/v2.3.0/apis/validator/blinded_block.yaml
|
# https://github.com/ethereum/beacon-APIs/blob/v2.3.0/apis/validator/blinded_block.yaml
|
||||||
router.api(MethodGet, "/eth/v1/validator/blinded_blocks/{slot}") do (
|
router.api(MethodGet, "/eth/v1/validator/blinded_blocks/{slot}") do (
|
||||||
slot: Slot, randao_reveal: Option[ValidatorSig],
|
slot: Slot, randao_reveal: Option[ValidatorSig],
|
||||||
graffiti: Option[GraffitiBytes]) -> RestApiResponse:
|
graffiti: Option[GraffitiBytes],
|
||||||
|
skip_randao_verification: Option[string]) -> RestApiResponse:
|
||||||
## Requests a beacon node to produce a valid blinded block, which can then
|
## Requests a beacon node to produce a valid blinded block, which can then
|
||||||
## be signed by a validator. A blinded block is a block with only a
|
## be signed by a validator. A blinded block is a block with only a
|
||||||
## transactions root, rather than a full transactions list.
|
## transactions root, rather than a full transactions list.
|
||||||
@ -408,6 +415,15 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||||||
return RestApiResponse.jsonError(Http400, InvalidSlotValueError,
|
return RestApiResponse.jsonError(Http400, InvalidSlotValueError,
|
||||||
"Slot cannot be in the future")
|
"Slot cannot be in the future")
|
||||||
res
|
res
|
||||||
|
let qskip_randao_verification =
|
||||||
|
if skip_randao_verification.isNone():
|
||||||
|
false
|
||||||
|
else:
|
||||||
|
let res = skip_randao_verification.get()
|
||||||
|
if res.isErr() or res.get() != "":
|
||||||
|
return RestApiResponse.jsonError(Http400,
|
||||||
|
InvalidSkipRandaoVerificationValue)
|
||||||
|
true
|
||||||
let qrandao =
|
let qrandao =
|
||||||
if randao_reveal.isNone():
|
if randao_reveal.isNone():
|
||||||
return RestApiResponse.jsonError(Http400, MissingRandaoRevealValue)
|
return RestApiResponse.jsonError(Http400, MissingRandaoRevealValue)
|
||||||
@ -439,6 +455,10 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||||||
if proposer.isNone():
|
if proposer.isNone():
|
||||||
return RestApiResponse.jsonError(Http400, ProposerNotFoundError)
|
return RestApiResponse.jsonError(Http400, ProposerNotFoundError)
|
||||||
|
|
||||||
|
if not node.verifyRandao(
|
||||||
|
qslot, proposer.get(), qrandao, qskip_randao_verification):
|
||||||
|
return RestApiResponse.jsonError(Http400, InvalidRandaoRevealValue)
|
||||||
|
|
||||||
template responsePlain(response: untyped): untyped =
|
template responsePlain(response: untyped): untyped =
|
||||||
if contentType == sszMediaType:
|
if contentType == sszMediaType:
|
||||||
RestApiResponse.sszResponse(response)
|
RestApiResponse.sszResponse(response)
|
||||||
|
@ -92,10 +92,7 @@ proc process_randao(
|
|||||||
# Verify RANDAO reveal
|
# Verify RANDAO reveal
|
||||||
let epoch = state.get_current_epoch()
|
let epoch = state.get_current_epoch()
|
||||||
|
|
||||||
if skipRandaoVerification in flags:
|
if skipBlsValidation notin flags and body.randao_reveal isnot TrustedSig:
|
||||||
if body.randao_reveal.toRaw != ValidatorSig.infinity.toRaw:
|
|
||||||
return err("process_randao: expected point-at-infinity for skipRandaoVerification")
|
|
||||||
elif skipBlsValidation notin flags:
|
|
||||||
let proposer_pubkey = state.validators.item(proposer_index.get).pubkey
|
let proposer_pubkey = state.validators.item(proposer_index.get).pubkey
|
||||||
|
|
||||||
# `state_transition.makeBeaconBlock` ensures this is run with a trusted
|
# `state_transition.makeBeaconBlock` ensures this is run with a trusted
|
||||||
@ -103,10 +100,7 @@ proc process_randao(
|
|||||||
# epoch signatures still have to be verified.
|
# epoch signatures still have to be verified.
|
||||||
if not verify_epoch_signature(
|
if not verify_epoch_signature(
|
||||||
state.fork, state.genesis_validators_root, epoch, proposer_pubkey,
|
state.fork, state.genesis_validators_root, epoch, proposer_pubkey,
|
||||||
when body.randao_reveal is ValidatorSig:
|
body.randao_reveal):
|
||||||
body.randao_reveal
|
|
||||||
else:
|
|
||||||
isomorphicCast[ValidatorSig](body.randao_reveal)):
|
|
||||||
|
|
||||||
return err("process_randao: invalid epoch signature")
|
return err("process_randao: invalid epoch signature")
|
||||||
|
|
||||||
|
@ -432,7 +432,6 @@ proc makeBeaconBlockForHeadAndSlot*[EP](
|
|||||||
node: BeaconNode, randao_reveal: ValidatorSig,
|
node: BeaconNode, randao_reveal: ValidatorSig,
|
||||||
validator_index: ValidatorIndex, graffiti: GraffitiBytes, head: BlockRef,
|
validator_index: ValidatorIndex, graffiti: GraffitiBytes, head: BlockRef,
|
||||||
slot: Slot,
|
slot: Slot,
|
||||||
skip_randao_verification_bool: bool,
|
|
||||||
execution_payload: Opt[EP],
|
execution_payload: Opt[EP],
|
||||||
transactions_root: Opt[Eth2Digest],
|
transactions_root: Opt[Eth2Digest],
|
||||||
execution_payload_root: Opt[Eth2Digest]):
|
execution_payload_root: Opt[Eth2Digest]):
|
||||||
@ -455,9 +454,9 @@ proc makeBeaconBlockForHeadAndSlot*[EP](
|
|||||||
let
|
let
|
||||||
state = maybeState.get
|
state = maybeState.get
|
||||||
payloadFut =
|
payloadFut =
|
||||||
if executionPayload.isSome:
|
if execution_payload.isSome:
|
||||||
let fut = newFuture[Opt[EP]]("given-payload")
|
let fut = newFuture[Opt[EP]]("given-payload")
|
||||||
fut.complete(executionPayload)
|
fut.complete(execution_payload)
|
||||||
fut
|
fut
|
||||||
elif slot.epoch < node.dag.cfg.BELLATRIX_FORK_EPOCH or
|
elif slot.epoch < node.dag.cfg.BELLATRIX_FORK_EPOCH or
|
||||||
not (
|
not (
|
||||||
@ -510,10 +509,7 @@ proc makeBeaconBlockForHeadAndSlot*[EP](
|
|||||||
(static(default(SignedBLSToExecutionChangeList))),
|
(static(default(SignedBLSToExecutionChangeList))),
|
||||||
noRollback, # Temporary state - no need for rollback
|
noRollback, # Temporary state - no need for rollback
|
||||||
cache,
|
cache,
|
||||||
# makeBeaconBlock doesn't verify BLS at all, but does have special case
|
verificationFlags = {},
|
||||||
# for skipRandaoVerification separately
|
|
||||||
verificationFlags =
|
|
||||||
if skip_randao_verification_bool: {skipRandaoVerification} else: {},
|
|
||||||
transactions_root = transactions_root,
|
transactions_root = transactions_root,
|
||||||
execution_payload_root = execution_payload_root).mapErr do (error: cstring) -> string:
|
execution_payload_root = execution_payload_root).mapErr do (error: cstring) -> string:
|
||||||
# This is almost certainly a bug, but it's complex enough that there's a
|
# This is almost certainly a bug, but it's complex enough that there's a
|
||||||
@ -530,21 +526,10 @@ proc makeBeaconBlockForHeadAndSlot*[EP](
|
|||||||
node: BeaconNode, randao_reveal: ValidatorSig,
|
node: BeaconNode, randao_reveal: ValidatorSig,
|
||||||
validator_index: ValidatorIndex, graffiti: GraffitiBytes, head: BlockRef,
|
validator_index: ValidatorIndex, graffiti: GraffitiBytes, head: BlockRef,
|
||||||
slot: Slot):
|
slot: Slot):
|
||||||
Future[ForkedBlockResult] {.async.} =
|
Future[ForkedBlockResult] =
|
||||||
return await makeBeaconBlockForHeadAndSlot[EP](
|
return makeBeaconBlockForHeadAndSlot[EP](
|
||||||
node, randao_reveal, validator_index, graffiti, head, slot,
|
node, randao_reveal, validator_index, graffiti, head, slot,
|
||||||
false, execution_payload = Opt.none(EP),
|
execution_payload = Opt.none(EP),
|
||||||
transactions_root = Opt.none(Eth2Digest),
|
|
||||||
execution_payload_root = Opt.none(Eth2Digest))
|
|
||||||
|
|
||||||
proc makeBeaconBlockForHeadAndSlot*[EP](
|
|
||||||
node: BeaconNode, randao_reveal: ValidatorSig,
|
|
||||||
validator_index: ValidatorIndex, graffiti: GraffitiBytes, head: BlockRef,
|
|
||||||
slot: Slot, skip_randao_verification: bool):
|
|
||||||
Future[ForkedBlockResult] {.async.} =
|
|
||||||
return await makeBeaconBlockForHeadAndSlot[EP](
|
|
||||||
node, randao_reveal, validator_index, graffiti, head, slot,
|
|
||||||
skip_randao_verification, execution_payload = Opt.none(EP),
|
|
||||||
transactions_root = Opt.none(Eth2Digest),
|
transactions_root = Opt.none(Eth2Digest),
|
||||||
execution_payload_root = Opt.none(Eth2Digest))
|
execution_payload_root = Opt.none(Eth2Digest))
|
||||||
|
|
||||||
@ -695,7 +680,6 @@ proc getBlindedBlockParts(
|
|||||||
|
|
||||||
let newBlock = await makeBeaconBlockForHeadAndSlot[bellatrix.ExecutionPayload](
|
let newBlock = await makeBeaconBlockForHeadAndSlot[bellatrix.ExecutionPayload](
|
||||||
node, randao, validator_index, graffiti, head, slot,
|
node, randao, validator_index, graffiti, head, slot,
|
||||||
skip_randao_verification_bool = false,
|
|
||||||
execution_payload = Opt.some shimExecutionPayload,
|
execution_payload = Opt.some shimExecutionPayload,
|
||||||
transactions_root = Opt.some executionPayloadHeader.get.transactions_root,
|
transactions_root = Opt.some executionPayloadHeader.get.transactions_root,
|
||||||
execution_payload_root =
|
execution_payload_root =
|
||||||
|
@ -206,74 +206,6 @@ suite "Block pool processing" & preset():
|
|||||||
# Getting an EpochRef should not result in states being stored
|
# Getting an EpochRef should not result in states being stored
|
||||||
db.getStateRoot(stateCheckpoint.bid.root, stateCheckpoint.slot).isOk()
|
db.getStateRoot(stateCheckpoint.bid.root, stateCheckpoint.slot).isOk()
|
||||||
|
|
||||||
test "Randao skip and non-skip":
|
|
||||||
process_slots(
|
|
||||||
dag.cfg, state[], getStateField(
|
|
||||||
state[], slot) + 1, cache, info, {}).expect("can advance 1")
|
|
||||||
|
|
||||||
let
|
|
||||||
proposer_index = get_beacon_proposer_index(
|
|
||||||
state[], cache, getStateField(state[], slot))
|
|
||||||
privKey = MockPrivKeys[proposer_index.get]
|
|
||||||
randao_reveal = get_epoch_signature(
|
|
||||||
getStateField(state[], fork),
|
|
||||||
getStateField(state[], genesis_validators_root),
|
|
||||||
getStateField(state[], slot).epoch, privKey).toValidatorSig()
|
|
||||||
var bad_randao_reveal = randao_reveal
|
|
||||||
bad_randao_reveal.blob[5] += 1
|
|
||||||
|
|
||||||
block: # bad randao + no skip = bad
|
|
||||||
let
|
|
||||||
tmpState = assignClone(state[])
|
|
||||||
message = makeBeaconBlock(
|
|
||||||
dag.cfg, tmpState[], proposer_index.get(),
|
|
||||||
bad_randao_reveal,
|
|
||||||
getStateField(tmpState[], eth1_data),
|
|
||||||
default(GraffitiBytes), @[], @[], BeaconBlockExits(),
|
|
||||||
default(SyncAggregate), default(ExecutionPayload),
|
|
||||||
default(SignedBLSToExecutionChangeList),
|
|
||||||
noRollback, cache)
|
|
||||||
check: message.isErr
|
|
||||||
|
|
||||||
block: # bad randao + skip = bad
|
|
||||||
let
|
|
||||||
tmpState = assignClone(state[])
|
|
||||||
message = makeBeaconBlock(
|
|
||||||
dag.cfg, tmpState[], proposer_index.get(),
|
|
||||||
bad_randao_reveal,
|
|
||||||
getStateField(tmpState[], eth1_data),
|
|
||||||
default(GraffitiBytes), @[], @[], BeaconBlockExits(),
|
|
||||||
default(SyncAggregate), default(ExecutionPayload),
|
|
||||||
default(SignedBLSToExecutionChangeList),
|
|
||||||
noRollback, cache, {skipRandaoVerification})
|
|
||||||
check: message.isErr
|
|
||||||
|
|
||||||
block: # infinity + no skip = bad
|
|
||||||
let
|
|
||||||
tmpState = assignClone(state[])
|
|
||||||
message = makeBeaconBlock(
|
|
||||||
dag.cfg, tmpState[], proposer_index.get(),
|
|
||||||
ValidatorSig.infinity(),
|
|
||||||
getStateField(tmpState[], eth1_data),
|
|
||||||
default(GraffitiBytes), @[], @[], BeaconBlockExits(),
|
|
||||||
default(SyncAggregate), default(ExecutionPayload),
|
|
||||||
default(SignedBLSToExecutionChangeList),
|
|
||||||
noRollback, cache, {})
|
|
||||||
check: message.isErr
|
|
||||||
|
|
||||||
block: # Infinity + skip = ok!
|
|
||||||
let
|
|
||||||
tmpState = assignClone(state[])
|
|
||||||
message = makeBeaconBlock(
|
|
||||||
dag.cfg, tmpState[], proposer_index.get(),
|
|
||||||
ValidatorSig.infinity(),
|
|
||||||
getStateField(tmpState[], eth1_data),
|
|
||||||
default(GraffitiBytes), @[], @[], BeaconBlockExits(),
|
|
||||||
default(SyncAggregate), default(ExecutionPayload),
|
|
||||||
default(SignedBLSToExecutionChangeList),
|
|
||||||
noRollback, cache, {skipRandaoVerification})
|
|
||||||
check: message.isOk
|
|
||||||
|
|
||||||
test "Adding the same block twice returns a Duplicate error" & preset():
|
test "Adding the same block twice returns a Duplicate error" & preset():
|
||||||
let
|
let
|
||||||
b10 = dag.addHeadBlock(verifier, b1, nilPhase0Callback)
|
b10 = dag.addHeadBlock(verifier, b1, nilPhase0Callback)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user