Implement the /eth/v1/validator/prepare_beacon_proposer end-point (#3901)
This commit is contained in:
parent
7d27e10315
commit
cd04f27c37
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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* =
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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
|
|
@ -88,6 +88,7 @@ type
|
|||
ImportDistributedKeystoresBody |
|
||||
ImportRemoteKeystoresBody |
|
||||
KeystoresAndSlashingProtection |
|
||||
PrepareBeaconProposerBody |
|
||||
ProposerSlashing |
|
||||
SetFeeRecipientRequest |
|
||||
SignedBlindedBeaconBlock |
|
||||
|
@ -123,6 +124,7 @@ type
|
|||
KeymanagerGenericError |
|
||||
KeystoresAndSlashingProtection |
|
||||
ListFeeRecipientResponse |
|
||||
PrepareBeaconProposerBody |
|
||||
ProduceBlockResponseV2 |
|
||||
RestDutyError |
|
||||
RestGenericError |
|
||||
|
|
|
@ -239,6 +239,10 @@ type
|
|||
epoch*: Epoch
|
||||
active*: bool
|
||||
|
||||
PrepareBeaconProposerBody* = object
|
||||
validator_index*: ValidatorIndex
|
||||
fee_recipient*: Eth1Address
|
||||
|
||||
RestPublishedSignedBeaconBlock* = distinct ForkedSignedBeaconBlock
|
||||
|
||||
RestPublishedBeaconBlock* = distinct ForkedBeaconBlock
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue