Implement the /eth/v1/validator/prepare_beacon_proposer end-point (#3901)

This commit is contained in:
zah 2022-07-25 23:12:53 +03:00 committed by GitHub
parent 7d27e10315
commit cd04f27c37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 113 additions and 6 deletions

View File

@ -22,6 +22,7 @@ import
blockchain_dag, block_quarantine, consensus_manager, exit_pool,
attestation_pool, sync_committee_msg_pool],
./spec/datatypes/[base, altair],
./spec/eth2_apis/dynamic_fee_recipients,
./sync/[optimistic_sync_light_client, sync_manager, request_manager],
./validators/[
action_tracker, message_router, validator_monitor, validator_pool],
@ -34,7 +35,7 @@ export
eth2_network, eth1_monitor, optimistic_sync_light_client,
request_manager, sync_manager, eth2_processor, blockchain_dag,
block_quarantine, base, exit_pool, message_router, validator_monitor,
consensus_manager
consensus_manager, dynamic_fee_recipients
type
EventBus* = object
@ -86,6 +87,7 @@ type
stateTtlCache*: StateTtlCache
nextExchangeTransitionConfTime*: Moment
router*: ref MessageRouter
dynamicFeeRecipientsStore*: DynamicFeeRecipientsStore
const
MaxEmptySlotCount* = uint64(10*60) div SECONDS_PER_SLOT

View File

@ -787,7 +787,8 @@ proc init*(T: type BeaconNode,
beaconClock: beaconClock,
validatorMonitor: validatorMonitor,
stateTtlCache: stateTtlCache,
nextExchangeTransitionConfTime: nextExchangeTransitionConfTime)
nextExchangeTransitionConfTime: nextExchangeTransitionConfTime,
dynamicFeeRecipientsStore: DynamicFeeRecipientsStore.init())
node.initLightClient(
rng, cfg, dag.forkDigests, getBeaconTime, dag.genesis_validators_root)
@ -1237,6 +1238,7 @@ proc onSlotEnd(node: BeaconNode, slot: Slot) {.async.} =
node.syncCommitteeMsgPool[].pruneData(slot)
if slot.is_epoch:
node.trackNextSyncCommitteeTopics(slot)
node.dynamicFeeRecipientsStore.pruneOldMappings(slot.epoch)
# Update upcoming actions - we do this every slot in case a reorg happens
let head = node.dag.head

View File

@ -165,6 +165,8 @@ const
"Unable to decode sync committee subscription request(s)"
InvalidContributionAndProofMessageError* =
"Unable to decode contribute and proof message(s)"
InvalidPrepareBeaconProposerError* =
"Unable to decode prepare beacon proposer request"
SyncCommitteeMessageValidationError* =
"Some errors happened while validating sync committee message(s)"
SyncCommitteeMessageValidationSuccess* =

View File

@ -721,7 +721,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
contentBody.get())
if dres.isErr():
return RestApiResponse.jsonError(Http400,
InvalidContributionAndProofMessageError)
InvalidContributionAndProofMessageError)
dres.get()
let pending =
@ -759,6 +759,29 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
ContributionAndProofValidationSuccess
)
# https://ethereum.github.io/beacon-APIs/#/ValidatorRequiredApi/prepareBeaconProposer
router.api(MethodPost,
"/eth/v1/validator/prepare_beacon_proposer") do (
contentBody: Option[ContentBody]) -> RestApiResponse:
let
proposerData =
block:
if contentBody.isNone():
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
let dres = decodeBody(PrepareBeaconProposerBody, contentBody.get())
if dres.isErr():
return RestApiResponse.jsonError(Http400,
InvalidPrepareBeaconProposerError)
dres.get()
currentEpoch = node.beaconClock.now.slotOrZero.epoch
node.dynamicFeeRecipientsStore.addMapping(
proposerData.validator_index,
proposerData.fee_recipient,
currentEpoch)
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(
@ -821,3 +844,8 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
"/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"
)

View File

