serviceability: batch multicast groups in UpdateMulticastGroupRoles and CreateSubscribeUser - #4120
serviceability: batch multicast groups in UpdateMulticastGroupRoles and CreateSubscribeUser#4120nikw9944 wants to merge 7 commits into
Conversation
…cribe (infra#2114) Extend UpdateMulticastGroupRoles (58) and CreateSubscribeUser (59) with a borsh-incremental extra_group_count and trailing writable MulticastGroup accounts, so a user subscribes to N groups atomically in one transaction. Builders derive the count from the slice; SDK commands take group lists; CLI, daemon-cli connect/multicast verbs, and delete/request-ban role stripping batch their calls.
…ticast batching (infra#2114) Program tests pin batch subscribe/rollback atomicity, single dz_ip allocate/deallocate across a batch, old-encoding wire compatibility, count-overrun rejection, and the EdgeSeat per-group coverage check with one seat tick. Daemon-cli and CLI tests pin one ledger call per flag pair and the single-transaction connect path.
…groups (infra#2114) Review follow-ups: hoist the 16-group transaction cap into the SDK (MAX_GROUPS_PER_TRANSACTION) and chunk every batch emitter (delete/ request-ban role strip, CLI user subscribe, daemon-cli multicast verbs) so heavily subscribed users stay under the 1232-byte transaction limit; dedupe group lists in the SDK commands; reject duplicate group accounts in both processors (aliased writes would depend on write order); add the missing owner check on the primary mgroup in CreateSubscribeUser; pin a max-size batch under the transaction limit with an SDK test.
…2114) Add the new extra_group_count field to feed_subscription_test arg literals introduced by #4113 and apply rustfmt to the rebased batch loop.
…g pair (infra#2114) Review feedback: the existing-user connect path now computes each group's desired (publisher, subscriber) pair as the union of the request and the roles already held, so a group in both --publish and --subscribe (or already holding the other role) keeps both instead of the last absolute-state write stripping the first (pre-existing bug). Also fix the garbled whitespace in the two over-cap bail messages, pin NotAllowed in the create-subscribe batch atomicity test, and add extra-vs-extra duplicate and trailing-Permission-PDA batch coverage.
There was a problem hiding this comment.
Pull request overview
This PR extends the DoubleZero Ledger serviceability workflow to batch multicast group role changes and initial user subscriptions into fewer atomic transactions by introducing an incremental extra_group_count and trailing extra MulticastGroup accounts, then updates SDK/CLI/daemon call sites and fixtures accordingly.
Changes:
- Add batched MulticastGroup handling to
UpdateMulticastGroupRoles(58) andCreateSubscribeUser(59) viaextra_group_count+ trailing group accounts, with duplicate rejection and account-count validation. - Update Rust SDK commands/builders and CLI/daemon flows to accept
Vec<Pubkey>group lists, chunk toMAX_GROUPS_PER_TRANSACTION, and preserve per-batch failure reporting. - Expand/adjust SVM tests and fixture generators to cover batching and wire-compat behavior (old encoding with no count byte).
Reviewed changes
Copilot reviewed 23 out of 27 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| smartcontract/sdk/rs/src/commands/user/requestban.rs | Batch role stripping into chunked UpdateMulticastGroupRolesCommand calls. |
| smartcontract/sdk/rs/src/commands/user/delete.rs | Batch role stripping into chunked calls; update unit tests for new builder signature/args. |
| smartcontract/sdk/rs/src/commands/user/create_subscribe.rs | Accept mgroup_pks: Vec<Pubkey>, dedupe, validate groups, and pass extras to builder. |
| smartcontract/sdk/rs/src/commands/multicastgroup/subscribe.rs | Accept group_pks: Vec<Pubkey>, dedupe, allowlist-check per group, add batch sizing test. |
| smartcontract/programs/doublezero-serviceability/tests/user_onchain_allocation_test.rs | Update test args to include extra_group_count. |
| smartcontract/programs/doublezero-serviceability/tests/rfc26_builders_test.rs | Update builder test call sites for new extra_groups parameter/arg. |
| smartcontract/programs/doublezero-serviceability/tests/multicastgroup_subscribe_test.rs | Add SVM batch role-update tests (atomicity, duplicates, count mismatch, old encoding). |
| smartcontract/programs/doublezero-serviceability/tests/feed_subscription_test.rs | Update test args for incremental extra_group_count. |
| smartcontract/programs/doublezero-serviceability/tests/feed_metro_gate_test.rs | Add SVM tests for batched EdgeSeat feed coverage behavior. |
| smartcontract/programs/doublezero-serviceability/tests/create_subscribe_user_test.rs | Add SVM tests for batched create+subscribe behavior, atomicity, duplicates, old encoding. |
| smartcontract/programs/doublezero-serviceability/src/processors/user/create_subscribe.rs | Parse extra group accounts, reject duplicates, authorize/apply extra subscriptions, and write back all mutated accounts. |
| smartcontract/programs/doublezero-serviceability/src/processors/multicastgroup/subscribe.rs | Parse extra group accounts, reject duplicates, apply per-group allowlist + role updates atomically, handle dz_ip transitions across batch. |
| smartcontract/programs/doublezero-serviceability/src/instructions.rs | Update instruction-name tests for new incremental args. |
| smartcontract/cli/src/user/subscribe.rs | Batch group updates by effective (publisher, subscriber) pair and chunk to max per tx; update tests. |
| smartcontract/cli/src/user/create_subscribe.rs | Adapt CLI to pass a one-element mgroup_pks vec into SDK command. |
| sdk/telemetry/testdata/fixtures/generate-fixtures/Cargo.lock | Bump embedded crate versions to v0.32.0 in fixture generator lockfile. |
| sdk/serviceability/testdata/fixtures/ix_create_subscribe_user.json | Update serialized instruction fixture bytes for new incremental args. |
| sdk/serviceability/testdata/fixtures/generate-fixtures/src/main.rs | Update fixture generator to pass extra_groups and extra_group_count. |
| sdk/serviceability/testdata/fixtures/generate-fixtures/Cargo.lock | Bump embedded crate versions to v0.32.0 in fixture generator lockfile. |
| sdk/geolocation/testdata/fixtures/generate-fixtures/Cargo.lock | Bump embedded crate versions to v0.32.0 in fixture generator lockfile. |
| crates/sentinel/src/dz_ledger_writer.rs | Update create-subscribe args to include extra_group_count. |
| crates/doublezero-serviceability-instruction/src/user.rs | Extend create_subscribe_user builder to accept extra_groups and derive extra_group_count; place extras before optional feed. |
| crates/doublezero-serviceability-instruction/src/multicastgroup.rs | Extend update_multicast_group_roles builder to accept extra_groups and derive extra_group_count. |
| crates/doublezero-daemon-cli/src/multicast.rs | Batch daemon-cli role updates by flag pair and chunk to transaction limit; adjust failure aggregation. |
| crates/doublezero-daemon-cli/src/connect.rs | Fold same-flag groups into create-subscribe; batch follow-up updates; add batching helper and update tests. |
| CHANGELOG.md | Document breaking SDK API changes and new batching behavior + deploy ordering constraints. |
Comments suppressed due to low confidence (1)
smartcontract/sdk/rs/src/commands/multicastgroup/subscribe.rs:311
- This test uses a fixed "margin" to account for the compute-budget prelude size. That can let the test pass even if
compute_budget_prelude()changes (or fail unexpectedly). It’s more robust to include the actual prelude instructions in the Message size calculation instead of relying on a magic number.
// The SDK's send_transaction prepends two compute-budget instructions
// (one program key + two short instructions), comfortably under this margin.
const COMPUTE_BUDGET_PRELUDE_MARGIN: usize = 100;
| pubkey_or_code: group_pk.to_string(), | ||
| } | ||
| .execute(client) | ||
| .map_err(|_err| eyre::eyre!("MulticastGroup not found ({group_pk})"))?; |
| pubkey_or_code: mgroup_pk.to_string(), | ||
| } | ||
| .execute(client) | ||
| .map_err(|_err| eyre::eyre!("MulticastGroup not found ({mgroup_pk})"))?; |
| assert!( | ||
| format!("{err:?}").contains("Custom(94)"), | ||
| "expected GroupNotInFeed (Custom(94)), got: {err:?}" | ||
| ); |
| @@ -2204,6 +2297,52 @@ mod tests { | |||
| /// Exercises the `(_, Some(mcast))` branch of `find_or_create_user_and_subscribe`, | |||
| /// which calls UpdateMulticastGroupRoles (the on-chain processor never had an epoch | |||
|
Addressed review feedback in e84a11a:
Not changed: the order-dependent |
# Conflicts: # CHANGELOG.md # smartcontract/programs/doublezero-serviceability/src/processors/user/create_subscribe.rs
…#2114) The #4119 feed update/delete orphan cleanup landed using the old group_pk field; its removal calls now pass one-element group_pks vecs (per-orphan reporting unchanged).
Summary of Changes
UpdateMulticastGroupRoles(58) andCreateSubscribeUser(59) accept additional writable MulticastGroup accounts (a borsh-incrementalextra_group_count: u8plus that many trailing accounts), so subscribing a user to N multicast groups — a feed routinely carries more than one — is one atomic transaction instead of N: one signature and fee, and a failure rolls back every group, so an EdgeSeat seat tick can no longer outlive a partial subscription.CreateSubscribeUserfor EdgeSeat passes (the seat still ticks once per user per feed). Duplicate group accounts in a batch are rejected, and a count larger than the supplied accounts isInvalidArgument. Old encodings without the count byte decode as 0, so existing clients are byte-identical.extra_group_countfrom a newextra_groups: &[Pubkey]parameter; the Rust SDK commands changegroup_pk: Pubkey→group_pks: Vec<Pubkey>andmgroup_pk→mgroup_pks(breaking, see CHANGELOG), deduplicate, and cap atMAX_GROUPS_PER_TRANSACTION(16).doublezero connect Multicastfolds all same-flag groups into the create (one transaction in the common case, no activation wait), the daemon-cli multicast verbs batch by carried role,doublezero user subscribebatches by effective flag pair, and theuser delete/request-banrole strip is batched. Daemon-cli failure reporting is per batch: a failed batch lists every group it carried, since none was applied.MIN_COMPATIBLE_VERSIONdoes not move: old clients remain fully valid emitters.Testing Verification
feed_metro_gate_test.rs: a batch inside the feed's group set succeeds with exactly one seat tick; an extra group outside the set is rejected withGroupNotInFeed.extra_group_countis derived, not caller-supplied; an SDK test pins a max-size 16-group batch (plus Permission PDA and compute-budget margin) under the 1232-byte transaction limit.SubscribeFeed/UnsubscribeFeedauthorization rework (serviceability: add SubscribeFeed and UnsubscribeFeed for EdgeSeat feeds #4113); the batch paths adopt its per-groupcheck_mgroup_allowlistsmodel.