ChainSafe / ChainSafe/canton-extending-mainnet
[P2-E5.8] Traffic bought before a member joins the dedicated synchronizer is never granted
- Dominant language
- Shell
- Stars
- 0
- Forks
- 0
- Avg merge
- 22h 58m
- Merged PRs (30d)
- 1
Description
`ReconcileDedicatedSequencerTrafficTrigger` is a thin subclass of the shared one-shot base (`ReconcileSequencerLimitWithMemberTrafficTriggerBase`, splice-multi-sync #15 and #21). It fires once per `MemberTraffic` contract and looks the member up on the dedicated sequencer. If the member has no traffic state yet, it logs `No traffic state found for member ...` and marks the contract done (`ReconcileSequencerLimitWithMemberTrafficTriggerBase.scala:131-136`). Nothing revisits that contract, so the member gets its traffic only when it buys again.
A member has no traffic state until it has joined the synchronizer, i.e. until its trust certificate has been sequenced there. Canton enforces this on both sides:
- the sequencer only reports traffic states for members in its topology (`BlockSequencer.trafficStatus`, `knownMembers()`)
- a `SetTrafficPurchased` is a sequenced message addressed to the member (`TrafficPurchasedSubmissionHandler.scala:170-174`), and the sequencer rejects submissions addressed to unknown members (`SubmissionRequestValidator.scala:430-451`, `UnknownRecipients`)
So "buy first, join later" cannot be granted at purchase time. With base rate 0 (#38) the member then has no allowance at all until a second purchase happens to trigger the grant.
In normal operation this order is rare: a validator joins first, and its top-up automation (#40) buys on the already connected participant. Manual purchases and tests can hit it.
A polling variant of the trigger (re-check every member's purchased total against its sequencer limit on each poll) was prototyped during #38 and dropped: one store query plus one sequencer call per poll, for a corner case.
**Acceptance criteria:**
- [ ] Decide between: document join-before-buy as a requirement; re-run the reconciliation for a member when it becomes known on the dedicated synchronizer; or accept the polling cost
- [ ] If a code path is chosen: on LocalNet, buy for a participant that has not joined the dedicated synchronizer, then join it; the limit rises to the purchased total without a second purchase
- [ ] If the requirement is chosen: the operator logs the skipped purchase at WARN rather than INFO, so it is visible
**Key files:** `apps/common/src/main/scala/.../automation/ReconcileSequencerLimitWithMemberTrafficTriggerBase.scala:131-136`, `apps/syncoperator/src/main/scala/.../automation/ReconcileDedicatedSequencerTrafficTrigger.scala`, `canton/community/base/src/main/scala/.../sequencing/traffic/TrafficPurchasedSubmissionHandler.scala:170-174`, `canton/community/synchronizer/src/main/scala/.../block/update/SubmissionRequestValidator.scala:430-451`
**Related:** #32 (the one-shot trigger), #38 (base rate 0), #40 (validator top-up on dedicated synchronizers).
**Epic:** ChainSafe/canton-extending-mainnet#68
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with ReconcileSequencerLimitWithMemberTrafficTriggerBase.scala:131-136 and trace how ReconcileDedicatedSequencerTrafficTrigger handles a missing member state. Then read the traffic purchase and recipient-validation paths in TrafficPurchasedSubmissionHandler.scala:170-174 and SubmissionRequestValidator.scala:430-451. Compare the three acceptance-criteria options, and validate the chosen behavior on LocalNet by buying before joining and checking that the limit reaches the purchased total without another purchase.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- scala
- Domain
- distributed-systems
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100