Change json serialization framework.
Make all calls which are possible.
This commit is contained in:
parent
b7f36be73c
commit
a1303f3e50
|
@ -37,7 +37,7 @@ import
|
|||
slashing_protection, keystore_management],
|
||||
./sync/[sync_manager, sync_protocol, request_manager],
|
||||
./rpc/[rest_utils, config_rest_api, debug_rest_api, node_rest_api,
|
||||
beacon_rest_api],
|
||||
beacon_rest_api, event_rest_api, validator_rest_api, nimbus_rest_api],
|
||||
./rpc/[beacon_api, config_api, debug_api, event_api, nimbus_api, node_api,
|
||||
validator_api],
|
||||
./spec/[
|
||||
|
@ -1192,10 +1192,10 @@ proc installRestHandlers(restServer: RestServerRef, node: BeaconNode) =
|
|||
restServer.router.installBeaconApiHandlers(node)
|
||||
restServer.router.installConfigApiHandlers(node)
|
||||
restServer.router.installDebugApiHandlers(node)
|
||||
# restServer.router.installEventApiHandlers(node)
|
||||
# restServer.router.installNimbusApiHandlers(node)
|
||||
restServer.router.installEventApiHandlers(node)
|
||||
restServer.router.installNimbusApiHandlers(node)
|
||||
restServer.router.installNodeApiHandlers(node)
|
||||
# restServer.router.installValidatorApiHandlers(node)
|
||||
restServer.router.installValidatorApiHandlers(node)
|
||||
|
||||
proc installMessageValidators(node: BeaconNode) =
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.1/specs/phase0/p2p-interface.md#attestations-and-aggregation
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
||||
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import ../spec/datatypes except readValue, writeValue
|
||||
import
|
||||
std/[typetraits, sequtils, strutils, deques, sets, options],
|
||||
stew/[results, base10],
|
||||
|
@ -12,9 +10,11 @@ import
|
|||
nimcrypto/utils as ncrutils,
|
||||
../beacon_node_common, ../networking/eth2_network,
|
||||
../consensus_object_pools/[blockchain_dag, exit_pool],
|
||||
../spec/[crypto, digest, validator],
|
||||
../gossip_processing/gossip_validation,
|
||||
../validators/validator_duties,
|
||||
../spec/[crypto, digest, validator, datatypes, network],
|
||||
../ssz/merkleization,
|
||||
./rest_utils
|
||||
./eth2_json_rest_serialization, ./rest_utils
|
||||
|
||||
logScope: topics = "rest_beaconapi"
|
||||
|
||||
|
@ -29,26 +29,14 @@ type
|
|||
index: ValidatorIndex
|
||||
balance: string
|
||||
|
||||
StateCommitteeTuple = tuple
|
||||
BeaconStatesCommitteesTuple* = tuple
|
||||
index: CommitteeIndex
|
||||
slot: Slot
|
||||
validators: seq[ValidatorIndex]
|
||||
|
||||
# # BlockHeaderMessageTuple = tuple
|
||||
# # slot: Slot
|
||||
# # proposer_index: string
|
||||
# # parent_root: Eth2Digest
|
||||
# # state_root: Eth2Digest
|
||||
# # body_root: Eth2Digest
|
||||
|
||||
# # SignedHeaderMessageTuple = tuple
|
||||
# # message: BlockHeaderMessageTuple
|
||||
# # signature: ValidatorSig
|
||||
|
||||
# # BlockHeaderTuple = tuple
|
||||
# # root: Eth2Digest
|
||||
# # canonical: bool
|
||||
# # header: SignedHeaderMessageTuple
|
||||
FailureTuple* = tuple
|
||||
index: uint64
|
||||
message: string
|
||||
|
||||
proc validateFilter(filters: seq[ValidatorFilter]): Result[ValidatorFilter,
|
||||
cstring] =
|
||||
|
@ -108,12 +96,6 @@ proc getStatus(validator: Validator,
|
|||
else:
|
||||
err("Invalid validator status")
|
||||
|
||||
proc toString*(digest: Eth2Digest): string =
|
||||
"0x" & ncrutils.toHex(digest.data, true)
|
||||
|
||||
proc toString*(version: Version): string =
|
||||
"0x" & ncrutils.toHex(cast[array[4, byte]](version))
|
||||
|
||||
proc toString*(kind: ValidatorFilterKind): string =
|
||||
case kind
|
||||
of ValidatorFilterKind.PendingInitialized:
|
||||
|
@ -135,214 +117,12 @@ proc toString*(kind: ValidatorFilterKind): string =
|
|||
of ValidatorFilterKind.WithdrawalDone:
|
||||
"withdrawal_done"
|
||||
|
||||
proc `%`*(s: Epoch): JsonNode = newJString(Base10.toString(uint64(s)))
|
||||
proc `%`*(s: Slot): JsonNode = newJString(Base10.toString(uint64(s)))
|
||||
proc `%`*(s: uint64): JsonNode = newJString(Base10.toString(s))
|
||||
proc `%`*(s: ValidatorIndex): JsonNode = newJString(Base10.toString(uint64(s)))
|
||||
proc `%`*(s: CommitteeIndex): JsonNode = newJString(Base10.toString(uint64(s)))
|
||||
proc `%`*(s: Checkpoint): JsonNode = %(epoch: s.epoch, root: s.root)
|
||||
proc `%`*(s: GraffitiBytes): JsonNode =
|
||||
newJString("0x" & ncrutils.toHex(distinctBase(s), true))
|
||||
proc `%`*(s: ValidatorSig): JsonNode =
|
||||
newJString("0x" & ncrutils.toHex(toRaw(s), true))
|
||||
proc `%`*(pubkey: ValidatorPubKey): JsonNode =
|
||||
newJString("0x" & ncrutils.toHex(toRaw(pubkey), true))
|
||||
proc `%`*(digest: Eth2Digest): JsonNode =
|
||||
newJString("0x" & ncrutils.toHex(digest.data, true))
|
||||
proc `%`*(bitlist: BitList): JsonNode =
|
||||
newJString("0x" & ncrutils.toHex(seq[byte](BitSeq(bitlist)), true))
|
||||
proc `%`*(s: Validator): JsonNode =
|
||||
let activation_eligibility_epoch =
|
||||
if s.activation_eligibility_epoch < 1:
|
||||
FarFutureEpochString
|
||||
else:
|
||||
Base10.toString(uint64(s.activation_eligibility_epoch))
|
||||
let activation_epoch =
|
||||
if s.activation_epoch < 1:
|
||||
FarFutureEpochString
|
||||
else:
|
||||
Base10.toString(uint64(s.activation_epoch))
|
||||
let exit_epoch =
|
||||
if s.exit_epoch < 1:
|
||||
FarFutureEpochString
|
||||
else:
|
||||
Base10.toString(uint64(s.exit_epoch))
|
||||
let withdrawable_epoch =
|
||||
if s.withdrawable_epoch < 1:
|
||||
FarFutureEpochString
|
||||
else:
|
||||
Base10.toString(uint64(s.withdrawable_epoch))
|
||||
%(
|
||||
pubkey: s.pubkey,
|
||||
withdrawal_credentials: s.withdrawal_credentials,
|
||||
effective_balance: Base10.toString(s.effective_balance),
|
||||
slashed: s.slashed,
|
||||
activation_eligibility_epoch: activation_eligibility_epoch,
|
||||
activation_epoch: activation_epoch,
|
||||
exit_epoch: exit_epoch,
|
||||
withdrawable_epoch: withdrawable_epoch
|
||||
)
|
||||
proc `%`*(s: AttestationData): JsonNode =
|
||||
%(
|
||||
slot: s.slot,
|
||||
index: Base10.toString(s.index),
|
||||
beacon_block_root: s.beacon_block_root,
|
||||
source: s.source,
|
||||
target: s.target
|
||||
)
|
||||
proc `%`*(s: TrustedAttestation): JsonNode =
|
||||
%(
|
||||
aggregation_bits: s.aggregation_bits,
|
||||
signature: cast[ValidatorSig](s.signature),
|
||||
data: s.data
|
||||
)
|
||||
proc `%`*(s: Attestation): JsonNode =
|
||||
%(
|
||||
aggregation_bits: s.aggregation_bits,
|
||||
signature: s.signature,
|
||||
data: s.data
|
||||
)
|
||||
proc `%`*(s: VoluntaryExit): JsonNode =
|
||||
%(epoch: s.epoch, validator_index: Base10.toString(s.validator_index))
|
||||
proc `%`*(s: SignedVoluntaryExit): JsonNode =
|
||||
%(message: s.message, signature: s.signature)
|
||||
proc `%`*(s: DepositData): JsonNode =
|
||||
%(
|
||||
pubkey: s.pubkey,
|
||||
withdrawal_credentials: s.withdrawal_credentials,
|
||||
amount: Base10.toString(s.amount),
|
||||
signature: s.signature
|
||||
)
|
||||
proc `%`*(s: Deposit): JsonNode =
|
||||
%(proof: s.proof, data: s.data)
|
||||
proc `%`*(s: BeaconBlockHeader): JsonNode =
|
||||
%(
|
||||
slot: s.slot,
|
||||
proposer_index: Base10.toString(s.proposer_index),
|
||||
parent_root: s.parent_root,
|
||||
state_root: s.state_root,
|
||||
body_root: s.body_root,
|
||||
)
|
||||
proc `%`*(s: SignedBeaconBlockHeader): JsonNode =
|
||||
%(message: s.message, signature: s.signature)
|
||||
proc `%`*(s: ProposerSlashing): JsonNode =
|
||||
%(signed_header_1: s.signed_header_1, signed_header_2: s.signed_header_2)
|
||||
proc `%`*(s: IndexedAttestation): JsonNode =
|
||||
%(
|
||||
attesting_indices: s.attesting_indices,
|
||||
data: s.data,
|
||||
signature: s.signature
|
||||
)
|
||||
proc `%`*(s: AttesterSlashing): JsonNode =
|
||||
%(attestation_1: s.attestation_1, attestation_2: s.attestation_2)
|
||||
proc `%`*(s: Eth1Data): JsonNode =
|
||||
%(
|
||||
deposit_root: s.deposit_root,
|
||||
deposit_count: Base10.toString(s.deposit_count),
|
||||
block_hash: s.block_hash
|
||||
)
|
||||
proc `%`*(s: TrustedBeaconBlockBody): JsonNode =
|
||||
%(
|
||||
randao_reveal: cast[ValidatorSig](s.randao_reveal),
|
||||
graffiti: s.graffiti,
|
||||
proposer_slashings: s.proposer_slashings,
|
||||
attester_slashings: s.attester_slashings,
|
||||
attestations: s.attestations,
|
||||
deposits: s.deposits,
|
||||
voluntary_exits: s.voluntary_exits
|
||||
)
|
||||
proc `%`*(s: TrustedBeaconBlock): JsonNode =
|
||||
%(
|
||||
slot: s.slot,
|
||||
proposer_index: Base10.toString(s.proposer_index),
|
||||
parent_root: s.parent_root,
|
||||
state_root: s.state_root,
|
||||
body: s.body
|
||||
)
|
||||
proc `%`*(s: TrustedSignedBeaconBlock): JsonNode =
|
||||
%(message: s.message, signature: cast[ValidatorSig](s.signature))
|
||||
|
||||
proc readValue*(reader: var JsonReader, value: var ValidatorSig)
|
||||
{.raises: [IOError, SerializationError, Defect].} =
|
||||
let hexValue = reader.readValue(string)
|
||||
let res = ValidatorSig.fromHex(hexValue)
|
||||
if res.isOk():
|
||||
value = res.get()
|
||||
else:
|
||||
reader.raiseUnexpectedValue($res.error())
|
||||
|
||||
proc readValue*(reader: var JsonReader, value: var Epoch)
|
||||
{.raises: [IOError, SerializationError, Defect].} =
|
||||
let svalue = reader.readValue(string)
|
||||
let res = Base10.decode(uint64, svalue)
|
||||
if res.isOk():
|
||||
value = Epoch(res.get())
|
||||
else:
|
||||
reader.raiseUnexpectedValue($res.error())
|
||||
|
||||
proc readValue*(reader: var JsonReader, value: var Slot)
|
||||
{.raises: [IOError, SerializationError, Defect].} =
|
||||
let svalue = reader.readValue(string)
|
||||
let res = Base10.decode(uint64, svalue)
|
||||
if res.isOk():
|
||||
value = Slot(res.get())
|
||||
else:
|
||||
reader.raiseUnexpectedValue($res.error())
|
||||
|
||||
proc readValue*(reader: var JsonReader, value: var uint64)
|
||||
{.raises: [IOError, SerializationError, Defect].} =
|
||||
let svalue = reader.readValue(string)
|
||||
let res = Base10.decode(uint64, svalue)
|
||||
if res.isOk():
|
||||
value = res.get()
|
||||
else:
|
||||
reader.raiseUnexpectedValue($res.error())
|
||||
|
||||
proc readValue*(reader: var JsonReader, value: var uint32)
|
||||
{.raises: [IOError, SerializationError, Defect].} =
|
||||
let svalue = reader.readValue(string)
|
||||
let res = Base10.decode(uint32, svalue)
|
||||
if res.isOk():
|
||||
value = res.get()
|
||||
else:
|
||||
reader.raiseUnexpectedValue($res.error())
|
||||
|
||||
proc readValue*(reader: var JsonReader, value: var ValidatorIndex)
|
||||
{.raises: [IOError, SerializationError, Defect].} =
|
||||
let svalue = reader.readValue(string)
|
||||
let res = Base10.decode(uint64, svalue)
|
||||
if res.isOk():
|
||||
let v = res.get()
|
||||
if v < VALIDATOR_REGISTRY_LIMIT:
|
||||
value = ValidatorIndex(v)
|
||||
else:
|
||||
reader.raiseUnexpectedValue(
|
||||
"Validator index is bigger then VALIDATOR_REGISTRY_LIMIT")
|
||||
else:
|
||||
reader.raiseUnexpectedValue($res.error())
|
||||
|
||||
proc decodeBody*[T](t: typedesc[T],
|
||||
body: ContentBody): Result[T, cstring] =
|
||||
if body.contentType != "application/json":
|
||||
return err("Unsupported content type")
|
||||
warn "Decoding data", data = cast[string](body.data)
|
||||
let data =
|
||||
try:
|
||||
Json.decode(cast[string](body.data), T)
|
||||
except SerializationError as exc:
|
||||
warn "Error happens while processing json", errMsg = exc.formatMsg("tmp.nim")
|
||||
return err("Unable to process data")
|
||||
except CatchableError as exc:
|
||||
warn "Error happens while parsing json", exc = exc.name, excMsg = exc.msg
|
||||
return err("Unable to parse application/json data")
|
||||
ok(data)
|
||||
|
||||
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:
|
||||
return RestApiResponse.jsonResponse(
|
||||
%(
|
||||
genesis_time: toString(node.chainDag.headState.data.data.genesis_time),
|
||||
(
|
||||
genesis_time: node.chainDag.headState.data.data.genesis_time,
|
||||
genesis_validators_root:
|
||||
node.chainDag.headState.data.data.genesis_validators_root,
|
||||
genesis_fork_version: node.runtimePreset.GENESIS_FORK_VERSION
|
||||
|
@ -352,30 +132,36 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateRoot
|
||||
router.api(MethodGet, "/api/eth/v1/beacon/states/{state_id}/root") do (
|
||||
state_id: StateIdent) -> RestApiResponse:
|
||||
if state_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid state_id",
|
||||
$state_id.error())
|
||||
let bres = node.getBlockSlot(state_id.get())
|
||||
if bres.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "State not found",
|
||||
$bres.error())
|
||||
node.withStateForStateIdent(bres.get()):
|
||||
return RestApiResponse.jsonResponse(%(root: hashedState().root))
|
||||
let bslot =
|
||||
block:
|
||||
if state_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid state_id",
|
||||
$state_id.error())
|
||||
let bres = node.getBlockSlot(state_id.get())
|
||||
if bres.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "State not found",
|
||||
$bres.error())
|
||||
bres.get()
|
||||
node.withStateForBlockSlot(bslot):
|
||||
return RestApiResponse.jsonResponse((root: hashedState().root))
|
||||
return RestApiResponse.jsonError(Http500, "Internal server error")
|
||||
|
||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateFork
|
||||
router.api(MethodGet, "/api/eth/v1/beacon/states/{state_id}/fork") do (
|
||||
state_id: StateIdent) -> RestApiResponse:
|
||||
if state_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid state_id",
|
||||
$state_id.error())
|
||||
let bres = node.getBlockSlot(state_id.get())
|
||||
if bres.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "State not found",
|
||||
$bres.error())
|
||||
node.withStateForStateIdent(bres.get()):
|
||||
let bslot =
|
||||
block:
|
||||
if state_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid state_id",
|
||||
$state_id.error())
|
||||
let bres = node.getBlockSlot(state_id.get())
|
||||
if bres.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "State not found",
|
||||
$bres.error())
|
||||
bres.get()
|
||||
node.withStateForBlockSlot(bslot):
|
||||
return RestApiResponse.jsonResponse(
|
||||
%(
|
||||
(
|
||||
previous_version: state().fork.previous_version,
|
||||
current_version: state().fork.current_version,
|
||||
epoch: state().fork.epoch
|
||||
|
@ -387,19 +173,22 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
router.api(MethodGet,
|
||||
"/api/eth/v1/beacon/states/{state_id}/finality_checkpoints") do (
|
||||
state_id: StateIdent) -> RestApiResponse:
|
||||
if state_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid state_id",
|
||||
$state_id.error())
|
||||
let bres = node.getBlockSlot(state_id.get())
|
||||
if bres.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "State not found",
|
||||
$bres.error())
|
||||
node.withStateForStateIdent(bres.get()):
|
||||
let bslot =
|
||||
block:
|
||||
if state_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid state_id",
|
||||
$state_id.error())
|
||||
let bres = node.getBlockSlot(state_id.get())
|
||||
if bres.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "State not found",
|
||||
$bres.error())
|
||||
bres.get()
|
||||
node.withStateForBlockSlot(bslot):
|
||||
return RestApiResponse.jsonResponse(
|
||||
%(
|
||||
previous_justified: state().previous_justified_checkpoint,
|
||||
current_justified: state().current_justified_checkpoint,
|
||||
finalized: state().finalized_checkpoint
|
||||
(
|
||||
previous_justified: state().previous_justified_checkpoint,
|
||||
current_justified: state().current_justified_checkpoint,
|
||||
finalized: state().finalized_checkpoint
|
||||
)
|
||||
)
|
||||
return RestApiResponse.jsonError(Http500, "Internal server error")
|
||||
|
@ -408,9 +197,16 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
router.api(MethodGet, "/api/eth/v1/beacon/states/{state_id}/validators") do (
|
||||
state_id: StateIdent, id: seq[ValidatorIdent],
|
||||
status: seq[ValidatorFilter]) -> RestApiResponse:
|
||||
if state_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid state_id",
|
||||
$state_id.error())
|
||||
let bslot =
|
||||
block:
|
||||
if state_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid state_id",
|
||||
$state_id.error())
|
||||
let bres = node.getBlockSlot(state_id.get())
|
||||
if bres.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "State not found",
|
||||
$bres.error())
|
||||
bres.get()
|
||||
let validatorIds =
|
||||
block:
|
||||
if id.isErr():
|
||||
|
@ -448,12 +244,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
res2.incl(item.index)
|
||||
(res1, res2)
|
||||
|
||||
let bres = node.getBlockSlot(state_id.get())
|
||||
if bres.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "State not found",
|
||||
$bres.error())
|
||||
|
||||
node.withStateForStateIdent(bres.get()):
|
||||
node.withStateForBlockSlot(bslot):
|
||||
let current_epoch = get_current_epoch(node.chainDag.headState.data.data)
|
||||
var res: seq[ValidatorTuple]
|
||||
for index, validator in state().validators.pairs():
|
||||
|
@ -478,7 +269,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
status: toString(vstatus),
|
||||
validator: validator
|
||||
))
|
||||
return RestApiResponse.jsonResponse(%res)
|
||||
return RestApiResponse.jsonResponse(res)
|
||||
|
||||
return RestApiResponse.jsonError(Http500, "Internal server error")
|
||||
|
||||
|
@ -486,18 +277,20 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
router.api(MethodGet,
|
||||
"/api/eth/v1/beacon/states/{state_id}/validators/{validator_id}") do (
|
||||
state_id: StateIdent, validator_id: ValidatorIdent) -> RestApiResponse:
|
||||
if state_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid state_id",
|
||||
$state_id.error())
|
||||
let bslot =
|
||||
block:
|
||||
if state_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid state_id",
|
||||
$state_id.error())
|
||||
let bres = node.getBlockSlot(state_id.get())
|
||||
if bres.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "State not found",
|
||||
$bres.error())
|
||||
bres.get()
|
||||
if validator_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid validator_id",
|
||||
$validator_id.error())
|
||||
|
||||
let bres = node.getBlockSlot(state_id.get())
|
||||
if bres.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "State not found",
|
||||
$bres.error())
|
||||
node.withStateForStateIdent(bres.get()):
|
||||
node.withStateForBlockSlot(bslot):
|
||||
let current_epoch = get_current_epoch(node.chainDag.headState.data.data)
|
||||
let vid = validator_id.get()
|
||||
case vid.kind
|
||||
|
@ -507,7 +300,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
let sres = validator.getStatus(current_epoch)
|
||||
if sres.isOk():
|
||||
return RestApiResponse.jsonResponse(
|
||||
%(
|
||||
(
|
||||
index: ValidatorIndex(index),
|
||||
balance: Base10.toString(state().balances[index]),
|
||||
status: toString(sres.get()),
|
||||
|
@ -526,7 +319,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
let sres = validator.getStatus(current_epoch)
|
||||
if sres.isOk():
|
||||
return RestApiResponse.jsonResponse(
|
||||
%(
|
||||
(
|
||||
index: ValidatorIndex(index),
|
||||
balance: Base10.toString(state().balances[index]),
|
||||
status: toString(sres.get()),
|
||||
|
@ -542,16 +335,22 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
router.api(MethodGet,
|
||||
"/api/eth/v1/beacon/states/{state_id}/validator_balances") do (
|
||||
state_id: StateIdent, id: seq[ValidatorIdent]) -> RestApiResponse:
|
||||
if state_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid state_id",
|
||||
$state_id.error())
|
||||
let bslot =
|
||||
block:
|
||||
if state_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid state_id",
|
||||
$state_id.error())
|
||||
let bres = node.getBlockSlot(state_id.get())
|
||||
if bres.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "State not found",
|
||||
$bres.error())
|
||||
bres.get()
|
||||
let validatorIds =
|
||||
block:
|
||||
if id.isErr():
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Invalid validator identifier(s)")
|
||||
id.get()
|
||||
|
||||
let (keySet, indexSet) =
|
||||
block:
|
||||
var res1: HashSet[ValidatorPubKey]
|
||||
|
@ -569,12 +368,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
"Only unique validator indexes allowed")
|
||||
res2.incl(item.index)
|
||||
(res1, res2)
|
||||
|
||||
let bres = node.getBlockSlot(state_id.get())
|
||||
if bres.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "State not found",
|
||||
$bres.error())
|
||||
node.withStateForStateIdent(bres.get()):
|
||||
node.withStateForBlockSlot(bslot):
|
||||
let current_epoch = get_current_epoch(node.chainDag.headState.data.data)
|
||||
var res: seq[ValidatorBalanceTuple]
|
||||
for index, validator in state().validators.pairs():
|
||||
|
@ -591,7 +385,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
index: ValidatorIndex(index),
|
||||
balance: Base10.toString(state().balances[index]),
|
||||
))
|
||||
return RestApiResponse.jsonResponse(%res)
|
||||
return RestApiResponse.jsonResponse(res)
|
||||
|
||||
return RestApiResponse.jsonError(Http500, "Internal server error")
|
||||
|
||||
|
@ -600,10 +394,16 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
"/api/eth/v1/beacon/states/{state_id}/committees") do (
|
||||
state_id: StateIdent, epoch: Option[Epoch], index: Option[CommitteeIndex],
|
||||
slot: Option[Slot]) -> RestApiResponse:
|
||||
|
||||
if state_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid state_id",
|
||||
$state_id.error())
|
||||
let bslot =
|
||||
block:
|
||||
if state_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid state_id",
|
||||
$state_id.error())
|
||||
let bres = node.getBlockSlot(state_id.get())
|
||||
if bres.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "State not found",
|
||||
$bres.error())
|
||||
bres.get()
|
||||
let vepoch =
|
||||
if epoch.isSome():
|
||||
let repoch = epoch.get()
|
||||
|
@ -631,20 +431,15 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
some(rslot.get())
|
||||
else:
|
||||
none[Slot]()
|
||||
|
||||
let bres = node.getBlockSlot(state_id.get())
|
||||
if bres.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "State not found",
|
||||
$bres.error())
|
||||
node.withStateForStateIdent(bres.get()):
|
||||
node.withStateForBlockSlot(bslot):
|
||||
proc getCommittee(slot: Slot,
|
||||
index: CommitteeIndex): StateCommitteeTuple =
|
||||
index: CommitteeIndex): BeaconStatesCommitteesTuple =
|
||||
let validators = get_beacon_committee(state, slot, index,
|
||||
cache).mapIt(it)
|
||||
(index: index, slot: slot, validators: validators)
|
||||
|
||||
proc forSlot(slot: Slot, cindex: Option[CommitteeIndex],
|
||||
res: var seq[StateCommitteeTuple]) =
|
||||
res: var seq[BeaconStatesCommitteesTuple]) =
|
||||
let committees_per_slot =
|
||||
get_committee_count_per_slot(state, Epoch(slot), cache)
|
||||
|
||||
|
@ -656,7 +451,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
if uint64(idx) < committees_per_slot:
|
||||
res.add(getCommittee(slot, CommitteeIndex(idx)))
|
||||
|
||||
var res: seq[StateCommitteeTuple]
|
||||
var res: seq[BeaconStatesCommitteesTuple]
|
||||
let qepoch =
|
||||
if vepoch.isNone:
|
||||
compute_epoch_at_slot(state().slot)
|
||||
|
@ -669,7 +464,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
else:
|
||||
forSlot(vslot.get(), vindex, res)
|
||||
|
||||
return RestApiResponse.jsonResponse(%res)
|
||||
return RestApiResponse.jsonResponse(res)
|
||||
|
||||
return RestApiResponse.jsonError(Http500, "Internal server error")
|
||||
|
||||
|
@ -681,27 +476,29 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockHeader
|
||||
router.api(MethodGet, "/api/eth/v1/beacon/headers/{block_id}") do (
|
||||
block_id: BlockIdent) -> RestApiResponse:
|
||||
if block_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid block_id",
|
||||
$block_id.error())
|
||||
let res = node.getBlockDataFromBlockIdent(block_id.get())
|
||||
if res.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "Block not found")
|
||||
let bdata =
|
||||
block:
|
||||
if block_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid block_id",
|
||||
$block_id.error())
|
||||
let res = node.getBlockDataFromBlockIdent(block_id.get())
|
||||
if res.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "Block not found")
|
||||
res.get()
|
||||
|
||||
let data = res.get()
|
||||
return RestApiResponse.jsonResponse(
|
||||
%(
|
||||
root: data.data.root,
|
||||
canonical: data.refs.isAncestorOf(node.chainDag.head),
|
||||
(
|
||||
root: bdata.data.root,
|
||||
canonical: bdata.refs.isAncestorOf(node.chainDag.head),
|
||||
header: (
|
||||
message: (
|
||||
slot: data.data.message.slot,
|
||||
proposer_index: Base10.toString(data.data.message.proposer_index),
|
||||
parent_root: data.data.message.parent_root,
|
||||
state_root: data.data.message.state_root,
|
||||
body_root: data.data.message.body.hash_tree_root()
|
||||
slot: bdata.data.message.slot,
|
||||
proposer_index: bdata.data.message.proposer_index,
|
||||
parent_root: bdata.data.message.parent_root,
|
||||
state_root: bdata.data.message.state_root,
|
||||
body_root: bdata.data.message.body.hash_tree_root()
|
||||
),
|
||||
signature: cast[ValidatorSig](data.data.signature)
|
||||
signature: bdata.data.signature
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -709,45 +506,79 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/publishBlock
|
||||
router.api(MethodPost, "/api/eth/v1/beacon/blocks") do (
|
||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||
discard
|
||||
let blck =
|
||||
block:
|
||||
if contentBody.isNone():
|
||||
return RestApiResponse.jsonError(Http400, "Empty request's body")
|
||||
let dres = decodeBody(SignedBeaconBlock, contentBody.get())
|
||||
if dres.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Unable to decode block " &
|
||||
"object", $dres.error())
|
||||
dres.get()
|
||||
let head = node.chainDag.head
|
||||
if not(node.isSynced(head)):
|
||||
return RestApiResponse.jsonError(Http503, "Beacon node is currently " &
|
||||
"syncing and not serving request on that endpoint")
|
||||
|
||||
if head.slot >= blck.message.slot:
|
||||
node.network.broadcast(getBeaconBlocksTopic(node.forkDigest), blck)
|
||||
return RestApiResponse.jsonError(Http202, "The block failed " &
|
||||
"validation, but was successfully broadcast anyway. It was not " &
|
||||
"integrated into the beacon node's database.")
|
||||
else:
|
||||
let res = proposeSignedBlock(node, head, AttachedValidator(), blck)
|
||||
if res == head:
|
||||
node.network.broadcast(getBeaconBlocksTopic(node.forkDigest), blck)
|
||||
return RestApiResponse.jsonError(Http202, "The block failed " &
|
||||
"validation, but was successfully broadcast anyway. It was not " &
|
||||
"integrated into the beacon node's database.")
|
||||
else:
|
||||
return RestApiResponse.jsonError(Http200, "The block was validated " &
|
||||
"successfully and has been broadcast")
|
||||
|
||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlock
|
||||
router.api(MethodGet, "/api/eth/v1/beacon/blocks/{block_id}") do (
|
||||
block_id: BlockIdent) -> RestApiResponse:
|
||||
if block_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid block_id",
|
||||
$block_id.error())
|
||||
let res = node.getBlockDataFromBlockIdent(block_id.get())
|
||||
if res.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "Block not found")
|
||||
let data = res.get()
|
||||
return RestApiResponse.jsonResponse(%(data.data))
|
||||
let bdata =
|
||||
block:
|
||||
if block_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid block_id",
|
||||
$block_id.error())
|
||||
let res = node.getBlockDataFromBlockIdent(block_id.get())
|
||||
if res.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "Block not found")
|
||||
res.get()
|
||||
return RestApiResponse.jsonResponse(bdata.data)
|
||||
|
||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockRoot
|
||||
router.api(MethodGet, "/api/eth/v1/beacon/blocks/{block_id}/root") do (
|
||||
block_id: BlockIdent) -> RestApiResponse:
|
||||
if block_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid block_id",
|
||||
$block_id.error())
|
||||
let res = node.getBlockDataFromBlockIdent(block_id.get())
|
||||
if res.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "Block not found")
|
||||
let data = res.get()
|
||||
return RestApiResponse.jsonResponse(%(root: data.data.root))
|
||||
let bdata =
|
||||
block:
|
||||
if block_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid block_id",
|
||||
$block_id.error())
|
||||
let res = node.getBlockDataFromBlockIdent(block_id.get())
|
||||
if res.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "Block not found")
|
||||
res.get()
|
||||
return RestApiResponse.jsonResponse((root: bdata.data.root))
|
||||
|
||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockAttestations
|
||||
router.api(MethodGet,
|
||||
"/api/eth/v1/beacon/blocks/{block_id}/attestations") do (
|
||||
block_id: BlockIdent) -> RestApiResponse:
|
||||
if block_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid block_id",
|
||||
$block_id.error())
|
||||
let res = node.getBlockDataFromBlockIdent(block_id.get())
|
||||
if res.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "Block not found")
|
||||
let data = res.get()
|
||||
let bdata =
|
||||
block:
|
||||
if block_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid block_id",
|
||||
$block_id.error())
|
||||
let res = node.getBlockDataFromBlockIdent(block_id.get())
|
||||
if res.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "Block not found")
|
||||
res.get()
|
||||
return RestApiResponse.jsonResponse(
|
||||
%data.data.message.body.attestations.asSeq()
|
||||
bdata.data.message.body.attestations.asSeq()
|
||||
)
|
||||
|
||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getPoolAttestations
|
||||
|
@ -773,67 +604,140 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
some(rslot.get())
|
||||
else:
|
||||
none[Slot]()
|
||||
|
||||
var res: seq[Attestation]
|
||||
for item in node.attestationPool[].attestations(vslot, vindex):
|
||||
res.add(item)
|
||||
return RestApiResponse.jsonResponse(%res)
|
||||
return RestApiResponse.jsonResponse(res)
|
||||
|
||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/submitPoolAttestations
|
||||
router.api(MethodPost, "/api/eth/v1/beacon/pool/attestations") do (
|
||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||
discard
|
||||
let attestations =
|
||||
block:
|
||||
if contentBody.isNone():
|
||||
return RestApiResponse.jsonError(Http400, "Empty request's body")
|
||||
let dres = decodeBody(seq[Attestation], contentBody.get())
|
||||
if dres.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Unable to decode " &
|
||||
"attestation object(s)",
|
||||
$dres.error())
|
||||
dres.get()
|
||||
|
||||
var failures: seq[FailureTuple]
|
||||
for atindex, attestation in attestations.pairs():
|
||||
let wallTime = node.processor.getWallTime()
|
||||
let res = node.attestationPool[].validateAttestation(
|
||||
attestation, wallTime, attestation.data.index, true
|
||||
)
|
||||
if res.isErr():
|
||||
failures.add((index: uint64(atindex), message: $res.error()))
|
||||
else:
|
||||
node.sendAttestation(attestation)
|
||||
|
||||
if len(failures) > 0:
|
||||
return RestApiResponse.jsonErrorList(Http400, "Some failures happened",
|
||||
failures)
|
||||
else:
|
||||
return RestApiResponse.jsonError(Http200,
|
||||
"Attestation(s) was broadcasted")
|
||||
|
||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getPoolAttesterSlashings
|
||||
router.api(MethodGet, "/api/eth/v1/beacon/pool/attester_slashings") do (
|
||||
) -> RestApiResponse:
|
||||
var res: seq[AttesterSlashing]
|
||||
if isNil(node.exitPool):
|
||||
return RestApiResponse.jsonResponse(%res)
|
||||
return RestApiResponse.jsonResponse(res)
|
||||
let length = len(node.exitPool.attester_slashings)
|
||||
res = newSeqOfCap[AttesterSlashing](length)
|
||||
for item in node.exitPool.attester_slashings.items():
|
||||
res.add(item)
|
||||
return RestApiResponse.jsonResponse(%res)
|
||||
return RestApiResponse.jsonResponse(res)
|
||||
|
||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/submitPoolAttesterSlashings
|
||||
router.api(MethodPost, "/api/eth/v1/beacon/pool/attester_slashings") do (
|
||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||
discard
|
||||
let slashing =
|
||||
block:
|
||||
if contentBody.isNone():
|
||||
return RestApiResponse.jsonError(Http400, "Empty request's body")
|
||||
let dres = decodeBody(AttesterSlashing, contentBody.get())
|
||||
if dres.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Unable to decode " &
|
||||
"attester slashing object", $dres.error())
|
||||
let res = dres.get()
|
||||
let vres = node.exitPool[].validateAttesterSlashing(res)
|
||||
if vres.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid attester " &
|
||||
"slashing, it will never pass validation so it's rejected",
|
||||
$vres.error())
|
||||
res
|
||||
node.sendAttesterSlashing(slashing)
|
||||
return RestApiResponse.jsonError(Http200,
|
||||
"Attester slashing was broadcasted")
|
||||
|
||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getPoolProposerSlashings
|
||||
router.api(MethodGet, "/api/eth/v1/beacon/pool/proposer_slashings") do (
|
||||
) -> RestApiResponse:
|
||||
var res: seq[ProposerSlashing]
|
||||
if isNil(node.exitPool):
|
||||
return RestApiResponse.jsonResponse(%res)
|
||||
return RestApiResponse.jsonResponse(res)
|
||||
let length = len(node.exitPool.proposer_slashings)
|
||||
res = newSeqOfCap[ProposerSlashing](length)
|
||||
for item in node.exitPool.proposer_slashings.items():
|
||||
res.add(item)
|
||||
return RestApiResponse.jsonResponse(%res)
|
||||
return RestApiResponse.jsonResponse(res)
|
||||
|
||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/submitPoolProposerSlashings
|
||||
router.api(MethodPost, "/api/eth/v1/beacon/pool/proposer_slashings") do (
|
||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||
discard
|
||||
let slashing =
|
||||
block:
|
||||
if contentBody.isNone():
|
||||
return RestApiResponse.jsonError(Http400, "Empty request's body")
|
||||
let dres = decodeBody(ProposerSlashing, contentBody.get())
|
||||
if dres.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Unable to decode " &
|
||||
"proposer slashing object", $dres.error())
|
||||
let res = dres.get()
|
||||
let vres = node.exitPool[].validateProposerSlashing(res)
|
||||
if vres.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid proposer " &
|
||||
"slashing, it will never pass validation so it's rejected",
|
||||
$vres.error())
|
||||
res
|
||||
node.sendProposerSlashing(slashing)
|
||||
return RestApiResponse.jsonError(Http200,
|
||||
"Proposer slashing was broadcasted")
|
||||
|
||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getPoolVoluntaryExits
|
||||
router.api(MethodGet, "/api/eth/v1/beacon/pool/voluntary_exits") do (
|
||||
) -> RestApiResponse:
|
||||
var res: seq[SignedVoluntaryExit]
|
||||
if isNil(node.exitPool):
|
||||
return RestApiResponse.jsonResponse(%res)
|
||||
return RestApiResponse.jsonResponse(res)
|
||||
let length = len(node.exitPool.voluntary_exits)
|
||||
res = newSeqOfCap[SignedVoluntaryExit](length)
|
||||
for item in node.exitPool.voluntary_exits.items():
|
||||
res.add(item)
|
||||
return RestApiResponse.jsonResponse(%res)
|
||||
return RestApiResponse.jsonResponse(res)
|
||||
|
||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/submitPoolVoluntaryExit
|
||||
router.api(MethodPost, "/api/eth/v1/beacon/pool/voluntary_exits") do (
|
||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||
if contentBody.isNone():
|
||||
return RestApiResponse.jsonError(Http400, "Empty request's body")
|
||||
let res = decodeBody(SignedVoluntaryExit, contentBody.get())
|
||||
warn "VoluntaryExit received", value = $res.get()
|
||||
let exit =
|
||||
block:
|
||||
if contentBody.isNone():
|
||||
return RestApiResponse.jsonError(Http400, "Empty request's body")
|
||||
let dres = decodeBody(SignedVoluntaryExit, contentBody.get())
|
||||
if dres.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Unable to decode " &
|
||||
"voluntary exit object", $dres.error())
|
||||
let res = dres.get()
|
||||
let vres = node.exitPool[].validateVoluntaryExit(res)
|
||||
if vres.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid voluntary exit, " &
|
||||
"it will never pass validation so it's rejected", $vres.error())
|
||||
res
|
||||
node.sendVoluntaryExit(exit)
|
||||
return RestApiResponse.jsonError(Http200,
|
||||
"Voluntary exit was broadcasted")
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
std/json,
|
||||
stew/endians2,
|
||||
stew/[endians2, base10],
|
||||
presto,
|
||||
rest_utils,
|
||||
chronicles,
|
||||
nimcrypto/utils as ncrutils,
|
||||
../beacon_node_common, ../eth1/eth1_monitor,
|
||||
../spec/[datatypes, digest, presets]
|
||||
../spec/[datatypes, digest, presets],
|
||||
./eth2_json_rest_serialization, ./rest_utils
|
||||
|
||||
logScope: topics = "rest_config"
|
||||
|
||||
|
@ -25,92 +25,140 @@ func getDepositAddress(node: BeaconNode): string =
|
|||
proc installConfigApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||
router.api(MethodGet,
|
||||
"/api/eth/v1/config/fork/schedule") do () -> RestApiResponse:
|
||||
# TODO: This implementation doesn't look right.
|
||||
return RestApiResponse.jsonResponse(
|
||||
%[node.chainDag.headState.data.data.fork]
|
||||
[node.chainDag.headState.data.data.fork]
|
||||
)
|
||||
|
||||
router.api(MethodGet,
|
||||
"/api/eth/v1/config/spec") do () -> RestApiResponse:
|
||||
return RestApiResponse.jsonResponse(
|
||||
%*{
|
||||
"MAX_COMMITTEES_PER_SLOT": $MAX_COMMITTEES_PER_SLOT,
|
||||
"TARGET_COMMITTEE_SIZE": $TARGET_COMMITTEE_SIZE,
|
||||
"MAX_VALIDATORS_PER_COMMITTEE": $MAX_VALIDATORS_PER_COMMITTEE,
|
||||
"MIN_PER_EPOCH_CHURN_LIMIT": $MIN_PER_EPOCH_CHURN_LIMIT,
|
||||
"CHURN_LIMIT_QUOTIENT": $CHURN_LIMIT_QUOTIENT,
|
||||
"SHUFFLE_ROUND_COUNT": $SHUFFLE_ROUND_COUNT,
|
||||
"MIN_GENESIS_ACTIVE_VALIDATOR_COUNT":
|
||||
$node.runtimePreset.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT,
|
||||
"MIN_GENESIS_TIME": $node.runtimePreset.MIN_GENESIS_TIME,
|
||||
"HYSTERESIS_QUOTIENT": $HYSTERESIS_QUOTIENT,
|
||||
"HYSTERESIS_DOWNWARD_MULTIPLIER": $HYSTERESIS_DOWNWARD_MULTIPLIER,
|
||||
"HYSTERESIS_UPWARD_MULTIPLIER": $HYSTERESIS_UPWARD_MULTIPLIER,
|
||||
"SAFE_SLOTS_TO_UPDATE_JUSTIFIED": $SAFE_SLOTS_TO_UPDATE_JUSTIFIED,
|
||||
"ETH1_FOLLOW_DISTANCE": $node.runtimePreset.ETH1_FOLLOW_DISTANCE,
|
||||
"TARGET_AGGREGATORS_PER_COMMITTEE": $TARGET_AGGREGATORS_PER_COMMITTEE,
|
||||
"RANDOM_SUBNETS_PER_VALIDATOR": $RANDOM_SUBNETS_PER_VALIDATOR,
|
||||
"EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION":
|
||||
$EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION,
|
||||
"SECONDS_PER_ETH1_BLOCK": $SECONDS_PER_ETH1_BLOCK,
|
||||
"DEPOSIT_CHAIN_ID": $DEPOSIT_CHAIN_ID,
|
||||
"DEPOSIT_NETWORK_ID": $DEPOSIT_NETWORK_ID,
|
||||
"DEPOSIT_CONTRACT_ADDRESS": node.getDepositAddress,
|
||||
"MIN_DEPOSIT_AMOUNT": $MIN_DEPOSIT_AMOUNT,
|
||||
"MAX_EFFECTIVE_BALANCE": $MAX_EFFECTIVE_BALANCE,
|
||||
"EJECTION_BALANCE": $EJECTION_BALANCE,
|
||||
"EFFECTIVE_BALANCE_INCREMENT": $EFFECTIVE_BALANCE_INCREMENT,
|
||||
"GENESIS_FORK_VERSION":
|
||||
(
|
||||
MAX_COMMITTEES_PER_SLOT:
|
||||
Base10.toString(MAX_COMMITTEES_PER_SLOT),
|
||||
TARGET_COMMITTEE_SIZE:
|
||||
Base10.toString(TARGET_COMMITTEE_SIZE),
|
||||
MAX_VALIDATORS_PER_COMMITTEE:
|
||||
Base10.toString(MAX_VALIDATORS_PER_COMMITTEE),
|
||||
MIN_PER_EPOCH_CHURN_LIMIT:
|
||||
Base10.toString(MIN_PER_EPOCH_CHURN_LIMIT),
|
||||
CHURN_LIMIT_QUOTIENT:
|
||||
Base10.toString(CHURN_LIMIT_QUOTIENT),
|
||||
SHUFFLE_ROUND_COUNT:
|
||||
Base10.toString(SHUFFLE_ROUND_COUNT),
|
||||
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT:
|
||||
Base10.toString(
|
||||
node.runtimePreset.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT
|
||||
),
|
||||
MIN_GENESIS_TIME:
|
||||
Base10.toString(node.runtimePreset.MIN_GENESIS_TIME),
|
||||
HYSTERESIS_QUOTIENT:
|
||||
Base10.toString(HYSTERESIS_QUOTIENT),
|
||||
HYSTERESIS_DOWNWARD_MULTIPLIER:
|
||||
Base10.toString(HYSTERESIS_DOWNWARD_MULTIPLIER),
|
||||
HYSTERESIS_UPWARD_MULTIPLIER:
|
||||
Base10.toString(HYSTERESIS_UPWARD_MULTIPLIER),
|
||||
SAFE_SLOTS_TO_UPDATE_JUSTIFIED:
|
||||
Base10.toString(SAFE_SLOTS_TO_UPDATE_JUSTIFIED),
|
||||
ETH1_FOLLOW_DISTANCE:
|
||||
Base10.toString(node.runtimePreset.ETH1_FOLLOW_DISTANCE),
|
||||
TARGET_AGGREGATORS_PER_COMMITTEE:
|
||||
Base10.toString(TARGET_AGGREGATORS_PER_COMMITTEE),
|
||||
RANDOM_SUBNETS_PER_VALIDATOR:
|
||||
Base10.toString(RANDOM_SUBNETS_PER_VALIDATOR),
|
||||
EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION:
|
||||
Base10.toString(EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION),
|
||||
SECONDS_PER_ETH1_BLOCK:
|
||||
Base10.toString(SECONDS_PER_ETH1_BLOCK),
|
||||
DEPOSIT_CHAIN_ID:
|
||||
Base10.toString(uint64(DEPOSIT_CHAIN_ID)),
|
||||
DEPOSIT_NETWORK_ID:
|
||||
Base10.toString(uint64(DEPOSIT_NETWORK_ID)),
|
||||
DEPOSIT_CONTRACT_ADDRESS:
|
||||
node.getDepositAddress(),
|
||||
MIN_DEPOSIT_AMOUNT:
|
||||
Base10.toString(MIN_DEPOSIT_AMOUNT),
|
||||
MAX_EFFECTIVE_BALANCE:
|
||||
Base10.toString(MAX_EFFECTIVE_BALANCE),
|
||||
EJECTION_BALANCE:
|
||||
Base10.toString(EJECTION_BALANCE),
|
||||
EFFECTIVE_BALANCE_INCREMENT:
|
||||
Base10.toString(EFFECTIVE_BALANCE_INCREMENT),
|
||||
GENESIS_FORK_VERSION:
|
||||
"0x" & $node.runtimePreset.GENESIS_FORK_VERSION,
|
||||
"BLS_WITHDRAWAL_PREFIX": "0x" & ncrutils.toHex([BLS_WITHDRAWAL_PREFIX]),
|
||||
"GENESIS_DELAY": $node.runtimePreset.GENESIS_DELAY,
|
||||
"SECONDS_PER_SLOT": $SECONDS_PER_SLOT,
|
||||
"MIN_ATTESTATION_INCLUSION_DELAY": $MIN_ATTESTATION_INCLUSION_DELAY,
|
||||
"SLOTS_PER_EPOCH": $SLOTS_PER_EPOCH,
|
||||
"MIN_SEED_LOOKAHEAD": $MIN_SEED_LOOKAHEAD,
|
||||
"MAX_SEED_LOOKAHEAD": $MAX_SEED_LOOKAHEAD,
|
||||
"EPOCHS_PER_ETH1_VOTING_PERIOD": $EPOCHS_PER_ETH1_VOTING_PERIOD,
|
||||
"SLOTS_PER_HISTORICAL_ROOT": $SLOTS_PER_HISTORICAL_ROOT,
|
||||
"MIN_VALIDATOR_WITHDRAWABILITY_DELAY":
|
||||
$MIN_VALIDATOR_WITHDRAWABILITY_DELAY,
|
||||
"SHARD_COMMITTEE_PERIOD": $SHARD_COMMITTEE_PERIOD,
|
||||
"MIN_EPOCHS_TO_INACTIVITY_PENALTY": $MIN_EPOCHS_TO_INACTIVITY_PENALTY,
|
||||
"EPOCHS_PER_HISTORICAL_VECTOR": $EPOCHS_PER_HISTORICAL_VECTOR,
|
||||
"EPOCHS_PER_SLASHINGS_VECTOR": $EPOCHS_PER_SLASHINGS_VECTOR,
|
||||
"HISTORICAL_ROOTS_LIMIT": $HISTORICAL_ROOTS_LIMIT,
|
||||
"VALIDATOR_REGISTRY_LIMIT": $VALIDATOR_REGISTRY_LIMIT,
|
||||
"BASE_REWARD_FACTOR": $BASE_REWARD_FACTOR,
|
||||
"WHISTLEBLOWER_REWARD_QUOTIENT": $WHISTLEBLOWER_REWARD_QUOTIENT,
|
||||
"PROPOSER_REWARD_QUOTIENT": $PROPOSER_REWARD_QUOTIENT,
|
||||
"INACTIVITY_PENALTY_QUOTIENT": $INACTIVITY_PENALTY_QUOTIENT,
|
||||
"MIN_SLASHING_PENALTY_QUOTIENT": $MIN_SLASHING_PENALTY_QUOTIENT,
|
||||
"PROPORTIONAL_SLASHING_MULTIPLIER": $PROPORTIONAL_SLASHING_MULTIPLIER,
|
||||
"MAX_PROPOSER_SLASHINGS": $MAX_PROPOSER_SLASHINGS,
|
||||
"MAX_ATTESTER_SLASHINGS": $MAX_ATTESTER_SLASHINGS,
|
||||
"MAX_ATTESTATIONS": $MAX_ATTESTATIONS,
|
||||
"MAX_DEPOSITS": $MAX_DEPOSITS,
|
||||
"MAX_VOLUNTARY_EXITS": $MAX_VOLUNTARY_EXITS,
|
||||
"DOMAIN_BEACON_PROPOSER":
|
||||
BLS_WITHDRAWAL_PREFIX:
|
||||
"0x" & ncrutils.toHex([BLS_WITHDRAWAL_PREFIX]),
|
||||
GENESIS_DELAY:
|
||||
Base10.toString(node.runtimePreset.GENESIS_DELAY),
|
||||
SECONDS_PER_SLOT:
|
||||
Base10.toString(SECONDS_PER_SLOT),
|
||||
MIN_ATTESTATION_INCLUSION_DELAY:
|
||||
Base10.toString(MIN_ATTESTATION_INCLUSION_DELAY),
|
||||
SLOTS_PER_EPOCH:
|
||||
Base10.toString(SLOTS_PER_EPOCH),
|
||||
MIN_SEED_LOOKAHEAD:
|
||||
Base10.toString(MIN_SEED_LOOKAHEAD),
|
||||
MAX_SEED_LOOKAHEAD:
|
||||
Base10.toString(MAX_SEED_LOOKAHEAD),
|
||||
EPOCHS_PER_ETH1_VOTING_PERIOD:
|
||||
Base10.toString(EPOCHS_PER_ETH1_VOTING_PERIOD),
|
||||
SLOTS_PER_HISTORICAL_ROOT:
|
||||
Base10.toString(SLOTS_PER_HISTORICAL_ROOT),
|
||||
MIN_VALIDATOR_WITHDRAWABILITY_DELAY:
|
||||
Base10.toString(MIN_VALIDATOR_WITHDRAWABILITY_DELAY),
|
||||
SHARD_COMMITTEE_PERIOD:
|
||||
Base10.toString(SHARD_COMMITTEE_PERIOD),
|
||||
MIN_EPOCHS_TO_INACTIVITY_PENALTY:
|
||||
Base10.toString(MIN_EPOCHS_TO_INACTIVITY_PENALTY),
|
||||
EPOCHS_PER_HISTORICAL_VECTOR:
|
||||
Base10.toString(EPOCHS_PER_HISTORICAL_VECTOR),
|
||||
EPOCHS_PER_SLASHINGS_VECTOR:
|
||||
Base10.toString(EPOCHS_PER_SLASHINGS_VECTOR),
|
||||
HISTORICAL_ROOTS_LIMIT:
|
||||
Base10.toString(HISTORICAL_ROOTS_LIMIT),
|
||||
VALIDATOR_REGISTRY_LIMIT:
|
||||
Base10.toString(VALIDATOR_REGISTRY_LIMIT),
|
||||
BASE_REWARD_FACTOR:
|
||||
Base10.toString(BASE_REWARD_FACTOR),
|
||||
WHISTLEBLOWER_REWARD_QUOTIENT:
|
||||
Base10.toString(WHISTLEBLOWER_REWARD_QUOTIENT),
|
||||
PROPOSER_REWARD_QUOTIENT:
|
||||
Base10.toString(PROPOSER_REWARD_QUOTIENT),
|
||||
INACTIVITY_PENALTY_QUOTIENT:
|
||||
Base10.toString(INACTIVITY_PENALTY_QUOTIENT),
|
||||
MIN_SLASHING_PENALTY_QUOTIENT:
|
||||
Base10.toString(MIN_SLASHING_PENALTY_QUOTIENT),
|
||||
PROPORTIONAL_SLASHING_MULTIPLIER:
|
||||
Base10.toString(PROPORTIONAL_SLASHING_MULTIPLIER),
|
||||
MAX_PROPOSER_SLASHINGS:
|
||||
Base10.toString(MAX_PROPOSER_SLASHINGS),
|
||||
MAX_ATTESTER_SLASHINGS:
|
||||
Base10.toString(MAX_ATTESTER_SLASHINGS),
|
||||
MAX_ATTESTATIONS:
|
||||
Base10.toString(MAX_ATTESTATIONS),
|
||||
MAX_DEPOSITS:
|
||||
Base10.toString(MAX_DEPOSITS),
|
||||
MAX_VOLUNTARY_EXITS:
|
||||
Base10.toString(MAX_VOLUNTARY_EXITS),
|
||||
DOMAIN_BEACON_PROPOSER:
|
||||
"0x" & ncrutils.toHex(uint32(DOMAIN_BEACON_PROPOSER).toBytesLE()),
|
||||
"DOMAIN_BEACON_ATTESTER":
|
||||
DOMAIN_BEACON_ATTESTER:
|
||||
"0x" & ncrutils.toHex(uint32(DOMAIN_BEACON_ATTESTER).toBytesLE()),
|
||||
"DOMAIN_RANDAO":
|
||||
DOMAIN_RANDAO:
|
||||
"0x" & ncrutils.toHex(uint32(DOMAIN_RANDAO).toBytesLE()),
|
||||
"DOMAIN_DEPOSIT":
|
||||
DOMAIN_DEPOSIT:
|
||||
"0x" & ncrutils.toHex(uint32(DOMAIN_DEPOSIT).toBytesLE()),
|
||||
"DOMAIN_VOLUNTARY_EXIT":
|
||||
DOMAIN_VOLUNTARY_EXIT:
|
||||
"0x" & ncrutils.toHex(uint32(DOMAIN_VOLUNTARY_EXIT).toBytesLE()),
|
||||
"DOMAIN_SELECTION_PROOF":
|
||||
DOMAIN_SELECTION_PROOF:
|
||||
"0x" & ncrutils.toHex(uint32(DOMAIN_SELECTION_PROOF).toBytesLE()),
|
||||
"DOMAIN_AGGREGATE_AND_PROOF":
|
||||
DOMAIN_AGGREGATE_AND_PROOF:
|
||||
"0x" & ncrutils.toHex(uint32(DOMAIN_AGGREGATE_AND_PROOF).toBytesLE())
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
router.api(MethodGet,
|
||||
"/api/eth/v1/config/deposit_contract") do () -> RestApiResponse:
|
||||
return RestApiResponse.jsonResponse(
|
||||
%*{
|
||||
"chain_id": $DEPOSIT_CHAIN_ID,
|
||||
"address": node.getDepositAddress()
|
||||
}
|
||||
(chain_id: $DEPOSIT_CHAIN_ID, address: node.getDepositAddress())
|
||||
)
|
||||
|
|
|
@ -4,7 +4,7 @@ import
|
|||
chronicles,
|
||||
../version, ../beacon_node_common,
|
||||
../spec/[datatypes, digest, presets],
|
||||
./rest_utils
|
||||
./eth2_json_rest_serialization, ./rest_utils
|
||||
|
||||
logScope: topics = "rest_debug"
|
||||
|
||||
|
@ -12,21 +12,22 @@ proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
router.api(MethodGet,
|
||||
"/api/eth/v1/debug/beacon/states/{state_id}") do (
|
||||
state_id: StateIdent) -> RestApiResponse:
|
||||
# TODO: This is very expensive call
|
||||
if state_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid state_id",
|
||||
$state_id.error())
|
||||
let bres = node.getBlockSlot(state_id.get())
|
||||
if bres.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "State not found",
|
||||
$bres.error())
|
||||
node.withStateForStateIdent(bres.get()):
|
||||
return RestApiResponse.jsonResponse(%state())
|
||||
|
||||
let bslot =
|
||||
block:
|
||||
if state_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid state_id",
|
||||
$state_id.error())
|
||||
let bres = node.getBlockSlot(state_id.get())
|
||||
if bres.isErr():
|
||||
return RestApiResponse.jsonError(Http404, "State not found",
|
||||
$bres.error())
|
||||
bres.get()
|
||||
node.withStateForBlockSlot(bslot):
|
||||
return RestApiResponse.jsonResponse(state())
|
||||
return RestApiResponse.jsonError(Http500, "Internal server error")
|
||||
|
||||
router.api(MethodGet,
|
||||
"/api/eth/v1/debug/beacon/heads") do () -> RestApiResponse:
|
||||
return RestApiResponse.jsonResponse(
|
||||
%node.chainDag.heads.mapIt((root: it.root, slot: it.slot))
|
||||
node.chainDag.heads.mapIt((root: it.root, slot: it.slot))
|
||||
)
|
||||
|
|
|
@ -0,0 +1,314 @@
|
|||
import
|
||||
std/[typetraits],
|
||||
stew/[results, base10, byteutils],
|
||||
chronicles, presto,
|
||||
faststreams/[outputs],
|
||||
serialization, json_serialization,
|
||||
nimcrypto/utils as ncrutils,
|
||||
../beacon_node_common, ../networking/eth2_network,
|
||||
../consensus_object_pools/[blockchain_dag, exit_pool],
|
||||
../spec/[crypto, digest, datatypes],
|
||||
../ssz/merkleization,
|
||||
rest_utils
|
||||
export json_serialization
|
||||
|
||||
Json.createFlavor RestJson
|
||||
|
||||
proc jsonResponseWRoot*(t: typedesc[RestApiResponse],
|
||||
data: auto,
|
||||
dependent_root: Eth2Digest): RestApiResponse =
|
||||
var stream = memoryOutput()
|
||||
var writer = JsonWriter[RestJson].init(stream)
|
||||
writer.beginRecord()
|
||||
writer.writeField("dependent_root", dependent_root)
|
||||
writer.writeField("data", data)
|
||||
writer.endRecord()
|
||||
ok(ContentBody(contentType: "application/json",
|
||||
data: stream.getOutput(seq[byte])))
|
||||
|
||||
proc jsonResponse*(t: typedesc[RestApiResponse],
|
||||
data: auto): RestApiResponse =
|
||||
var stream = memoryOutput()
|
||||
var writer = JsonWriter[RestJson].init(stream)
|
||||
writer.beginRecord()
|
||||
writer.writeField("data", data)
|
||||
writer.endRecord()
|
||||
ok(ContentBody(contentType: "application/json",
|
||||
data: stream.getOutput(seq[byte])))
|
||||
|
||||
|
||||
proc jsonError*(t: typedesc[RestApiResponse], status: HttpCode = Http200,
|
||||
msg: string = "", stacktrace: string = ""): RestApiResponse =
|
||||
let data =
|
||||
block:
|
||||
var stream = memoryOutput()
|
||||
var writer = JsonWriter[RestJson].init(stream)
|
||||
writer.beginRecord()
|
||||
writer.writeField("code", Base10.toString(uint64(status.toInt())))
|
||||
writer.writeField("message", msg)
|
||||
if len(stacktrace) > 0:
|
||||
writer.writeField("stacktrace", stacktrace)
|
||||
writer.endRecord()
|
||||
stream.getOutput(string)
|
||||
RestApiResponse.error(status, data, "application/json")
|
||||
|
||||
proc jsonErrorList*(t: typedesc[RestApiResponse],
|
||||
status: HttpCode = Http200,
|
||||
msg: string = "", failures: auto): RestApiResponse =
|
||||
let data =
|
||||
block:
|
||||
var stream = memoryOutput()
|
||||
var writer = JsonWriter[RestJson].init(stream)
|
||||
writer.beginRecord()
|
||||
writer.writeField("code", Base10.toString(uint64(status.toInt())))
|
||||
writer.writeField("message", msg)
|
||||
writer.writeField("failures", failures)
|
||||
writer.endRecord()
|
||||
stream.getOutput(string)
|
||||
RestApiResponse.error(status, data, "application/json")
|
||||
|
||||
template hexCompressed(data: openarray[byte]): string =
|
||||
let offset =
|
||||
block:
|
||||
var res = 0
|
||||
for i in 0 ..< len(data):
|
||||
if data[i] != 0x00'u8:
|
||||
res = i
|
||||
break
|
||||
res
|
||||
"0x" & ncrutils.toHex(data.toOpenArray(offset, len(data) - 1), true)
|
||||
|
||||
template hexOriginal(data: openarray[byte]): string =
|
||||
"0x" & ncrutils.toHex(data, true)
|
||||
|
||||
## uint64
|
||||
proc writeValue*(w: var JsonWriter[RestJson], value: uint64) =
|
||||
writeValue(w, Base10.toString(value))
|
||||
|
||||
proc readValue*(reader: var JsonReader[RestJson], value: var uint64) {.
|
||||
raises: [IOError, SerializationError, Defect].} =
|
||||
let svalue = reader.readValue(string)
|
||||
let res = Base10.decode(uint64, svalue)
|
||||
if res.isOk():
|
||||
value = res.get()
|
||||
else:
|
||||
reader.raiseUnexpectedValue($res.error())
|
||||
|
||||
## Slot
|
||||
proc writeValue*(writer: var JsonWriter[RestJson], value: Slot) {.
|
||||
raises: [IOError, Defect].} =
|
||||
writeValue(writer, Base10.toString(uint64(value)))
|
||||
|
||||
proc readValue*(reader: var JsonReader[RestJson], value: var Slot) {.
|
||||
raises: [IOError, SerializationError, Defect].} =
|
||||
let svalue = reader.readValue(string)
|
||||
let res = Base10.decode(uint64, svalue)
|
||||
if res.isOk():
|
||||
value = Slot(res.get())
|
||||
else:
|
||||
reader.raiseUnexpectedValue($res.error())
|
||||
|
||||
## Epoch
|
||||
proc writeValue*(writer: var JsonWriter[RestJson], value: Epoch) {.
|
||||
raises: [IOError, Defect].} =
|
||||
writeValue(writer, Base10.toString(uint64(value)))
|
||||
|
||||
proc readValue*(reader: var JsonReader[RestJson], value: var Epoch) {.
|
||||
raises: [IOError, SerializationError, Defect].} =
|
||||
let svalue = reader.readValue(string)
|
||||
let res = Base10.decode(uint64, svalue)
|
||||
if res.isOk():
|
||||
value = Epoch(res.get())
|
||||
else:
|
||||
reader.raiseUnexpectedValue($res.error())
|
||||
|
||||
## ValidatorIndex
|
||||
proc writeValue*(writer: var JsonWriter[RestJson], value: ValidatorIndex) {.
|
||||
raises: [IOError, Defect].} =
|
||||
writeValue(writer, Base10.toString(uint64(value)))
|
||||
|
||||
proc readValue*(reader: var JsonReader[RestJson], value: var ValidatorIndex) {.
|
||||
raises: [IOError, SerializationError, Defect].} =
|
||||
let svalue = reader.readValue(string)
|
||||
let res = Base10.decode(uint64, svalue)
|
||||
if res.isOk():
|
||||
let v = res.get()
|
||||
if v < VALIDATOR_REGISTRY_LIMIT:
|
||||
value = ValidatorIndex(v)
|
||||
else:
|
||||
reader.raiseUnexpectedValue(
|
||||
"Validator index is bigger then VALIDATOR_REGISTRY_LIMIT")
|
||||
else:
|
||||
reader.raiseUnexpectedValue($res.error())
|
||||
|
||||
## CommitteeIndex
|
||||
proc writeValue*(writer: var JsonWriter[RestJson], value: CommitteeIndex) {.
|
||||
raises: [IOError, Defect].} =
|
||||
writeValue(writer, Base10.toString(uint64(value)))
|
||||
|
||||
proc readValue*(reader: var JsonReader[RestJson], value: var CommitteeIndex) {.
|
||||
raises: [IOError, SerializationError, Defect].} =
|
||||
let svalue = reader.readValue(string)
|
||||
let res = Base10.decode(uint64, svalue)
|
||||
if res.isOk():
|
||||
value = CommitteeIndex(res.get())
|
||||
else:
|
||||
reader.raiseUnexpectedValue($res.error())
|
||||
|
||||
## ValidatorSig
|
||||
proc writeValue*(writer: var JsonWriter[RestJson], value: ValidatorSig) {.
|
||||
raises: [IOError, Defect].} =
|
||||
writeValue(writer, hexOriginal(toRaw(value)))
|
||||
|
||||
proc readValue*(reader: var JsonReader[RestJson], value: var ValidatorSig) {.
|
||||
raises: [IOError, SerializationError, Defect].} =
|
||||
let hexValue = reader.readValue(string)
|
||||
let res = ValidatorSig.fromHex(hexValue)
|
||||
if res.isOk():
|
||||
value = res.get()
|
||||
else:
|
||||
reader.raiseUnexpectedValue($res.error())
|
||||
|
||||
## TrustedSig
|
||||
proc writeValue*(writer: var JsonWriter[RestJson], value: TrustedSig) {.
|
||||
raises: [IOError, Defect].} =
|
||||
writeValue(writer, hexOriginal(toRaw(value)))
|
||||
|
||||
proc readValue*(reader: var JsonReader[RestJson], value: var TrustedSig) {.
|
||||
raises: [IOError, SerializationError, Defect].} =
|
||||
let hexValue = reader.readValue(string)
|
||||
let res = ValidatorSig.fromHex(hexValue)
|
||||
if res.isOk():
|
||||
value = cast[TrustedSig](res.get())
|
||||
else:
|
||||
reader.raiseUnexpectedValue($res.error())
|
||||
|
||||
## ValidatorPubKey
|
||||
proc writeValue*(writer: var JsonWriter[RestJson], value: ValidatorPubKey) {.
|
||||
raises: [IOError, Defect].} =
|
||||
writeValue(writer, hexOriginal(toRaw(value)))
|
||||
|
||||
proc readValue*(reader: var JsonReader[RestJson], value: var ValidatorPubKey) {.
|
||||
raises: [IOError, SerializationError, Defect].} =
|
||||
let hexValue = reader.readValue(string)
|
||||
let res = ValidatorPubKey.fromHex(hexValue)
|
||||
if res.isOk():
|
||||
value = res.get()
|
||||
else:
|
||||
reader.raiseUnexpectedValue($res.error())
|
||||
|
||||
## BitSeq
|
||||
proc readValue*(reader: var JsonReader[RestJson], value: var BitSeq) {.
|
||||
raises: [IOError, SerializationError, Defect].} =
|
||||
try:
|
||||
value = BitSeq hexToSeqByte(reader.readValue(string))
|
||||
except ValueError:
|
||||
raiseUnexpectedValue(reader, "A BitSeq value should be a valid hex string")
|
||||
|
||||
proc writeValue*(writer: var JsonWriter[RestJson], value: BitSeq) {.
|
||||
raises: [IOError, Defect].} =
|
||||
writeValue(writer, hexCompressed(value.bytes()))
|
||||
|
||||
## BitList
|
||||
proc readValue*(reader: var JsonReader[RestJson], value: var BitList) =
|
||||
type T = type(value)
|
||||
value = T readValue(reader, BitSeq)
|
||||
|
||||
proc writeValue*(writer: var JsonWriter[RestJson], value: BitList) =
|
||||
writeValue(writer, BitSeq value)
|
||||
|
||||
## Eth2Digest
|
||||
proc readValue*(reader: var JsonReader[RestJson], value: var Eth2Digest) {.
|
||||
raises: [IOError, SerializationError, Defect].} =
|
||||
try:
|
||||
hexToByteArray(reader.readValue(string), value.data)
|
||||
except ValueError:
|
||||
raiseUnexpectedValue(reader,
|
||||
"Eth2Digest value should be a valid hex string")
|
||||
|
||||
proc writeValue*(writer: var JsonWriter[RestJson], value: Eth2Digest) {.
|
||||
raises: [IOError, Defect].} =
|
||||
writeValue(writer, hexOriginal(value.data))
|
||||
|
||||
## HashArray
|
||||
proc readValue*(reader: var JsonReader[RestJson], value: var HashArray) {.
|
||||
raises: [IOError, SerializationError, Defect].} =
|
||||
readValue(reader, value.data)
|
||||
|
||||
proc writeValue*(writer: var JsonWriter[RestJson], value: HashArray) {.
|
||||
raises: [IOError, Defect].} =
|
||||
writeValue(writer, value.data)
|
||||
|
||||
## HashList
|
||||
proc readValue*(reader: var JsonReader[RestJson], value: var HashList) {.
|
||||
raises: [IOError, SerializationError, Defect].} =
|
||||
readValue(reader, value.data)
|
||||
|
||||
proc writeValue*(writer: var JsonWriter[RestJson], value: HashList) {.
|
||||
raises: [IOError, Defect].} =
|
||||
writeValue(writer, value.data)
|
||||
|
||||
## Eth1Address
|
||||
proc readValue*(reader: var JsonReader[RestJson], value: var Eth1Address) {.
|
||||
raises: [IOError, SerializationError, Defect].} =
|
||||
try:
|
||||
hexToByteArray(reader.readValue(string), distinctBase(value))
|
||||
except ValueError:
|
||||
raiseUnexpectedValue(reader,
|
||||
"Eth1Address value should be a valid hex string")
|
||||
|
||||
proc writeValue*(writer: var JsonWriter[RestJson], value: Eth1Address) {.
|
||||
raises: [IOError, Defect].} =
|
||||
writeValue(writer, hexOriginal(distinctBase(value)))
|
||||
|
||||
## Version
|
||||
proc readValue*(reader: var JsonReader[RestJson], value: var Version) {.
|
||||
raises: [IOError, SerializationError, Defect].} =
|
||||
try:
|
||||
hexToByteArray(reader.readValue(string), distinctBase(value))
|
||||
except ValueError:
|
||||
raiseUnexpectedValue(reader,
|
||||
"Version value should be a valid hex string")
|
||||
|
||||
proc writeValue*(writer: var JsonWriter[RestJson], value: Version) {.
|
||||
raises: [IOError, Defect].} =
|
||||
writeValue(writer, hexOriginal(distinctBase(value)))
|
||||
|
||||
## ForkDigest
|
||||
proc readValue*(reader: var JsonReader[RestJson], value: var ForkDigest) {.
|
||||
raises: [IOError, SerializationError, Defect].} =
|
||||
try:
|
||||
hexToByteArray(reader.readValue(string), distinctBase(value))
|
||||
except ValueError:
|
||||
raiseUnexpectedValue(reader,
|
||||
"ForkDigest value should be a valid hex string")
|
||||
|
||||
proc writeValue*(writer: var JsonWriter[RestJson], value: ForkDigest) {.
|
||||
raises: [IOError, Defect].} =
|
||||
writeValue(writer, hexOriginal(distinctBase(value)))
|
||||
|
||||
## GraffitiBytes
|
||||
proc readValue*(reader: var JsonReader[RestJson], value: var GraffitiBytes) {.
|
||||
raises: [IOError, SerializationError, Defect].} =
|
||||
try:
|
||||
hexToByteArray(reader.readValue(string), distinctBase(value))
|
||||
except ValueError:
|
||||
raiseUnexpectedValue(reader,
|
||||
"GraffitiBytes value should be a valid hex string")
|
||||
|
||||
proc writeValue*(writer: var JsonWriter[RestJson], value: GraffitiBytes) {.
|
||||
raises: [IOError, Defect].} =
|
||||
writeValue(writer, hexOriginal(distinctBase(value)))
|
||||
|
||||
proc decodeBody*[T](t: typedesc[T],
|
||||
body: ContentBody): Result[T, cstring] =
|
||||
if body.contentType != "application/json":
|
||||
return err("Unsupported content type")
|
||||
let data =
|
||||
try:
|
||||
RestJson.decode(cast[string](body.data), T)
|
||||
except SerializationError as exc:
|
||||
return err("Unable to deserialize data")
|
||||
except CatchableError as exc:
|
||||
return err("Unexpected deserialization error")
|
||||
ok(data)
|
|
@ -0,0 +1,66 @@
|
|||
# Copyright (c) 2018-2020 Status Research & Development GmbH
|
||||
# Licensed and distributed under either of
|
||||
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
||||
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
stew/results,
|
||||
chronicles,
|
||||
presto,
|
||||
./eth2_json_rest_serialization, ./rest_utils,
|
||||
../beacon_node_common
|
||||
|
||||
logScope: topics = "rest_eventapi"
|
||||
|
||||
proc validateEventTopics(events: seq[EventTopic]): Result[EventTopics,
|
||||
cstring] =
|
||||
const NonUniqueError = cstring("Event topics must be unique")
|
||||
var res: set[EventTopic]
|
||||
for item in events:
|
||||
case item
|
||||
of EventTopic.Head:
|
||||
if EventTopic.Head in res:
|
||||
return err(NonUniqueError)
|
||||
res.incl(EventTopic.Head)
|
||||
of EventTopic.Block:
|
||||
if EventTopic.Block in res:
|
||||
return err(NonUniqueError)
|
||||
res.incl(EventTopic.Block)
|
||||
of EventTopic.Attestation:
|
||||
if EventTopic.Attestation in res:
|
||||
return err(NonUniqueError)
|
||||
res.incl(EventTopic.Attestation)
|
||||
of EventTopic.VoluntaryExit:
|
||||
if EventTopic.VoluntaryExit in res:
|
||||
return err(NonUniqueError)
|
||||
res.incl(EventTopic.VoluntaryExit)
|
||||
of EventTopic.FinalizedCheckpoint:
|
||||
if EventTopic.FinalizedCheckpoint in res:
|
||||
return err(NonUniqueError)
|
||||
res.incl(EventTopic.FinalizedCheckpoint)
|
||||
of EventTopic.ChainReorg:
|
||||
if EventTopic.ChainReorg in res:
|
||||
return err(NonUniqueError)
|
||||
res.incl(EventTopic.ChainReorg)
|
||||
if res == {}:
|
||||
err("Empty topics list")
|
||||
else:
|
||||
ok(res)
|
||||
|
||||
proc installEventApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||
router.api(MethodGet, "/api/eth/v1/events") do (
|
||||
topics: seq[EventTopic]) -> RestApiResponse:
|
||||
|
||||
let eventTopics =
|
||||
block:
|
||||
if topics.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid topics value",
|
||||
$topics.error())
|
||||
let res = validateEventTopics(topics.get())
|
||||
if res.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid topics value",
|
||||
$res.error())
|
||||
res.get()
|
||||
|
||||
return RestApiResponse.jsonError(Http500, "Not implemented yet")
|
|
@ -0,0 +1,259 @@
|
|||
# Copyright (c) 2018-2020 Status Research & Development GmbH
|
||||
# Licensed and distributed under either of
|
||||
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
||||
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
std/[deques, sequtils],
|
||||
stew/results,
|
||||
chronicles,
|
||||
presto,
|
||||
libp2p/[multiaddress, multicodec],
|
||||
libp2p/protocols/pubsub/pubsubpeer,
|
||||
./eth2_json_rest_serialization, ./rest_utils,
|
||||
../eth1/eth1_monitor,
|
||||
../validators/validator_duties,
|
||||
../beacon_node_common, ../nimbus_binary_common
|
||||
|
||||
logScope: topics = "rest_nimbusapi"
|
||||
|
||||
type
|
||||
PeerInfoTuple* = tuple
|
||||
peerId: string
|
||||
addrs: seq[string]
|
||||
protocols: seq[string]
|
||||
protoVersion: string
|
||||
agentVersion: string
|
||||
|
||||
SimplePeerTuple* = tuple
|
||||
info: PeerInfoTuple
|
||||
connectionState: string
|
||||
score: int
|
||||
|
||||
FutureInfoTuple* = tuple
|
||||
id: int
|
||||
procname: string
|
||||
filename: string
|
||||
line: int
|
||||
state: string
|
||||
|
||||
PubSubPeerTuple* = tuple
|
||||
peerId: PeerID
|
||||
score: float64
|
||||
iWantBudget: int
|
||||
iHaveBudget: int
|
||||
outbound: bool
|
||||
appScore: float64
|
||||
behaviourPenalty: float64
|
||||
sendConnAvail: bool
|
||||
closed: bool
|
||||
atEof: bool
|
||||
address: string
|
||||
backoff: string
|
||||
agent: string
|
||||
|
||||
PeerStatsTuple* = tuple
|
||||
peerId: PeerID
|
||||
null: bool
|
||||
connected: bool
|
||||
expire: string
|
||||
score: float64
|
||||
|
||||
PeerStatusTuple* = tuple
|
||||
peerId: PeerID
|
||||
connected: bool
|
||||
|
||||
proc toNode(v: PubSubPeer, backoff: Moment): PubSubPeerTuple =
|
||||
(
|
||||
peerId: v.peerId,
|
||||
score: v.score,
|
||||
iWantBudget: v.iWantBudget,
|
||||
iHaveBudget: v.iHaveBudget,
|
||||
outbound: v.outbound,
|
||||
appScore: v.appScore,
|
||||
behaviourPenalty: v.behaviourPenalty,
|
||||
sendConnAvail: v.sendConn != nil,
|
||||
closed: v.sendConn != nil and v.sendConn.closed,
|
||||
atEof: v.sendConn != nil and v.sendConn.atEof,
|
||||
address:
|
||||
if v.address.isSome():
|
||||
$v.address.get()
|
||||
else:
|
||||
"<no address>",
|
||||
backoff: $(backoff - Moment.now()),
|
||||
agent:
|
||||
when defined(libp2p_agents_metrics):
|
||||
v.shortAgent
|
||||
else:
|
||||
"unknown"
|
||||
)
|
||||
|
||||
proc installNimbusApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||
router.api(MethodGet, "/api/nimbus/v1/beacon/head") do () -> RestApiResponse:
|
||||
return RestApiResponse.jsonResponse(node.chainDag.head.slot)
|
||||
|
||||
router.api(MethodGet, "/api/nimbus/v1/chain/head") do() -> RestApiResponse:
|
||||
let
|
||||
head = node.chainDag.head
|
||||
finalized = node.chainDag.headState.data.data.finalized_checkpoint
|
||||
justified = node.chainDag.headState.data.data.current_justified_checkpoint
|
||||
return RestApiResponse.jsonResponse(
|
||||
(
|
||||
head_slot: head.slot,
|
||||
head_block_root: head.root.data.toHex(),
|
||||
finalized_slot: finalized.epoch * SLOTS_PER_EPOCH,
|
||||
finalized_block_root: finalized.root.data.toHex(),
|
||||
justified_slot: justified.epoch * SLOTS_PER_EPOCH,
|
||||
justified_block_root: justified.root.data.toHex()
|
||||
)
|
||||
)
|
||||
|
||||
router.api(MethodGet, "/api/nimbus/v1/syncmanager/status") do (
|
||||
) -> RestApiResponse:
|
||||
return RestApiResponse.jsonResponse(node.syncManager.inProgress)
|
||||
|
||||
router.api(MethodGet, "/api/nimbus/v1/node/peerid") do (
|
||||
) -> RestApiResponse:
|
||||
return RestApiResponse.jsonResponse((peerid: $node.network.peerId()))
|
||||
|
||||
router.api(MethodGet, "/api/nimbus/v1/node/version") do (
|
||||
) -> RestApiResponse:
|
||||
return RestApiResponse.jsonResponse((version: "Nimbus/" & fullVersionStr))
|
||||
|
||||
router.api(MethodGet, "/api/nimbus/v1/network/ids") do (
|
||||
) -> RestApiResponse:
|
||||
var res: seq[PeerID]
|
||||
for peerId, peer in node.network.peerPool:
|
||||
res.add(peerId)
|
||||
return RestApiResponse.jsonResponse((peerids: res))
|
||||
|
||||
router.api(MethodGet, "/api/nimbus/v1/network/peers") do (
|
||||
) -> RestApiResponse:
|
||||
var res: seq[SimplePeerTuple]
|
||||
for id, peer in node.network.peerPool:
|
||||
res.add((
|
||||
info: shortLog(peer.info),
|
||||
connectionState: $peer.connectionState,
|
||||
score: peer.score,
|
||||
))
|
||||
return RestApiResponse.jsonResponse((peers: res))
|
||||
|
||||
router.api(MethodPost, "/api/nimbus/v1/chronicles/settings") do (
|
||||
log_level: Option[string]) -> RestApiResponse:
|
||||
if log_level.isSome():
|
||||
let level =
|
||||
block:
|
||||
let res = log_level.get()
|
||||
if res.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Invalid log_level value",
|
||||
$res.error())
|
||||
res.get()
|
||||
{.gcsafe.}:
|
||||
updateLogLevel(level)
|
||||
return RestApiResponse.jsonResponse((result: true))
|
||||
|
||||
router.api(MethodGet, "/api/nimbus/v1/eth1/chain") do (
|
||||
) -> RestApiResponse:
|
||||
let res =
|
||||
if not(isNil(node.eth1Monitor)):
|
||||
mapIt(node.eth1Monitor.blocks, it)
|
||||
else:
|
||||
@[]
|
||||
return RestApiResponse.jsonResponse(res)
|
||||
|
||||
router.api(MethodGet, "/api/nimbus/v1/eth1/proposal_data") do (
|
||||
) -> RestApiResponse:
|
||||
let wallSlot = node.beaconClock.now.slotOrZero
|
||||
let head =
|
||||
block:
|
||||
let res = node.getCurrentHead(wallSlot)
|
||||
if res.isErr():
|
||||
return RestApiResponse.jsonError(Http503, "Node is not synced yet")
|
||||
res.get()
|
||||
let proposalState = assignClone(node.chainDag.headState)
|
||||
node.chainDag.withState(proposalState[], head.atSlot(wallSlot)):
|
||||
return RestApiResponse.jsonResponse(node.getBlockProposalEth1Data(state))
|
||||
|
||||
router.api(MethodGet, "/api/nimbus/v1/debug/chronos/futures") do (
|
||||
) -> RestApiResponse:
|
||||
when defined(chronosFutureTracking):
|
||||
var res: seq[FutureInfoTuple]
|
||||
for item in pendingFutures():
|
||||
let loc = item.location[LocCreateIndex][]
|
||||
res.add(
|
||||
(
|
||||
id: item.id,
|
||||
procname: $loc.procedure,
|
||||
filename: $loc.file,
|
||||
line: loc.line,
|
||||
state: $item.state
|
||||
)
|
||||
)
|
||||
return RestApiResponse.jsonResponse(res)
|
||||
else:
|
||||
return RestApiResponse.jsonError(Http503,
|
||||
"Compile with '-d:chronosFutureTracking' to get this request working")
|
||||
|
||||
router.api(MethodGet, "/api/nimbus/v1/debug/gossip/peers") do (
|
||||
) -> RestApiResponse:
|
||||
|
||||
let gossipPeers =
|
||||
block:
|
||||
var res: seq[tuple[topic: string, peers: seq[PubSubPeerTuple]]]
|
||||
for topic, v in node.network.pubsub.gossipsub:
|
||||
var peers: seq[PubSubPeerTuple]
|
||||
let backoff = node.network.pubsub.backingOff.getOrDefault(topic)
|
||||
for peer in v:
|
||||
peers.add(peer.toNode(backOff.getOrDefault(peer.peerId)))
|
||||
res.add((topic: topic, peers: peers))
|
||||
res
|
||||
let meshPeers =
|
||||
block:
|
||||
var res: seq[tuple[topic: string, peers: seq[PubSubPeerTuple]]]
|
||||
for topic, v in node.network.pubsub.mesh:
|
||||
var peers: seq[PubSubPeerTuple]
|
||||
let backoff = node.network.pubsub.backingOff.getOrDefault(topic)
|
||||
for peer in v:
|
||||
peers.add(peer.toNode(backOff.getOrDefault(peer.peerId)))
|
||||
res.add((topic: topic, peers: peers))
|
||||
res
|
||||
let colocationPeers =
|
||||
block:
|
||||
var res: seq[tuple[address: string, peerids: seq[PeerID]]]
|
||||
for k, v in node.network.pubsub.peersInIP:
|
||||
var peerids: seq[PeerID]
|
||||
for id in v:
|
||||
peerids.add(id)
|
||||
res.add(($k, peerids))
|
||||
res
|
||||
let peerStats =
|
||||
block:
|
||||
var stats: seq[PeerStatsTuple]
|
||||
for peerId, pstats in node.network.pubsub.peerStats:
|
||||
let peer = node.network.pubsub.peers.getOrDefault(peerId)
|
||||
stats.add(
|
||||
(
|
||||
peerId: peerId,
|
||||
null: isNil(peer),
|
||||
connected: if isNil(peer): false else: peer.connected(),
|
||||
expire: $(pstats.expire - Moment.now()),
|
||||
score: pstats.score
|
||||
)
|
||||
)
|
||||
stats
|
||||
let allPeers =
|
||||
block:
|
||||
var peers: seq[PeerStatusTuple]
|
||||
for peerId, peer in node.network.pubsub.peers:
|
||||
peers.add((peerId: peerId, connected: peer.connected))
|
||||
peers
|
||||
return RestApiResponse.jsonResponse(
|
||||
(
|
||||
gossip_peers: gossipPeers,
|
||||
mesh_peers: meshPeers,
|
||||
colocation_peers: colocationPeers,
|
||||
peer_stats: peerStats,
|
||||
all_peers: allPeers
|
||||
)
|
||||
)
|
|
@ -9,7 +9,7 @@ import
|
|||
../networking/[eth2_network, peer_pool],
|
||||
../spec/[datatypes, digest, presets],
|
||||
../spec/eth2_apis/callsigs_types,
|
||||
./rest_utils
|
||||
./eth2_json_rest_serialization, ./rest_utils
|
||||
|
||||
logScope: topics = "rest_node"
|
||||
|
||||
|
@ -138,41 +138,47 @@ proc installNodeApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
newSeq[string]()
|
||||
|
||||
return RestApiResponse.jsonResponse(
|
||||
%(
|
||||
(
|
||||
peer_id: $node.network.peerId(),
|
||||
enr: node.network.enrRecord().toUri(),
|
||||
p2p_addresses: p2pAddresses,
|
||||
discovery_addresses: discoveryAddresses,
|
||||
metadata: (node.network.metadata.seq_number,
|
||||
"0x" & ncrutils.toHex(node.network.metadata.attnets.bytes))
|
||||
metadata: (
|
||||
seq_number: node.network.metadata.seq_number,
|
||||
attnets: "0x" & ncrutils.toHex(node.network.metadata.attnets.bytes)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
router.api(MethodGet, "/api/eth/v1/node/peers") do (
|
||||
states: seq[PeerStateKind],
|
||||
directions: seq[PeerDirectKind]) -> RestApiResponse:
|
||||
if states.isErr():
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Invalid state value(s)",
|
||||
$states.error())
|
||||
if directions.isErr():
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Invalid direction value(s)",
|
||||
$directions.error())
|
||||
let sres = validateState(states.get())
|
||||
if sres.isErr():
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Invalid state value(s)",
|
||||
$sres.error())
|
||||
let dres = validateDirection(directions.get())
|
||||
if dres.isErr():
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Invalid direction value(s)",
|
||||
$dres.error())
|
||||
let connectionMask =
|
||||
block:
|
||||
if states.isErr():
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Invalid state value(s)",
|
||||
$states.error())
|
||||
let sres = validateState(states.get())
|
||||
if sres.isErr():
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Invalid state value(s)",
|
||||
$sres.error())
|
||||
sres.get()
|
||||
let directionMask =
|
||||
block:
|
||||
if directions.isErr():
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Invalid direction value(s)",
|
||||
$directions.error())
|
||||
let dres = validateDirection(directions.get())
|
||||
if dres.isErr():
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Invalid direction value(s)",
|
||||
$dres.error())
|
||||
dres.get()
|
||||
|
||||
var res: seq[NodePeerTuple]
|
||||
let connectionMask = sres.get()
|
||||
let directionMask = dres.get()
|
||||
for item in node.network.peers.values():
|
||||
if (item.connectionState in connectionMask) and
|
||||
(item.direction in directionMask):
|
||||
|
@ -186,8 +192,7 @@ proc installNodeApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
proto: item.info.protoVersion # part of specification.
|
||||
)
|
||||
res.add(peer)
|
||||
|
||||
return RestApiResponse.jsonResponse(%res)
|
||||
return RestApiResponse.jsonResponse(res)
|
||||
|
||||
router.api(MethodGet, "/api/eth/v1/node/peer_count") do () -> RestApiResponse:
|
||||
var res: NodePeerCountTuple
|
||||
|
@ -203,19 +208,22 @@ proc installNodeApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
inc(res.disconnected)
|
||||
of ConnectionState.None:
|
||||
discard
|
||||
return RestApiResponse.jsonResponse(%res)
|
||||
return RestApiResponse.jsonResponse(res)
|
||||
|
||||
router.api(MethodGet, "/api/eth/v1/node/peers/{peer_id}") do (
|
||||
peer_id: PeerID) -> RestApiResponse:
|
||||
if peer_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "PeerID could not be parsed",
|
||||
$peer_id.error())
|
||||
let peer = node.network.peers.getOrDefault(peer_id.get())
|
||||
if isNil(peer):
|
||||
return RestApiResponse.jsonError(Http404, "Peer not found")
|
||||
|
||||
let peer =
|
||||
block:
|
||||
if peer_id.isErr():
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Unable to parse PeerID value",
|
||||
$peer_id.error())
|
||||
let res = node.network.peers.getOrDefault(peer_id.get())
|
||||
if isNil(res):
|
||||
return RestApiResponse.jsonError(Http404, "Peer not found")
|
||||
res
|
||||
return RestApiResponse.jsonResponse(
|
||||
%(
|
||||
(
|
||||
peer_id: $peer.info.peerId,
|
||||
enr: if peer.enr.isSome(): peer.enr.get().toUri() else: "",
|
||||
last_seen_p2p_address: peer.info.getLastSeenAddress(),
|
||||
|
@ -228,13 +236,11 @@ proc installNodeApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
|
||||
router.api(MethodGet, "/api/eth/v1/node/version") do () -> RestApiResponse:
|
||||
return RestApiResponse.jsonResponse(
|
||||
%(version: "Nimbus/" & fullVersionStr)
|
||||
(version: "Nimbus/" & fullVersionStr)
|
||||
)
|
||||
|
||||
router.api(MethodGet, "/api/eth/v1/node/syncing") do () -> RestApiResponse:
|
||||
return RestApiResponse.jsonResponse(
|
||||
%node.syncManager.getInfo()
|
||||
)
|
||||
return RestApiResponse.jsonResponse(node.syncManager.getInfo())
|
||||
|
||||
router.api(MethodGet, "/api/eth/v1/node/health") do () -> RestApiResponse:
|
||||
# TODO: Add ability to detect node's issues and return 503 error according
|
||||
|
@ -244,4 +250,4 @@ proc installNodeApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|||
(health: 206)
|
||||
else:
|
||||
(health: 200)
|
||||
return RestApiResponse.jsonResponse(%res)
|
||||
return RestApiResponse.jsonResponse(res)
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import std/json
|
||||
import presto
|
||||
import libp2p/peerid
|
||||
import stew/[base10, byteutils]
|
||||
import nimcrypto/utils as ncrutils
|
||||
import ../spec/[crypto, digest, datatypes]
|
||||
import ../beacon_node_common
|
||||
import ../consensus_object_pools/[block_pools_types, blockchain_dag]
|
||||
|
||||
import presto,
|
||||
libp2p/peerid,
|
||||
stew/[base10, byteutils],
|
||||
faststreams/[outputs],
|
||||
serialization, json_serialization,
|
||||
nimcrypto/utils as ncrutils,
|
||||
../spec/[crypto, digest, datatypes],
|
||||
../beacon_node_common,
|
||||
../consensus_object_pools/[block_pools_types, blockchain_dag]
|
||||
export blockchain_dag, presto
|
||||
|
||||
const
|
||||
|
@ -31,6 +31,8 @@ const
|
|||
|
||||
FarFutureEpochString* = "18446744073709551615"
|
||||
|
||||
MaxEpoch* = compute_epoch_at_slot(not(0'u64))
|
||||
|
||||
type
|
||||
ValidatorQueryKind* {.pure.} = enum
|
||||
Index, Key
|
||||
|
@ -85,26 +87,10 @@ type
|
|||
PeerDirectKind* {.pure.} = enum
|
||||
Inbound, Outbound
|
||||
|
||||
proc toString*(s: uint64): string =
|
||||
Base10.toString(s)
|
||||
EventTopic* {.pure.} = enum
|
||||
Head, Block, Attestation, VoluntaryExit, FinalizedCheckpoint, ChainReorg
|
||||
|
||||
proc `%`*(s: Eth2Digest): JsonNode =
|
||||
JsonNode(kind: JString,
|
||||
str: "0x" & ncrutils.toHex(s.data, true))
|
||||
|
||||
proc toJsonHex(data: openArray[byte]): string =
|
||||
# Per the eth2 API spec, hex arrays are printed with leading 0x
|
||||
"0x" & ncrutils.toHex(data, true)
|
||||
|
||||
proc `%`*(list: List): JsonNode =
|
||||
%(asSeq(list))
|
||||
|
||||
proc `%`*(bitlist: BitList): JsonNode =
|
||||
newJString(toJsonHex(seq[byte](BitSeq(bitlist))))
|
||||
|
||||
proc `%`*(s: Version): JsonNode =
|
||||
JsonNode(kind: JString,
|
||||
str: "0x" & ncrutils.toHex(cast[array[4, byte]](s), true))
|
||||
EventTopics* = set[EventTopic]
|
||||
|
||||
func match(data: openarray[char], charset: set[char]): int =
|
||||
for ch in data:
|
||||
|
@ -373,6 +359,24 @@ proc decodeString*(t: typedesc[PeerDirectKind],
|
|||
else:
|
||||
err("Incorrect peer's direction value")
|
||||
|
||||
proc decodeString*(t: typedesc[EventTopic],
|
||||
value: string): Result[EventTopic, cstring] =
|
||||
case value
|
||||
of "head":
|
||||
ok(EventTopic.Head)
|
||||
of "block":
|
||||
ok(EventTopic.Block)
|
||||
of "attestation":
|
||||
ok(EventTopic.Attestation)
|
||||
of "voluntary_exit":
|
||||
ok(EventTopic.VoluntaryExit)
|
||||
of "finalized_checkpoint":
|
||||
ok(EventTopic.FinalizedCheckpoint)
|
||||
of "chain_reorg":
|
||||
ok(EventTopic.ChainReorg)
|
||||
else:
|
||||
err("Incorrect event's topic value")
|
||||
|
||||
proc decodeString*(t: typedesc[ValidatorSig],
|
||||
value: string): Result[ValidatorSig, cstring] =
|
||||
if len(value) != ValidatorSigSize + 2:
|
||||
|
@ -388,6 +392,10 @@ proc decodeString*(t: typedesc[GraffitiBytes],
|
|||
except ValueError:
|
||||
err("Unable to decode graffiti value")
|
||||
|
||||
proc decodeString*(t: typedesc[string],
|
||||
value: string): Result[string, cstring] =
|
||||
ok(value)
|
||||
|
||||
proc jsonResponse*(t: typedesc[RestApiResponse], j: JsonNode): RestApiResponse =
|
||||
let data = %*{"data": j}
|
||||
ok(ContentBody(contentType: "application/json",
|
||||
|
@ -407,8 +415,7 @@ proc getCurrentHead*(node: BeaconNode,
|
|||
|
||||
proc getCurrentHead*(node: BeaconNode,
|
||||
epoch: Epoch): Result[BlockRef, cstring] =
|
||||
const maxEpoch = compute_epoch_at_slot(not(0'u64))
|
||||
if epoch >= maxEpoch:
|
||||
if epoch >= MaxEpoch:
|
||||
return err("Requesting epoch for which slot would overflow")
|
||||
node.getCurrentHead(compute_start_slot_at_epoch(epoch))
|
||||
|
||||
|
@ -443,7 +450,6 @@ proc getBlockSlot*(node: BeaconNode,
|
|||
|
||||
proc getBlockDataFromBlockIdent*(node: BeaconNode,
|
||||
id: BlockIdent): Result[BlockData, cstring] =
|
||||
warn "Searching for block", ident = $id
|
||||
case id.kind
|
||||
of BlockQueryKind.Named:
|
||||
case id.value
|
||||
|
@ -465,8 +471,8 @@ proc getBlockDataFromBlockIdent*(node: BeaconNode,
|
|||
return err("Block not found")
|
||||
ok(node.chainDag.get(blockSlot.blck))
|
||||
|
||||
template withStateForStateIdent*(node: BeaconNode,
|
||||
blockSlot: BlockSlot, body: untyped): untyped =
|
||||
template withStateForBlockSlot*(node: BeaconNode,
|
||||
blockSlot: BlockSlot, body: untyped): untyped =
|
||||
template isState(state: StateData): bool =
|
||||
state.blck.atSlot(state.data.data.slot) == blockSlot
|
||||
|
||||
|
@ -478,12 +484,3 @@ template withStateForStateIdent*(node: BeaconNode,
|
|||
let rpcState = assignClone(node.chainDag.headState)
|
||||
node.chainDag.withState(rpcState[], blockSlot):
|
||||
body
|
||||
|
||||
proc jsonError*(t: typedesc[RestApiResponse], status: HttpCode = Http200,
|
||||
msg: string = "", stacktrace: string = ""): RestApiResponse =
|
||||
let data =
|
||||
if len(stacktrace) > 0:
|
||||
%*{"code": status.toInt(), "message": msg, "stacktrace": stacktrace}
|
||||
else:
|
||||
%*{"code": status.toInt(), "message": msg}
|
||||
RestApiResponse.error(status, $data, "application/json")
|
||||
|
|
|
@ -0,0 +1,386 @@
|
|||
# Copyright (c) 2018-2020 Status Research & Development GmbH
|
||||
# Licensed and distributed under either of
|
||||
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
||||
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
import
|
||||
std/[typetraits, strutils, deques, sets, options],
|
||||
stew/[results, base10],
|
||||
chronicles,
|
||||
nimcrypto/utils as ncrutils,
|
||||
../beacon_node_common, ../networking/eth2_network,
|
||||
../consensus_object_pools/[blockchain_dag, spec_cache, attestation_pool],
|
||||
../gossip_processing/gossip_validation,
|
||||
../validators/validator_duties,
|
||||
../spec/[crypto, digest, datatypes, network],
|
||||
../ssz/merkleization,
|
||||
./eth2_json_rest_serialization, ./rest_utils
|
||||
|
||||
logScope: topics = "rest_validatorapi"
|
||||
|
||||
type
|
||||
AttesterDutyTuple* = tuple
|
||||
pubkey: ValidatorPubKey
|
||||
validator_index: ValidatorIndex
|
||||
committee_index: CommitteeIndex
|
||||
committee_length: uint64
|
||||
committees_at_slot: uint64
|
||||
validator_committee_index: ValidatorIndex
|
||||
slot: Slot
|
||||
|
||||
ProposerDutyTuple* = tuple
|
||||
pubkey: ValidatorPubKey
|
||||
validator_index: ValidatorIndex
|
||||
slot: Slot
|
||||
|
||||
CommitteeSubscriptionTuple* = tuple
|
||||
validator_index: ValidatorIndex
|
||||
committee_index: CommitteeIndex
|
||||
committees_at_slot: uint64
|
||||
slot: Slot
|
||||
is_aggregator: bool
|
||||
|
||||
proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||
# https://ethereum.github.io/eth2.0-APIs/#/Validator/getAttesterDuties
|
||||
router.api(MethodPost, "/api/eth/v1/validator/duties/attester/{epoch}") do (
|
||||
epoch: Epoch, contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||
let indexList =
|
||||
block:
|
||||
if contentBody.isNone():
|
||||
return RestApiResponse.jsonError(Http400, "Empty request's body")
|
||||
let dres = decodeBody(seq[ValidatorIndex], contentBody.get())
|
||||
if dres.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Unable to decode " &
|
||||
"list of validator indexes", $dres.error())
|
||||
dres.get()
|
||||
let qepoch =
|
||||
block:
|
||||
if epoch.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Incorrect epoch value",
|
||||
$epoch.error())
|
||||
let res = epoch.get()
|
||||
if res >= MaxEpoch:
|
||||
return RestApiResponse.jsonError(Http400, "Requesting epoch for " &
|
||||
"which slot would overflow")
|
||||
res
|
||||
let qhead =
|
||||
block:
|
||||
let res = node.getCurrentHead(qepoch)
|
||||
if res.isErr():
|
||||
if not(node.isSynced(node.chainDag.head)):
|
||||
return RestApiResponse.jsonError(Http503, "Beacon node is " &
|
||||
"currently syncing and not serving request on that endpoint")
|
||||
else:
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Cound not find head for slot",
|
||||
$res.error())
|
||||
res.get()
|
||||
let droot =
|
||||
if qepoch >= Epoch(2):
|
||||
let bref = node.chainDag.getBlockByPreciseSlot(
|
||||
compute_start_slot_at_epoch(qepoch - 1) - 1
|
||||
)
|
||||
if isNil(bref):
|
||||
if not(node.isSynced(node.chainDag.head)):
|
||||
return RestApiResponse.jsonError(Http503, "Beacon node is " &
|
||||
"currently syncing and not serving request on that endpoint")
|
||||
else:
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Cound not find slot data")
|
||||
bref.root
|
||||
else:
|
||||
node.chainDag.genesis.root
|
||||
let duties =
|
||||
block:
|
||||
var res: seq[AttesterDutyTuple]
|
||||
let epochRef = node.chainDag.getEpochRef(qhead, qepoch)
|
||||
let committees_per_slot = get_committee_count_per_slot(epochRef)
|
||||
for i in 0 ..< SLOTS_PER_EPOCH:
|
||||
let slot = compute_start_slot_at_epoch(qepoch) + i
|
||||
for committee_index in 0'u64 ..< committees_per_slot:
|
||||
let commitee = get_beacon_committee(
|
||||
epochRef, slot, CommitteeIndex(committee_index)
|
||||
)
|
||||
for index_in_committee, validator_index in commitee:
|
||||
if validator_index < ValidatorIndex(len(epochRef.validator_keys)):
|
||||
let validator_key = epochRef.validator_keys[validator_index]
|
||||
if validator_index in indexList:
|
||||
res.add(
|
||||
(
|
||||
pubkey: validator_key,
|
||||
validator_index: validator_index,
|
||||
committee_index: CommitteeIndex(committee_index),
|
||||
committee_length: lenu64(commitee),
|
||||
committees_at_slot: committees_per_slot,
|
||||
validator_committee_index:
|
||||
ValidatorIndex(index_in_committee),
|
||||
slot: slot
|
||||
)
|
||||
)
|
||||
res
|
||||
return RestApiResponse.jsonResponseWRoot(duties, droot)
|
||||
|
||||
# https://ethereum.github.io/eth2.0-APIs/#/Validator/getProposerDuties
|
||||
router.api(MethodGet, "/api/eth/v1/validator/duties/proposer/{epoch}") do (
|
||||
epoch: Epoch) -> RestApiResponse:
|
||||
let qepoch =
|
||||
block:
|
||||
if epoch.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Incorrect epoch value",
|
||||
$epoch.error())
|
||||
let res = epoch.get()
|
||||
if res >= MaxEpoch:
|
||||
return RestApiResponse.jsonError(Http400, "Requesting epoch for " &
|
||||
"which slot would overflow")
|
||||
res
|
||||
let qhead =
|
||||
block:
|
||||
let res = node.getCurrentHead(qepoch)
|
||||
if res.isErr():
|
||||
if not(node.isSynced(node.chainDag.head)):
|
||||
return RestApiResponse.jsonError(Http503, "Beacon node is " &
|
||||
"currently syncing and not serving request on that endpoint")
|
||||
else:
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Cound not find head for slot",
|
||||
$res.error())
|
||||
res.get()
|
||||
let droot =
|
||||
if qepoch >= Epoch(2):
|
||||
let bref = node.chainDag.getBlockByPreciseSlot(
|
||||
compute_start_slot_at_epoch(qepoch - 1) - 1
|
||||
)
|
||||
if isNil(bref):
|
||||
if not(node.isSynced(node.chainDag.head)):
|
||||
return RestApiResponse.jsonError(Http503, "Beacon node is " &
|
||||
"currently syncing and not serving request on that endpoint")
|
||||
else:
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Cound not find slot data")
|
||||
bref.root
|
||||
else:
|
||||
node.chainDag.genesis.root
|
||||
let duties =
|
||||
block:
|
||||
var res: seq[ProposerDutyTuple]
|
||||
let epochRef = node.chainDag.getEpochRef(qhead, qepoch)
|
||||
for i in 0 ..< SLOTS_PER_EPOCH:
|
||||
if epochRef.beacon_proposers[i].isSome():
|
||||
let proposer = epochRef.beacon_proposers[i].get()
|
||||
res.add(
|
||||
(
|
||||
pubkey: proposer[1],
|
||||
validator_index: proposer[0],
|
||||
slot: compute_start_slot_at_epoch(qepoch) + i
|
||||
)
|
||||
)
|
||||
res
|
||||
return RestApiResponse.jsonResponseWRoot(duties, droot)
|
||||
|
||||
# https://ethereum.github.io/eth2.0-APIs/#/Validator/produceBlock
|
||||
router.api(MethodGet, "/api/eth/v1/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, "Incorrect slot value",
|
||||
$slot.error())
|
||||
slot.get()
|
||||
let qrandao =
|
||||
if randao_reveal.isNone():
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Missing randao_reveal value")
|
||||
else:
|
||||
let res = randao_reveal.get()
|
||||
if res.isErr():
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Incorrect randao_reveal value",
|
||||
$res.error())
|
||||
res.get()
|
||||
let qgraffiti =
|
||||
if graffiti.isNone():
|
||||
defaultGraffitiBytes()
|
||||
else:
|
||||
let res = graffiti.get()
|
||||
if res.isErr():
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Incorrect graffiti bytes value",
|
||||
$res.error())
|
||||
res.get()
|
||||
let qhead =
|
||||
block:
|
||||
let res = node.getCurrentHead(qslot)
|
||||
if res.isErr():
|
||||
if not(node.isSynced(node.chainDag.head)):
|
||||
return RestApiResponse.jsonError(Http503, "Beacon node is " &
|
||||
"currently syncing and not serving request on that endpoint")
|
||||
else:
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Cound not find head for slot",
|
||||
$res.error())
|
||||
res.get()
|
||||
let proposer = node.chainDag.getProposer(qhead, qslot)
|
||||
if proposer.isNone():
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Could not retrieve block for slot")
|
||||
let res = makeBeaconBlockForHeadAndSlot(
|
||||
node, qrandao, proposer.get()[0], qgraffiti, qhead, qslot)
|
||||
if res.isNone():
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Could not make block for slot")
|
||||
res.get()
|
||||
return RestApiResponse.jsonResponse(message)
|
||||
|
||||
# https://ethereum.github.io/eth2.0-APIs/#/Validator/produceAttestationData
|
||||
router.api(MethodGet, "/api/eth/v1/validator/attestation_data") do (
|
||||
slot: Option[Slot],
|
||||
committee_index: Option[CommitteeIndex]) -> RestApiResponse:
|
||||
let adata =
|
||||
block:
|
||||
let qslot =
|
||||
block:
|
||||
if slot.isNone():
|
||||
return RestApiResponse.jsonError(Http400, "Missing slot value")
|
||||
let res = slot.get()
|
||||
if res.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Incorrect slot value",
|
||||
$res.error())
|
||||
res.get()
|
||||
let qindex =
|
||||
block:
|
||||
if committee_index.isNone():
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Missing committee_index value")
|
||||
let res = committee_index.get()
|
||||
if res.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Incorrect " &
|
||||
"committee_index value",
|
||||
$res.error())
|
||||
res.get()
|
||||
let qhead =
|
||||
block:
|
||||
let res = node.getCurrentHead(qslot)
|
||||
if res.isErr():
|
||||
if not(node.isSynced(node.chainDag.head)):
|
||||
return RestApiResponse.jsonError(Http503, "Beacon node is " &
|
||||
"currently syncing and not serving request on that endpoint")
|
||||
else:
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Cound not find head for slot",
|
||||
$res.error())
|
||||
res.get()
|
||||
let epochRef = node.chainDag.getEpochRef(qhead, qslot.epoch)
|
||||
makeAttestationData(epochRef, qhead.atSlot(qslot), qindex)
|
||||
return RestApiResponse.jsonResponse(adata)
|
||||
|
||||
# https://ethereum.github.io/eth2.0-APIs/#/Validator/getAggregatedAttestation
|
||||
router.api(MethodGet, "/api/eth/v1/validator/aggregate_attestation") do (
|
||||
attestation_data_root: Option[Eth2Digest],
|
||||
slot: Option[Slot]) -> RestApiResponse:
|
||||
let attestation =
|
||||
block:
|
||||
let qslot =
|
||||
block:
|
||||
if slot.isNone():
|
||||
return RestApiResponse.jsonError(Http400, "Missing slot value")
|
||||
let res = slot.get()
|
||||
if res.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Incorrect slot value",
|
||||
$res.error())
|
||||
res.get()
|
||||
let qroot =
|
||||
block:
|
||||
if attestation_data_root.isNone():
|
||||
return RestApiResponse.jsonError(Http400, "Missing " &
|
||||
"attestation_data_root value")
|
||||
let res = attestation_data_root.get()
|
||||
if res.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Incorrect " &
|
||||
"attestation_data_root value",
|
||||
$res.error())
|
||||
res.get()
|
||||
let res = node.attestationPool[].getAggregatedAttestation(qslot, qroot)
|
||||
if res.isNone():
|
||||
return RestApiResponse.jsonError(Http400, "Could not retrieve an " &
|
||||
"aggregated attestation")
|
||||
res.get()
|
||||
return RestApiResponse.jsonResponse(attestation)
|
||||
|
||||
# https://ethereum.github.io/eth2.0-APIs/#/Validator/publishAggregateAndProofs
|
||||
router.api(MethodPost, "/api/eth/v1/validator/aggregate_and_proofs") do (
|
||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||
let payload =
|
||||
block:
|
||||
if contentBody.isNone():
|
||||
return RestApiResponse.jsonError(Http400, "Empty request's body")
|
||||
let dres = decodeBody(SignedAggregateAndProof, contentBody.get())
|
||||
if dres.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Unable to decode " &
|
||||
"SignedAggregateAndProof object", $dres.error())
|
||||
dres.get()
|
||||
|
||||
let wallTime = node.processor.getWallTime()
|
||||
let res = node.attestationPool[].validateAggregate(payload, wallTime)
|
||||
if res.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Aggregate and proofs " &
|
||||
"verification failed", $res.error())
|
||||
node.network.broadcast(node.topicAggregateAndProofs, payload)
|
||||
return RestApiResponse.jsonError(Http200,
|
||||
"Aggregate and proofs was broadcasted")
|
||||
|
||||
# https://ethereum.github.io/eth2.0-APIs/#/Validator/prepareBeaconCommitteeSubnet
|
||||
router.api(MethodPost,
|
||||
"/api/eth/v1/validator/beacon_committee_subscriptions") do (
|
||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||
# TODO: This call could not be finished because more complex peer manager
|
||||
# is needed.
|
||||
let requests =
|
||||
block:
|
||||
if contentBody.isNone():
|
||||
return RestApiResponse.jsonError(Http400, "Empty request's body")
|
||||
let dres = decodeBody(seq[CommitteeSubscriptionTuple],
|
||||
contentBody.get())
|
||||
if dres.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Unable to decode " &
|
||||
"subscription request(s)")
|
||||
dres.get()
|
||||
if not(node.isSynced(node.chainDag.head)):
|
||||
return RestApiResponse.jsonError(Http503, "Beacon node is " &
|
||||
"currently syncing and not serving request on that endpoint")
|
||||
|
||||
for request in requests:
|
||||
if uint64(request.committee_index) >= uint64(ATTESTATION_SUBNET_COUNT):
|
||||
return RestApiResponse.jsonError(Http400, "Invalid committee_index " &
|
||||
"value")
|
||||
let validator_pubkey =
|
||||
block:
|
||||
let idx = request.validator_index
|
||||
if uint64(idx) >=
|
||||
lenu64(node.chainDag.headState.data.data.validators):
|
||||
return RestApiResponse.jsonError(Http400,
|
||||
"Invalid validator_index value")
|
||||
node.chainDag.headState.data.data.validators[idx].pubkey
|
||||
|
||||
let wallSlot = node.beaconClock.now.slotOrZero
|
||||
if wallSlot > request.slot + 1:
|
||||
return RestApiResponse.jsonError(Http400, "Past slot requested")
|
||||
let epoch = request.slot.epoch
|
||||
if epoch >= wallSlot.epoch and epoch - wallSlot.epoch > 1:
|
||||
return RestApiResponse.jsonError(Http400, "Slot requested not in " &
|
||||
"next wall-slot epoch")
|
||||
let head =
|
||||
block:
|
||||
let res = node.getCurrentHead(epoch)
|
||||
if res.isErr():
|
||||
return RestApiResponse.jsonError(Http400, "Unable to obtain head",
|
||||
$res.error())
|
||||
res.get()
|
||||
let epochRef = node.chainDag.getEpochRef(head, epoch)
|
||||
let subnet = uint8(compute_subnet_for_attestation(
|
||||
get_committee_count_per_slot(epochRef), request.slot,
|
||||
request.committee_index)
|
||||
)
|
||||
return RestApiResponse.jsonError(Http500, "Not implemented yet")
|
Loading…
Reference in New Issue