nimbus-eth2/beacon_chain/rpc/rest_nimbus_api.nim
Jacek Sieka a5151bc546
validator_duties->beacon_validators (#5333)
This PR renames the existing `validator_duties` to `beacon_validators`
and in doing so, names validators running inside the beacon node process
"beacon validators" while those running the VC can be referred to as
"client validators" to disambiguate the two.

The existing `validator_duties` instead takes on a new responsibility:
as a home for logic shared between beacon and client validators - ie
code that provides consistency in implementation and behavior between
the two modes of operation.

Not only does this simplify reasoning about where to put code -it also
reduces the number of dependencies the validator client has from ~5000
to ~3000 modules (!) according to `nim genDepend` significantly reducing
compile times.
2023-08-23 19:39:57 +03:00

518 lines
18 KiB
Nim

# beacon_chain
# Copyright (c) 2018-2023 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/[sequtils],
stew/[results, base10],
chronicles,
chronos/apps/http/httpdebug,
libp2p/[multiaddress, multicodec, peerstore],
libp2p/protocols/pubsub/pubsubpeer,
./rest_utils,
../el/el_manager,
../validators/beacon_validators,
../spec/[forks, beacon_time],
../beacon_node, ../nimbus_binary_common
export rest_utils
when defined(chronosFutureTracking):
import stew/base10
logScope: topics = "rest_nimbusapi"
type
RestPeerInfo* = object
peerId*: string
addrs*: seq[string]
protocols*: seq[string]
protoVersion*: string
agentVersion*: string
RestPeerInfoTuple* = tuple
peerId: string
addrs: seq[string]
protocols: seq[string]
protoVersion: string
agentVersion: string
RestSimplePeer* = object
info*: RestPeerInfo
connectionState*: string
score*: int
RestFutureInfo* = object
id*: string
child_id*: string
procname*: string
filename*: string
line*: int
state*: string
RestChronosMetricsInfo* = object
tcp_transports*: uint64
udp_transports*: uint64
tcp_servers*: uint64
stream_readers*: uint64
stream_writers*: uint64
http_client_connections*: uint64
http_client_requests*: uint64
http_client_responses*: uint64
http_server_secure_connections*: uint64
http_server_unsecure_connections*: uint64
http_server_requests*: uint64
http_server_responses*: uint64
http_body_readers*: uint64
http_body_writers*: uint64
RestConnectionInfo* = object
handle*: string
query*: string
connection_type*: string
connection_state*: string
remote_address*: string
local_address*: string
since_accept*: string
since_create*: string
RestPubSubPeer* = object
peerId*: PeerId
score*: float64
iHaveBudget*: int
outbound*: bool
appScore*: float64
behaviourPenalty*: float64
sendConnAvail*: bool
closed*: bool
atEof*: bool
address*: string
backoff*: string
agent*: string
RestPeerStats* = object
peerId*: PeerId
null*: bool
connected*: bool
expire*: string
score*: float64
RestPeerStatus* = object
peerId*: PeerId
connected*: bool
proc toInfo(node: BeaconNode, peerId: PeerId): RestPeerInfo =
RestPeerInfo(
peerId: $peerId,
addrs: node.network.switch.peerStore[AddressBook][peerId].mapIt($it),
protocols: node.network.switch.peerStore[ProtoBook][peerId],
protoVersion: node.network.switch.peerStore[ProtoVersionBook][peerId],
agentVersion: node.network.switch.peerStore[AgentBook][peerId]
)
proc toNode(v: PubSubPeer, backoff: Moment): RestPubSubPeer =
RestPubSubPeer(
peerId: v.peerId,
score: v.score,
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, "/nimbus/v1/beacon/head") do () -> RestApiResponse:
return RestApiResponse.jsonResponse(node.dag.head.slot)
router.api(MethodGet, "/nimbus/v1/chain/head") do() -> RestApiResponse:
let
head = node.dag.head
finalized = getStateField(node.dag.headState, finalized_checkpoint)
justified =
getStateField(node.dag.headState, 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, "/nimbus/v1/syncmanager/status") do (
) -> RestApiResponse:
return RestApiResponse.jsonResponse(node.syncManager.inProgress)
router.api(MethodGet, "/nimbus/v1/node/peerid") do (
) -> RestApiResponse:
return RestApiResponse.jsonResponse((peerid: $node.network.peerId()))
router.api(MethodGet, "/nimbus/v1/node/version") do (
) -> RestApiResponse:
return RestApiResponse.jsonResponse((version: "Nimbus/" & fullVersionStr))
router.api(MethodGet, "/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, "/nimbus/v1/network/peers") do (
) -> RestApiResponse:
var res: seq[RestSimplePeer]
for id, peer in node.network.peerPool:
res.add(
RestSimplePeer(
info: toInfo(node, id),
connectionState: $peer.connectionState,
score: peer.score
)
)
return RestApiResponse.jsonResponse((peers: res))
router.api(MethodPost, "/nimbus/v1/graffiti") do (
contentBody: Option[ContentBody]) -> RestApiResponse:
if contentBody.isNone:
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
template setGraffitiAux(node: BeaconNode,
graffitiStr: string): RestApiResponse =
node.graffitiBytes = try:
GraffitiBytes.init(graffitiStr)
except CatchableError as err:
return RestApiResponse.jsonError(Http400, InvalidGraffitiBytesValue,
err.msg)
RestApiResponse.jsonResponse((result: true))
let body = contentBody.get()
if body.contentType == ApplicationJsonMediaType:
let graffitiBytes = decodeBody(GraffitiBytes, body)
if graffitiBytes.isErr():
return RestApiResponse.jsonError(Http400, InvalidGraffitiBytesValue,
$graffitiBytes.error())
node.graffitiBytes = graffitiBytes.get()
return RestApiResponse.jsonResponse((result: true))
elif body.contentType == TextPlainMediaType:
return node.setGraffitiAux body.strData()
elif body.contentType == UrlEncodedMediaType:
return node.setGraffitiAux decodeUrl(body.strData())
else:
return RestApiResponse.jsonError(Http400, "Unsupported content type: " &
$body.contentType)
router.api(MethodGet, "/nimbus/v1/graffiti") do (
) -> RestApiResponse:
return RestApiResponse.jsonResponse(node.graffitiBytes)
router.api(MethodPost, "/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, InvalidLogLevelValueError,
$res.error())
res.get()
{.gcsafe.}:
updateLogLevel(level)
return RestApiResponse.jsonResponse((result: true))
router.api(MethodGet, "/nimbus/v1/eth1/chain") do (
) -> RestApiResponse:
let res = mapIt(node.elManager.eth1ChainBlocks, it)
return RestApiResponse.jsonResponse(res)
router.api(MethodGet, "/nimbus/v1/eth1/proposal_data") do (
) -> RestApiResponse:
let wallSlot = node.beaconClock.now.slotOrZero
let head =
block:
let res = node.getSyncedHead(wallSlot)
if res.isErr():
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError,
$res.error())
let tres = res.get()
if not tres.executionValid:
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
tres
let proposalState = assignClone(node.dag.headState)
node.dag.withUpdatedState(
proposalState[],
head.atSlot(wallSlot).toBlockSlotId().expect("not nil")):
return RestApiResponse.jsonResponse(
node.getBlockProposalEth1Data(updatedState))
do:
return RestApiResponse.jsonError(Http400, PrunedStateError)
router.api(MethodGet, "/nimbus/v1/debug/chronos/futures") do (
) -> RestApiResponse:
when defined(chronosFutureTracking):
var res: seq[RestFutureInfo]
for item in pendingFutures():
let loc = item.location[LocCreateIndex][]
let futureId = Base10.toString(item.id)
let childId =
if isNil(item.child): ""
else: Base10.toString(item.child.id)
res.add(
RestFutureInfo(
id: futureId,
child_id: childId,
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, "/nimbus/v1/debug/chronos/metrics") do (
) -> RestApiResponse:
template getCount(name: string): uint64 =
let res = getTrackerCounter(name)
uint64(res.opened - res.closed)
let res = RestChronosMetricsInfo(
tcp_transports: getCount(StreamTransportTrackerName),
udp_transports: getCount(DgramTransportTrackerName),
tcp_servers: getCount(StreamServerTrackerName),
stream_readers: getCount(AsyncStreamReaderTrackerName),
stream_writers: getCount(AsyncStreamWriterTrackerName),
http_client_connections: getCount(HttpClientConnectionTrackerName),
http_client_requests: getCount(HttpClientRequestTrackerName),
http_client_responses: getCount(HttpClientResponseTrackerName),
http_server_secure_connections:
getCount(HttpServerSecureConnectionTrackerName),
http_server_unsecure_connections:
getCount(HttpServerUnsecureConnectionTrackerName),
http_server_requests: getCount(HttpServerRequestTrackerName),
http_server_responses: getCount(HttpServerResponseTrackerName),
http_body_readers: getCount(HttpBodyReaderTrackerName),
http_body_writers: getCount(HttpBodyWriterTrackerName)
)
return RestApiResponse.jsonResponse(res)
router.api(MethodGet, "/nimbus/v1/debug/chronos/restserver/connections") do (
) -> RestApiResponse:
var res: seq[RestConnectionInfo]
for connection in node.restServer.server.getConnections():
let
connectionState =
case connection.connectionState
of httpdebug.ConnectionState.Accepted: "accepted"
of httpdebug.ConnectionState.Alive: "alive"
of httpdebug.ConnectionState.Closing: "closing"
of httpdebug.ConnectionState.Closed: "closed"
connectionType =
case connection.connectionType
of ConnectionType.Secure: "secure"
of ConnectionType.NonSecure: "non-secure"
localAddress =
if connection.localAddress.isNone():
"not available"
else:
$connection.localAddress.get()
remoteAddress =
if connection.remoteAddress.isNone():
"not available"
else:
$connection.remoteAddress.get()
query = connection.query.get("not available")
handle = Base10.toString(uint64(connection.handle))
moment = Moment.now()
sinceAccept = $(moment - connection.acceptMoment)
sinceCreate =
if connection.createMoment.isSome():
$(moment - connection.createMoment.get())
else:
"not available"
res.add(
RestConnectionInfo(
handle: handle,
query: query,
connection_state: connectionState,
connection_type: connectionType,
local_address: localAddress,
remote_address: remoteAddress,
since_accept: sinceAccept,
since_create: sinceCreate
)
)
return RestApiResponse.jsonResponse(res)
router.api(MethodPost, "/nimbus/v1/validator/activity/{epoch}") do (
epoch: Epoch, contentBody: Option[ContentBody]) -> RestApiResponse:
let indexList =
block:
if contentBody.isNone():
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
let dres = decodeBody(seq[RestValidatorIndex], contentBody.get())
if dres.isErr():
return RestApiResponse.jsonError(Http400,
InvalidValidatorIndexValueError,
$dres.error())
var
res: seq[ValidatorIndex]
dupset: HashSet[ValidatorIndex]
let items = dres.get()
for item in items:
let vres = item.toValidatorIndex()
if vres.isErr():
case vres.error()
of ValidatorIndexError.TooHighValue:
return RestApiResponse.jsonError(Http400,
TooHighValidatorIndexValueError)
of ValidatorIndexError.UnsupportedValue:
return RestApiResponse.jsonError(Http500,
UnsupportedValidatorIndexValueError)
let index = vres.get()
if index in dupset:
return RestApiResponse.jsonError(Http400,
DuplicateValidatorIndexArrayError)
dupset.incl(index)
res.add(index)
if len(res) == 0:
return RestApiResponse.jsonError(Http400,
EmptyValidatorIndexArrayError)
res
let qepoch =
block:
if epoch.isErr():
return RestApiResponse.jsonError(Http400, InvalidEpochValueError,
$epoch.error())
let
res = epoch.get()
wallEpoch = node.currentSlot().epoch()
nextEpoch =
if wallEpoch == FAR_FUTURE_EPOCH:
wallEpoch
else:
wallEpoch + 1
prevEpoch = get_previous_epoch(wallEpoch)
if (res < prevEpoch) or (res > nextEpoch):
return RestApiResponse.jsonError(Http400, InvalidEpochValueError,
"Requested epoch is more than one epoch from current epoch")
res
let response = indexList.mapIt(
RestActivityItem(
index: it,
epoch: qepoch,
active: node.attestationPool[].validatorSeenAtEpoch(qepoch, it)
)
)
return RestApiResponse.jsonResponse(response)
router.api(MethodGet, "/nimbus/v1/debug/gossip/peers") do (
) -> RestApiResponse:
let gossipPeers =
block:
var res: seq[tuple[topic: string, peers: seq[RestPubSubPeer]]]
for topic, v in node.network.pubsub.gossipsub:
var peers: seq[RestPubSubPeer]
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[RestPubSubPeer]]]
for topic, v in node.network.pubsub.mesh:
var peers: seq[RestPubSubPeer]
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[RestPeerStats]
for peerId, pstats in node.network.pubsub.peerStats:
let peer = node.network.pubsub.peers.getOrDefault(peerId)
stats.add(
RestPeerStats(
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[RestPeerStatus]
for peerId, peer in node.network.pubsub.peers:
peers.add(RestPeerStatus(peerId: peerId, connected: peer.connected))
peers
return RestApiResponse.jsonResponse(
(
gossip_peers: gossipPeers,
mesh_peers: meshPeers,
colocation_peers: colocationPeers,
peer_stats: peerStats,
all_peers: allPeers
)
)
router.api(MethodPost, "/nimbus/v1/timesync") do (
contentBody: Option[ContentBody]) -> RestApiResponse:
let
timestamp2 = getTimestamp()
timestamp1 =
block:
if contentBody.isNone():
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
let dres = decodeBody(RestNimbusTimestamp1, contentBody.get())
if dres.isErr():
return RestApiResponse.jsonError(Http400,
InvalidTimestampValue,
$dres.error())
dres.get().timestamp1
let
delay = node.processingDelay.valueOr: ZeroDuration
response = RestNimbusTimestamp2(
timestamp1: timestamp1,
timestamp2: timestamp2,
timestamp3: getTimestamp(),
delay: uint64(delay.nanoseconds)
)
return RestApiResponse.jsonResponsePlain(response)