Skip to content

Commit

Permalink
REST API produceBlockV3 implementation (#5474)
Browse files Browse the repository at this point in the history
Co-authored-by: Etan Kissling <etan@status.im>
Co-authored-by: Jacek Sieka <jacek@status.im>
  • Loading branch information
3 people committed Nov 28, 2023
1 parent 3a527d6 commit e2e4912
Show file tree
Hide file tree
Showing 11 changed files with 857 additions and 68 deletions.
5 changes: 3 additions & 2 deletions AllTests-mainnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -455,8 +455,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 @@ -721,4 +722,4 @@ OK: 2/2 Fail: 0/2 Skip: 0/2
OK: 9/9 Fail: 0/9 Skip: 0/9

---TOTAL---
OK: 410/415 Fail: 0/415 Skip: 5/415
OK: 411/416 Fail: 0/416 Skip: 5/416
196 changes: 170 additions & 26 deletions beacon_chain/rpc/rest_validator_api.nim
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
let data =
when consensusFork >= ConsensusFork.Deneb:
let blobsBundle = message.blobsBundleOpt.get()
DenebBlockContents(
deneb.BlockContents(
`block`: forkyBlck,
kzg_proofs: blobsBundle.proofs,
blobs: blobsBundle.blobs)
Expand Down Expand Up @@ -519,31 +519,175 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
Http500, "Unable to initialize payload builder client: " & $error)
contextFork = node.dag.cfg.consensusForkAtEpoch(node.currentSlot.epoch)

case contextFork
of ConsensusFork.Deneb:
# TODO
# We should return a block with sidecars here
# https://github.com/ethereum/beacon-APIs/pull/302/files
return RestApiResponse.jsonError(
Http400, "Deneb builder API beacon API not yet supported: " & $denebImplementationMissing)
of ConsensusFork.Capella:
let res = await makeBlindedBeaconBlockForHeadAndSlot[
capella_mev.BlindedBeaconBlock](
node, payloadBuilderClient, qrandao, proposer, qgraffiti, qhead, qslot)
if res.isErr():
return RestApiResponse.jsonError(Http400, res.error())
return responseVersioned(res.get().blindedBlckPart, contextFork)
of ConsensusFork.Bellatrix:
return RestApiResponse.jsonError(Http400, "Pre-Capella builder API unsupported")
of ConsensusFork.Altair, ConsensusFork.Phase0:
# Pre-Bellatrix, this endpoint will return a BeaconBlock
let res = await makeBeaconBlockForHeadAndSlot(
bellatrix.ExecutionPayloadForSigning, node, qrandao,
proposer, qgraffiti, qhead, qslot)
if res.isErr():
return RestApiResponse.jsonError(Http400, res.error())
withBlck(res.get().blck):
return responseVersioned(forkyBlck, contextFork)
withConsensusFork(contextFork):
when consensusFork >= ConsensusFork.Capella:
let res = await makeBlindedBeaconBlockForHeadAndSlot[
consensusFork.BlindedBeaconBlock](
node, payloadBuilderClient, qrandao,
proposer, qgraffiti, qhead, qslot)
if res.isErr():
return RestApiResponse.jsonError(Http400, res.error())
return responseVersioned(res.get().blindedBlckPart, contextFork)
elif consensusFork >= ConsensusFork.Bellatrix:
return RestApiResponse.jsonError(
Http400, "Pre-Capella builder API unsupported")
else:
# Pre-Bellatrix, this endpoint will return a BeaconBlock
let res = await makeBeaconBlockForHeadAndSlot(
bellatrix.ExecutionPayloadForSigning, node, qrandao,
proposer, qgraffiti, qhead, qslot)
if res.isErr():
return RestApiResponse.jsonError(Http400, res.error())
withBlck(res.get().blck):
return responseVersioned(forkyBlck, contextFork)

func getMaybeBlindedHeaders(
consensusFork: ConsensusFork,
isBlinded: bool,
executionValue: Opt[UInt256],
consensusValue: Opt[UInt256]): HttpTable =
var res = HttpTable.init()
res.add("eth-consensus-version", consensusFork.toString())
if isBlinded:
res.add("eth-execution-payload-blinded", "true")
else:
res.add("eth-execution-payload-blinded", "false")
if executionValue.isSome():
res.add("eth-execution-payload-value", $(executionValue.get()))
if consensusValue.isSome():
res.add("eth-consensus-block-value", $(consensusValue.get()))
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)
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)

