Skip to content

feat(eth2api): add submit_proposal_preparations client method#549

Open
emlautarom1-agent[bot] wants to merge 1 commit into
mainfrom
worktree-eth2api-proposal-preparations
Open

feat(eth2api): add submit_proposal_preparations client method#549
emlautarom1-agent[bot] wants to merge 1 commit into
mainfrom
worktree-eth2api-proposal-preparations

Conversation

@emlautarom1-agent

@emlautarom1-agent emlautarom1-agent Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Closes #527.

Summary

Adds the client half of proposal preparations: EthBeaconNodeApiClient::submit_proposal_preparations, which forwards fee-recipient preparations to a node's own beacon node (POST /eth/v1/validator/prepare_beacon_proposer). Until now only the VC-facing router handler existed, so a Pluto node could accept preparations from its validator client but had no way to relay them upstream. This is the prerequisite for the setFeeRecipient slot subscriber, which republishes preparations at the start of each epoch.

The method mirrors go-eth2-client's SubmitProposalPreparations — the call Charon's setFeeRecipient makes in app/app.go. It takes a list of ProposalPreparation { validator_index, fee_recipient } and serializes each to { validator_index: <decimal string>, fee_recipient: <0x address> }, matching the beacon API spec and go-eth2-client's JSON encoding.

Differences from Charon

  1. The beaconmock records submissions instead of swallowing them. Charon's beaconmock no-ops on this endpoint (testutil/beaconmock/server.go). Pluto captures each submission and exposes it through MockState::proposal_preparations, so the follow-up subscriber can be exercised end-to-end against the mock — which is the reason the mock side is built here at all.
  2. The client wraps the generated OpenAPI method rather than a hand-rolled request. submit_proposal_preparations builds on the code-generated prepare_beacon_proposer client and puts an ergonomic, strongly-typed ProposalPreparation in front of the stringly-typed generated request body.
  3. Non-success responses collapse to a single error. Any non-2xx maps to UnexpectedResponse rather than surfacing the beacon node's code/message, consistent with the other wrappers in this crate.

Out of scope

The setFeeRecipient slot subscriber that drives this method once per epoch is a separate follow-up ticket; this PR lands only the client method and the mock support it depends on.

Add a `submit_proposal_preparations` extension method on
`EthBeaconNodeApiClient` (POST /eth/v1/validator/prepare_beacon_proposer)
so a Pluto node can send fee-recipient proposal preparations to its own
beacon node. Mirrors go-eth2-client's `SubmitProposalPreparations`
(Charon app/app.go setFeeRecipient).

- New `ProposalPreparation { validator_index, fee_recipient }` type.
- Method serializes to `[{validator_index, fee_recipient}]` per the
  beacon API spec and maps a non-200 response to an error.
- beaconmock now records submitted preparations at a recording route
  (higher priority than the default swallow handler) and exposes them
  via `MockState::proposal_preparations` for the follow-up fee-recipient
  subscriber tests.
- Unit tests cover body serialization, the success path, the error
  path, accumulation across submissions, and invalid-body rejection.

Part of #402. Closes #527.

@emlautarom1-agent emlautarom1-agent Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few notes to guide review — deliberate deviations from the Charon/go-eth2-client reference and one why-not-the-existing-API call.

/// Priority for the recording route; lower value (higher priority) than the
/// default 200 handler in `defaults.rs`, so submissions are captured instead of
/// silently swallowed.
const PROPOSAL_PREPARATION_PRIORITY: u8 = 100;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Differs from Charon. Charon's beaconmock no-ops this endpoint (testutil/beaconmock/server.go); here it records instead. defaults.rs already mounts a 200-swallow route at the default priority 255, so this recording route must sit at a higher priority (lower number) to win — hence 100, matching the attestation store's override pattern. Without it, submissions would hit the swallow route and never be recorded.

.iter()
.map(|preparation| PrepareBeaconProposerRequestBodyItem {
validator_index: preparation.validator_index.to_string(),
fee_recipient: format!("0x{}", hex::encode(preparation.fee_recipient)),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parity quirk. fee_recipient is intentionally lowercase 0x-hex, not EIP-55 checksummed. This matches go-eth2-client's ExecutionAddress.String() (%#x) that the beacon node receives — deliberately not the checksumming bellatrix::execution_address_serde used elsewhere in this crate, which is for a different (response) code path. The spec pattern ^0x[a-fA-F0-9]{40}$ accepts either case.

.await?
{
PrepareBeaconProposerResponse::Ok => Ok(()),
_ => Err(EthBeaconNodeApiClientError::UnexpectedResponse),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consistency. Non-2xx collapses to UnexpectedResponse, dropping the node's code/message. Chosen to match the existing wrappers in this file (get_spec/get_genesis/get_fork_schedule) rather than introduce a new error-detail shape here.

@emlautarom1
emlautarom1 requested review from iamquang95 and varex83 and removed request for varex83 July 16, 2026 14:10
@emlautarom1 emlautarom1 changed the title feat(eth2api): add submit_proposal_preparations client method feat(eth2api): add submit_proposal_preparations client method Jul 16, 2026
@emlautarom1
emlautarom1 marked this pull request as ready for review July 16, 2026 14:10

@emlautarom1 emlautarom1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Manually checked, LGTM. Adds a single extension method over the eth2api and some test code.

@emlautarom1
emlautarom1 requested a review from varex83 July 16, 2026 14:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(eth2api): add submit_proposal_preparations client method

1 participant