nimbus-eth2/beacon_chain/rpc/beacon_api.nim

431 lines
16 KiB
Nim
Raw Normal View History

# 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/[parseutils, sequtils, strutils, deques, sets],
stew/results,
json_rpc/[rpcserver, jsonmarshal],
chronicles,
nimcrypto/utils as ncrutils,
../beacon_node_common, ../eth2_json_rpc_serialization, ../eth2_network,
../validator_duties,
../block_pools/chain_dag, ../exit_pool,
../spec/[crypto, digest, datatypes, validator, network],
../spec/eth2_apis/callsigs_types,
../ssz/merkleization,
./rpc_utils
logScope: topics = "beaconapi"
type
RpcServer = RpcHttpServer
ValidatorQuery = object
keyset: HashSet[ValidatorPubKey]
ids: seq[uint64]
template unimplemented() =
raise (ref CatchableError)(msg: "Unimplemented")
proc parsePubkey(str: string): ValidatorPubKey =
const expectedLen = RawPubKeySize * 2 + 2
if str.len != expectedLen: # +2 because of the `0x` prefix
raise newException(ValueError,
"A hex public key should be exactly " & $expectedLen & " characters. " &
$str.len & " provided")
let pubkeyRes = fromHex(ValidatorPubKey, str)
if pubkeyRes.isErr:
raise newException(CatchableError, "Not a valid public key")
return pubkeyRes[]
proc createQuery(ids: seq[string]): Result[ValidatorQuery, string] =
# validatorIds array should have maximum 30 items, and all items should be
# unique.
if len(ids) > 30:
return err("The number of ids exceeds the limit")
# All ids in validatorIds must be unique.
if len(ids) != len(toHashSet(ids)):
return err("ids array must have unique item")
var res = ValidatorQuery(
keyset: initHashSet[ValidatorPubKey](),
ids: newSeq[uint64]()
)
for item in ids:
if item.startsWith("0x"):
if len(item) != RawPubKeySize * 2 + 2:
return err("Incorrect hexadecimal key")
let pubkeyRes = ValidatorPubKey.fromHex(item)
if pubkeyRes.isErr:
return err("Incorrect public key")
res.keyset.incl(pubkeyRes.get())
else:
var tmp: uint64
if parseBiggestUInt(item, tmp) != len(item):
return err("Incorrect index value")
res.ids.add(tmp)
ok(res)
proc getValidatorInfoFromValidatorId(
state: BeaconState,
current_epoch: Epoch,
validatorId: string,
status = ""):
Option[BeaconStatesValidatorsTuple] =
const allowedStatuses = ["", "pending", "pending_initialized", "pending_queued",
"active", "active_ongoing", "active_exiting", "active_slashed", "exited",
"exited_unslashed", "exited_slashed", "withdrawal", "withdrawal_possible",
"withdrawal_done"]
if status notin allowedStatuses:
raise newException(CatchableError, "Invalid status requested")
var validatorIdx: uint64
let validator = if validatorId.startsWith("0x"):
let pubkey = parsePubkey(validatorId)
let idx = state.validators.asSeq.findIt(it.pubKey == pubkey)
if idx == -1:
raise newException(CatchableError, "Could not find validator")
validatorIdx = idx.uint64
state.validators[idx]
else:
if parseBiggestUInt(validatorId, validatorIdx) != validatorId.len:
raise newException(CatchableError, "Not a valid index")
if validatorIdx > state.validators.lenu64:
raise newException(CatchableError, "Index out of bounds")
state.validators[validatorIdx]
# time to determine the status of the validator - the code mimics
# whatever is detailed here: https://hackmd.io/ofFJ5gOmQpu1jjHilHbdQQ
let actual_status = if validator.activation_epoch > current_epoch:
# pending
if validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH:
"pending_initialized"
else:
# validator.activation_eligibility_epoch < FAR_FUTURE_EPOCH:
"pending_queued"
elif validator.activation_epoch <= current_epoch and
current_epoch < validator.exit_epoch:
# active
if validator.exit_epoch == FAR_FUTURE_EPOCH:
"active_ongoing"
elif not validator.slashed:
# validator.exit_epoch < FAR_FUTURE_EPOCH
"active_exiting"
else:
# validator.exit_epoch < FAR_FUTURE_EPOCH and validator.slashed:
"active_slashed"
elif validator.exit_epoch <= current_epoch and
current_epoch < validator.withdrawable_epoch:
# exited
if not validator.slashed:
"exited_unslashed"
else:
# validator.slashed
"exited_slashed"
elif validator.withdrawable_epoch <= current_epoch:
# withdrawal
if validator.effective_balance != 0:
"withdrawal_possible"
else:
# validator.effective_balance == 0
"withdrawal_done"
else:
raise newException(CatchableError, "Invalid validator status")
# if the requested status doesn't match the actual status
if status != "" and status notin actual_status:
return none(BeaconStatesValidatorsTuple)
return some((validator: validator,
index: validatorIdx,
status: actual_status,
balance: validator.effective_balance))
proc getBlockDataFromBlockId(node: BeaconNode, blockId: string): BlockData =
result = case blockId:
of "head":
node.chainDag.get(node.chainDag.head)
of "genesis":
node.chainDag.getGenesisBlockData()
of "finalized":
node.chainDag.get(node.chainDag.finalizedHead.blck)
else:
if blockId.startsWith("0x"):
let blckRoot = parseRoot(blockId)
let blockData = node.chainDag.get(blckRoot)
if blockData.isNone:
raise newException(CatchableError, "Block not found")
blockData.get()
else:
let blockSlot = node.getBlockSlotFromString(blockId)
if blockSlot.blck.isNil:
raise newException(CatchableError, "Block not found")
node.chainDag.get(blockSlot.blck)
proc installBeaconApiHandlers*(rpcServer: RpcServer, node: BeaconNode) =
rpcServer.rpc("get_v1_beacon_genesis") do () -> BeaconGenesisTuple:
return (
genesis_time: node.chainDag.headState.data.data.genesis_time,
genesis_validators_root:
node.chainDag.headState.data.data.genesis_validators_root,
genesis_fork_version: node.config.runtimePreset.GENESIS_FORK_VERSION
)
rpcServer.rpc("get_v1_beacon_states_root") do (stateId: string) -> Eth2Digest:
withStateForStateId(stateId):
return hashedState.root
rpcServer.rpc("get_v1_beacon_states_fork") do (stateId: string) -> Fork:
withStateForStateId(stateId):
return state.fork
rpcServer.rpc("get_v1_beacon_states_finality_checkpoints") do (
stateId: string) -> BeaconStatesFinalityCheckpointsTuple:
withStateForStateId(stateId):
return (previous_justified: state.previous_justified_checkpoint,
current_justified: state.current_justified_checkpoint,
finalized: state.finalized_checkpoint)
rpcServer.rpc("get_v1_beacon_states_stateId_validators") do (
stateId: string, validatorIds: seq[string],
status: string) -> seq[BeaconStatesValidatorsTuple]:
let current_epoch = get_current_epoch(node.chainDag.headState.data.data)
withStateForStateId(stateId):
for validatorId in validatorIds:
let res = state.getValidatorInfoFromValidatorId(
current_epoch, validatorId, status)
if res.isSome():
result.add(res.get())
rpcServer.rpc("get_v1_beacon_states_stateId_validators_validatorId") do (
stateId: string, validatorId: string) -> BeaconStatesValidatorsTuple:
let current_epoch = get_current_epoch(node.chainDag.headState.data.data)
withStateForStateId(stateId):
let res = state.getValidatorInfoFromValidatorId(current_epoch, validatorId)
if res.isNone:
# TODO should we raise here? Maybe this is different from the array case...
raise newException(CatchableError, "Validator status differs")
return res.get()
rpcServer.rpc("get_v1_beacon_states_stateId_validator_balances") do (
stateId: string, validatorsId: Option[seq[string]]) -> seq[BalanceTuple]:
var res: seq[BalanceTuple]
withStateForStateId(stateId):
if validatorsId.isNone():
for index, value in state.balances.pairs():
let balance = (index: uint64(index), balance: value)
res.add(balance)
else:
let qres = createQuery(validatorsId.get())
if qres.isErr:
raise newException(CatchableError, qres.error)
var query = qres.get()
for index in query.ids:
if index < lenu64(state.validators):
let validator = state.validators[index]
query.keyset.excl(validator.pubkey)
let balance = (index: uint64(index),
balance: validator.effective_balance)
res.add(balance)
for index, validator in state.validators.pairs():
if validator.pubkey in query.keyset:
let balance = (index: uint64(index),
balance: validator.effective_balance)
res.add(balance)
return res
rpcServer.rpc("get_v1_beacon_states_stateId_committees_epoch") do (
stateId: string, epoch: uint64, index: uint64, slot: uint64) ->
seq[BeaconStatesCommitteesTuple]:
checkEpochToSlotOverflow(epoch.Epoch)
withStateForStateId(stateId):
proc getCommittee(slot: Slot, index: CommitteeIndex): BeaconStatesCommitteesTuple =
let vals = get_beacon_committee(state, slot, index, cache).mapIt(it.uint64)
return (index: index.uint64, slot: slot.uint64, validators: vals)
proc forSlot(slot: Slot, res: var seq[BeaconStatesCommitteesTuple]) =
let committees_per_slot =
get_committee_count_per_slot(state, slot.epoch, cache)
if index == 0: # parameter is missing (it's optional)
for committee_index in 0'u64..<committees_per_slot:
res.add(getCommittee(slot, committee_index.CommitteeIndex))
else:
if index >= committees_per_slot:
raise newException(ValueError, "Committee index out of bounds")
res.add(getCommittee(slot, index.CommitteeIndex))
if slot == 0: # parameter is missing (it's optional)
for i in 0 ..< SLOTS_PER_EPOCH:
forSlot(compute_start_slot_at_epoch(epoch.Epoch) + i, result)
else:
forSlot(slot.Slot, result)
rpcServer.rpc("get_v1_beacon_headers") do (
slot: Option[string], parent_root: Option[string]) ->
seq[BeaconHeadersTuple]:
unimplemented()
rpcServer.rpc("get_v1_beacon_headers_blockId") do (
blockId: string) ->
tuple[canonical: bool, header: SignedBeaconBlockHeader]:
let bd = node.getBlockDataFromBlockId(blockId)
let tsbb = bd.data
result.header.signature = ValidatorSig.init tsbb.signature.data
result.header.message.slot = tsbb.message.slot
result.header.message.proposer_index = tsbb.message.proposer_index
result.header.message.parent_root = tsbb.message.parent_root
result.header.message.state_root = tsbb.message.state_root
result.header.message.body_root = tsbb.message.body.hash_tree_root()
result.canonical = bd.refs.isAncestorOf(node.chainDag.head)
rpcServer.rpc("post_v1_beacon_blocks") do (blck: SignedBeaconBlock) -> int:
if not(node.syncManager.inProgress):
raise newException(CatchableError,
"Beacon node is currently syncing, try again later.")
let head = node.chainDag.head
if head.slot >= blck.message.slot:
node.network.broadcast(getBeaconBlocksTopic(node.forkDigest), blck)
# The block failed validation, but was successfully broadcast anyway.
# It was not integrated into the beacon node''s database.
return 202
else:
let res = await proposeSignedBlock(node, head, AttachedValidator(), blck)
if res == head:
node.network.broadcast(getBeaconBlocksTopic(node.forkDigest), blck)
# The block failed validation, but was successfully broadcast anyway.
# It was not integrated into the beacon node''s database.
return 202
else:
# The block was validated successfully and has been broadcast.
# It has also been integrated into the beacon node's database.
return 200
rpcServer.rpc("get_v1_beacon_blocks_blockId") do (
blockId: string) -> TrustedSignedBeaconBlock:
return node.getBlockDataFromBlockId(blockId).data
rpcServer.rpc("get_v1_beacon_blocks_blockId_root") do (
blockId: string) -> Eth2Digest:
return node.getBlockDataFromBlockId(blockId).data.message.state_root
rpcServer.rpc("get_v1_beacon_blocks_blockId_attestations") do (
blockId: string) -> seq[TrustedAttestation]:
return node.getBlockDataFromBlockId(blockId).data.message.body.attestations.asSeq
rpcServer.rpc("get_v1_beacon_pool_attestations") do (
slot: Option[string], committee_index: Option[string]) ->
seq[AttestationTuple]:
var res: seq[AttestationTuple]
let qslot =
if slot.isSome():
var tmp: uint64
let sslot = slot.get()
if parseBiggestUInt(sslot, tmp) != len(sslot):
raise newException(CatchableError, "Incorrect slot number")
some(Slot(tmp))
else:
none[Slot]()
let qindex =
if committee_index.isSome():
var tmp: uint64
let scommittee_index = committee_index.get()
if parseBiggestUInt(scommittee_index, tmp) != len(scommittee_index):
raise newException(CatchableError, "Incorrect committee_index number")
some(CommitteeIndex(tmp))
else:
none[CommitteeIndex]()
for item in node.attestationPool[].attestations(qslot, qindex):
let atuple = (
aggregation_bits: "0x" & ncrutils.toHex(item.aggregation_bits.bytes),
data: item.data,
signature: item.signature
)
res.add(atuple)
return res
rpcServer.rpc("post_v1_beacon_pool_attestations") do (
attestation: Attestation) -> bool:
node.sendAttestation(attestation)
return true
rpcServer.rpc("get_v1_beacon_pool_attester_slashings") do (
) -> seq[AttesterSlashing]:
var res: seq[AttesterSlashing]
if isNil(node.exitPool):
return res
let length = len(node.exitPool.attester_slashings)
res = newSeqOfCap[AttesterSlashing](length)
for item in node.exitPool.attester_slashings.items():
res.add(item)
return res
rpcServer.rpc("post_v1_beacon_pool_attester_slashings") do (
slashing: AttesterSlashing) -> bool:
if isNil(node.exitPool):
raise newException(CatchableError, "Exit pool is not yet available!")
let validity = node.exitPool[].validateAttesterSlashing(slashing)
if validity.isOk:
node.sendAttesterSlashing(slashing)
else:
raise newException(CatchableError, $(validity.error[1]))
return true
rpcServer.rpc("get_v1_beacon_pool_proposer_slashings") do (
) -> seq[ProposerSlashing]:
var res: seq[ProposerSlashing]
if isNil(node.exitPool):
return res
let length = len(node.exitPool.proposer_slashings)
res = newSeqOfCap[ProposerSlashing](length)
for item in node.exitPool.proposer_slashings.items():
res.add(item)
return res
rpcServer.rpc("post_v1_beacon_pool_proposer_slashings") do (
slashing: ProposerSlashing) -> bool:
if isNil(node.exitPool):
raise newException(CatchableError, "Exit pool is not yet available!")
let validity = node.exitPool[].validateProposerSlashing(slashing)
if validity.isOk:
node.sendProposerSlashing(slashing)
else:
raise newException(CatchableError, $(validity.error[1]))
return true
rpcServer.rpc("get_v1_beacon_pool_voluntary_exits") do (
) -> seq[SignedVoluntaryExit]:
var res: seq[SignedVoluntaryExit]
if isNil(node.exitPool):
return res
let length = len(node.exitPool.voluntary_exits)
res = newSeqOfCap[SignedVoluntaryExit](length)
for item in node.exitPool.voluntary_exits.items():
res.add(item)
return res
rpcServer.rpc("post_v1_beacon_pool_voluntary_exits") do (
exit: SignedVoluntaryExit) -> bool:
if isNil(node.exitPool):
raise newException(CatchableError, "Exit pool is not yet available!")
let validity = node.exitPool[].validateVoluntaryExit(exit)
if validity.isOk:
node.sendVoluntaryExit(exit)
else:
raise newException(CatchableError, $(validity.error[1]))
return true