Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REST API produceBlockV3 implementation #5474

Merged
merged 22 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions AllTests-mainnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,8 +456,9 @@ OK: 1/1 Fail: 0/1 Skip: 0/1
```diff
+ RestErrorMessage parser tests OK
+ RestErrorMessage writer tests OK
+ strictParse(Stuint) tests OK
```
OK: 2/2 Fail: 0/2 Skip: 0/2
OK: 3/3 Fail: 0/3 Skip: 0/3
## Shufflings
```diff
+ Accelerated shuffling computation OK
Expand Down Expand Up @@ -715,4 +716,4 @@ OK: 2/2 Fail: 0/2 Skip: 0/2
OK: 9/9 Fail: 0/9 Skip: 0/9

---TOTAL---
OK: 404/409 Fail: 0/409 Skip: 5/409
OK: 405/410 Fail: 0/410 Skip: 5/410
117 changes: 117 additions & 0 deletions beacon_chain/rpc/rest_validator_api.nim
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,123 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
withBlck(res.get().blck):
return responseVersioned(forkyBlck, contextFork)

func getHeaders(forked: ForkedAndBlindedBeaconBlock): HttpTable =
var res = HttpTable.init()
let blinded =
if forked.kind in {ConsensusBlindedFork.CapellaBlinded,
ConsensusBlindedFork.DenebBlinded}:
"true"
else:
"false"
res.add("eth-consensus-version", toString(forked.kind))
res.add("eth-execution-payload-blinded", blinded)
if forked.executionValue.isSome():
res.add("eth-execution-payload-value", $(forked.executionValue.get()))
if forked.consensusValue.isSome():
res.add("eth-consensus-payload-value", $(forked.consensusValue.get()))
etan-status marked this conversation as resolved.
Show resolved Hide resolved
res

# https://ethereum.github.io/beacon-APIs/#/Validator/produceBlockV3
router.api(MethodGet, "/eth/v3/validator/blocks/{slot}") do (
slot: Slot, randao_reveal: Option[ValidatorSig],
graffiti: Option[GraffitiBytes],
skip_randao_verification: Option[string]) -> RestApiResponse:
let
contentType = preferredContentType(jsonMediaType, sszMediaType).valueOr:
return RestApiResponse.jsonError(Http406, ContentNotAcceptableError)
let message =
block:
let
qslot =
block:
if slot.isErr():
return RestApiResponse.jsonError(Http400, InvalidSlotValueError,
$slot.error())
let res = slot.get()

if res <= node.dag.finalizedHead.slot:
return RestApiResponse.jsonError(Http400, InvalidSlotValueError,
"Slot already finalized")
let wallTime =
node.beaconClock.now() + MAXIMUM_GOSSIP_CLOCK_DISPARITY
if res > wallTime.slotOrZero:
return RestApiResponse.jsonError(Http400, InvalidSlotValueError,
"Slot cannot be in the future")
res
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
qrandao =
if randao_reveal.isNone():
return RestApiResponse.jsonError(Http400,
MissingRandaoRevealValue)
else:
let res = randao_reveal.get()
if res.isErr():
return RestApiResponse.jsonError(Http400,
InvalidRandaoRevealValue,
$res.error())
res.get()
qgraffiti =
if graffiti.isNone():
defaultGraffitiBytes()
else:
let res = graffiti.get()
if res.isErr():
return RestApiResponse.jsonError(Http400,
InvalidGraffitiBytesValue,
$res.error())
res.get()
qhead =
block:
let res = node.getSyncedHead(qslot)
if res.isErr():
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError,
$res.error())
let tres = res.get()
if not tres.executionValid:
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
tres
proposer = node.dag.getProposer(qhead, qslot).valueOr:
return RestApiResponse.jsonError(Http400, ProposerNotFoundError)

if not node.verifyRandao(
qslot, proposer, qrandao, qskip_randao_verification):
return RestApiResponse.jsonError(Http400, InvalidRandaoRevealValue)

(await node.makeBeaconBlockForHeadAndSlotV3(qrandao, qgraffiti, qhead,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It apparently needs ForkedAndBlindedBeaconBlock here primarily because it's storing one as message, but then it immediately does two things with this message:

  1. calls getHeaders (defined above); and
  2. calls RestApiResponse.sszResponse, not even on the Forked type per se, but on each specific case object field. See question there about why writeValue is defined on ForkedAndBlindedBeaconBlock, as a result.

But neither really requires storage of a forked/"any fork" version of a BlindedBeaconBlock or BlindedBeaconBlockContents, because what this produceBlockV3 endpoint returns is neither, but a bytestring of some sort (e.g., SSZ). So it's still unclear to me why have all this infrastructure for a ForkedAndBlindedBeaconBlock which seems not to be necessary to begin with.

The result is hundreds of lines of

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need some form of ForkedBlindedBeaconBlock, because v3 may return either forked or non-forked depending on which one is better. And that is not known at call-time.

This, unless you want to do that logic inside the REST layer, which doesn't seem correct, because it makes it non-reusable, e.g., for the non-VC local block proposal flow.

Also having a Forked type instead of forky MaybeBlindedBeaconBlock makes it consistent with the existing v1/v2 makeBeaconBlockForHeadAndSlot which also returns a ForkedBeaconBlock.

qslot)).valueOr:
# HTTP 400 error is only for incorrect parameters.
return RestApiResponse.jsonError(Http500, error)

let headers = message.getHeaders()
if contentType == sszMediaType:
case message.kind
of ConsensusBlindedFork.Phase0:
RestApiResponse.sszResponse(message.phase0Data, headers)
tersec marked this conversation as resolved.
Show resolved Hide resolved
of ConsensusBlindedFork.Altair:
RestApiResponse.sszResponse(message.altairData, headers)
of ConsensusBlindedFork.Bellatrix:
RestApiResponse.sszResponse(message.bellatrixData, headers)
of ConsensusBlindedFork.Capella:
RestApiResponse.sszResponse(message.capellaData, headers)
of ConsensusBlindedFork.CapellaBlinded:
RestApiResponse.sszResponse(message.capellaBlinded, headers)
of ConsensusBlindedFork.Deneb:
RestApiResponse.sszResponse(message.denebData, headers)
of ConsensusBlindedFork.DenebBlinded:
RestApiResponse.sszResponse(message.denebBlinded, headers)
elif contentType == jsonMediaType:
RestApiResponse.jsonResponsePlain(message, headers)
else:
raiseAssert "preferredContentType() returns invalid content type"

# https://ethereum.github.io/beacon-APIs/#/Validator/produceAttestationData
router.api(MethodGet, "/eth/v1/validator/attestation_data") do (
slot: Option[Slot],
Expand Down
5 changes: 5 additions & 0 deletions beacon_chain/spec/datatypes/deneb.nim
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,11 @@ type
SigVerifiedBeaconBlockBody |
TrustedBeaconBlockBody

BlockContents* = object
`block`*: BeaconBlock
blob_sidecars*:
List[BlobSidecar, Limit MAX_BLOBS_PER_BLOCK]

# TODO: There should be only a single generic HashedBeaconState definition
func initHashedBeaconState*(s: BeaconState): HashedBeaconState =
HashedBeaconState(data: s)
Expand Down
Loading