Skip to content

feat(core): implement validatorapi proposal + submit_proposal + submit_blinded_proposal handlers#461

Open
varex83agent wants to merge 1 commit into
bohdan/validatorapi-plumbingfrom
bohdan/validatorapi-proposal
Open

feat(core): implement validatorapi proposal + submit_proposal + submit_blinded_proposal handlers#461
varex83agent wants to merge 1 commit into
bohdan/validatorapi-plumbingfrom
bohdan/validatorapi-proposal

Conversation

@varex83agent
Copy link
Copy Markdown
Collaborator

Summary

Ports the three proposal endpoints from
core/validatorapi/validatorapi.go in Charon v1.7.1:

  • Proposalvalidatorapi.go:388-450. Resolves the proposer
    pubkey via a new proposer_pubkey_fn hook, derives the epoch from
    the slot through the upstream beacon node, builds a partial
    SignedRandao wrapper, runs verify_partial_sig against this
    node's pubshare, fans out the randao parsig to subscribers, then
    blocks (24s ~ 2 slots) on await_proposal_fn for the consensus
    proposal landing in the dutydb. The return envelope matches Go's
    wrapResponse(proposal) — no metadata.
  • SubmitProposalvalidatorapi.go:551-605. Pulls the
    consensus-side unsigned proposal, validates it against the VC
    submission via a new proposal_matches_duty helper (the Rust port
    of propDataMatchesDuty), constructs a partial
    signeddata::VersionedSignedProposal::new_partial, verifies the
    partial proposer signature, and fans the parsig set out to
    subscribers.
  • SubmitBlindedProposalvalidatorapi.go:606-673. Same fan-out
    shape; uses the existing from_blinded_proposal /
    new_partial_from_blinded_proposal helpers in
    signeddata::VersionedSignedProposal to bridge the blinded payload
    through the same proposal_matches_duty check.
  • getProposerPubkeyvalidatorapi.go:1334. Pluto's existing
    duty_def_fn is intentionally type-erased (Box<dyn Any>); this PR
    adds a typed register_proposer_pubkey hook that mirrors the
    existing register_pub_key_by_attestation shape and lets the
    proposal handlers fetch the proposer pubkey without downcasting.

The VersionedProposal / VersionedSignedProposal /
VersionedSignedBlindedProposal placeholder types in
validatorapi/types.rs are now pub use re-exports of the populated
signeddata::* / eth2api::versioned::* wrappers, so the Handler
trait carries real payloads. The trait method signatures are
unchanged.

Go reference

Endpoint Go (core/validatorapi/validatorapi.go) Rust (validatorapi/component.rs)
Proposal lines 388-450 proposal
propDataMatchesDuty lines 451-549 proposal_matches_duty + per-fork helpers
SubmitProposal lines 551-605 submit_proposal
SubmitBlindedProposal lines 606-673 submit_blinded_proposal
getProposerPubkey lines 1334-1349 Component::lookup_proposer_pubkey (hook)
verifyPartialSig line 1352 Component::verify_partial_sig (PR-1)

Test plan

  • cargo +nightly fmt --all --check
  • cargo clippy -p pluto-core --all-targets --all-features -- -D warnings
  • cargo test -p pluto-core --all-features — 386/386 passing (12 new tests)

New tests:

  • proposal_returns_proposal_from_hook_and_fans_out_randao — happy path
  • proposal_returns_blinded_proposal_in_builder_mode — builder-mode branch
  • proposal_rejects_when_proposer_pubkey_hook_missing — 503 on missing hook
  • proposal_times_out_when_consensus_proposal_never_arrives — 408 on
    PROPOSAL_TIMEOUT
  • submit_proposal_fans_out_partial_signed_to_subscribers — happy path
  • submit_proposal_rejects_version_mismatch — 400 on version diff
  • submit_proposal_rejects_proposer_index_mismatch — 400 on index diff
  • submit_proposal_rejects_blinded_mismatch — 400 on blinded flag diff
  • submit_proposal_rejects_when_verification_fails — 500 on unknown pubshare
  • submit_proposal_uses_dutydb_fallback_when_hook_missing — dutydb path
  • submit_blinded_proposal_fans_out_partial_signed_to_subscribers — happy
  • submit_blinded_proposal_rejects_proposer_index_mismatch — 400 on index

…t_blinded_proposal handlers

Ports the three proposal endpoints from Charon's
core/validatorapi/validatorapi.go: Proposal (lines 388-450),
SubmitProposal (lines 551-605) and SubmitBlindedProposal (lines
606-673), plus the propDataMatchesDuty helper (lines 451-549) and
getProposerPubkey (line 1334).

- crates/core/src/validatorapi/types.rs: replaces the three proposal
  placeholders with re-exports of the concrete signeddata /
  eth2api::versioned wrappers so the Handler trait now carries real
  payloads (no Handler signature change — the trait method types just
  point at populated structs instead of empty placeholders).
- crates/core/src/validatorapi/component.rs:
  - Adds ProposerPubkeyFn / register_proposer_pubkey. Pluto's
    duty_def_fn is intentionally type-erased (Box<dyn Any>) so we add
    a thin typed hook for the proposer-pubkey lookup, mirroring the
    existing pub_key_by_att_fn shape. This is the Rust equivalent of
    Go's getProposerPubkey at validatorapi.go:1334.
  - proposal: resolves proposer pubkey, derives epoch from slot via
    pluto_eth2util::helpers::epoch_from_slot (Go's
    eth2util.EpochFromSlot), builds a SignedRandao::new_partial wrapper,
    verifies the partial randao signature, fans the parsig set out to
    subscribers, and finally blocks (PROPOSAL_TIMEOUT = 24s, ~2 slots,
    same sizing as ATTESTATION_DATA_TIMEOUT) on the await_proposal_fn
    hook (with a dutydb.await_proposal fallback for tests). Always
    returns finalized=false, execution_optimistic=false,
    dependent_root=None — Go writes wrapResponse(proposal) which has
    no metadata.
  - submit_proposal / submit_blinded_proposal: pull the consensus-side
    unsigned proposal for the slot, cross-check version, blinded flag,
    proposer index, and SSZ tree-hash root against the VC submission
    (proposal_matches_duty mirrors propDataMatchesDuty's per-fork
    branches), then build a partial VersionedSignedProposal via
    signeddata::VersionedSignedProposal::new_partial (or
    new_partial_from_blinded_proposal for the blinded path), verify
    the partial signature against this node's public share, and fan
    out a single-entry ParSignedDataSet to subscribers.
- Adds a small fork-aware helper bundle on the eth2api
  SignedProposalBlock / SignedBlindedProposalBlock enums so the
  matches-duty check can reach proposer_index, slot, version, and the
  per-variant SSZ root for both signed and blinded payloads. These
  helpers are private to component.rs and follow Go's structure 1:1.

Tests: 12 new tests cover proposal (happy path with subscriber fanout,
builder-mode blinded branch, missing-hook 503, never-arrives 408),
submit_proposal (happy path, version mismatch, proposer-index
mismatch, blinded mismatch, unknown pubshare rejection, dutydb
fallback), and submit_blinded_proposal (happy path, proposer-index
mismatch).

Co-Authored-By: Bohdan Ohorodnii <35969035+varex83@users.noreply.github.com>
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.

1 participant