serviceability-instruction: topology + feed builders (RFC-26 R7) - #4056
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.
6bf08fb to
410820c
Compare
bdeefdb to
37c6ca9
Compare
410820c to
a019701
Compare
37c6ca9 to
e3295d4
Compare
## 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_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 #4016. 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 ← **this PR** 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.
## 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.
a019701 to
98cc7f5
Compare
e3295d4 to
76b7862
Compare
98cc7f5 to
60a78c9
Compare
76b7862 to
b4e18c4
Compare
60a78c9 to
6ec8cab
Compare
b4e18c4 to
cf56203
Compare
…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.
6ec8cab to
7c8171e
Compare
…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.
Topology create/delete plus batched clear_topology / assign_topology_node_segments (single-chunk builder + *_batched convenience; CLEAR_BATCH_SIZE=16 / BACKFILL_BATCH_SIZE=4 consts moved into the crate). Feed create/update/delete. Topology PDA derived from args.name; feed PDA from args.code + args.exchange. Refs #4022, RFC-26.
…builder When the topology account still exists and at least one link is cleared, the clear processor asserts topology_account.is_writable and decrements its reference_count. The builder passed topology read-only, tripping the assert. Mark it writable (harmless on the closed-topology path, which skips the write).
…ssign builders (RFC-26 R7 review)
…ed test (RFC-26 R7 review)
…erate SDK byte-parity break (RFC-26 R7 review)
cf56203 to
5f7758f
Compare
ben-dz
left a comment
There was a problem hiding this comment.
The topology module doc's name-canonicalization rationale is factually wrong: get_topology_pda (pda.rs:121-124) has uppercased internally since c60e14d, so delete/clear/assign deriving "from the raw arg" still canonicalize and a non-uppercase name can never reference a non-existent account or revert — the builders' to_ascii_uppercase() is a PDA no-op whose only effect is packing the canonical name (itself a byte-parity divergence from the SDK for non-canonical input). Either drop the canonicalization or rewrite the rationale, and fix the test comments repeating the claim. Separately, the live SDK ClearTopologyCommand readonly bug this PR correctly documents is user-facing (clearing a live topology is the mandatory precursor to delete) and has no tracking issue. Builder account layouts, writability flags, wire tags, batch math, and the FEED_AUTHORITY/AUTHORIZE_GATED_FLAGS gap claim all verified correct; remaining items are low-severity doc and test-fixture fixes.
- Medium: the live SDK bug this PR documents at topology.rs:101 ("the SDK command is the one that needs fixing") is real, user-facing, and untracked.
smartcontract/sdk/rs/src/commands/topology/clear.rspasses the topology PDAnew_readonly, while the processor (clear.rs:124-127) assertsis_writablewhenever the topology exists and at least one link is cleared; sincedelete_topologyrequiresreference_count == 0(delete.rs:77), clearing a live topology is the mandatory precursor to delete — the SDK/CLItopology clearreverts in exactly its primary use case. No GitHub issue tracks it. File a tracking issue, or fix the one-linenew_readonly→newin a small follow-up, so this doc's pointer doesn't dangle indefinitely.
…ORIZE_GATED_FLAGS gap (RFC-26 R7 review)
5f7758f to
cccffbc
Compare
…racking issue #4078 (RFC-26 R7 review)
…Args fixture, FEED_AUTHORITY canary (RFC-26 R7 review)
#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 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_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 #4022. 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.