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,
|
blockchain_dag, block_quarantine, consensus_manager, exit_pool,
|
||||||
attestation_pool, sync_committee_msg_pool],
|
attestation_pool, sync_committee_msg_pool],
|
||||||
./spec/datatypes/[base, altair],
|
./spec/datatypes/[base, altair],
|
||||||
|
./spec/eth2_apis/dynamic_fee_recipients,
|
||||||
./sync/[optimistic_sync_light_client, sync_manager, request_manager],
|
./sync/[optimistic_sync_light_client, sync_manager, request_manager],
|
||||||
./validators/[
|
./validators/[
|
||||||
action_tracker, message_router, validator_monitor, validator_pool],
|
action_tracker, message_router, validator_monitor, validator_pool],
|
||||||
|
@ -34,7 +35,7 @@ export
|
||||||
eth2_network, eth1_monitor, optimistic_sync_light_client,
|
eth2_network, eth1_monitor, optimistic_sync_light_client,
|
||||||
request_manager, sync_manager, eth2_processor, blockchain_dag,
|
request_manager, sync_manager, eth2_processor, blockchain_dag,
|
||||||
block_quarantine, base, exit_pool, message_router, validator_monitor,
|
block_quarantine, base, exit_pool, message_router, validator_monitor,
|
||||||
consensus_manager
|
consensus_manager, dynamic_fee_recipients
|
||||||
|
|
||||||
type
|
type
|
||||||
EventBus* = object
|
EventBus* = object
|
||||||
|
@ -86,6 +87,7 @@ type
|
||||||
stateTtlCache*: StateTtlCache
|
stateTtlCache*: StateTtlCache
|
||||||
nextExchangeTransitionConfTime*: Moment
|
nextExchangeTransitionConfTime*: Moment
|
||||||
router*: ref MessageRouter
|
router*: ref MessageRouter
|
||||||
|
dynamicFeeRecipientsStore*: DynamicFeeRecipientsStore
|
||||||
|
|
||||||
const
|
const
|
||||||
MaxEmptySlotCount* = uint64(10*60) div SECONDS_PER_SLOT
|
MaxEmptySlotCount* = uint64(10*60) div SECONDS_PER_SLOT
|
||||||
|
|
|
@ -787,7 +787,8 @@ proc init*(T: type BeaconNode,
|
||||||
beaconClock: beaconClock,
|
beaconClock: beaconClock,
|
||||||
validatorMonitor: validatorMonitor,
|
validatorMonitor: validatorMonitor,
|
||||||
stateTtlCache: stateTtlCache,
|
stateTtlCache: stateTtlCache,
|
||||||
nextExchangeTransitionConfTime: nextExchangeTransitionConfTime)
|
nextExchangeTransitionConfTime: nextExchangeTransitionConfTime,
|
||||||
|
dynamicFeeRecipientsStore: DynamicFeeRecipientsStore.init())
|
||||||
|
|
||||||
node.initLightClient(
|
node.initLightClient(
|
||||||
rng, cfg, dag.forkDigests, getBeaconTime, dag.genesis_validators_root)
|
rng, cfg, dag.forkDigests, getBeaconTime, dag.genesis_validators_root)
|
||||||
|
@ -1237,6 +1238,7 @@ proc onSlotEnd(node: BeaconNode, slot: Slot) {.async.} =
|
||||||
node.syncCommitteeMsgPool[].pruneData(slot)
|
node.syncCommitteeMsgPool[].pruneData(slot)
|
||||||
if slot.is_epoch:
|
if slot.is_epoch:
|
||||||
node.trackNextSyncCommitteeTopics(slot)
|
node.trackNextSyncCommitteeTopics(slot)
|
||||||
|
node.dynamicFeeRecipientsStore.pruneOldMappings(slot.epoch)
|
||||||
|
|
||||||
# Update upcoming actions - we do this every slot in case a reorg happens
|
# Update upcoming actions - we do this every slot in case a reorg happens
|
||||||
let head = node.dag.head
|
let head = node.dag.head
|
||||||
|
|
|
@ -165,6 +165,8 @@ const
|
||||||
"Unable to decode sync committee subscription request(s)"
|
"Unable to decode sync committee subscription request(s)"
|
||||||
InvalidContributionAndProofMessageError* =
|
InvalidContributionAndProofMessageError* =
|
||||||
"Unable to decode contribute and proof message(s)"
|
"Unable to decode contribute and proof message(s)"
|
||||||
|
InvalidPrepareBeaconProposerError* =
|
||||||
|
"Unable to decode prepare beacon proposer request"
|
||||||
SyncCommitteeMessageValidationError* =
|
SyncCommitteeMessageValidationError* =
|
||||||
"Some errors happened while validating sync committee message(s)"
|
"Some errors happened while validating sync committee message(s)"
|
||||||
SyncCommitteeMessageValidationSuccess* =
|
SyncCommitteeMessageValidationSuccess* =
|
||||||
|
|
|
@ -721,7 +721,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
contentBody.get())
|
contentBody.get())
|
||||||
if dres.isErr():
|
if dres.isErr():
|
||||||
return RestApiResponse.jsonError(Http400,
|
return RestApiResponse.jsonError(Http400,
|
||||||
InvalidContributionAndProofMessageError)
|
InvalidContributionAndProofMessageError)
|
||||||
dres.get()
|
dres.get()
|
||||||
|
|
||||||
let pending =
|
let pending =
|
||||||
|
@ -759,6 +759,29 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
ContributionAndProofValidationSuccess
|
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
|
# Legacy URLS - Nimbus <= 1.5.5 used to expose the REST API with an additional
|
||||||
# `/api` path component
|
# `/api` path component
|
||||||
router.redirect(
|
router.redirect(
|
||||||
|
@ -821,3 +844,8 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
"/api/eth/v1/validator/contribution_and_proofs",
|
"/api/eth/v1/validator/contribution_and_proofs",
|
||||||
"/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 |
|
ImportDistributedKeystoresBody |
|
||||||
ImportRemoteKeystoresBody |
|
ImportRemoteKeystoresBody |
|
||||||
KeystoresAndSlashingProtection |
|
KeystoresAndSlashingProtection |
|
||||||
|
PrepareBeaconProposerBody |
|
||||||
ProposerSlashing |
|
ProposerSlashing |
|
||||||
SetFeeRecipientRequest |
|
SetFeeRecipientRequest |
|
||||||
SignedBlindedBeaconBlock |
|
SignedBlindedBeaconBlock |
|
||||||
|
@ -123,6 +124,7 @@ type
|
||||||
KeymanagerGenericError |
|
KeymanagerGenericError |
|
||||||
KeystoresAndSlashingProtection |
|
KeystoresAndSlashingProtection |
|
||||||
ListFeeRecipientResponse |
|
ListFeeRecipientResponse |
|
||||||
|
PrepareBeaconProposerBody |
|
||||||
ProduceBlockResponseV2 |
|
ProduceBlockResponseV2 |
|
||||||
RestDutyError |
|
RestDutyError |
|
||||||
RestGenericError |
|
RestGenericError |
|
||||||
|
|
|
@ -239,6 +239,10 @@ type
|
||||||
epoch*: Epoch
|
epoch*: Epoch
|
||||||
active*: bool
|
active*: bool
|
||||||
|
|
||||||
|
PrepareBeaconProposerBody* = object
|
||||||
|
validator_index*: ValidatorIndex
|
||||||
|
fee_recipient*: Eth1Address
|
||||||
|
|
||||||
RestPublishedSignedBeaconBlock* = distinct ForkedSignedBeaconBlock
|
RestPublishedSignedBeaconBlock* = distinct ForkedSignedBeaconBlock
|
||||||
|
|
||||||
RestPublishedBeaconBlock* = distinct ForkedBeaconBlock
|
RestPublishedBeaconBlock* = distinct ForkedBeaconBlock
|
||||||
|
|
|
@ -86,3 +86,8 @@ proc publishContributionAndProofs*(body: seq[RestSignedContributionAndProof]): R
|
||||||
rest, endpoint: "/eth/v1/validator/contribution_and_proofs",
|
rest, endpoint: "/eth/v1/validator/contribution_and_proofs",
|
||||||
meth: MethodPost.}
|
meth: MethodPost.}
|
||||||
## https://ethereum.github.io/beacon-APIs/#/Validator/publishContributionAndProofs
|
## 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,
|
state, finalizedEpochRef.eth1_data,
|
||||||
finalizedEpochRef.eth1_deposit_index)
|
finalizedEpochRef.eth1_deposit_index)
|
||||||
|
|
||||||
|
# TODO: This copies the entire BeaconState on each call
|
||||||
proc forkchoice_updated(state: bellatrix.BeaconState,
|
proc forkchoice_updated(state: bellatrix.BeaconState,
|
||||||
head_block_hash: Eth2Digest,
|
head_block_hash: Eth2Digest,
|
||||||
finalized_block_hash: Eth2Digest,
|
finalized_block_hash: Eth2Digest,
|
||||||
|
@ -340,7 +341,10 @@ proc get_execution_payload(
|
||||||
await execution_engine.getPayload(payload_id.get))
|
await execution_engine.getPayload(payload_id.get))
|
||||||
|
|
||||||
proc getExecutionPayload(
|
proc getExecutionPayload(
|
||||||
node: BeaconNode, proposalState: auto, pubkey: ValidatorPubKey):
|
node: BeaconNode, proposalState: auto,
|
||||||
|
epoch: Epoch,
|
||||||
|
validator_index: ValidatorIndex,
|
||||||
|
pubkey: ValidatorPubKey):
|
||||||
Future[ExecutionPayload] {.async.} =
|
Future[ExecutionPayload] {.async.} =
|
||||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/bellatrix/validator.md#executionpayload
|
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/bellatrix/validator.md#executionpayload
|
||||||
|
|
||||||
|
@ -374,8 +378,11 @@ proc getExecutionPayload(
|
||||||
terminalBlockHash
|
terminalBlockHash
|
||||||
latestFinalized =
|
latestFinalized =
|
||||||
node.dag.loadExecutionBlockRoot(node.dag.finalizedHead.blck)
|
node.dag.loadExecutionBlockRoot(node.dag.finalizedHead.blck)
|
||||||
feeRecipient = node.config.getSuggestedFeeRecipient(pubkey).valueOr:
|
dynamicFeeRecipient = node.dynamicFeeRecipientsStore.getDynamicFeeRecipient(
|
||||||
node.config.defaultFeeRecipient
|
validator_index, epoch)
|
||||||
|
feeRecipient = dynamicFeeRecipient.valueOr:
|
||||||
|
node.config.getSuggestedFeeRecipient(pubkey).valueOr:
|
||||||
|
node.config.defaultFeeRecipient
|
||||||
payload_id = (await forkchoice_updated(
|
payload_id = (await forkchoice_updated(
|
||||||
proposalState.bellatrixData.data, latestHead, latestFinalized,
|
proposalState.bellatrixData.data, latestHead, latestFinalized,
|
||||||
feeRecipient,
|
feeRecipient,
|
||||||
|
@ -461,6 +468,7 @@ proc makeBeaconBlockForHeadAndSlot*(node: BeaconNode,
|
||||||
let pubkey = node.dag.validatorKey(validator_index)
|
let pubkey = node.dag.validatorKey(validator_index)
|
||||||
(await getExecutionPayload(
|
(await getExecutionPayload(
|
||||||
node, proposalState,
|
node, proposalState,
|
||||||
|
slot.epoch, validator_index,
|
||||||
# TODO https://github.com/nim-lang/Nim/issues/19802
|
# TODO https://github.com/nim-lang/Nim/issues/19802
|
||||||
if pubkey.isSome: pubkey.get.toPubKey else: default(ValidatorPubKey))),
|
if pubkey.isSome: pubkey.get.toPubKey else: default(ValidatorPubKey))),
|
||||||
noRollback, # Temporary state - no need for rollback
|
noRollback, # Temporary state - no need for rollback
|
||||||
|
|
Loading…
Reference in New Issue