Skip to content

serviceability: batch multicast groups in UpdateMulticastGroupRoles and CreateSubscribeUser - #4120

Open
nikw9944 wants to merge 7 commits into
mainfrom
nikw9944/infra-2114
Open

serviceability: batch multicast groups in UpdateMulticastGroupRoles and CreateSubscribeUser#4120
nikw9944 wants to merge 7 commits into
mainfrom
nikw9944/infra-2114

Conversation

@nikw9944

Copy link
Copy Markdown
Contributor

Summary of Changes

  • UpdateMulticastGroupRoles (58) and CreateSubscribeUser (59) accept additional writable MulticastGroup accounts (a borsh-incremental extra_group_count: u8 plus 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.
  • Each batch member is authorized exactly like a single-group call: per-group allowlist checks in both variants, and per-extra-group feed coverage checks in CreateSubscribeUser for 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 is InvalidArgument. Old encodings without the count byte decode as 0, so existing clients are byte-identical.
  • The RFC-26 builders derive extra_group_count from a new extra_groups: &[Pubkey] parameter; the Rust SDK commands change group_pk: Pubkeygroup_pks: Vec<Pubkey> and mgroup_pkmgroup_pks (breaking, see CHANGELOG), deduplicate, and cap at MAX_GROUPS_PER_TRANSACTION (16).
  • Call sites batch by (publisher, subscriber) flag pair, chunked to 16 groups per transaction: doublezero connect Multicast folds 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 subscribe batches by effective flag pair, and the user delete/request-ban role strip is batched. Daemon-cli failure reporting is per batch: a failed batch lists every group it carried, since none was applied.
  • Deploy ordering (RFC-1): the program must deploy to all clusters before any client release that emits batches — an old program would misread the extra group accounts as the trailing optional accounts. MIN_COMPATIBLE_VERSION does not move: old clients remain fully valid emitters.
  • Non-test additions come to ~590 lines, slightly over the ~500-line guidance; the natural split if preferred is program+builders+SDK first, call sites second.
  • Fixes malbeclabs/infra#2114

Testing Verification

  • New SVM program tests for both variants: batch subscribe across two groups (counters and user lists), atomic rollback when one group is off the allowlist or outside the feed's group set (including the seat tick rolling back and no user account surviving), dz_ip allocated exactly once on a batch publisher add and deallocated once on batch removal, duplicate-group and count-overrun rejection, and old-encoding (no count byte) wire compatibility via byte-stripped instructions.
  • EdgeSeat batch tests in 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 with GroupNotInFeed.
  • Builder tests pin the account layout with 0 and N extra groups (extras between dz_prefix blocks and the optional feed) and that extra_group_count is 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.
  • CLI/daemon-cli tests assert one ledger call per flag pair (not per group), the single-transaction connect path (create carries all same-flag groups, no follow-up calls), batch splitting when effective flag pairs differ, and per-batch failure reporting naming every group in a failed batch.
  • Rebased onto the SubscribeFeed/UnsubscribeFeed authorization rework (serviceability: add SubscribeFeed and UnsubscribeFeed for EdgeSeat feeds #4113); the batch paths adopt its per-group check_mgroup_allowlists model.

nikw9944 added 4 commits July 30, 2026 16:14
…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.
@nikw9944 nikw9944 self-assigned this Jul 30, 2026
…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.
@nikw9944
nikw9944 marked this pull request as ready for review July 30, 2026 18:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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) and CreateSubscribeUser (59) via extra_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 to MAX_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})"))?;
Comment on lines +685 to +688
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
@nikw9944

Copy link
Copy Markdown
Contributor Author

Addressed review feedback in e84a11a:

  • Existing-user connect drops publisher for a group in both lists (medium, pre-existing): fixed here rather than deferred — the existing-user path now batches by each group's effective (publisher, subscriber) pair, computed as the union of the request and the roles the user already holds, so no absolute-state write strips a role a prior batch (or the user) already had. Regression test test_connect_existing_user_group_in_both_lists_keeps_both_roles pins one update carrying both flags. This also fixes the sibling case where --publish alone stripped an existing subscriber role.
  • Garbled bail messages: the wrapped string literals in UpdateMulticastGroupRolesCommand and CreateSubscribeUserCommand are joined with a single space.
  • Bare is_err() in the batch atomicity test: now matches NotAllowed (Custom(8)), consistent with the sibling test.
  • Coverage gaps: added extra-vs-extra duplicate rejection tests for both instructions (test_batch_duplicate_extra_group_rejected, test_create_subscribe_user_batch_duplicate_extra_rejected) and test_batch_removal_with_trailing_permission_pda, which exercises a USER_ADMIN batch strip with the payer's Permission PDA appended after payer/system alongside extra group accounts.

Not changed: the order-dependent Pubkey::new_unique flake in the three CLI feed tests reproduces on origin/main and is unrelated to this diff — proposed follow-up issue: "cli: user create-subscribe feed tests are order-dependent via the Pubkey::new_unique global counter" (fix by using from_str_const keys or codes guaranteed not to parse as pubkeys).

nikw9944 added 2 commits July 31, 2026 00:20
# 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).
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