Skip to content

Commit

Permalink
Implement the /eth/v1/validator/prepare_beacon_proposer end-point (#3901
Browse files Browse the repository at this point in the history
)
  • Loading branch information
zah authored Jul 25, 2022
1 parent 7d27e10 commit cd04f27
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 6 deletions.
4 changes: 3 additions & 1 deletion beacon_chain/beacon_node.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand All @@ -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
Expand Down Expand Up @@ -86,6 +87,7 @@ type
stateTtlCache*: StateTtlCache
nextExchangeTransitionConfTime*: Moment
router*: ref MessageRouter
dynamicFeeRecipientsStore*: DynamicFeeRecipientsStore

const
MaxEmptySlotCount* = uint64(10*60) div SECONDS_PER_SLOT
Expand Down
4 changes: 3 additions & 1 deletion beacon_chain/nimbus_beacon_node.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions beacon_chain/rpc/rest_constants.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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* =
Expand Down
30 changes: 29 additions & 1 deletion beacon_chain/rpc/rest_validator_api.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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"
)
54 changes: 54 additions & 0 deletions beacon_chain/spec/eth2_apis/dynamic_fee_recipients.nim
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ type
ImportDistributedKeystoresBody |
ImportRemoteKeystoresBody |
KeystoresAndSlashingProtection |
PrepareBeaconProposerBody |
ProposerSlashing |
SetFeeRecipientRequest |
SignedBlindedBeaconBlock |
Expand Down Expand Up @@ -123,6 +124,7 @@ type
KeymanagerGenericError |
KeystoresAndSlashingProtection |
ListFeeRecipientResponse |
PrepareBeaconProposerBody |
ProduceBlockResponseV2 |
RestDutyError |
RestGenericError |
Expand Down
4 changes: 4 additions & 0 deletions beacon_chain/spec/eth2_apis/rest_types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@ type
epoch*: Epoch
active*: bool

PrepareBeaconProposerBody* = object
validator_index*: ValidatorIndex
fee_recipient*: Eth1Address

RestPublishedSignedBeaconBlock* = distinct ForkedSignedBeaconBlock

RestPublishedBeaconBlock* = distinct ForkedBeaconBlock
Expand Down
5 changes: 5 additions & 0 deletions beacon_chain/spec/eth2_apis/rest_validator_calls.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
14 changes: 11 additions & 3 deletions beacon_chain/validators/validator_duties.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit cd04f27

Please sign in to comment.