withConsensusFork(node.dag.cfg.consensusForkAtEpoch(qslot.epoch)):
when consensusFork >= ConsensusFork.Capella:
let
message = (await node.makeMaybeBlindedBeaconBlockForHeadAndSlot(
consensusFork, qrandao, qgraffiti, qhead, qslot)).valueOr:
# HTTP 400 error is only for incorrect parameters.
return RestApiResponse.jsonError(Http500, error)
headers = consensusFork.getMaybeBlindedHeaders(
message.blck.isBlinded,
message.executionValue,
message.consensusValue)

if contentType == sszMediaType:
if message.blck.isBlinded:
RestApiResponse.sszResponse(message.blck.blindedData, headers)
else:
RestApiResponse.sszResponse(message.blck.data, headers)
elif contentType == jsonMediaType:
let forked =
if message.blck.isBlinded:
ForkedMaybeBlindedBeaconBlock.init(
message.blck.blindedData,
message.executionValue,
message.consensusValue)
else:
ForkedMaybeBlindedBeaconBlock.init(
message.blck.data,
message.executionValue,
message.consensusValue)
RestApiResponse.jsonResponsePlain(forked, headers)
else:
raiseAssert "preferredContentType() returns invalid content type"
else:
when consensusFork >= ConsensusFork.Bellatrix:
type PayloadType = consensusFork.ExecutionPayloadForSigning
else:
type PayloadType = bellatrix.ExecutionPayloadForSigning
let
message = (await PayloadType.makeBeaconBlockForHeadAndSlot(
node, qrandao, proposer, qgraffiti, qhead, qslot)).valueOr:
return RestApiResponse.jsonError(Http500, error)
executionValue = Opt.some(UInt256(message.blockValue))
consensusValue = Opt.none(UInt256)
headers = consensusFork.getMaybeBlindedHeaders(
isBlinded = false, executionValue, consensusValue)

doAssert message.blck.kind == consensusFork
template forkyBlck: untyped = message.blck.forky(consensusFork)
if contentType == sszMediaType:
RestApiResponse.sszResponse(forkyBlck, headers)
elif contentType == jsonMediaType:
let forked =
when consensusFork >= ConsensusFork.Bellatrix:
ForkedMaybeBlindedBeaconBlock.init(
forkyBlck, executionValue, consensusValue)
else:
ForkedMaybeBlindedBeaconBlock.init(forkyBlck)
RestApiResponse.jsonResponsePlain(forked, 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 (
Expand Down
6 changes: 0 additions & 6 deletions beacon_chain/spec/datatypes/base.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1023,12 +1023,6 @@ func checkForkConsistency*(cfg: RuntimeConfig) =
assertForkEpochOrder(cfg.BELLATRIX_FORK_EPOCH, cfg.CAPELLA_FORK_EPOCH)
assertForkEpochOrder(cfg.CAPELLA_FORK_EPOCH, cfg.DENEB_FORK_EPOCH)

# This is a readily/uniquely searchable token of where a false assertion is
# due to a Deneb implementation missing. checkForkConsistency() checks that
# Nimbus does not run any non-FAR_FUTURE_EPOCH Deneb network, so such cases
# won't be hit.
const denebImplementationMissing* = false

func ofLen*[T, N](ListType: type List[T, N], n: int): ListType =
if n < N:
distinctBase(result).setLen(n)
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 @@ -501,6 +501,11 @@ type
SigVerifiedBeaconBlockBody |
TrustedBeaconBlockBody

BlockContents* = object
`block`*: BeaconBlock
kzg_proofs*: KzgProofs
blobs*: Blobs

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

0 comments on commit e2e4912

Please sign in to comment.