Skip to content

feat(app): wire priority + infosync into the node - #574

Merged
varex83agent merged 3 commits into
mainfrom
feat/wire-priority-infosync
Jul 31, 2026
Merged

feat(app): wire priority + infosync into the node#574
varex83agent merged 3 commits into
mainfrom
feat/wire-priority-infosync

Conversation

@varex83agent

Copy link
Copy Markdown
Collaborator

What

Wires the priority protocol behaviour and infosync component into the running node, and triggers a cluster-wide priority exchange on the last slot of each epoch.

Previously both crates were implemented and tested but never mounted (crates/app/src/node/mod.rs carried a TODO(#402 part B)), so a pluto node neither advertised charon/priority/2.0.0 nor proposed into the info_sync QBFT round. In a mixed cluster this made Charon peers log protocols not supported: [charon/priority/2.0.0] and Priority protocol consensus: consensus timeout every epoch.

Scope — participation only ("A")

This is the participation half of #402 part B:

  • CoreBehaviour gains a priority field, constructed via pluto_priority::new_component on the shared p2p_context and riding the existing Arc<qbft::Consensus> (priority uses raw QBFT, matching Charon's wirePrioritise).
  • infosync::Component is built with version::SUPPORTED, the concatenated protocols() of consensus/parsigex/peerinfo/priority, and [Builder?]+Full proposal types.
  • A per-epoch trigger is registered as a scheduler slot subscriber, guarded by Slot::last_in_epoch() (6s exchange timeout, min_required = cluster threshold — both matching Charon).

Deferred (TODO(#402 part B)): consuming the decided result — routing the duty path through ConsensusController and registering the protocol-switch subscriber (set_current_consensus_for_protocol). That is a functional no-op while QBFTv2 is the only consensus protocol, so it is intentionally left out. This PR does not wire proposals() into the fetcher, because Charon does not either (its Proposals()/Protocols() accessors are dead code; full-vs-blinded is decided by --builder-api).

Verification

  • cargo +nightly fmt --all --check, cargo clippy --workspace --all-targets --all-features -- -D warnings, cargo test --workspace --all-features all pass.
  • End-to-end on a 2-charon + 2-pluto kurtosis cluster: at the epoch boundary all four nodes reach byte-identical info_sync results (e.g. protocol: qbft=4000, parsigex=3996, peerinfo=3992, priority=3988), proving pluto's calculateResult is deterministic-equivalent to Charon's. Charon's protocols not supported and consensus timeout warnings stop; regular duties are unaffected.

🤖 Generated with Claude Code

Mount the priority protocol behaviour and construct the infosync
component in the node's P2P wiring, and trigger a cluster-wide priority
exchange on the last slot of each epoch. The priority component rides the
existing QBFT consensus and shares the node-wide P2P context; infosync
advertises this node's supported versions, protocols, and proposal types.

This is the participation half of #402 part B ("A"): it makes pluto take
part in the per-epoch priority/QBFT info_sync round so mixed clusters with
Charon reach quorum. Consuming the decided result (routing the duty path
through ConsensusController and registering the protocol-switch
subscriber) is a functional no-op while QBFTv2 is the only consensus
protocol and is left as a TODO(#402 part B).

Verified on a 2-charon + 2-pluto kurtosis cluster: all four nodes reach
identical info_sync results, and Charon's "protocols not supported:
[charon/priority/2.0.0]" and priority "consensus timeout" warnings stop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@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.

LGTM, some potential simplifications. See if we can reduce the overly-verbose comments and inline variables/avoid clones. Also, consider if we can add some tests to verify the wiring.

Comment thread crates/app/src/node/mod.rs Outdated
Comment on lines +396 to +399
let priority_deadline_calc =
pluto_core::deadline::DutyDeadlineCalculator::from_client(&eth2_cl)
.await
.map_err(AppError::Deadline)?;

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.

You can clone the existing dealine calculator and not create a new one:

Suggested change
let priority_deadline_calc =
pluto_core::deadline::DutyDeadlineCalculator::from_client(&eth2_cl)
.await
.map_err(AppError::Deadline)?;
let priority_deadline_calc = Arc::clone(&deadline_calc);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done in 669fe0f — dropped the second DutyDeadlineCalculator and now pass Arc::clone(&deadline_calc) (the shared Arc<dyn DeadlineCalculator>); wire_p2p takes the Arc and new_component accepts it via the blanket impl DeadlineCalculator for Arc<T>.

Comment on lines +626 to +633
async move {
if slot.last_in_epoch()
&& let Err(err) = infosync.trigger(ct.child_token(), slot.slot).await
{
tracing::warn!(%err, slot = ?slot.slot, "infosync trigger failed");
}
Ok::<(), AppError>(())
}

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.

The subscribe_slot handles errors by logging already, so we can propagate errors:

Suggested change
async move {
if slot.last_in_epoch()
&& let Err(err) = infosync.trigger(ct.child_token(), slot.slot).await
{
tracing::warn!(%err, slot = ?slot.slot, "infosync trigger failed");
}
Ok::<(), AppError>(())
}
async move {
if slot.last_in_epoch() {
infosync.trigger(ct.child_token(), slot.slot).await?;
}
Ok::<(), AppError>(())
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done in 669fe0f — the trigger error now propagates via ? and subscribe_slot logs it; dropped the manual warn!.

pub infosync: Arc<pluto_infosync::Component>,
}

/// Composes the core behaviours and builds the libp2p [`Node`].

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.

Probably better to revisit this with the new set of arguments.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done in 669fe0f — replaced the 14 positional args (and the too_many_arguments allow) with a WireP2PParams struct destructured at the top of wire_p2p.

varex83agent and others added 2 commits July 31, 2026 11:33
… wiring tests

- wire_p2p: replace the 14 positional args (and its too_many_arguments
  allow) with a WireP2PParams struct destructured at the top.
- Reuse the shared Arc<dyn DeadlineCalculator> for priority instead of
  building a second DutyDeadlineCalculator; wire_p2p now takes the Arc.
- infosync slot subscriber: propagate the trigger error via `?` (the
  scheduler's subscribe_slot already logs it) instead of catching/warning.
- Extract local_protocols()/local_proposal_types() as pure helpers and
  unit-test the Charon-parity protocol precedence and builder-first
  proposal ordering.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Condense the wiring comments to the rationale that isn't obvious from the
code (Charon parity, 6s exchange timeout, move-only expired receiver,
deferred consensus-switch), dropping restatements of the code itself.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@varex83agent
varex83agent merged commit 3c031a6 into main Jul 31, 2026
11 checks passed
@varex83agent
varex83agent deleted the feat/wire-priority-infosync branch July 31, 2026 12:21
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.

2 participants