serviceability-instruction: device domain builders (RFC-26 R1) - #4050
Conversation
…RFC-26 R0) (#4049) ## Summary RFC-26 **R0**: scaffold the pure, RPC-free instruction-builder crate `doublezero-serviceability-instruction` — SPL-style builders that return a single unsigned `Instruction` per serviceability instruction, no signing/sending. - `common::build` (no-permission trailing `[payer, system]`) + `common::build_with_permission` (the deferred, activate-in-one-place Permission append) + `compute_budget_prelude` (1.4M CU / 256 KiB). - Four exemplar builders establishing the pattern every later PR copies: `create_device`, `delete_device` (legacy/atomic), `create_link`, `create_subscribe_user`. - Deps limited to `doublezero-serviceability` + `solana-program` + `solana-system-interface` + `solana-compute-budget-interface` — no RPC tree. The `suspend_device` exemplar from the RFC was replaced with `delete_device` (`SuspendDevice` is a deprecated variant); the RFC and #4015 were updated, and the "length-detected family" classification was corrected (only `CreateUser` is length-detected). ## Why Instruction assembly is currently coupled to signing+sending inside `commands/*::execute()`; there is no reusable `build_xxx(args) -> Instruction`. See [rfcs/rfc26-rust-instruction-builder-library.md](rfcs/rfc26-rust-instruction-builder-library.md). ## Testing Verification - Unit tests assert the exact `AccountMeta` list (incl. trailing `[payer, system]`) and the borsh tag byte for each exemplar, including the `delete_device` legacy vs atomic layouts and the `create_subscribe_user` optional-feed placement. Closes #4015. Part of RFC-26. --- ### PR stack (RFC-26 builder library) Stacked PRs, merge in order (each is based on the previous one's branch): 1. #4049 — R0 scaffold + exemplars ← **this PR** 2. #4050 — R1 device 3. #4051 — R2 link 4. #4052 — R3 user 5. #4053 — R4 location/exchange/contributor 6. #4054 — R5 multicastgroup + allowlists 7. #4055 — R6 tenant + permission 8. #4056 — R7 topology + feed 9. #4057 — R8 accesspass + resource 10. #4058 — R9 globalstate/config/allowlist/index/migrate R10 (commands/* migration + program-test) and RF (fixtures) follow as separate PRs.
5f2d846 to
1ee524d
Compare
nikw9944
left a comment
There was a problem hiding this comment.
RFC-26 R1 device builders (RFC). Account order, writable/readonly flags, conditional-account logic, and variant tags (23/83/73/74/76) verified line-by-line against all five processors and the existing SDK commands — they match for valid inputs, and a mis-stated old_dz_prefix_count can only fail the transaction, never corrupt state. All 16 crate tests pass; clippy and fmt clean. The dz-ledger-program-review checklist pass found the on-chain-only classes correctly untouched (no AUTHORIZE_GATED_FLAGS / resource verify changes needed).
One High finding: create_device_interface/delete_device_interface leave use_onchain_allocation/use_onchain_deallocation caller-controlled (default false), which the processors reject unconditionally — the SDK forces true, and the builders should too (they already own topology_count). Medium: the release-mode u8 clamp (debug_assert! + unwrap_or(u8::MAX)) can emit a topology_count that disagrees with the account list, contradicting the R0 siblings' explicit panic-on-overflow policy in the same file; old_dz_prefix_count should be coupled to the dz_prefixes it qualifies; and the tri-state's Some(&[]) clear-topologies case is untested. Lows are hardening/doc/test-coverage items, including full-struct PartialEq asserts per the repo's testing standard.
- Medium (test gap): update_device_interface's tri-state middle value
Some(&[])(clear all topologies) is untested — the one combination exercising the processor's presence heuristic with update_topologies=true, topology_count=0, seg-ext appended, zero topology accounts. Add test_update_device_interface_empty_topologies_clears asserting [device, contributor, globalstate, sri, payer, system], update_topologies == true, topology_count == 0. - Low (awareness only): contributor/globalstate/globalconfig are sent writable although the processors only read them — deliberate byte-parity with the SDK per common.rs; downgrade to new_readonly when the fixture-parity constraint is lifted.
- Low (test gap): update_device resource-loop tests only cover old == new (1,1) and the no-prefix path; the max() asymmetry — shrink (old=3,new=1) and grow (old=1,new=3) — is untested, and shrink is the branch where the processor closes orphaned DzPrefixBlock accounts.
Complete the device domain on top of the R0 exemplars (create/delete): update_device (max(old,new) dz_prefix block), set_device_health, create_device_interface (Vpnv4 topology PDAs), delete_device_interface, update_device_interface (segment-routing + topology reconcile). All route through authorize() -> build_with_permission. Refs #4016, RFC-26.
…e interface builders create_device_interface and delete_device_interface left use_onchain_allocation/use_onchain_deallocation caller-controlled, but the processors reject the false default as their first statement (InvalidArgument), so default args produced an always-reverting instruction. Force the flag inside each builder (as the SDK command does) and assert it in the unpack tests. Also replace the release-mode topology_count clamp (unwrap_or(u8::MAX)) with .expect() in both interface builders, matching the panic-on-overflow policy of the R0 builders in the same file (a clamped count would disagree with the account list).
22b2125 to
f852772
Compare
## Summary RFC-26 **R2** (stacked on the previous phase's branch). accept_link, update_link (LinkUpdateAuthority preamble + conditional tunnel_net / tunnel-resource / topology-union sections), delete_link (topology reference-count accounts), set_link_health. All builders route through `authorize()` -> `build_with_permission` unless noted; each carries a verbatim account-layout doc-comment copied from its processor. ## Testing Verification - Unit tests assert the exact `AccountMeta` list (incl. trailing `[payer, system]`) and the borsh tag byte for every builder, covering the conditional/variable-account paths. Closes #4017. Part of RFC-26 ([rfcs/rfc26-rust-instruction-builder-library.md](rfcs/rfc26-rust-instruction-builder-library.md)). --- ### PR stack (RFC-26 builder library) Stacked PRs, merge in order (each is based on the previous one's branch): 1. #4049 — R0 scaffold + exemplars 2. #4050 — R1 device 3. #4051 — R2 link ← **this PR** 4. #4052 — R3 user 5. #4053 — R4 location/exchange/contributor 6. #4054 — R5 multicastgroup + allowlists 7. #4055 — R6 tenant + permission 8. #4056 — R7 topology + feed 9. #4057 — R8 accesspass + resource 10. #4058 — R9 globalstate/config/allowlist/index/migrate R10 (commands/* migration + program-test) and RF (fixtures) follow as separate PRs.
## Summary RFC-26 **R3** (stacked on the previous phase's branch). create_user (length-detected -> build, no permission), update_user, delete_user, request_ban_user, check_user_access_pass, and set_user_bgp_status (metrics-publisher check, no authorize -> build). All builders route through `authorize()` -> `build_with_permission` unless noted; each carries a verbatim account-layout doc-comment copied from its processor. ## Testing Verification - Unit tests assert the exact `AccountMeta` list (incl. trailing `[payer, system]`) and the borsh tag byte for every builder, covering the conditional/variable-account paths. Closes #4018. Part of RFC-26 ([rfcs/rfc26-rust-instruction-builder-library.md](rfcs/rfc26-rust-instruction-builder-library.md)). --- ### PR stack (RFC-26 builder library) Stacked PRs, merge in order (each is based on the previous one's branch): 1. #4049 — R0 scaffold + exemplars 2. #4050 — R1 device 3. #4051 — R2 link 4. #4052 — R3 user ← **this PR** 5. #4053 — R4 location/exchange/contributor 6. #4054 — R5 multicastgroup + allowlists 7. #4055 — R6 tenant + permission 8. #4056 — R7 topology + feed 9. #4057 — R8 accesspass + resource 10. #4058 — R9 globalstate/config/allowlist/index/migrate R10 (commands/* migration + program-test) and RF (fixtures) follow as separate PRs.
…FC-26 R4) (#4053) ## Summary RFC-26 **R4** (stacked on the previous phase's branch). Three account_index-seeded CRUD domains (create/update/suspend/resume/delete). Exchange create/update carry globalconfig; set_device_exchange carries the device; create_contributor carries the owner. All builders route through `authorize()` -> `build_with_permission` unless noted; each carries a verbatim account-layout doc-comment copied from its processor. ## Testing Verification - Unit tests assert the exact `AccountMeta` list (incl. trailing `[payer, system]`) and the borsh tag byte for every builder, covering the conditional/variable-account paths. Closes #4019. Part of RFC-26 ([rfcs/rfc26-rust-instruction-builder-library.md](rfcs/rfc26-rust-instruction-builder-library.md)). --- ### PR stack (RFC-26 builder library) Stacked PRs, merge in order (each is based on the previous one's branch): 1. #4049 — R0 scaffold + exemplars 2. #4050 — R1 device 3. #4051 — R2 link 4. #4052 — R3 user 5. #4053 — R4 location/exchange/contributor ← **this PR** 6. #4054 — R5 multicastgroup + allowlists 7. #4055 — R6 tenant + permission 8. #4056 — R7 topology + feed 9. #4057 — R8 accesspass + resource 10. #4058 — R9 globalstate/config/allowlist/index/migrate R10 (commands/* migration + program-test) and RF (fixtures) follow as separate PRs.
…26 R5) (#4054) ## Summary RFC-26 **R5** (stacked on the previous phase's branch). create/update (conditional multicast_group_block)/suspend/reactivate/delete, update_multicast_group_roles, and the four pub/sub allowlist add/remove builders. All builders route through `authorize()` -> `build_with_permission` unless noted; each carries a verbatim account-layout doc-comment copied from its processor. ## Testing Verification - Unit tests assert the exact `AccountMeta` list (incl. trailing `[payer, system]`) and the borsh tag byte for every builder, covering the conditional/variable-account paths. Closes #4020. Part of RFC-26 ([rfcs/rfc26-rust-instruction-builder-library.md](rfcs/rfc26-rust-instruction-builder-library.md)). --- ### PR stack (RFC-26 builder library) Stacked PRs, merge in order (each is based on the previous one's branch): 1. #4049 — R0 scaffold + exemplars 2. #4050 — R1 device 3. #4051 — R2 link 4. #4052 — R3 user 5. #4053 — R4 location/exchange/contributor 6. #4054 — R5 multicastgroup + allowlists ← **this PR** 7. #4055 — R6 tenant + permission 8. #4056 — R7 topology + feed 9. #4057 — R8 accesspass + resource 10. #4058 — R9 globalstate/config/allowlist/index/migrate R10 (commands/* migration + program-test) and RF (fixtures) follow as separate PRs.
…4055) ## Summary RFC-26 **R6** (stacked on the previous phase's branch). Tenant CRUD + add/remove administrator + update_payment_status (globalstate writable on create, read-only elsewhere), and permission create/update/suspend/resume/delete (target PDA derived from args.user_payer). All builders route through `authorize()` -> `build_with_permission` unless noted; each carries a verbatim account-layout doc-comment copied from its processor. ## Testing Verification - Unit tests assert the exact `AccountMeta` list (incl. trailing `[payer, system]`) and the borsh tag byte for every builder, covering the conditional/variable-account paths. Closes #4021. Part of RFC-26 ([rfcs/rfc26-rust-instruction-builder-library.md](rfcs/rfc26-rust-instruction-builder-library.md)). --- ### PR stack (RFC-26 builder library) Stacked PRs, merge in order (each is based on the previous one's branch): 1. #4049 — R0 scaffold + exemplars 2. #4050 — R1 device 3. #4051 — R2 link 4. #4052 — R3 user 5. #4053 — R4 location/exchange/contributor 6. #4054 — R5 multicastgroup + allowlists 7. #4055 — R6 tenant + permission ← **this PR** 8. #4056 — R7 topology + feed 9. #4057 — R8 accesspass + resource 10. #4058 — R9 globalstate/config/allowlist/index/migrate R10 (commands/* migration + program-test) and RF (fixtures) follow as separate PRs.
## Summary RFC-26 **R7** (stacked on the previous phase's branch). Topology create/delete plus batched clear_topology / assign_topology_node_segments (single-chunk + *_batched; CLEAR_BATCH_SIZE=16 / BACKFILL_BATCH_SIZE=4 moved into the crate). Feed create/update/delete. All builders route through `authorize()` -> `build_with_permission` unless noted; each carries a verbatim account-layout doc-comment copied from its processor. ## Testing Verification - Unit tests assert the exact `AccountMeta` list (incl. trailing `[payer, system]`) and the borsh tag byte for every builder, covering the conditional/variable-account paths. Closes #4022. Part of RFC-26 ([rfcs/rfc26-rust-instruction-builder-library.md](rfcs/rfc26-rust-instruction-builder-library.md)). --- ### PR stack (RFC-26 builder library) Stacked PRs, merge in order (each is based on the previous one's branch): 1. #4049 — R0 scaffold + exemplars 2. #4050 — R1 device 3. #4051 — R2 link 4. #4052 — R3 user 5. #4053 — R4 location/exchange/contributor 6. #4054 — R5 multicastgroup + allowlists 7. #4055 — R6 tenant + permission 8. #4056 — R7 topology + feed ← **this PR** 9. #4057 — R8 accesspass + resource 10. #4058 — R9 globalstate/config/allowlist/index/migrate R10 (commands/* migration + program-test) and RF (fixtures) follow as separate PRs.
#4057) ## Summary RFC-26 **R8** (stacked on the previous phase's branch). Access-pass set (conditional tenant pair)/close/check-status/set-feeds, and resource-extension allocate/create/deallocate/close (resource PDA + associated account derived from the data-bearing args.resource_type). All builders route through `authorize()` -> `build_with_permission` unless noted; each carries a verbatim account-layout doc-comment copied from its processor. ## Testing Verification - Unit tests assert the exact `AccountMeta` list (incl. trailing `[payer, system]`) and the borsh tag byte for every builder, covering the conditional/variable-account paths. Closes #4023. Part of RFC-26 ([rfcs/rfc26-rust-instruction-builder-library.md](rfcs/rfc26-rust-instruction-builder-library.md)). --- ### PR stack (RFC-26 builder library) Stacked PRs, merge in order (each is based on the previous one's branch): 1. #4049 — R0 scaffold + exemplars 2. #4050 — R1 device 3. #4051 — R2 link 4. #4052 — R3 user 5. #4053 — R4 location/exchange/contributor 6. #4054 — R5 multicastgroup + allowlists 7. #4055 — R6 tenant + permission 8. #4056 — R7 topology + feed 9. #4057 — R8 accesspass + resource ← **this PR** 10. #4058 — R9 globalstate/config/allowlist/index/migrate R10 (commands/* migration + program-test) and RF (fixtures) follow as separate PRs.
…migrate builders (RFC-26 R9) (#4058) ## Summary RFC-26 **R9** (stacked on the previous phase's branch). init_global_state and migrate (no authorize -> build); setters, set_global_config (config PDA + all 8 resource pools), foundation/QA allowlist toggles, index create/delete. After this every buildable variant has a builder. All builders route through `authorize()` -> `build_with_permission` unless noted; each carries a verbatim account-layout doc-comment copied from its processor. ## Testing Verification - Unit tests assert the exact `AccountMeta` list (incl. trailing `[payer, system]`) and the borsh tag byte for every builder, covering the conditional/variable-account paths. Closes #4024. Part of RFC-26 ([rfcs/rfc26-rust-instruction-builder-library.md](rfcs/rfc26-rust-instruction-builder-library.md)). --- ### PR stack (RFC-26 builder library) Stacked PRs, merge in order (each is based on the previous one's branch): 1. #4049 — R0 scaffold + exemplars 2. #4050 — R1 device 3. #4051 — R2 link 4. #4052 — R3 user 5. #4053 — R4 location/exchange/contributor 6. #4054 — R5 multicastgroup + allowlists 7. #4055 — R6 tenant + permission 8. #4056 — R7 topology + feed 9. #4057 — R8 accesspass + resource 10. #4058 — R9 globalstate/config/allowlist/index/migrate ← **this PR** R10 (commands/* migration + program-test) and RF (fixtures) follow as separate PRs.
…4059) ## Summary RFC-26 **RF**: golden fixtures for the instruction builders + a CI drift guard. - Extend the serviceability fixture generator to depend on `doublezero-serviceability-instruction` and emit, per instruction, `ix_<name>.bin` (wire bytes = tag + borsh) and `ix_<name>.json` (`{ variant, data_hex, accounts: [{pubkey, is_signer, is_writable}] }`) from fixed, deterministic inputs. - Representative set covering the trickiest layouts: `create_device` (variable dz_prefix), `delete_device` (atomic close), `create_link`, `create_subscribe_user` (optional feed), `create_user` (length-detected), `clear_topology` / `assign_topology_node_segments` (batched), `set_global_config` (config PDA + all 8 pools). - `make generate-fixtures-check` (regenerate + fail on drift, scoped to the emitted `.bin`/`.json`) and a `fixtures-check` job in the `rust` workflow. These fixtures capture byte-for-byte the current SDK trailing convention (they will drive Go/Python/TS parity in a later phase). ## Testing Verification - `make generate-fixtures-check` passes (regenerated output is byte-identical to the committed fixtures); a hand-edited fixture makes it fail as expected. Closes #4026. Part of RFC-26 ([rfcs/rfc26-rust-instruction-builder-library.md](rfcs/rfc26-rust-instruction-builder-library.md)). --- ### PR stack (RFC-26 builder library) Stacked PRs, merge in order (each based on the previous one's branch): 1. #4049 — R0 scaffold + exemplars 2. #4050 — R1 device 3. #4051 — R2 link 4. #4052 — R3 user 5. #4053 — R4 location/exchange/contributor 6. #4054 — R5 multicastgroup + allowlists 7. #4055 — R6 tenant + permission 8. #4056 — R7 topology + feed 9. #4057 — R8 accesspass + resource 10. #4058 — R9 globalstate/config/allowlist/index/migrate 11. this PR — RF fixtures + CI guard R10 (commands/* migration + program-test) is the final PR and will carry the global changelog entry.
Summary
RFC-26 R1 (stacked on the previous phase's branch). Complete the device domain on top of the R0 exemplars: update_device (max(old,new) dz_prefix block), set_device_health, and interface create/delete/update (Vpnv4 topology PDAs, segment-routing reconcile).
All builders route through
authorize()->build_with_permissionunless noted; each carries a verbatim account-layout doc-comment copied from its processor.Testing Verification
AccountMetalist (incl. trailing[payer, system]) and the borsh tag byte for every builder, covering the conditional/variable-account paths.Closes #4016. Part of RFC-26 (rfcs/rfc26-rust-instruction-builder-library.md).
PR stack (RFC-26 builder library)
Stacked PRs, merge in order (each is based on the previous one's branch):
R10 (commands/* migration + program-test) and RF (fixtures) follow as separate PRs.