@ -0,0 +1,54 @@
import
std/tables,
stew/results,
web3/ethtypes,
../datatypes/base
type
Entry = object
recipient: Eth1Address
addedAt: Epoch
DynamicFeeRecipientsStore* = object
mappings: Table[ValidatorIndex, Entry]
func init*(T: type DynamicFeeRecipientsStore): T =
T(mappings: initTable[ValidatorIndex, Entry]())
func addMapping*(store: var DynamicFeeRecipientsStore,
validator: ValidatorIndex,
feeRecipient: Eth1Address,
currentEpoch: Epoch) =
store.mappings[validator] = Entry(recipient: feeRecipient,
addedAt: currentEpoch)
func getDynamicFeeRecipient*(store: var DynamicFeeRecipientsStore,
validator: ValidatorIndex,
currentEpoch: Epoch): Opt[Eth1Address] =
store.mappings.withValue(validator, entry) do:
# https://ethereum.github.io/beacon-APIs/#/ValidatorRequiredApi/prepareBeaconProposer
#
# The information supplied for each validator index will persist
# through the epoch in which the call is submitted and for a further
# two epochs after that, or until the beacon node restarts.
#
# It is expected that validator clients will send this information
# periodically, for example each epoch, to ensure beacon nodes have
# correct and timely fee recipient information.
return if (currentEpoch - entry.addedAt) > 2:
err()
else:
ok entry.recipient
do:
return err()
func pruneOldMappings*(store: var DynamicFeeRecipientsStore,
currentEpoch: Epoch) =
var toPrune: seq[ValidatorIndex]
for idx, entry in store.mappings:
if (currentEpoch - entry.addedAt) > 2:
toPrune.add idx
for idx in toPrune:
store.mappings.del idx

View File

@ -88,6 +88,7 @@ type
ImportDistributedKeystoresBody |
ImportRemoteKeystoresBody |
KeystoresAndSlashingProtection |
PrepareBeaconProposerBody |
ProposerSlashing |
SetFeeRecipientRequest |
SignedBlindedBeaconBlock |
@ -123,6 +124,7 @@ type
KeymanagerGenericError |
KeystoresAndSlashingProtection |
ListFeeRecipientResponse |
PrepareBeaconProposerBody |
ProduceBlockResponseV2 |
RestDutyError |
RestGenericError |

View File

@ -239,6 +239,10 @@ type
epoch*: Epoch
active*: bool
PrepareBeaconProposerBody* = object
validator_index*: ValidatorIndex
fee_recipient*: Eth1Address
RestPublishedSignedBeaconBlock* = distinct ForkedSignedBeaconBlock
RestPublishedBeaconBlock* = distinct ForkedBeaconBlock

View File

@ -86,3 +86,8 @@ proc publishContributionAndProofs*(body: seq[RestSignedContributionAndProof]): R
rest, endpoint: "/eth/v1/validator/contribution_and_proofs",
meth: MethodPost.}
## https://ethereum.github.io/beacon-APIs/#/Validator/publishContributionAndProofs
proc prepareBeaconProposer*(body: PrepareBeaconProposerBody): RestPlainResponse {.
rest, endpoint: "/eth/v1/validator/prepare_beacon_proposer",
meth: MethodPost.}
## https://ethereum.github.io/beacon-APIs/#/ValidatorRequiredApi/prepareBeaconProposer

View File

@ -305,6 +305,7 @@ proc getBlockProposalEth1Data*(node: BeaconNode,
state, finalizedEpochRef.eth1_data,
finalizedEpochRef.eth1_deposit_index)
# TODO: This copies the entire BeaconState on each call
proc forkchoice_updated(state: bellatrix.BeaconState,
head_block_hash: Eth2Digest,
finalized_block_hash: Eth2Digest,
@ -340,7 +341,10 @@ proc get_execution_payload(
await execution_engine.getPayload(payload_id.get))
proc getExecutionPayload(
node: BeaconNode, proposalState: auto, pubkey: ValidatorPubKey):
node: BeaconNode, proposalState: auto,
epoch: Epoch,
validator_index: ValidatorIndex,
pubkey: ValidatorPubKey):
Future[ExecutionPayload] {.async.} =
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/bellatrix/validator.md#executionpayload
@ -374,8 +378,11 @@ proc getExecutionPayload(
terminalBlockHash
latestFinalized =
node.dag.loadExecutionBlockRoot(node.dag.finalizedHead.blck)
feeRecipient = node.config.getSuggestedFeeRecipient(pubkey).valueOr:
node.config.defaultFeeRecipient
dynamicFeeRecipient = node.dynamicFeeRecipientsStore.getDynamicFeeRecipient(
validator_index, epoch)
feeRecipient = dynamicFeeRecipient.valueOr:
node.config.getSuggestedFeeRecipient(pubkey).valueOr:
node.config.defaultFeeRecipient
payload_id = (await forkchoice_updated(
proposalState.bellatrixData.data, latestHead, latestFinalized,
feeRecipient,
@ -461,6 +468,7 @@ proc makeBeaconBlockForHeadAndSlot*(node: BeaconNode,
let pubkey = node.dag.validatorKey(validator_index)
(await getExecutionPayload(
node, proposalState,
slot.epoch, validator_index,
# TODO https://github.com/nim-lang/Nim/issues/19802
if pubkey.isSome: pubkey.get.toPubKey else: default(ValidatorPubKey))),
noRollback, # Temporary state - no need for rollback