Skip to content

feat: extract reusable member-traffic reconciliation trigger (Scala) - #15

Merged
sadiq1971 merged 5 commits into
feat/dedicated-syncfrom
refactor/reusable-traffic-triggers
Aug 25, 2026
Merged

feat: extract reusable member-traffic reconciliation trigger (Scala)#15
sadiq1971 merged 5 commits into
feat/dedicated-syncfrom
refactor/reusable-traffic-triggers

Conversation

@sadiq1971

@sadiq1971 sadiq1971 commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Rebased onto feat/dedicated-sync now that #14 has merged. The reconciliation logic is SV-shaped today: it asks SynchronizerNodeService for its own sequencer, so there is no way to point it at another synchronizer. This factors the logic out so the Sync Operator Node can run it against a dedicated synchronizer. No behavior change for the SV.

What this does

  • New ReconcileSequencerLimitWithMemberTrafficTriggerBase (apps/common/automation), following the existing PackageVettingTrigger pattern: shared logic in an abstract trigger, app-specific dependencies as abstract defs (targetSynchronizerId, sequencerAdminConnection, getTotalPurchasedMemberTraffic, trafficLimitOffset).
  • The SV trigger becomes a thin subclass, 175 lines down to 78, binding those hooks to SvDsoStore, SynchronizerNodeService and the synchronizer id already held by SvDsoAutomationService.
  • Contracts for other synchronizers are now skipped on a local comparison, before any sequencer RPC. Previously every foreign contract cost a getStatus round trip.

The SV subclass deliberately keeps its name and package. identifyTriggerClassByName returns getCanonicalName and that string keys automationConfig.pausedTriggers, while metrics use getSimpleName as trigger_name. Moving the concrete class would silently change both, including in dashboards and cluster configs outside this repo. Only the reusable logic moves out of singlesv.

One deliberate behavior change

A mismatch between the configured target and the sequencer the connection actually serves is now Status.INTERNAL rather than a skip. After this change those are different conditions: a contract naming another synchronizer is normal and still skips, but the configured target disagreeing with the connection would credit the wrong sequencer, so it should be loud rather than silently granting nothing. This cannot fire from an LSU switchover, which preserves the logical synchronizer id.

How it's verified

apps-sv/compile, scalafmtCheck, headerCheck and scalafix --check pass locally on both apps-common and apps-sv.

No new tests: apps/sv holds 79 triggers and no trigger unit tests. Equivalence rests on the target being the same value the old code compared against, which holds by type (PhysicalSynchronizerId.logical : SynchronizerId) and because SvDsoAutomationService receives the decentralized synchronizer id.

Tracked in

Implements E2-1 (ChainSafe/canton-extending-mainnet#31); unblocks E2-2 (#32).

Note for E2-2: getTotalPurchasedMemberTraffic filters on migration_id, and MemberTraffic ingestion filters on payload.migrationId == domainMigrationId, so an operator node built on this path would inherit #60 and grant nothing on a network past migration 0. #16 avoids that by not matching on the node's own migration id, since a registered synchronizer is pinned to migration id 0.

@sadiq1971 sadiq1971 changed the title Refactor: extract reusable member-traffic reconciliation trigger (Scala) feat: extract reusable member-traffic reconciliation trigger (Scala) Aug 11, 2026
@sadiq1971
sadiq1971 force-pushed the refactor/reusable-traffic-triggers branch from 39ac0d2 to c2a3d42 Compare August 14, 2026 11:38

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

A mismatch between the configured target and the sequencer the connection actually serves is now Status.INTERNAL rather than a skip.

The code ships the better version of this — FAILED_PRECONDITION with retryable semantics, so a connection mid-switchover recovers on its own (the in-code comment makes that argument well). Worth updating this paragraph to match so the merged description doesn't contradict the implementation.

@moritzkiefer-da moritzkiefer-da left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks! This overall goes in the right direction but some of the checks are a bit redundant or misplaced. Also left some comments wrt to BFT syncs. I don't think that needs any code changes so more of an FYI•

@timwu20
timwu20 changed the base branch from fix/sync-id-parse-hardening to feat/dedicated-sync August 24, 2026 18:10
…igger [ci]

Move the reconciliation logic into an abstract trigger in apps/common, parameterized
by the target synchronizer and its sequencer admin connection, so the Sync Operator
Node can reconcile a dedicated synchronizer with the same code. The SV subclass keeps
its name and package so its canonical name, metrics and paused-trigger key are
unchanged.

Signed-off-by: sadiq1971 <sadiqurr8@gmail.com>
- report a target/connection mismatch as FAILED_PRECONDITION so it is retryable
  instead of dropping the purchase with an ERROR
- warn once when the configured target is not served by the sequencer, which a
  per-contract check cannot see because every contract skips first
- carry the skip reason through trafficLimitOffset instead of overloading Option
- pin the target to a stable value and drop the redundant synchronizer id from the
  store hooks
- skip members before opening the sequencer connection

Signed-off-by: sadiq1971 <sadiqurr8@gmail.com>
… the sequencer [ci]

The one-off target check was sequenced into the skip, so an unreachable sequencer
could make skipping a foreign contract retry or fail. It is now best-effort, guarded
by a compare-and-set so only one check runs at a time and it logs at most once, and
it reuses a single helper for resolving what the sequencer serves.

Signed-off-by: sadiq1971 <sadiqurr8@gmail.com>
…ape [ci]

The mismatch branch claimed to be reported by warnOnceIfTargetNotServed
but never called it, so a wrong connection produced retry noise with no
diagnosis. It already knows what the sequencer serves, so it now warns
directly through a shared at-most-once helper.

Splits the single settled flag in two: confirming the wiring must not
consume the one warning a later mismatch is entitled to.

Also sketches the BFT extension on the class docstring, as canton-network#31 asks.

Signed-off-by: sadiq1971 <sadiqurr8@gmail.com>
@sadiq1971
sadiq1971 force-pushed the refactor/reusable-traffic-triggers branch from ce0b45a to 6a63721 Compare August 24, 2026 18:17
Drops targetSynchronizerId and everything that existed to police it: both
warn helpers, both flags and the target-vs-served mismatch branch. The
foreign-contract skip now compares the contract against what the sequencer
reports it serves, so there is no configured value left to disagree with.

This restores the pre-extraction behaviour, where the synchronizer id was
read from the connection per contract. The SV subclass keeps its own id for
its store queries.

Also restores the unparseable-synchronizer-id warning from #14, which a
merge commit carried and the rebase onto the merged base dropped.

Signed-off-by: sadiq1971 <sadiqurr8@gmail.com>
@sadiq1971

Copy link
Copy Markdown
Collaborator Author

Thanks! This overall goes in the right direction but some of the checks are a bit redundant or misplaced. Also left some comments wrt to BFT syncs. I don't think that needs any code changes so more of an FYI•

@moritzkiefer-da updated the code

@moritzkiefer-da moritzkiefer-da left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nice thanks

@sadiq1971
sadiq1971 merged commit 95b5681 into feat/dedicated-sync Aug 25, 2026
118 of 120 checks passed
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.

3 participants