From 5b0b48f6e9f314d408152672b8e39ef35f8334fa Mon Sep 17 00:00:00 2001 From: tersec Date: Tue, 13 Sep 2022 13:52:26 +0200 Subject: [PATCH] implement /eth/v1/validator/register_validator (#4115) --- beacon_chain/beacon_node.nim | 2 + beacon_chain/rpc/rest_beacon_api.nim | 128 ------------------- beacon_chain/rpc/rest_config_api.nim | 18 --- beacon_chain/rpc/rest_debug_api.nim | 23 ---- beacon_chain/rpc/rest_event_api.nim | 8 -- beacon_chain/rpc/rest_key_management_api.nim | 88 ++----------- beacon_chain/rpc/rest_nimbus_api.nim | 59 --------- beacon_chain/rpc/rest_node_api.nim | 38 ------ beacon_chain/rpc/rest_validator_api.nim | 93 ++++---------- beacon_chain/validators/validator_duties.nim | 27 ++++ ncli/resttest-rules.json | 26 ++++ tests/simulation/restapi.sh | 2 +- 12 files changed, 96 insertions(+), 416 deletions(-) diff --git a/beacon_chain/beacon_node.nim b/beacon_chain/beacon_node.nim index 60fffc8e4..c2e8c9e98 100644 --- a/beacon_chain/beacon_node.nim +++ b/beacon_chain/beacon_node.nim @@ -90,6 +90,8 @@ type nextExchangeTransitionConfTime*: Moment router*: ref MessageRouter dynamicFeeRecipientsStore*: ref DynamicFeeRecipientsStore + externalBuilderRegistrations*: + Table[ValidatorPubKey, SignedValidatorRegistrationV1] const MaxEmptySlotCount* = uint64(10*60) div SECONDS_PER_SLOT diff --git a/beacon_chain/rpc/rest_beacon_api.nim b/beacon_chain/rpc/rest_beacon_api.nim index 48b3fede5..4c64b03b2 100644 --- a/beacon_chain/rpc/rest_beacon_api.nim +++ b/beacon_chain/rpc/rest_beacon_api.nim @@ -1119,131 +1119,3 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = VoluntaryExitValidationError, $res.error()) return RestApiResponse.jsonMsgResponse(VoluntaryExitValidationSuccess) - - # Legacy URLS - Nimbus <= 1.5.5 used to expose the REST API with an additional - # `/api` path component - router.redirect( - MethodGet, - "/api/eth/v1/beacon/genesis", - "/eth/v1/beacon/genesis", - ) - router.redirect( - MethodGet, - "/api/eth/v1/beacon/states/{state_id}/root", - "/eth/v1/beacon/states/{state_id}/root", - ) - router.redirect( - MethodGet, - "/api/eth/v1/beacon/states/{state_id}/fork", - "/eth/v1/beacon/states/{state_id}/fork", - ) - router.redirect( - MethodGet, - "/api/eth/v1/beacon/states/{state_id}/finality_checkpoints", - "/eth/v1/beacon/states/{state_id}/finality_checkpoints" - ) - router.redirect( - MethodGet, - "/api/eth/v1/beacon/states/{state_id}/validators", - "/eth/v1/beacon/states/{state_id}/validators" - ) - router.redirect( - MethodGet, - "/api/eth/v1/beacon/states/{state_id}/validators/{validator_id}", - "/eth/v1/beacon/states/{state_id}/validators/{validator_id}" - ) - router.redirect( - MethodGet, - "/api/eth/v1/beacon/states/{state_id}/validator_balances", - "/eth/v1/beacon/states/{state_id}/validator_balances" - ) - router.redirect( - MethodGet, - "/api/eth/v1/beacon/states/{state_id}/committees", - "/eth/v1/beacon/states/{state_id}/committees" - ) - router.redirect( - MethodGet, - "/api/eth/v1/beacon/states/{state_id}/sync_committees", - "/eth/v1/beacon/states/{state_id}/sync_committees" - ) - router.redirect( - MethodGet, - "/api/eth/v1/beacon/headers", - "/eth/v1/beacon/headers" - ) - router.redirect( - MethodGet, - "/api/eth/v1/beacon/headers/{block_id}", - "/eth/v1/beacon/headers/{block_id}" - ) - router.redirect( - MethodPost, - "/api/eth/v1/beacon/blocks", - "/eth/v1/beacon/blocks" - ) - router.redirect( - MethodGet, - "/api/eth/v1/beacon/blocks/{block_id}", - "/eth/v1/beacon/blocks/{block_id}" - ) - router.redirect( - MethodGet, - "/api/eth/v2/beacon/blocks/{block_id}", - "/eth/v2/beacon/blocks/{block_id}" - ) - router.redirect( - MethodGet, - "/api/eth/v1/beacon/blocks/{block_id}/root", - "/eth/v1/beacon/blocks/{block_id}/root" - ) - router.redirect( - MethodGet, - "/api/eth/v1/beacon/blocks/{block_id}/attestations", - "/eth/v1/beacon/blocks/{block_id}/attestations" - ) - router.redirect( - MethodGet, - "/api/eth/v1/beacon/pool/attestations", - "/eth/v1/beacon/pool/attestations" - ) - router.redirect( - MethodPost, - "/api/eth/v1/beacon/pool/attestations", - "/eth/v1/beacon/pool/attestations" - ) - router.redirect( - MethodPost, - "/api/eth/v1/beacon/pool/attester_slashings", - "/eth/v1/beacon/pool/attester_slashings" - ) - router.redirect( - MethodGet, - "/api/eth/v1/beacon/pool/attester_slashings", - "/eth/v1/beacon/pool/attester_slashings" - ) - router.redirect( - MethodPost, - "/api/eth/v1/beacon/pool/proposer_slashings", - "/eth/v1/beacon/pool/proposer_slashings" - ) - router.redirect( - MethodGet, - "/api/eth/v1/beacon/pool/proposer_slashings", - "/eth/v1/beacon/pool/proposer_slashings" - ) - router.redirect( - MethodPost, - "/api/eth/v1/beacon/pool/sync_committees", - "/eth/v1/beacon/pool/sync_committees" - ) - router.redirect( - MethodPost, - "/api/eth/v1/beacon/pool/voluntary_exits", - "/eth/v1/beacon/pool/voluntary_exits" - ) - router.redirect( - MethodGet, - "/api/eth/v1/beacon/pool/voluntary_exits", - "/eth/v1/beacon/pool/voluntary_exits" - ) diff --git a/beacon_chain/rpc/rest_config_api.nim b/beacon_chain/rpc/rest_config_api.nim index 24b4192a8..3de84e4fc 100644 --- a/beacon_chain/rpc/rest_config_api.nim +++ b/beacon_chain/rpc/rest_config_api.nim @@ -282,21 +282,3 @@ proc installConfigApiHandlers*(router: var RestRouter, node: BeaconNode) = "/eth/v1/config/deposit_contract") do () -> RestApiResponse: return RestApiResponse.response(cachedDepositContract, Http200, "application/json") - - # Legacy URLS - Nimbus <= 1.5.5 used to expose the REST API with an additional - # `/api` path component - router.redirect( - MethodGet, - "/api/eth/v1/config/fork_schedule", - "/eth/v1/config/fork_schedule" - ) - router.redirect( - MethodGet, - "/api/eth/v1/config/spec", - "/eth/v1/config/spec" - ) - router.redirect( - MethodGet, - "/api/eth/v1/config/deposit_contract", - "/eth/v1/config/deposit_contract" - ) diff --git a/beacon_chain/rpc/rest_debug_api.nim b/beacon_chain/rpc/rest_debug_api.nim index e8514d13e..864be5adc 100644 --- a/beacon_chain/rpc/rest_debug_api.nim +++ b/beacon_chain/rpc/rest_debug_api.nim @@ -178,26 +178,3 @@ proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) = bestDescendant: item.bestDescendant, invalid: item.invalid)) return RestApiResponse.jsonResponse(responses) - - # Legacy URLS - Nimbus <= 1.5.5 used to expose the REST API with an additional - # `/api` path component - router.redirect( - MethodGet, - "/api/eth/v1/debug/beacon/states/{state_id}", - "/eth/v1/debug/beacon/states/{state_id}" - ) - router.redirect( - MethodGet, - "/api/eth/v2/debug/beacon/states/{state_id}", - "/eth/v2/debug/beacon/states/{state_id}" - ) - router.redirect( - MethodGet, - "/api/eth/v1/debug/beacon/heads", - "/eth/v1/debug/beacon/heads" - ) - router.redirect( - MethodGet, - "/api/eth/v2/debug/beacon/heads", - "/eth/v2/debug/beacon/heads" - ) diff --git a/beacon_chain/rpc/rest_event_api.nim b/beacon_chain/rpc/rest_event_api.nim index 18c74b852..e8336be39 100644 --- a/beacon_chain/rpc/rest_event_api.nim +++ b/beacon_chain/rpc/rest_event_api.nim @@ -173,11 +173,3 @@ proc installEventApiHandlers*(router: var RestRouter, node: BeaconNode) = res await allFutures(pending) return - - # Legacy URLS - Nimbus <= 1.5.5 used to expose the REST API with an additional - # `/api` path component - router.redirect( - MethodGet, - "/api/eth/v1/events", - "/eth/v1/events" - ) diff --git a/beacon_chain/rpc/rest_key_management_api.nim b/beacon_chain/rpc/rest_key_management_api.nim index a4ea751fb..36b261902 100644 --- a/beacon_chain/rpc/rest_key_management_api.nim +++ b/beacon_chain/rpc/rest_key_management_api.nim @@ -137,7 +137,7 @@ proc handleAddRemoteValidatorReq(host: KeymanagerHost, proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) = # https://ethereum.github.io/keymanager-APIs/#/Keymanager/ListKeys - router.api(MethodGet, "/api/eth/v1/keystores") do () -> RestApiResponse: + router.api(MethodGet, "/eth/v1/keystores") do () -> RestApiResponse: let authStatus = checkAuthorization(request, host) if authStatus.isErr(): return authErrorResponse authStatus.error @@ -146,7 +146,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) = return RestApiResponse.jsonResponsePlain(response) # https://ethereum.github.io/keymanager-APIs/#/Keymanager/ImportKeystores - router.api(MethodPost, "/api/eth/v1/keystores") do ( + router.api(MethodPost, "/eth/v1/keystores") do ( contentBody: Option[ContentBody]) -> RestApiResponse: let authStatus = checkAuthorization(request, host) if authStatus.isErr(): @@ -198,7 +198,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) = return RestApiResponse.jsonResponsePlain(response) # https://ethereum.github.io/keymanager-APIs/#/Keymanager/DeleteKeys - router.api(MethodDelete, "/api/eth/v1/keystores") do ( + router.api(MethodDelete, "/eth/v1/keystores") do ( contentBody: Option[ContentBody]) -> RestApiResponse: let authStatus = checkAuthorization(request, host) if authStatus.isErr(): @@ -263,7 +263,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) = return RestApiResponse.jsonResponsePlain(response) # https://ethereum.github.io/keymanager-APIs/#/Remote%20Key%20Manager/ListRemoteKeys - router.api(MethodGet, "/api/eth/v1/remotekeys") do () -> RestApiResponse: + router.api(MethodGet, "/eth/v1/remotekeys") do () -> RestApiResponse: let authStatus = checkAuthorization(request, host) if authStatus.isErr(): return authErrorResponse authStatus.error @@ -272,7 +272,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) = return RestApiResponse.jsonResponsePlain(response) # https://ethereum.github.io/keymanager-APIs/#/Remote%20Key%20Manager/ImportRemoteKeys - router.api(MethodPost, "/api/eth/v1/remotekeys") do ( + router.api(MethodPost, "/eth/v1/remotekeys") do ( contentBody: Option[ContentBody]) -> RestApiResponse: let authStatus = checkAuthorization(request, host) if authStatus.isErr(): @@ -303,7 +303,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) = return RestApiResponse.jsonResponsePlain(response) # https://ethereum.github.io/keymanager-APIs/#/Remote%20Key%20Manager/DeleteRemoteKeys - router.api(MethodDelete, "/api/eth/v1/remotekeys") do ( + router.api(MethodDelete, "/eth/v1/remotekeys") do ( contentBody: Option[ContentBody]) -> RestApiResponse: let authStatus = checkAuthorization(request, host) if authStatus.isErr(): @@ -323,7 +323,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) = return RestApiResponse.jsonResponsePlain(response) # https://ethereum.github.io/keymanager-APIs/#/Fee%20Recipient/ListFeeRecipient - router.api(MethodGet, "/api/eth/v1/validator/{pubkey}/feerecipient") do ( + router.api(MethodGet, "/eth/v1/validator/{pubkey}/feerecipient") do ( pubkey: ValidatorPubKey) -> RestApiResponse: let authStatus = checkAuthorization(request, host) if authStatus.isErr(): @@ -345,7 +345,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) = keymanagerApiError(Http500, "Error reading fee recipient file") # https://ethereum.github.io/keymanager-APIs/#/Fee%20Recipient/SetFeeRecipient - router.api(MethodPost, "/api/eth/v1/validator/{pubkey}/feerecipient") do ( + router.api(MethodPost, "/eth/v1/validator/{pubkey}/feerecipient") do ( pubkey: ValidatorPubKey, contentBody: Option[ContentBody]) -> RestApiResponse: let authStatus = checkAuthorization(request, host) @@ -372,7 +372,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) = Http500, "Failed to set fee recipient: " & status.error) # https://ethereum.github.io/keymanager-APIs/#/Fee%20Recipient/DeleteFeeRecipient - router.api(MethodDelete, "/api/eth/v1/validator/{pubkey}/feerecipient") do ( + router.api(MethodDelete, "/eth/v1/validator/{pubkey}/feerecipient") do ( pubkey: ValidatorPubKey) -> RestApiResponse: let authStatus = checkAuthorization(request, host) if authStatus.isErr(): @@ -389,8 +389,8 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) = Http500, "Failed to remove fee recipient file: " & res.error) # TODO: These URLs will be changed once we submit a proposal for - # /api/eth/v2/remotekeys that supports distributed keys. - router.api(MethodGet, "/api/eth/v1/remotekeys/distributed") do () -> RestApiResponse: + # /eth/v2/remotekeys that supports distributed keys. + router.api(MethodGet, "/eth/v1/remotekeys/distributed") do () -> RestApiResponse: let authStatus = checkAuthorization(request, host) if authStatus.isErr(): return authErrorResponse authStatus.error @@ -399,8 +399,8 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) = return RestApiResponse.jsonResponsePlain(response) # TODO: These URLs will be changed once we submit a proposal for - # /api/eth/v2/remotekeys that supports distributed keys. - router.api(MethodPost, "/api/eth/v1/remotekeys/distributed") do ( + # /eth/v2/remotekeys that supports distributed keys. + router.api(MethodPost, "/eth/v1/remotekeys/distributed") do ( contentBody: Option[ContentBody]) -> RestApiResponse: let authStatus = checkAuthorization(request, host) if authStatus.isErr(): @@ -428,7 +428,7 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) = return RestApiResponse.jsonResponsePlain(response) - router.api(MethodDelete, "/api/eth/v1/remotekeys/distributed") do ( + router.api(MethodDelete, "/eth/v1/remotekeys/distributed") do ( contentBody: Option[ContentBody]) -> RestApiResponse: let authStatus = checkAuthorization(request, host) if authStatus.isErr(): @@ -447,63 +447,3 @@ proc installKeymanagerHandlers*(router: var RestRouter, host: KeymanagerHost) = response.data.add handleRemoveValidatorReq(host, key) return RestApiResponse.jsonResponsePlain(response) - - router.redirect( - MethodGet, - "/eth/v1/keystores", - "/api/eth/v1/keystores") - - router.redirect( - MethodPost, - "/eth/v1/keystores", - "/api/eth/v1/keystores") - - router.redirect( - MethodDelete, - "/eth/v1/keystores", - "/api/eth/v1/keystores") - - router.redirect( - MethodGet, - "/eth/v1/remotekeys", - "/api/eth/v1/remotekeys") - - router.redirect( - MethodPost, - "/eth/v1/remotekeys", - "/api/eth/v1/remotekeys") - - router.redirect( - MethodDelete, - "/eth/v1/remotekeys", - "/api/eth/v1/remotekeys") - - router.redirect( - MethodGet, - "/eth/v1/validator/{pubkey}/feerecipient", - "/api/eth/v1/validator/{pubkey}/feerecipient") - - router.redirect( - MethodPost, - "/eth/v1/validator/{pubkey}/feerecipient", - "/api/eth/v1/validator/{pubkey}/feerecipient") - - router.redirect( - MethodDelete, - "/eth/v1/validator/{pubkey}/feerecipient", - "/api/eth/v1/validator/{pubkey}/feerecipient") - - router.redirect( - MethodGet, - "/eth/v1/remotekeys/distributed", - "/api/eth/v1/remotekeys/distributed") - - router.redirect( - MethodPost, - "/eth/v1/remotekeys/distributed", - "/api/eth/v1/remotekeys/distributed") - - router.redirect( - MethodDelete, - "/eth/v1/remotekeys/distributed", - "/api/eth/v1/remotekeys/distributed") diff --git a/beacon_chain/rpc/rest_nimbus_api.nim b/beacon_chain/rpc/rest_nimbus_api.nim index 856f59224..6cd9d35ad 100644 --- a/beacon_chain/rpc/rest_nimbus_api.nim +++ b/beacon_chain/rpc/rest_nimbus_api.nim @@ -381,62 +381,3 @@ proc installNimbusApiHandlers*(router: var RestRouter, node: BeaconNode) = all_peers: allPeers ) ) - - # Legacy URLS - Nimbus <= 1.5.5 used to expose the REST API with an additional - # `/api` path component - router.redirect( - MethodGet, - "/api/nimbus/v1/beacon/head", - "/nimbus/v1/beacon/head") - router.redirect( - MethodGet, - "/api/nimbus/v1/chain/head", - "/nimbus/v1/chain/head") - router.redirect( - MethodGet, - "/api/nimbus/v1/syncmanager/status", - "/nimbus/v1/syncmanager/status") - router.redirect( - MethodGet, - "/api/nimbus/v1/node/peerid", - "/nimbus/v1/node/peerid") - router.redirect( - MethodGet, - "/api/nimbus/v1/node/version", - "/nimbus/v1/node/version") - router.redirect( - MethodGet, - "/api/nimbus/v1/network/ids", - "/nimbus/v1/network/ids") - router.redirect( - MethodGet, - "/api/nimbus/v1/network/peers", - "/nimbus/v1/network/peers") - router.redirect( - MethodPost, - "/api/nimbus/v1/graffiti", - "/nimbus/v1/graffiti") - router.redirect( - MethodGet, - "/api/nimbus/v1/graffiti", - "/nimbus/v1/graffiti") - router.redirect( - MethodPost, - "/api/nimbus/v1/chronicles/settings", - "/nimbus/v1/chronicles/settings") - router.redirect( - MethodGet, - "/api/nimbus/v1/eth1/chain", - "/nimbus/v1/eth1/chain") - router.redirect( - MethodGet, - "/api/nimbus/v1/eth1/proposal_data", - "/nimbus/v1/eth1/proposal_data") - router.redirect( - MethodGet, - "/api/nimbus/v1/debug/chronos/futures", - "/nimbus/v1/debug/chronos/futures") - router.redirect( - MethodGet, - "/api/nimbus/v1/debug/gossip/peers", - "/nimbus/v1/debug/gossip/peers") diff --git a/beacon_chain/rpc/rest_node_api.nim b/beacon_chain/rpc/rest_node_api.nim index ee9d711ee..44442f2a7 100644 --- a/beacon_chain/rpc/rest_node_api.nim +++ b/beacon_chain/rpc/rest_node_api.nim @@ -284,41 +284,3 @@ proc installNodeApiHandlers*(router: var RestRouter, node: BeaconNode) = else: Http200 return RestApiResponse.response("", status, contentType = "") - - # Legacy URLS - Nimbus <= 1.5.5 used to expose the REST API with an additional - # `/api` path component - router.redirect( - MethodGet, - "/api/eth/v1/node/identity", - "/eth/v1/node/identity" - ) - router.redirect( - MethodGet, - "/api/eth/v1/node/peers", - "/eth/v1/node/peers" - ) - router.redirect( - MethodGet, - "/api/eth/v1/node/peer_count", - "/eth/v1/node/peer_count" - ) - router.redirect( - MethodGet, - "/api/eth/v1/node/peers/{peer_id}", - "/eth/v1/node/peers/{peer_id}" - ) - router.redirect( - MethodGet, - "/api/eth/v1/node/version", - "/eth/v1/node/version" - ) - router.redirect( - MethodGet, - "/api/eth/v1/node/syncing", - "/eth/v1/node/syncing" - ) - router.redirect( - MethodGet, - "/api/eth/v1/node/health", - "/eth/v1/node/health" - ) diff --git a/beacon_chain/rpc/rest_validator_api.nim b/beacon_chain/rpc/rest_validator_api.nim index 10bb846c1..36e4c7f24 100644 --- a/beacon_chain/rpc/rest_validator_api.nim +++ b/beacon_chain/rpc/rest_validator_api.nim @@ -786,70 +786,29 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = return RestApiResponse.response("", Http200, "text/plain") - # Legacy URLS - Nimbus <= 1.5.5 used to expose the REST API with an additional - # `/api` path component - router.redirect( - MethodPost, - "/api/eth/v1/validator/duties/attester/{epoch}", - "/eth/v1/validator/duties/attester/{epoch}" - ) - router.redirect( - MethodGet, - "/api/eth/v1/validator/duties/proposer/{epoch}", - "/eth/v1/validator/duties/proposer/{epoch}" - ) - router.redirect( - MethodPost, - "/api/eth/v1/validator/duties/sync/{epoch}", - "/eth/v1/validator/duties/sync/{epoch}" - ) - router.redirect( - MethodGet, - "/api/eth/v1/validator/blocks/{slot}", - "/eth/v1/validator/blocks/{slot}" - ) - router.redirect( - MethodGet, - "/api/eth/v2/validator/blocks/{slot}", - "/eth/v2/validator/blocks/{slot}" - ) - router.redirect( - MethodGet, - "/api/eth/v1/validator/attestation_data", - "/eth/v1/validator/attestation_data" - ) - router.redirect( - MethodGet, - "/api/eth/v1/validator/aggregate_attestation", - "/eth/v1/validator/aggregate_attestation" - ) - router.redirect( - MethodPost, - "/api/eth/v1/validator/aggregate_and_proofs", - "/eth/v1/validator/aggregate_and_proofs" - ) - router.redirect( - MethodPost, - "/api/eth/v1/validator/beacon_committee_subscriptions", - "/eth/v1/validator/beacon_committee_subscriptions" - ) - router.redirect( - MethodPost, - "/api/eth/v1/validator/sync_committee_subscriptions", - "/eth/v1/validator/sync_committee_subscriptions" - ) - router.redirect( - MethodGet, - "/api/eth/v1/validator/sync_committee_contribution", - "/eth/v1/validator/sync_committee_contribution" - ) - router.redirect( - MethodPost, - "/api/eth/v1/validator/contribution_and_proofs", - "/eth/v1/validator/contribution_and_proofs" - ) - router.redirect( - MethodPost, - "/api/eth/v1/validator/prepare_beacon_proposer", - "/eth/v1/validator/prepare_beacon_proposer" - ) + # https://ethereum.github.io/beacon-APIs/#/Validator/registerValidator + # https://github.com/ethereum/beacon-APIs/blob/v2.3.0/apis/validator/register_validator.yaml + router.api(MethodPost, + "/eth/v1/validator/register_validator") do ( + contentBody: Option[ContentBody]) -> RestApiResponse: + let + body = + block: + if contentBody.isNone(): + return RestApiResponse.jsonError(Http400, EmptyRequestBodyError) + let dres = decodeBody(seq[SignedValidatorRegistrationV1], contentBody.get()) + if dres.isErr(): + return RestApiResponse.jsonError(Http400, + InvalidPrepareBeaconProposerError) + dres.get() + + for signedValidatorRegistration in body: + # Don't validate beyond syntactically, because + # "requests containing currently inactive or unknown validator pubkeys + # will be accepted, as they may become active at a later epoch". Along + # these lines, even if it's adding a validator the BN already has as a + # local validator, the keymanager API might remove that from the BN. + node.externalBuilderRegistrations[signedValidatorRegistration.message.pubkey] = + signedValidatorRegistration + + return RestApiResponse.response("", Http200, "text/plain") diff --git a/beacon_chain/validators/validator_duties.nim b/beacon_chain/validators/validator_duties.nim index 698abff03..eb4d38c23 100644 --- a/beacon_chain/validators/validator_duties.nim +++ b/beacon_chain/validators/validator_duties.nim @@ -1307,7 +1307,34 @@ proc registerValidators(node: BeaconNode, epoch: Epoch) {.async.} = # https://github.com/ethereum/builder-specs/blob/v0.2.0/specs/validator.md#validator-registration var validatorRegistrations: seq[SignedValidatorRegistrationV1] + + # First, check for VC-added keys; cheaper because provided pre-signed + var nonExitedVcPubkeys: HashSet[ValidatorPubKey] + if node.externalBuilderRegistrations.len > 0: + withState(node.dag.headState): + let currentEpoch = node.currentSlot().epoch + for i in 0 ..< forkyState.data.validators.len: + # https://github.com/ethereum/beacon-APIs/blob/v2.3.0/apis/validator/register_validator.yaml + # "requests containing currently inactive or unknown validator + # pubkeys will be accepted, as they may become active at a later + # epoch" which means filtering is needed here, because including + # any validators not pending or active may cause the request, as + # a whole, to fail. + let pubkey = forkyState.data.validators.item(i).pubkey + if pubkey in node.externalBuilderRegistrations and + forkyState.data.validators.item(i).exit_epoch > currentEpoch: + let signedValidatorRegistration = + node.externalBuilderRegistrations[pubkey] + nonExitedVcPubkeys.incl signedValidatorRegistration.message.pubkey + validatorRegistrations.add signedValidatorRegistration + for key in attachedValidatorPubkeys: + # Already included from VC + if key in nonExitedVcPubkeys: + warn "registerValidators: same validator registered by beacon node and validator client", + pubkey = shortLog(key) + continue + # Time passed during awaits; REST keymanager API might have removed it if key notin node.attachedValidators[].validators: continue diff --git a/ncli/resttest-rules.json b/ncli/resttest-rules.json index 92312ac60..ac3396e11 100644 --- a/ncli/resttest-rules.json +++ b/ncli/resttest-rules.json @@ -3324,6 +3324,32 @@ "body": [{"operator": "jstructcmpns", "value": {"code": "", "message": ""}}] } }, + { + "topics": ["validator", "register_validators"], + "request": { + "url": "/eth/v1/validator/register_validator", + "method": "POST", + "headers": {"Accept": "application/json"}, + "body": {"content-type": "application/json", "data": "[{\"message\":{\"fee_recipient\":\"0xb943c2c22b1b186a34f47c4dbe2fe367de9ec180\",\"gas_limit\":\"40000000\",\"timestamp\":\"1661879190\",\"pubkey\":\"0xa37b7bb9c412b8cc318fabf7b1fec33eb9634680687f07b977393180ce99889dbcfda81900f3afb9f2281930cf49f5d8\"},\"signature\":\"0xa493085fab365d13bea2376434abc3dbfba00a576276c853acabd7b9cb2f2b4b0a90738dd9baeaef75d0f42fa94119a70a09b0ed38fbebb6dde92c9ca062447018821f36c19d6fe34eb8c357d62e5d33e5c1d35035472ef7dd22a7425cdba0c5\"}]"} + }, + "response": { + "status": {"operator": "equals", "value": "200"}, + "headers": [{"key": "Content-Type", "value": "text/plain", "operator": "equals"}] + } + }, + { + "topics": ["validator", "register_validators"], + "request": { + "url": "/eth/v1/validator/register_validator", + "method": "POST", + "headers": {"Accept": "application/json"}, + "body": {"content-type": "application/json", "data": "[{\"message\":{\"fee_recipient\":\"0xb943c2c22b1b186a34f47c4dbe2fe367de9ec180\",\"gas_limit\":\"40000000\",\"timestamp\":\"1661879190\",\"pubkey\":\"0xa37b7bb9c412b8cc318fabf7b1fec33eb9634680687f07b977393180ce99889dbcfda81900f3afb9f2281930cf49f5d8\"},\"signature\":\"0xa493085fab365d13bea2376434abc3dbfba00a576276c853acabd7b9cb2f2b4b0a90738dd9baeaef75d0f42fa94119a70a09b0ed38fbebb6dde92c9ca062447018821f36c19d6fe34eb8c357d62e5d33e5c1d35035472ef7dd22a7425cdba0c\"}]"} + }, + "response": { + "status": {"operator": "equals", "value": "400"}, + "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}] + } + }, { "topics": ["key_management", "list_keys"], "request": { diff --git a/tests/simulation/restapi.sh b/tests/simulation/restapi.sh index a464ea154..3a50b3b3f 100755 --- a/tests/simulation/restapi.sh +++ b/tests/simulation/restapi.sh @@ -255,7 +255,7 @@ if [[ ${BEACON_NODE_STATUS} -eq 0 ]]; then --skip-topic=slow \ --connections=4 \ --rules-file="${RESTTEST_RULES}" \ - http://${REST_ADDRESS}:${BASE_REST_PORT}/api \ + http://${REST_ADDRESS}:${BASE_REST_PORT} \ > ${LOG_TEST_FILE} 2>&1 RESTTEST_STATUS=$?