Implementation for v2 REST API calls. (#2731)

* Implementation for v2 rest api calls.

* fix phase0 block deserialization

Co-authored-by: Jacek Sieka <jacek@status.im>
This commit is contained in:
Eugene Kabanov 2021-08-09 09:08:18 +03:00 committed by GitHub
parent 61825f4979
commit b69f566ed1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 239 additions and 74 deletions

View File

@ -13,7 +13,7 @@ import
../gossip_processing/gossip_validation,
../validators/validator_duties,
../spec/[crypto, digest, forkedbeaconstate_helpers, network],
../spec/datatypes/phase0,
../spec/datatypes/[phase0, altair],
../ssz/merkleization,
./rest_utils
@ -98,6 +98,13 @@ proc toString*(kind: ValidatorFilterKind): string =
of ValidatorFilterKind.WithdrawalDone:
"withdrawal_done"
proc getBeaconBlocksTopic(node: BeaconNode, kind: BeaconBlockFork): string =
case kind
of BeaconBlockFork.Phase0:
getBeaconBlocksTopic(node.dag.forkDigests.phase0)
of BeaconBlockFork.Altair:
getBeaconBlocksTopic(node.dag.forkDigests.altair)
proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getGenesis
router.api(MethodGet, "/api/eth/v1/beacon/genesis") do () -> RestApiResponse:
@ -143,9 +150,12 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
node.withStateForBlockSlot(bslot):
return RestApiResponse.jsonResponse(
(
previous_version: getStateField(stateData.data, fork).previous_version,
current_version: getStateField(stateData.data, fork).current_version,
epoch: getStateField(stateData.data, fork).epoch
previous_version:
getStateField(stateData.data, fork).previous_version,
current_version:
getStateField(stateData.data, fork).current_version,
epoch:
getStateField(stateData.data, fork).epoch
)
)
return RestApiResponse.jsonError(Http500, InternalServerError)
@ -289,7 +299,8 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
let vid = validator_id.get()
case vid.kind
of ValidatorQueryKind.Key:
for index, validator in getStateField(stateData.data, validators).pairs():
for index, validator in getStateField(stateData.data,
validators).pairs():
if validator.pubkey == vid.key:
let sres = validator.getStatus(current_epoch)
if sres.isOk():
@ -297,7 +308,9 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
(
index: ValidatorIndex(index),
balance:
Base10.toString(getStateField(stateData.data, balances)[index]),
Base10.toString(
getStateField(stateData.data, balances)[index]
),
status: toString(sres.get()),
validator: validator
)
@ -320,7 +333,8 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
UnsupportedValidatorIndexValueError)
vres.get()
if uint64(vindex) >= uint64(len(getStateField(stateData.data, validators))):
if uint64(vindex) >=
uint64(len(getStateField(stateData.data, validators))):
return RestApiResponse.jsonError(Http404, ValidatorNotFoundError)
let validator = getStateField(stateData.data, validators)[vindex]
let sres = validator.getStatus(current_epoch)
@ -328,7 +342,9 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
return RestApiResponse.jsonResponse(
(
index: vindex,
balance: Base10.toString(getStateField(stateData.data, balances)[vindex]),
balance: Base10.toString(
getStateField(stateData.data, balances)[vindex]
),
status: toString(sres.get()),
validator: validator
)
@ -499,7 +515,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockHeaders
router.api(MethodGet, "/api/eth/v1/beacon/headers") do (
slot: Option[Slot], parent_root: Option[Eth2Digest]) -> RestApiResponse:
# TODO (cheatfate): This call is not complete, because structure
# TODO (cheatfate): This call is incomplete, because structure
# of database do not allow to query blocks by `parent_root`.
let qslot =
if slot.isSome():
@ -532,24 +548,26 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
node.dag.get(blockSlot.blck)
return RestApiResponse.jsonResponse(
[(
# TODO Altair insofar as it should detect the error condition rather
# than crashing. This API is only specified for phase 0
root: bdata.data.phase0Block.root,
canonical: bdata.refs.isAncestorOf(node.dag.head),
header: (
message: (
slot: bdata.data.phase0Block.message.slot,
proposer_index: bdata.data.phase0Block.message.proposer_index,
parent_root: bdata.data.phase0Block.message.parent_root,
state_root: bdata.data.phase0Block.message.state_root,
body_root: bdata.data.phase0Block.message.body.hash_tree_root()
),
signature: bdata.data.phase0Block.signature
return
withBlck(bdata.data):
RestApiResponse.jsonResponse(
[
(
root: blck.root,
canonical: bdata.refs.isAncestorOf(node.dag.head),
header: (
message: (
slot: blck.message.slot,
proposer_index: blck.message.proposer_index,
parent_root: blck.message.parent_root,
state_root: blck.message.state_root,
body_root: blck.message.body.hash_tree_root()
),
signature: blck.signature
)
)
]
)
)]
)
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockHeader
router.api(MethodGet, "/api/eth/v1/beacon/headers/{block_id}") do (
@ -564,58 +582,92 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
res.get()
return RestApiResponse.jsonResponse(
(
# TODO for Altair, check that it's a phase 0 block and return error if
# not, since /v1/ APIs don't support Altair
root: bdata.data.phase0Block.root,
canonical: bdata.refs.isAncestorOf(node.dag.head),
header: (
message: (
slot: bdata.data.phase0Block.message.slot,
proposer_index: bdata.data.phase0Block.message.proposer_index,
parent_root: bdata.data.phase0Block.message.parent_root,
state_root: bdata.data.phase0Block.message.state_root,
body_root: bdata.data.phase0Block.message.body.hash_tree_root()
),
signature: bdata.data.phase0Block.signature
return
withBlck(bdata.data):
RestApiResponse.jsonResponse(
(
root: blck.root,
canonical: bdata.refs.isAncestorOf(node.dag.head),
header: (
message: (
slot: blck.message.slot,
proposer_index: blck.message.proposer_index,
parent_root: blck.message.parent_root,
state_root: blck.message.state_root,
body_root: blck.message.body.hash_tree_root()
),
signature: blck.signature
)
)
)
)
)
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/publishBlock
router.api(MethodPost, "/api/eth/v1/beacon/blocks") do (
contentBody: Option[ContentBody]) -> RestApiResponse:
let blck =
let blockData =
block:
if contentBody.isNone():
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
let dres = decodeBody(phase0.SignedBeaconBlock, contentBody.get())
if dres.isErr():
return RestApiResponse.jsonError(Http400, InvalidBlockObjectError,
$dres.error())
var res = dres.get()
# `SignedBeaconBlock` deserialization do not update `root` field, so we
# need to calculate it.
res.root = hash_tree_root(res.message)
res
let body = contentBody.get()
let altairRes = decodeBody(altair.SignedBeaconBlock, body)
if altairRes.isOk():
var res = altairRes.get()
if res.message.slot.epoch < node.dag.cfg.ALTAIR_FORK_EPOCH:
# This message deserialized successfully as altair but should
# actually be a phase0 block - try again with phase0
let phase0res = decodeBody(phase0.SignedBeaconBlock, body)
if phase0res.isOk():
var res = phase0res.get()
# `SignedBeaconBlock` deserialization do not update `root` field,
# so we need to calculate it.
res.root = hash_tree_root(res.message)
ForkedSignedBeaconBlock.init(res)
else:
return RestApiResponse.jsonError(Http400, InvalidBlockObjectError,
$phase0res.error())
else:
# `SignedBeaconBlock` deserialization do not update `root` field,
# so we need to calculate it.
res.root = hash_tree_root(res.message)
ForkedSignedBeaconBlock.init(res)
else:
let phase0res = decodeBody(phase0.SignedBeaconBlock, body)
if phase0res.isOk():
var res = phase0res.get()
# `SignedBeaconBlock` deserialization do not update `root` field,
# so we need to calculate it.
res.root = hash_tree_root(res.message)
ForkedSignedBeaconBlock.init(res)
else:
return RestApiResponse.jsonError(Http400, InvalidBlockObjectError,
$phase0res.error())
let head = node.dag.head
if not(node.isSynced(head)):
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
if head.slot >= blck.message.slot:
# TODO altair-transition, but not for immediate testnet-priority
let blocksTopic = getBeaconBlocksTopic(node.dag.forkDigests.phase0)
node.network.broadcast(blocksTopic, blck)
if head.slot >= blockData.slot():
let blocksTopic = node.getBeaconBlocksTopic(blockData.kind)
withBlck(blockData):
node.network.broadcast(blocksTopic, blck)
return RestApiResponse.jsonError(Http202, BlockValidationError)
else:
let res = await proposeSignedBlock(
node, head, AttachedValidator(), blck)
let res =
when compiles(node.proposeSignedBlock(head, AttachedValidator(),
blockData)):
await node.proposeSignedBlock(head, AttachedValidator(), blockData)
else:
case blockData.kind
of BeaconBlockFork.Phase0:
await node.proposeSignedBlock(head, AttachedValidator(),
blockData.phase0Block)
of BeaconBlockFork.Altair:
head
if res == head:
# TODO altair-transition, but not for immediate testnet-priority
let blocksTopic = getBeaconBlocksTopic(node.dag.forkDigests.phase0)
node.network.broadcast(blocksTopic, blck)
let blocksTopic = node.getBeaconBlocksTopic(blockData.kind)
withBlck(blockData):
node.network.broadcast(blocksTopic, blck)
return RestApiResponse.jsonError(Http202, BlockValidationError)
else:
return RestApiResponse.jsonMsgResponse(BlockValidationSuccess)
@ -632,8 +684,35 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
if res.isErr():
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
res.get()
static: doAssert bdata.data.phase0Block is phase0.TrustedSignedBeaconBlock
return RestApiResponse.jsonResponse(bdata.data.phase0Block)
return
case bdata.data.kind
of BeaconBlockFork.Phase0:
RestApiResponse.jsonResponse(bdata.data.phase0Block)
of BeaconBlockFork.Altair:
RestApiResponse.jsonError(Http404, BlockNotFoundError)
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockV2
router.api(MethodGet, "/api/eth/v2/beacon/blocks/{block_id}") do (
block_id: BlockIdent) -> RestApiResponse:
let bdata =
block:
if block_id.isErr():
return RestApiResponse.jsonError(Http400, InvalidBlockIdValueError,
$block_id.error())
let res = node.getBlockDataFromBlockIdent(block_id.get())
if res.isErr():
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
res.get()
return
case bdata.data.kind
of BeaconBlockFork.Phase0:
RestApiResponse.jsonResponse(
(version: "phase0", data: bdata.data.phase0Block)
)
of BeaconBlockFork.Altair:
RestApiResponse.jsonResponse(
(version: "altair", data: bdata.data.altairBlock)
)
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockRoot
router.api(MethodGet, "/api/eth/v1/beacon/blocks/{block_id}/root") do (
@ -647,8 +726,9 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
if res.isErr():
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
res.get()
# TODO check whether block is altair, and if so, return error
return RestApiResponse.jsonResponse((root: bdata.data.phase0Block.root))
return
withBlck(bdata.data):
RestApiResponse.jsonResponse((root: blck.root))
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockAttestations
router.api(MethodGet,
@ -663,10 +743,9 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
if res.isErr():
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
res.get()
# TODO check whether block is altair, and if so, return error
return RestApiResponse.jsonResponse(
bdata.data.phase0Block.message.body.attestations.asSeq()
)
return
withBlck(bdata.data):
RestApiResponse.jsonResponse(blck.message.body.attestations.asSeq())
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getPoolAttestations
router.api(MethodGet, "/api/eth/v1/beacon/pool/attestations") do (
@ -712,7 +791,8 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
var failures: seq[RestAttestationsFailure]
for atindex, attestation in attestations.pairs():
debug "Attestation for pool", attestation = attestation, signature = $attestation.signature
debug "Attestation for pool", attestation = attestation,
signature = $attestation.signature
if not await node.sendAttestation(attestation):
failures.add(RestAttestationsFailure(
index: uint64(atindex), message: "Attestation failed validation"))
@ -885,6 +965,11 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
"/eth/v1/beacon/blocks/{block_id}",
"/api/eth/v1/beacon/blocks/{block_id}"
)
router.redirect(
MethodGet,
"/eth/v2/beacon/blocks/{block_id}",
"/api/eth/v2/beacon/blocks/{block_id}"
)
router.redirect(
MethodGet,
"/eth/v1/beacon/blocks/{block_id}/root",

View File

@ -13,7 +13,7 @@ import
../gossip_processing/gossip_validation,
../validators/validator_duties,
../spec/[crypto, digest, forkedbeaconstate_helpers, network],
../spec/datatypes/base,
../spec/datatypes/[base, phase0],
../ssz/merkleization,
./rest_utils
@ -155,7 +155,6 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
return RestApiResponse.jsonError(Http400, InvalidSlotValueError,
$slot.error())
slot.get()
let qrandao =
if randao_reveal.isNone():
return RestApiResponse.jsonError(Http400, MissingRandaoRevealValue)
@ -194,8 +193,84 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
if res.isNone():
return RestApiResponse.jsonError(Http400, BlockProduceError)
res.get()
return
when message is phase0.BeaconBlock:
# TODO (cheatfate): This could be removed when `altair` branch will be
# merged.
RestApiResponse.jsonResponse(message)
else:
case message.kind
of BeaconBlockFork.Phase0:
RestApiResponse.jsonResponse(message.phase0Block)
of BeaconBlockFork.Altair:
return RestApiResponse.jsonError(Http400, BlockProduceError)
return RestApiResponse.jsonResponse(message)
router.api(MethodGet, "/api/eth/v2/validator/blocks/{slot}") do (
slot: Slot, randao_reveal: Option[ValidatorSig],
graffiti: Option[GraffitiBytes]) -> RestApiResponse:
let message =
block:
let qslot =
block:
if slot.isErr():
return RestApiResponse.jsonError(Http400, InvalidSlotValueError,
$slot.error())
slot.get()
let 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()
let qgraffiti =
if graffiti.isNone():
defaultGraffitiBytes()
else:
let res = graffiti.get()
if res.isErr():
return RestApiResponse.jsonError(Http400,
InvalidGraffitiBytesValye,
$res.error())
res.get()
let qhead =
block:
let res = node.getCurrentHead(qslot)
if res.isErr():
if not(node.isSynced(node.dag.head)):
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
else:
return RestApiResponse.jsonError(Http400, NoHeadForSlotError,
$res.error())
res.get()
let proposer = node.dag.getProposer(qhead, qslot)
if proposer.isNone():
return RestApiResponse.jsonError(Http400, ProposerNotFoundError)
let res = await makeBeaconBlockForHeadAndSlot(
node, qrandao, proposer.get(), qgraffiti, qhead, qslot)
if res.isNone():
return RestApiResponse.jsonError(Http400, BlockProduceError)
res.get()
return
when message is phase0.BeaconBlock:
# TODO (cheatfate): This could be removed when `altair` branch will be
# merged.
RestApiResponse.jsonResponse(
(version: "phase0", data: message)
)
else:
case message.kind
of BeaconBlockFork.Phase0:
RestApiResponse.jsonResponse(
(version: "phase0", data: message.phase0Block)
)
of BeaconBlockFork.Altair:
RestApiResponse.jsonResponse(
(version: "altair", data: message.altairBlock)
)
# https://ethereum.github.io/eth2.0-APIs/#/Validator/produceAttestationData
router.api(MethodGet, "/api/eth/v1/validator/attestation_data") do (
@ -366,6 +441,11 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
"/eth/v1/validator/blocks/{slot}",
"/api/eth/v1/validator/blocks/{slot}"
)
router.redirect(
MethodGet,
"/eth/v2/validator/blocks/{slot}",
"/api/eth/v2/validator/blocks/{slot}"
)
router.redirect(
MethodGet,
"/eth/v1/validator/attestation_data",